Skip to content

Commit 3d1bd64

Browse files
jonomonGraylinKim
authored andcommitted
added new creepTracker, supplyTracker and tests
1 parent dc824ca commit 3d1bd64

File tree

9 files changed

+318
-269
lines changed

9 files changed

+318
-269
lines changed

sc2reader/engine/plugins/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@
55
from sc2reader.engine.plugins.selection import SelectionTracker
66
from sc2reader.engine.plugins.context import ContextLoader
77
from sc2reader.engine.plugins.gameheart import GameHeartNormalizer
8+
from sc2reader.engine.plugins.supply import SupplyTracker
9+
from sc2reader.engine.plugins.creeptracker import CreepTracker
Lines changed: 230 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,41 @@
11
# -*- coding: utf-8 -*-
22
from __future__ import absolute_import, print_function, unicode_literals, division
33

4+
from itertools import dropwhile
5+
from sets import Set
6+
from Image import open as PIL_open
7+
from Image import ANTIALIAS
8+
from StringIO import StringIO
49
from collections import defaultdict
5-
from sc2reader.engine.plugins.creeptrackerClass import creep_tracker
610

11+
# The creep tracker plugin
712
class CreepTracker(object):
813
'''
914
The Creep tracker populates player.max_creep_spread and
1015
player.creep_spread by minute
1116
This uses the creep_tracker class to calculate the features
1217
'''
1318
def handleInitGame(self, event, replay):
14-
try:
15-
replay.tracker_events
16-
except AttributeError:
17-
print("Replay does not have tracker events")
18-
return
19-
try:
20-
replay.map.minimap
21-
except AttributeError:
22-
print("Map was not loaded")
19+
if len( replay.tracker_events) ==0 :
2320
return
21+
if replay.map is None:
22+
replay.load_map()
2423
self.creepTracker = creep_tracker(replay)
2524
for player in replay.players:
2625
self.creepTracker.init_cgu_lists(player.pid)
2726

28-
def handleEvent(self, event, replay):
29-
self.creepTracker.add_event(event)
27+
def handleUnitDiedEvent(self, event, replay):
28+
self.creepTracker.remove_from_list(event.unit_id,event.second)
29+
30+
def handleUnitInitEvent(self,event,replay):
31+
if event.unit_type_name in ["CreepTumor", "Hatchery","NydusCanal"] :
32+
self.creepTracker.add_to_list(event.control_pid,event.unit_id,\
33+
(event.x, event.y), event.unit_type_name,event.second)
34+
35+
def handleUnitBornEvent(self,event,replay):
36+
if event.unit_type_name== "Hatchery":
37+
self.creepTracker.add_to_list(event.control_pid, event.unit_id,\
38+
(event.x,event.y),event.unit_type_name,event.second)
3039

3140
def handleEndGame(self, event, replay):
3241
for player_id in replay.player:
@@ -38,3 +47,212 @@ def handleEndGame(self, event, replay):
3847
player.max_creep_spread = max(player.creep_spread_by_minute.items(),key=lambda x:x[1])
3948
else:
4049
player.max_creep_spread =0
50+
51+
## The class used to used to calculate the creep spread
52+
class creep_tracker():
53+
def __init__(self,replay):
54+
#if the debug option is selected, minimaps will be printed to a file
55+
##and a stringIO containing the minimap image will be saved for
56+
##every minite in the game and the minimap with creep highlighted
57+
## will be printed out.
58+
self.debug = replay.opt.debug
59+
##This list contains creep spread area for each player
60+
self.creep_spread_by_minute = dict()
61+
## this list contains a minimap highlighted with creep spread for each player
62+
if self.debug:
63+
self.creep_spread_image_by_minute = dict()
64+
## This list contains all the active cgus in every time frame
65+
self.creep_gen_units = dict()
66+
## Thist list corresponds to creep_gen_units storing the time of each CGU
67+
self.creep_gen_units_times= dict()
68+
## convert all possible cgu radii into a sets of coordinates centred around the origin,
69+
## in order to use this with the CGUs, the centre point will be
70+
## subtracted with coordinates of cgus created in game
71+
self.unit_name_to_radius={'CreepTumor': 10,"Hatchery":8,"NydusCanal": 5}
72+
self.radius_to_coordinates= dict()
73+
for x in self.unit_name_to_radius:
74+
self.radius_to_coordinates[self.unit_name_to_radius[x]] =\
75+
self.radius_to_map_positions(self.unit_name_to_radius[x])
76+
77+
#Get map information
78+
replayMap = replay.map
79+
# extract image from replay package
80+
mapsio = StringIO(replayMap.minimap)
81+
im = PIL_open(mapsio)
82+
##remove black box around minimap
83+
cropped = im.crop(im.getbbox())
84+
cropsize = cropped.size
85+
self.map_height = 100.0
86+
# resize height to MAPHEIGHT, and compute new width that
87+
# would preserve aspect ratio
88+
self.map_width = int(cropsize[0] * (float(self.map_height) / cropsize[1]))
89+
self.mapSize =self.map_height * self.map_width
90+
## the following parameters are only needed if minimaps have to be printed
91+
minimapSize = ( self.map_width,int(self.map_height) )
92+
self.minimap_image = cropped.resize(minimapSize, ANTIALIAS)
93+
mapOffsetX= replayMap.map_info.camera_left
94+
mapOffsetY = replayMap.map_info.camera_bottom
95+
mapCenter = [mapOffsetX + cropsize[0]/2.0, mapOffsetY + cropsize[1]/2.0]
96+
# this is the center of the minimap image, in pixel coordinates
97+
imageCenter = [(self.map_width/2), self.map_height/2]
98+
# this is the scaling factor to go from the SC2 coordinate
99+
# system to pixel coordinates
100+
self.image_scale = float(self.map_height) / cropsize[0]
101+
self.transX =imageCenter[0] + self.image_scale * (mapCenter[0])
102+
self.transY = imageCenter[1] + self.image_scale * (mapCenter[1])
103+
104+
def radius_to_map_positions(self,radius):
105+
## this function converts all radius into map coordinates
106+
## centred around the origin that the creep can exist
107+
## the cgu_radius_to_map_position function will simply
108+
## substract every coordinate with the centre point of the tumour
109+
output_coordinates = list()
110+
# Sample a square area using the radius
111+
for x in range (-radius,radius):
112+
for y in range (-radius, radius):
113+
if (x**2 + y**2) <= (radius * radius):
114+
output_coordinates.append((x,y))
115+
return output_coordinates
116+
117+
def init_cgu_lists(self, player_id):
118+
self.creep_spread_by_minute[player_id] = defaultdict(int)
119+
if self.debug:
120+
self.creep_spread_image_by_minute[player_id] = defaultdict(StringIO)
121+
self.creep_gen_units[player_id] = list()
122+
self.creep_gen_units_times[player_id] = list()
123+
124+
def add_to_list(self,player_id,unit_id,unit_location,unit_type,event_time):
125+
# This functions adds a new time frame to creep_generating_units_list
126+
# Each time frame contains a list of all CGUs that are alive
127+
length_cgu_list = len(self.creep_gen_units[player_id])
128+
if length_cgu_list==0:
129+
self.creep_gen_units[player_id].append([(unit_id, unit_location,unit_type)])
130+
self.creep_gen_units_times[player_id].append(event_time)
131+
else:
132+
#if the list is not empty, take the previous time frame,
133+
# add the new CGU to it and append it as a new time frame
134+
previous_list = self.creep_gen_units[player_id][length_cgu_list-1][:]
135+
previous_list.append((unit_id, unit_location,unit_type))
136+
self.creep_gen_units[player_id].append(previous_list)
137+
self.creep_gen_units_times[player_id].append(event_time)
138+
139+
def remove_from_list(self,unit_id,time_frame):
140+
## This function searches is given a unit ID for every unit who died
141+
## the unit id will be searched in cgu_gen_units for matches
142+
## if there are any, that unit will be removed from active CGUs
143+
## and appended as a new time frame
144+
for player_id in self.creep_gen_units:
145+
length_cgu_list = len(self.creep_gen_units[player_id])
146+
if length_cgu_list ==0:
147+
break
148+
cgu_per_player = self.creep_gen_units[player_id]\
149+
[length_cgu_list-1]
150+
creep_generating_died = dropwhile(lambda x: x[0] != \
151+
unit_id, cgu_per_player)
152+
for creep_generating_died_unit in creep_generating_died:
153+
cgu_per_player.remove(creep_generating_died_unit)
154+
self.creep_gen_units[player_id].append(cgu_per_player)
155+
self.creep_gen_units_times[player_id].append(time_frame)
156+
157+
def reduce_cgu_per_minute(self,player_id):
158+
#the creep_gen_units_lists contains every single time frame
159+
#where a CGU is added,
160+
#To reduce the calculations required, the time frame containing
161+
#the most cgus every minute will be used to represent that minute
162+
last_minute_found = 0
163+
cgu_per_player_new = list()
164+
cgu_time_per_player_new = list()
165+
cgu_last_minute_list = list()
166+
for index,cgu_time in enumerate(self.creep_gen_units_times[player_id]):
167+
cgu_last_minute_list.append(self.creep_gen_units[player_id][index])
168+
if (cgu_time/60) ==0:
169+
cgu_per_player_new.append(self.creep_gen_units[player_id][0])
170+
cgu_time_per_player_new.append(0)
171+
cgu_last_minute_list = list()
172+
if (cgu_time/60)>last_minute_found:
173+
cgu_max_in_min = max(cgu_last_minute_list,key = len)
174+
cgu_per_player_new.append(cgu_max_in_min)
175+
cgu_max_in_min_index = self.creep_gen_units[player_id].index(cgu_max_in_min)
176+
cgu_time_per_player_new.append(self.creep_gen_units_times[player_id][cgu_max_in_min_index])
177+
last_minute_found = cgu_time/60
178+
cgu_last_minute_list=list()
179+
180+
self.creep_gen_units[player_id] = cgu_per_player_new
181+
self.creep_gen_units_times[player_id] = cgu_time_per_player_new
182+
183+
def get_creep_spread_area(self,player_id):
184+
## iterates through all cgus and and calculate the area
185+
for index,cgu_per_player in enumerate(self.creep_gen_units[player_id]):
186+
# convert cgu list into centre of circles and radius
187+
cgu_radius = map(lambda x: (x[1], self.unit_name_to_radius[x[2]]),\
188+
cgu_per_player)
189+
# convert event coords to minimap coords
190+
cgu_radius = self.convert_cgu_radius_event_to_map_coord(cgu_radius)
191+
creep_area_positions = self.cgu_radius_to_map_positions(cgu_radius,self.radius_to_coordinates)
192+
cgu_last_event_time = self.creep_gen_units_times[player_id][index]/60
193+
if self.debug:
194+
self.print_image(creep_area_positions,player_id,cgu_last_event_time)
195+
creep_area = len(creep_area_positions)
196+
self.creep_spread_by_minute[player_id][cgu_last_event_time]=\
197+
float(creep_area)/self.mapSize*100
198+
return self.creep_spread_by_minute[player_id]
199+
200+
def cgu_radius_to_map_positions(self,cgu_radius,radius_to_coordinates):
201+
## This function uses the output of radius_to_map_positions
202+
total_points_on_map = Set()
203+
if len(cgu_radius)==0:
204+
return []
205+
for cgu in cgu_radius:
206+
point = cgu[0]
207+
radius = cgu[1]
208+
## subtract all radius_to_coordinates with centre of
209+
## cgu radius to change centre of circle
210+
cgu_map_position = map( lambda x:(x[0]+point[0],x[1]+point[1])\
211+
,self.radius_to_coordinates[radius])
212+
total_points_on_map= total_points_on_map | Set(cgu_map_position)
213+
return total_points_on_map
214+
215+
def print_image(self,total_points_on_map,player_id,time_stamp):
216+
minimap_copy = self.minimap_image.copy()
217+
# Convert all creeped points to white
218+
for points in total_points_on_map:
219+
x = points[0]
220+
y = points[1]
221+
x,y = self.check_image_pixel_within_boundary(x,y)
222+
minimap_copy.putpixel((x,y) , (255, 255, 255))
223+
creeped_image = minimap_copy
224+
# write creeped minimap image to a string as a png
225+
creeped_imageIO = StringIO()
226+
creeped_image.save(creeped_imageIO, "png")
227+
self.creep_spread_image_by_minute[player_id][time_stamp]=creeped_imageIO
228+
##debug for print out the images
229+
f = open(str(player_id)+'image'+str(time_stamp)+'.png','w')
230+
f.write(creeped_imageIO.getvalue())
231+
creeped_imageIO.close()
232+
f.close()
233+
234+
def check_image_pixel_within_boundary(self,pointX, pointY):
235+
pointX = 0 if pointX <0 else pointX
236+
pointY=0 if pointY <0 else pointY
237+
# put a minus 1 to make sure the pixel is not directly on the edge
238+
pointX = int(self.map_width-1 if pointX >= self.map_width else pointX)
239+
pointY = int(self.map_height-1 if pointY >= self.map_height else pointY)
240+
return pointX,pointY
241+
242+
def convert_cgu_radius_event_to_map_coord(self,cgu_radius):
243+
cgu_radius_new = list()
244+
for cgu in cgu_radius:
245+
x = cgu[0][0]
246+
y = cgu[0][1]
247+
(x,y) = self.convert_event_coord_to_map_coord(x,y)
248+
cgu = ((x,y),cgu[1])
249+
cgu_radius_new.append(cgu)
250+
return cgu_radius_new
251+
252+
def convert_event_coord_to_map_coord(self,x,y):
253+
imageX = int(self.map_height - self.transX + self.image_scale * x)
254+
imageY = int(self.transY - self.image_scale * y)
255+
return imageX, imageY
256+
257+
258+

0 commit comments

Comments
 (0)