-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathFinalAI.py
More file actions
297 lines (265 loc) · 12.6 KB
/
FinalAI.py
File metadata and controls
297 lines (265 loc) · 12.6 KB
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
#FinalAI.py
#Jazmin Gonzalez-Rivero, Zachary Homans, Elizabeth Mahon, Brendan Ritter
#Artificial Intelligence, Olin College, Spring 13
#This is what determines how the enemy AI works.
import math
from feworld import *
#checks whether the units attacking each side are different or not
#takes two lists that contain a unit, an int (damage), and a list of spaces
#as well as two arrays of those lists
#returns whether it found different attackers
def check_diff(dir_attacker1,dir_attacker2,array1,array2):
#takes an array of the lists
#returns the one that has the unit that does the most damage
def maxdamage(array):
new_dir_attacker = None
for unit in array:
if new_dir_attacker == None:
new_dir_attacker = unit
else:
if unit[1] > new_dir_attacker[1]:
new_dir_attacker = unit
return new_dir_attacker
if dir_attacker1 == dir_attacker2 and dir_attacker1 != None and array1 != [] and array2 != []:
new_dir_attacker1=maxdamage(array1)
new_dir_attacker2=maxdamage(array2)
if new_dir_attacker1[1] > new_dir_attacker2[1]:
dir_attacker1 = new_dir_attacker1
array1.remove(new_dir_attacker1)
else:
dir_attacker2 = new_dir_attacker2
array2.remove(new_dir_attacker2)
return False
else:
return True
#finds the unit that can do the most damage from a list of units
#takes in an array of lists that contain:
# a unit, an int (damage), and a list of spaces
#returns a tuple that contains:
# the list from the array that contains the unit that can do the most damage
# the array that came in minus the other element of the tuple
def max_damage(dir_attacker,array):
for unit in array:
if dir_attacker == None:
dir_attacker = unit
else:
if unit[1] > dir_attacker[1]:
dir_attacker = unit
if array !=[]:
array.remove(dir_attacker)
return (dir_attacker,array)
#calculates Manhattan distance between two spaces
def distance(space1, space2):
return (abs(space1.get_x() - space2.get_x()) + abs(space1.get_y() - space2.get_y()))
#finds the closest space in the list to the desired space
#desired is a space object, move_list is a list of space objects
def find_closest(desired, move_list):
#init outputs
dist = distance(desired,move_list[0])
closest = move_list[0]
for place in move_list:
new_dist = distance(desired, place)
if new_dist < dist:
dist = new_dist
closest = place
return closest
#the actual full code that executes
#takes the player it is playing for
#the level map, and what strategy it should use
def computer_player(com, world, strat = "t"):
#initialize all the data structures we need
damage = dict()
enemies = dict()
enemy_attack = dict()
dist = dict()
opponent = com.opponent
for enemy in opponent.units:
enemies[enemy] = []
enemy_attack[enemy] = []
#figure out which units can attack
available_units = list()
for unit in com.units:
if unit not in com.actedUnits:
available_units.append(unit)
#determine which units can attack which opponent units
for unit in available_units:
#unit is of type unit
unit.move_list = unit.get_move_list()
unit.attack_list = unit.get_attack_list()
dist[unit] = dict()
for enemy in opponent.units:
#enemy is of type unit
if enemy.space in unit.attack_list:
surrounding_spaces = []
for move_poss in ([0, 1], [0, -1], [1, 0], [-1, 0]):
surrounding_space=world.get_space(enemy.get_x()+move_poss[0], enemy.get_y()+move_poss[1])
if surrounding_space in unit.move_list:
#check which spaces the unit can attack the enemy from
surrounding_spaces.append(surrounding_space)
#for every enemy, remember which units can attack it
#how much they can do, and where they can attack from
enemies[enemy].append([unit, (unit.attack - enemy.defense - enemy.space.terrain.defenseMod) * (unit.accuracy - enemy.space.terrain.evasionMod), surrounding_spaces])
#Attack * Hit % can be changed to a better scaler
else:
#not in range; remember distance
dist[unit][distance(enemy.get_space(),unit.get_space())] = enemy
#now that we know who can attack who
#figure out the best combination of attackers
for enemy in opponent.units:
#enemy is of type unit
#initialize spaces to attack from and who can attack from each
top_space = world.get_space(enemy.get_x(), enemy.get_y()-1)
units_that_can_attack_top = []
bottom_space = world.get_space(enemy.get_x(), enemy.get_y()+1)
units_that_can_attack_bottom = []
left_space = world.get_space(enemy.get_x()-1, enemy.get_y())
units_that_can_attack_left = []
right_space = world.get_space(enemy.get_x()+1, enemy.get_y())
units_that_can_attack_right = []
#go through each potential attacker for each enemy
for unit in enemies[enemy]:
#unit is a list of a unit, an int (damage), and a list of spaces
#get lists of who can attack from each space
if top_space in unit[2]:
units_that_can_attack_top.append(unit)
if bottom_space in unit[2]:
units_that_can_attack_bottom.append(unit)
if left_space in unit[2]:
units_that_can_attack_left.append(unit)
if right_space in unit[2]:
units_that_can_attack_right.append(unit)
final_attackers = False
top_attacker = None
bottom_attacker = None
right_attacker = None
left_attacker = None
#determine which unit can do the most damage in each position
#the outer ifs are to prevent errors from being on the edge of the map
#the loop prioritizes moving to better terrain
for i in range(3,-1,-1):
if isinstance(top_space, space):
if top_space.terrain.defenseMod == i:
temptop=max_damage(top_attacker,units_that_can_attack_top)
top_attacker=temptop[0]
units_that_can_attack_top=temptop[1]
if isinstance(bottom_space, space):
if bottom_space.terrain.defenseMod == i:
tempbottom=max_damage(bottom_attacker,units_that_can_attack_bottom)
bottom_attacker=tempbottom[0]
units_that_can_attack_bottom=tempbottom[1]
if isinstance(left_space, space):
if left_space.terrain.defenseMod == i:
templeft=max_damage(left_attacker,units_that_can_attack_left)
left_attacker=templeft[0]
units_that_can_attack_left=templeft[1]
if isinstance(right_space, space):
if right_space.terrain.defenseMod == i:
tempright=max_damage(right_attacker,units_that_can_attack_right)
right_attacker=tempright[0]
units_that_can_attack_right=tempright[1]
while final_attackers == False:
#check whether the same unit is in two slots
#if so, find the next best one for one of the slots
#only loops if duplicates are found
#this is final_attackers
#the short variable name is to keep things pretty.
b = True
b = b and check_diff(top_attacker,bottom_attacker,units_that_can_attack_top,units_that_can_attack_bottom)
b = b and check_diff(top_attacker,right_attacker,units_that_can_attack_top,units_that_can_attack_right)
b = b and check_diff(top_attacker,left_attacker,units_that_can_attack_top,units_that_can_attack_left)
b = b and check_diff(bottom_attacker,right_attacker,units_that_can_attack_bottom,units_that_can_attack_right)
b = b and check_diff(bottom_attacker,left_attacker,units_that_can_attack_bottom,units_that_can_attack_left)
b = b and check_diff(right_attacker,left_attacker,units_that_can_attack_right,units_that_can_attack_left)
final_attackers = b
#we've finally figured out the best configurations for each enemy
attackers=[top_attacker,bottom_attacker,left_attacker,right_attacker]
total_damage = 0.0
#calculate how much damage we can do to each enemy
for attacker in attackers:
if attacker != None:
total_damage += attacker[1]
damage[enemy] = total_damage
enemy_attack[enemy]= attackers
optimal = None
for enemy in opponent.units:
#figure out which enemy to attack first
if optimal == None:
optimal = enemy
else:
if damage[optimal]/optimal.hp<damage[enemy]/enemy.hp:
optimal = enemy
#Only the strongest attacks before recalculation
move_next = None
for attacker in enemy_attack[optimal]:
if (move_next == None and attacker != None):
move_next = attacker
if (attacker != None and attacker[0].attack > move_next[0].attack):
move_next = attacker
#deal with what happens if no one can attack
if move_next == None and strat == "d":
#defensive strategy: just stay in place
com.movedUnits = com.units
com.actedUnits = com.units
elif move_next==None and strat == "t":
#tricky strategy: move just out of range of player units
#construct a no-go zone of spaces enemies can attack
no_go = []
for enemy in com.opponent.units:
for tile in enemy.get_attack_list():
if tile not in no_go:
no_go.append(tile)
#this for loop deals with all units that can't attack
for unit in dist.keys():
#find closest space where you could be attacked
edge = find_closest(unit.get_space(),no_go)
if distance(unit.get_space(),edge)==1 or unit.get_space() in no_go:
#in the sweet spot or inside the threat range; stay put
com.move_Unit(unit,world, unit.get_x(),unit.get_y())
else:
#not on the edge of the attack range
#not inside the attack range
#move to the edge
possible = list()
desired = None
#find which side of edge space you want to be on
for side in ([0,1],[0,-1],[1,0],[-1,0]):
poss_space = world.get_space(edge.get_x()+side[0],edge.get_y()+side[1])
if poss_space not in no_go and poss_space != None:
possible.append(poss_space)
if poss_space in unit.get_move_list():
#if we can just go there, do that
desired = poss_space
#if can't go straight there, get as close as possible
if desired == None:
desired = find_closest(unit.get_space(),possible)
final = find_closest(desired, unit.get_move_list())
com.move_Unit(unit,world,final.get_x(),final.get_y())
com.actedUnits = com.units
com.movedUnits = com.units
elif move_next == None:
#agressive strategy: chaaarge!
#this is the default strategy
for unit in dist.keys():
dists = dist[unit].keys()[:]
dists.sort()
min_dist = dists[0]
closest = find_closest(dist[unit][min_dist],unit.get_move_list())
com.move_Unit(unit,world,closest.get_x(),closest.get_y())
com.actedUnits = com.units
com.movedUnits = com.units
else:
#can attack, so do it!
#figure out which slot the next unit to move was in then move it there
slot = enemy_attack[optimal].index(move_next)
toMove = move_next[0]
if slot == 0:
com.move_Unit(toMove,world,optimal.get_x(),optimal.get_y()-1)
elif slot == 1:
com.move_Unit(toMove,world,optimal.get_x(),optimal.get_y()+1)
elif slot == 2:
com.move_Unit(toMove,world,optimal.get_x()-1,optimal.get_y())
elif slot == 3:
com.move_Unit(toMove,world,optimal.get_x()+1,optimal.get_y())
else:
print "Shouldn't get here."
com.act_Unit(toMove, world, optimal)