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