1
1
# -*- coding: utf-8 -*-
2
2
from __future__ import absolute_import , print_function , unicode_literals , division
3
3
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
4
9
from collections import defaultdict
5
- from sc2reader .engine .plugins .creeptrackerClass import creep_tracker
6
10
11
+ # The creep tracker plugin
7
12
class CreepTracker (object ):
8
13
'''
9
14
The Creep tracker populates player.max_creep_spread and
10
15
player.creep_spread by minute
11
16
This uses the creep_tracker class to calculate the features
12
17
'''
13
18
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 :
23
20
return
21
+ if replay .map is None :
22
+ replay .load_map ()
24
23
self .creepTracker = creep_tracker (replay )
25
24
for player in replay .players :
26
25
self .creepTracker .init_cgu_lists (player .pid )
27
26
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 )
30
39
31
40
def handleEndGame (self , event , replay ):
32
41
for player_id in replay .player :
@@ -38,3 +47,212 @@ def handleEndGame(self, event, replay):
38
47
player .max_creep_spread = max (player .creep_spread_by_minute .items (),key = lambda x :x [1 ])
39
48
else :
40
49
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