-
Notifications
You must be signed in to change notification settings - Fork 0
/
AIBase.py
245 lines (202 loc) · 10.5 KB
/
AIBase.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
import Brisk, config, utils
import sys, time, json
import Fortify_Evaluator
import features
# Error codes
class ReinforceError(object):
_prefix = "ReinforceError: "
LEFTOVER_ARMY = _prefix + "You did not deploy all of your troops."
TOO_MUCH_ARMY = _prefix + "You deployed more troops than you have."
@staticmethod
def TERRITORY_NOT_OWNED(territoryIds):
return "ReinforceError: You don't own the following territories: %s" % str(territoryIds)
class AttackError(object):
@staticmethod
def ILLEGAL_ATTACK(tattack, tdefend):
return "AttackError: Cannot attack %d from %d: they are not adjacent, or you do not own the attacking territory." % ( tdefend, tattack)
@staticmethod
def NOT_ENOUGH_ARMY(tattack, tdefend, num_armies):
return "AttackError: Cannot attack %d from %d: less than %d armies to move." % (tdefend, tattack, num_armies)
@staticmethod
def TOO_MUCH_ARMY(tattack, tdefend, num_armies):
return "AttackError: Cannot attack %d from %d with %d armies: cannot attack with more than 3 armies" % (tdefend, tattack, num_armies)
class FortifyError(object):
@staticmethod
def ILLEGAL_FORTIFY(tfrom, tto):
return "FortifyError: Cannot fortify from %d to %d: they are not adjacent, you do not own at least one of the territories, or you have only 1 troop in the source territory." % (tfrom, tto)
@staticmethod
def NOT_ENOUGH_ARMY(tfrom, tto, num_armies):
return "FortifyError: Cannot fortify from %d to %d: less than %d armies to move." % (tfrom, tto, num_armies)
class AIBase(Brisk.Brisk):
# Methods to be implemented by subclasses
def reinforce(self, num_reserves):
''' Returns {<territoryId>: <num troops deployed>} '''
pass
def battle(self, legal_territories_to_attack):
''' Returns (attack, defend, num_armies_to_attack), or a None value if not attacking '''
pass
def fortify(self, legal_territories_to_fortify):
''' Returns (from_territory, to_territory, num_armies_to_move), or a None value if ending turn '''
pass
# Private utilities
def _create_set_of_legal_battles(self):
battles = []
territories = self.get_game_state()['territories']
ours = filter(lambda t: t['player'] is self.player_id and t['num_armies'] > 1, territories)
for t in ours:
adjacent_territories = self.map_layout['territories'][t['territory']-1]['adjacent_territories']
for a in adjacent_territories:
if territories[a-1]['player'] is not self.player_id:
battles.append((t, territories[a-1]))
# battles = [battle]
# battle = (territory, territory)
# territory = {'territory': 2, 'num_armies': 3, 'player': 1}
return battles
def _create_set_of_legal_fortifications(self):
fortifications = []
territories = self.get_game_state()['territories']
ours = filter(lambda t: t['player'] is self.player_id and t['num_armies'] > 1, territories)
for t in ours:
adjacent_territories = self.map_layout['territories'][t['territory']-1]['adjacent_territories']
for a in adjacent_territories:
if territories[a-1]['player'] is self.player_id:
fortifications.append((t, territories[a-1]))
return fortifications
def _generate_continent_ratings(self):
self.continent_rating = {}
for continent in self.map_layout['continents']:
continent['border_territories'] = []
for territory_id in continent['territories']:
#TODO no index-1
for adjacent_id in self.map_layout['territories'][territory_id-1]['adjacent_territories']:
if adjacent_id not in continent['territories']:
continent['border_territories'].append(territory_id)
break
continent['rating'] = float(15 + continent['continent_bonus'] - 4 * len(continent['border_territories'])) / len(continent['territories'])
def _refresh_state(self):
start = time.time()
self.game_state = self.get_game_state()
# print "refreshed game state in %f seconds" % (time.time() - start)
start = time.time()
self.player_status = self.get_player_status()
# print "refreshed player status in %f seconds" % (time.time() - start)
start = time.time()
self.player_status_lite = self.get_player_status(True)
# print "refreshed player status lite in %f seconds" % (time.time() - start)
start = time.time()
self.enemy_status = self.get_enemy_status()
# print "refreshed enemy status in %f seconds" % (time.time() - start)
def _err(self, msg):
print "Current game state:"
utils.pp(self.game_state)
print "\nCurrent player status:"
utils.pp(self.player_status)
print msg
sys.exit(1)
# Core AI execution methods
def __init__(self):
super(AIBase, self).__init__()
self.map_layout = self.get_map_layout()
self._refresh_state()
self._generate_continent_ratings()
self.fe = Fortify_Evaluator.Fortify_Evaluator()
def do_reinforce(self):
num_reserves = self.player_status['num_reserves']
reinforcements = self.reinforce(num_reserves)
# TODO handle error when placing in enemy territory
invalid_ids = filter(lambda t_id: utils.get_territory_by_id(t_id,self.player_status['territories']) == None, reinforcements.keys())
if invalid_ids:
self._err(ReinforceError.TERRITORY_NOT_OWNED(invalid_ids))
num_deployed = sum(reinforcements.values())
# All troops must be deployed
if (num_deployed < num_reserves):
self._err(ReinforceError.LEFTOVER_ARMY)
# Cannot deploy too many troops
elif (num_deployed > num_reserves):
self._err(ReinforceError.TOO_MUCH_ARMY)
# Execute reinforcement
for t_id, num_troops in reinforcements.iteritems():
self.place_armies(t_id, num_troops)
def do_battle(self):
''' Returns True when done, false otherwise '''
legal_battles = self._create_set_of_legal_battles()
battle = self.battle(legal_battles)
if battle:
tattack, tdefend, num_armies = battle
legal_battles_to_ids = map(lambda (a,d): (a['territory'], d['territory']), legal_battles)
if ((tattack, tdefend) not in legal_battles_to_ids):
self._err(AttackError.ILLEGAL_ATTACK(tattack, tdefend))
else:
t = utils.get_territory_by_id(tattack, self.player_status['territories'])
# There must be enough troops to attack
if num_armies > t['num_armies'] - 1:
self._err(AttackError.NOT_ENOUGH_ARMY(tattack, tdefend, num_armies))
# There can't be more than 3 troops attacking
elif num_armies > 3:
self._err(AttackError.TOO_MUCH_ARMY(tattack, tdefend, num_armies))
else:
# Everything is valid
self.attack(tattack, tdefend, num_armies)
time.sleep(config.DELAY_BETWEEN_ACTIONS)
self._refresh_state()
if utils.get_territory_by_id(tdefend, self.player_status['territories']):
print "Conquered"
attack_move = (utils.get_territory_by_id(tattack, self.player_status['territories']), \
utils.get_territory_by_id(tdefend, self.player_status['territories']), \
utils.get_territory_by_id(tattack, self.player_status['territories'])['num_armies'] - 1)
sim_score = self.fe.evaluate_action(attack_move, self.map_layout, self.player_status, self.enemy_status)
score = features.evaluate_fortify(self.map_layout, self.player_status, self.enemy_status)
if sim_score > score:
print "ATTACK MOVE!"
self.transfer_armies(tattack, tdefend, utils.get_territory_by_id(tattack, self.player_status['territories'])['num_armies'] - 1)
return False
else:
return True
def do_fortify(self):
legal_forts = self._create_set_of_legal_fortifications()
fortification = self.fortify(legal_forts)
if fortification:
tfrom, tto, num_armies = fortification
legal_forts_to_ids = map(lambda (f,t): (f['territory'], t['territory']), legal_forts)
# Fortification must occur between adjacent territories
if (tfrom, tto) not in legal_forts_to_ids:
self._err(FortifyError.ILLEGAL_FORTIFY(tfrom, tto))
else:
t = utils.get_territory_by_id(tfrom, self.player_status['territories'])
# There must be enough troops to move
if num_armies > t['num_armies'] - 1:
self._err(FortifyError.NOT_ENOUGH_ARMY(tfrom, tto, num_armies))
else:
# Everything is valid
self.transfer_armies(tfrom, tto, num_armies)
else:
self.end_turn()
def run(self):
print "Game ID: %d" % self.game_id
while not self.game_state['winner']:
if self.player_status['current_turn']:
print "New turn"
print "Reinforcing..."
self._refresh_state()
# Handles the possibility that previous fortify has not ended the turn right away.
# if self.player_status['num_reserves'] == 0:
# print "num_reserves is 0, retrying...\n"
# time.sleep(config.POLL_TIME)
# self._refresh_state()
# continue # Redo all the initial checks of the loop
self.do_reinforce()
time.sleep(config.DELAY_BETWEEN_ACTIONS)
print "Attacking..."
done = False
while not done:
self._refresh_state()
done = self.do_battle()
time.sleep(config.DELAY_BETWEEN_ACTIONS)
print "Fortifying..."
# refresh state
self._refresh_state()
self.do_fortify()
time.sleep(config.DELAY_BETWEEN_ACTIONS)
print "End turn!\n"
time.sleep(config.POLL_TIME)
self._refresh_state()