11# -*- coding: utf-8 -*-
22from __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
49from collections import defaultdict
5- from sc2reader .engine .plugins .creeptrackerClass import creep_tracker
610
11+ # The creep tracker plugin
712class 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