/
MyStrategy.py
384 lines (333 loc) · 15.5 KB
/
MyStrategy.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
import cPickle as pickle
import collections
from copy import deepcopy
import os
from actions import Position
import actions
import battle_evaluator
from constants import *
import constants
from context import Context
from dfs import BattleSearcher
import global_vars
from global_vars import PlayerOrder
import map_util
from model.ActionType import ActionType
from model.TrooperStance import TrooperStance
from model.TrooperType import TrooperType
import scouting
import util
# Remembers bonuses and enemies as we've seen them last time.
BONUSES = {}
ENEMIES = {}
UnitID = collections.namedtuple('UnitID', ['player_id', 'unit_type'])
MoveID = collections.namedtuple('MoveID', ['move_index', 'unit_type'])
def UID(unit):
return UnitID(unit.player_id, unit.type)
PREV_MOVE_ID = MoveID(-1, -1)
# To detect which player moves first.
# Ids are "who did we see" (UnitID). Values are where and when did we see them: (xy, MoveID)
ENEMIES_SEEN_LAST_TURN = {}
ALLIES_HISTORY = {}
ENEMIES_HISTORY = {}
LAST_NON_CONT_MOVE = None
# Enemies we killed for sure (useful for knowing when enemy didn't move, but was killed).
KILLED = set()
def VisibleBySomeone(context, allies, enemy):
return any(util.CanSee(context, ally, enemy) for ally in allies)
def EnemyMoved(old_enemy, new_enemy):
return (old_enemy.xy != new_enemy.xy or
old_enemy.stance != new_enemy.stance or
old_enemy.holding_grenade != new_enemy.holding_grenade or
old_enemy.holding_medikit != new_enemy.holding_medikit or
old_enemy.holding_field_ration != new_enemy.holding_field_ration)
def UnitsMoved(context):
# Finds all units which moved since last move.
# Returns (unit_id, confidence) -- unit_id is unit which moved; confidence is how sure we are that it moved:
# *) If unit was not there before, and is visible now -- we are sure it moved; confidence is 2
# *) If unit was there before, and now disappeared -- he can be killed by some other player; confidence is 1.
global ALLIES_HISTORY, ENEMIES_HISTORY, LAST_NON_CONT_MOVE
old_allies = ALLIES_HISTORY[LAST_NON_CONT_MOVE]
new_allies = context.allies.values()
old_enemies = ENEMIES_HISTORY[LAST_NON_CONT_MOVE]
new_enemies = {UID(enemy): enemy for enemy in context.enemies.itervalues()}
all_enemies = set(old_enemies.keys() + new_enemies.keys())
for unit_id in all_enemies:
old_enemy = old_enemies.get(unit_id, None)
new_enemy = new_enemies.get(unit_id, None)
if old_enemy is not None and new_enemy is not None:
if EnemyMoved(old_enemy, new_enemy):
yield unit_id, 2
elif old_enemy is None:
assert new_enemy is not None
if VisibleBySomeone(context, old_allies, new_enemy):
yield unit_id, 2
else:
assert new_enemy is None
assert old_enemy is not None
if unit_id not in KILLED and VisibleBySomeone(context, new_allies, old_enemy):
yield unit_id, 1
def _UpdatePlayersOrder(context):
global ALLIES_HISTORY, ENEMIES_HISTORY, PREV_MOVE_ID, LAST_NON_CONT_MOVE
current_move_id = MoveID(context.world.move_index, context.me.type)
if IsContinuingMove(context):
ALLIES_HISTORY[current_move_id].append(deepcopy(context.me))
for enemy in context.enemies.itervalues():
ENEMIES_HISTORY[current_move_id][UID(enemy)] = deepcopy(enemy)
else:
ALLIES_HISTORY[current_move_id] = [deepcopy(ally) for ally in context.allies.itervalues()]
LAST_NON_CONT_MOVE = PREV_MOVE_ID
ENEMIES_HISTORY[current_move_id] = {UID(enemy): deepcopy(enemy) for enemy in context.enemies.itervalues()}
if LAST_NON_CONT_MOVE.move_index < 0:
# Very-very first turn. No info.
return
if context.me.type == LAST_NON_CONT_MOVE.unit_type:
# Only one our unit left. Anyone could have moved.
return
for unit_id, confidence in UnitsMoved(context):
if unit_id.unit_type == context.me.type:
global_vars.SetPlayerOrder(unit_id.player_id, PlayerOrder.BEFORE_ME, confidence)
elif unit_id.unit_type == LAST_NON_CONT_MOVE.unit_type:
global_vars.SetPlayerOrder(unit_id.player_id, PlayerOrder.AFTER_ME, confidence)
def UnitsMoveInOrder(u1, u2, u3):
assert u1 != u2, '%s != %s' % (u1, u2)
assert u2 != u3, '%s != %s' % (u2, u3)
assert u1 != u3, '%s != %s' % (u1, u3)
assert len(global_vars.UNITS_ORDER) == global_vars.UNITS_IN_GAME
n1 = global_vars.UNITS_ORDER.index(u1)
n2 = global_vars.UNITS_ORDER.index(u2)
n3 = global_vars.UNITS_ORDER.index(u3)
if n1 < n3:
return n1 < n2 < n3
else:
return n2 > n1 or n2 < n3
def IsContinuingMove(context):
return MoveID(context.world.move_index, context.me.type) == PREV_MOVE_ID
class MyStrategy(object):
def Init(self, context):
print 'Players'
for player in context.world.players:
print 'Players', player.id, player.name
print map_util.HashOfMap(context)
if global_vars.FIRST_MOVES_RANDOM > 0:
print 'First %d moves at random!' % global_vars.FIRST_MOVES_RANDOM
for ally in context.allies.itervalues():
if ally.type == TrooperType.SNIPER:
if ally.stance == TrooperStance.STANDING:
assert ally.shooting_range == global_vars.SNIPER_SHOOTING_RANGE, (ally.shooting_range, global_vars.SNIPER_SHOOTING_RANGE)
self.FillCornersOrder(context)
# Algorithm sucks if gets stuck on the longer side of fefer map. Make him go mid.
if map_util.MapName(context) == 'fefer':
p3 = Point(X / 2, Y / 2)
else:
p1, p2 = global_vars.ORDER_OF_CORNERS[0], global_vars.ORDER_OF_CORNERS[3]
p3 = Point((p1.x*2 + p2.x) / 3, (p1.y*2 + p2.y) / 3)
global_vars.SetNextGoal(context, util.ClosestEmptyCell(context, p3))
global_vars.UNITS_IN_GAME = len([t for t in context.world.troopers if t.teammate])
global_vars.ORDER_OF_PLAYERS = {player.id: global_vars.PlayerOrder.UNKNOWN for player in context.world.players}
global_vars.CONFIDENCE_ORDER = {player.id: 0 for player in context.world.players}
if global_vars.STDOUT_LOGGING:
print 'Start from', context.me.x, context.me.y
util._PrecomputeDistances(context)
global_vars.INITIALIZED = True
def _PreMove(self, context):
if context.me.type not in global_vars.UNITS_ORDER:
assert context.world.move_index == 0, context.world.move_index
global_vars.UNITS_ORDER.append(context.me.type)
if not IsContinuingMove(context):
global_vars.POSITION_AT_START_MOVE = context.me.xy
# Check players order BEFORE updating enemies.
_UpdatePlayersOrder(context)
# Update enemies.
self._MergeEnemiesInfo(context)
self._MergeBonusesInfo(context)
def _MergeBonusesInfo(self, context):
# Update everything we know about bonuses.
global BONUSES
if IsContinuingMove(context):
res = {p: b for p, b in BONUSES.iteritems() if p != context.me.xy}
else:
res = {}
for xy, bonus in context.bonuses.iteritems():
res[xy] = bonus
context.bonuses = res
BONUSES = res
def _MergeEnemiesInfo(self, context):
global ENEMIES
if IsContinuingMove(context):
res = ENEMIES
elif context.me.type != PREV_MOVE_ID.unit_type:
def CanSeeHim(enemy):
# If I can see him, he'll be in the context.enemies anyway.
return any(util.CanSee(context, ally, enemy) for ally in context.allies.itervalues())
def ReliableInfo(enemy):
# len(UNITS_ORDER) < TOTAL_UNITS means this is very first move.
if len(global_vars.UNITS_ORDER) < global_vars.UNITS_IN_GAME:
return True
if enemy.type == context.me.type:
return global_vars.GetPlayerOrder(enemy) == PlayerOrder.AFTER_ME
if enemy.type == PREV_MOVE_ID.unit_type:
return global_vars.GetPlayerOrder(enemy) == PlayerOrder.BEFORE_ME
return not UnitsMoveInOrder(PREV_MOVE_ID.unit_type, enemy.type, context.me.type)
res = dict([(p, enemy) for p, enemy in ENEMIES.iteritems() if ReliableInfo(enemy) and not CanSeeHim(enemy)])
else:
# Only one unit left. Every enemy had a chance to move.
res = {}
for xy, enemy in context.enemies.iteritems():
res[xy] = enemy
context.enemies = res
ENEMIES = res
global_vars.UpdateSeenEnemies(context, context.enemies.keys())
def AdjustEnemiesToDmg(self, xy, dmg):
global ENEMIES, KILLED
if xy in ENEMIES:
if dmg > ENEMIES[xy].hitpoints:
global_vars.ALIVE_ENEMIES[ENEMIES[xy].type] = False
KILLED.add(UID(ENEMIES[xy]))
del ENEMIES[xy]
else:
ENEMIES[xy].hitpoints -= dmg
def _PostMove(self, context, move):
xy = Point(move.x, move.y)
if move.action == ActionType.SHOOT:
dmg = util.ShootDamage(context.me)
self.AdjustEnemiesToDmg(xy, dmg)
elif move.action == ActionType.THROW_GRENADE:
dmg = context.game.grenade_direct_damage
self.AdjustEnemiesToDmg(xy, dmg)
for d in ALL_DIRS:
self.AdjustEnemiesToDmg(util.PointAndDir(xy, d), context.game.grenade_collateral_damage)
def MaybeSaveLog(self, context):
if constants.LOG_DIR is None:
return
cv = context.world.cell_visibilities
if global_vars.TURN_INDEX == 0:
cv_file = os.path.join(constants.LOG_DIR, 'visibilities')
with open(cv_file, 'w') as cv_file:
pickle.dump(cv, cv_file)
context.world.cell_visibilities = None
log_file = os.path.join(constants.LOG_DIR, '%03d_%s_%s.pickle' % (global_vars.TURN_INDEX, context.world.move_index, context.me.type))
with open(log_file, 'w') as fout:
pickle.dump(context, fout)
context.world.cell_visibilities = cv
def ReactAtPass(self, context, move):
# If smart algorithm decided to pass, see maybe we can do something safe and good.
me = context.me
# If there is an point we can not see, but we can shoot -- shoot at it.
if me.action_points >= me.shoot_cost:
my_shooting_range = util.ShootingRange(context, me)
def ShootInvisible(where):
if where in context.allies:
return False
for S in constants.ALL_STANCES:
can_see = False
for p in context.allies.itervalues():
discount = 0 if p.type == TrooperType.SCOUT else context.game.sniper_prone_stealth_bonus
sniper_vision_range = p.vision_range - discount
can_see |= util.IsVisible(context, sniper_vision_range, p.x, p.y, p.stance, where.x, where.y, S)
if not can_see and util.IsVisible(context, my_shooting_range, me.x, me.y, me.stance, where.x, where.y, S):
return True
return False
# Shoot at the closest point we can not see.
best_dist = 1000
found = False
for x in range(int(me.x - my_shooting_range), int(me.x + my_shooting_range + 1.01)):
for y in range(int(me.y - my_shooting_range), int(me.y + my_shooting_range + 1.01)):
where = Point(x, y)
if context.IsPassable(where):
value = util.ManhDist(where, global_vars.NextGoal())
if value < best_dist and ShootInvisible(where):
move.action = ActionType.SHOOT
move.x, move.y = x, y
best_dist = value
found = True
if found:
return
# Try to make a step further, and then run back here -- to see if there are enemies.
if me.action_points >= 2 * util.MoveCost(context, me.stance):
g = global_vars.NextGoal()
for d in ALL_DIRS:
p1 = util.PointAndDir(me.xy, d)
if context.CanMoveTo(p1) and util.ManhDist(p1, g) < util.ManhDist(me.xy, g):
move.action = ActionType.MOVE
move.x, move.y = p1.x, p1.y
global_vars.FORCED_ACTIONS = [actions.Walk(context, me.xy)]
global_vars.FORCED_MOVE_ID = context.world.move_index, me.type
global_vars.FORCED_MOVE_WITH_ENEMIES = bool(context.enemies)
return
# Lower stance to stay low.
if me.action_points >= context.game.stance_change_cost and me.stance == TrooperStance.STANDING:
move.action = ActionType.LOWER_STANCE
return
@util.TimeMe
def move(self, me, world, game, move):
context = Context(me, world, game)
if not global_vars.INITIALIZED:
self.Init(context)
self.MaybeSaveLog(context)
self._PreMove(context)
move.action = ActionType.END_TURN
self.RealMove(context, move)
self._PostMove(context, move)
if move.action == ActionType.END_TURN:
self.ReactAtPass(context, move)
if global_vars.STDOUT_LOGGING:
unit_type = util.GetName(TrooperType, me.type)
my_stance = util.GetName(TrooperStance, me.stance)
action_name = util.GetName(ActionType, move.action)
move_desc = ('(%2d, %2d)' % (move.x, move.y)) if move.x != -1 else ''
print ('Move %d: %11s@(%2d, %2d) %s (%2d ap)'
% (world.move_index, unit_type, me.xy.x, me.xy.y, my_stance, me.action_points) + ': %s -> %s' % (action_name, move_desc))
if global_vars.AT_HOME:
if move.action == ActionType.MOVE and me.action_points < util.MoveCost(context, me.stance):
print 'Please check bug at C:/Coding/CodeTroopers/bad_context.pickle'
with open('C:/Coding/CodeTroopers/bad_context.pickle', 'w') as fout:
pickle.dump(context, fout)
assert False
global PREV_MOVE_ID
PREV_MOVE_ID = MoveID(world.move_index, me.type)
global_vars.TURN_INDEX += 1
def CombatMove(self, context, move):
print 'Battle! Enemies:', ', '.join(
'%s: %s %s' % (xy, util.GetName(TrooperType, enemy.type), util.GetName(TrooperStance, enemy.stance))
for xy, enemy in context.enemies.iteritems())
searcher = BattleSearcher()
return searcher.DoSearch(battle_evaluator.EvaluatePosition, context, move)
def RealMove(self, context, move):
if global_vars.FIRST_MOVES_RANDOM > context.world.move_index:
return None
if (global_vars.FORCED_ACTIONS and
(context.world.move_index, context.me.type) == global_vars.FORCED_MOVE_ID and
global_vars.FORCED_MOVE_WITH_ENEMIES == bool(context.enemies)):
print 'Using pre-computed action!'
act = global_vars.FORCED_ACTIONS[0]
global_vars.FORCED_ACTIONS = global_vars.FORCED_ACTIONS[1:]
pos = Position(context)
act.SetMove(pos, move)
return global_vars.FORCED_ACTIONS
global_vars.FORCED_ACTIONS = []
global_vars.MOVE_INDEX = None
if context.enemies:
import time
t0 = time.time()
plan = self.CombatMove(context, move)
t1 = time.time()
util.MOVE_TIMES[context.world.move_index] = util.MOVE_TIMES.get(context.world.move_index, 0) + (t1 - t0)
return plan
else:
print 'Last seen:', global_vars.LAST_ENEMY_POSITION
return scouting.ScoutingMove(context, move)
def FillCornersOrder(self, context):
"""Finds another corner to run to at the start of the game (second closest corner to this trooper)."""
me = context.me
global_vars.ORDER_OF_CORNERS = []
x = 0 if me.x < X / 2 else X - 1
y = 0 if me.y < Y / 2 else Y - 1
global_vars.ORDER_OF_CORNERS.append(util.ClosestEmptyCell(context, Point(x, y)))
y = 0 if y > Y / 2 else Y - 1 # Switch y
global_vars.ORDER_OF_CORNERS.append(util.ClosestEmptyCell(context, Point(x, y)))
x = 0 if me.x > X / 2 else X - 1
global_vars.ORDER_OF_CORNERS.append(util.ClosestEmptyCell(context, Point(x, y)))
y = 0 if y > Y / 2 else Y - 1 # Switch y
global_vars.ORDER_OF_CORNERS.append(util.ClosestEmptyCell(context, Point(x, y)))
global_vars.NEXT_CORNER = 2 if context.IsDuel() else global_vars.ITERATION_ORDER