Skip to content

Commit dc824ca

Browse files
jonomonGraylinKim
authored andcommitted
Added Creep Tracker Class
1 parent 0ffbe41 commit dc824ca

File tree

2 files changed

+278
-0
lines changed

2 files changed

+278
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import absolute_import, print_function, unicode_literals, division
3+
4+
from collections import defaultdict
5+
from sc2reader.engine.plugins.creeptrackerClass import creep_tracker
6+
7+
class CreepTracker(object):
8+
'''
9+
The Creep tracker populates player.max_creep_spread and
10+
player.creep_spread by minute
11+
This uses the creep_tracker class to calculate the features
12+
'''
13+
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")
23+
return
24+
self.creepTracker = creep_tracker(replay)
25+
for player in replay.players:
26+
self.creepTracker.init_cgu_lists(player.pid)
27+
28+
def handleEvent(self, event, replay):
29+
self.creepTracker.add_event(event)
30+
31+
def handleEndGame(self, event, replay):
32+
for player_id in replay.player:
33+
self.creepTracker.reduce_cgu_per_minute(player_id)
34+
for player in replay.players:
35+
player.creep_spread_by_minute = self.creepTracker.get_creep_spread_area(player.pid)
36+
for player in replay.players:
37+
if player.creep_spread_by_minute:
38+
player.max_creep_spread = max(player.creep_spread_by_minute.items(),key=lambda x:x[1])
39+
else:
40+
player.max_creep_spread =0
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
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

Comments
 (0)