forked from HoelzelJon/MM25-Python-Starter-Pack
-
Notifications
You must be signed in to change notification settings - Fork 0
/
API.py
245 lines (226 loc) · 9.7 KB
/
API.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
from queue import Queue
import copy
import sys
# class for Position Object
class Position:
# A position will have an x and y coordinate
def __init__(self, position_json):
self.x = position_json["x"]
self.y = position_json["y"]
# Class for a Unit Object
class Unit:
# A unit will have the following fields
def __init__(self, unit_json):
self.hp = unit_json["hp"]
self.speed = unit_json["speed"]
self.attack = unit_json["attack"]
self.terrain = unit_json["terrain"]
self.id = unit_json["id"]
self.player_id = unit_json["playerNum"]
self.pos = Position(unit_json["pos"])
# Class for a Tile Object
class Tile:
# A Tile will have an id, hp, and type where type is either 'BLANK', 'DESTRUCTIBLE', or 'INDESTRUCTIBLe'
def __init__(self, tile_json):
self.id = tile_json["id"]
self.hp = tile_json["hp"]
self.type = tile_json["type"]
# Class for a Game Object
class Game:
# A Game Object will have a player_id, game_id, and the game represented as a json
def __init__(self, game_json):
self.game = game_json
self.player_id = self.game['playerNum']
self.game_id = self.game["gameId"]
if "tiles" in game_json:
self.tiles = game_json["tiles"]
self.turnsTaken = game_json["turnsTaken"]
self.units = game_json["units"]
else:
self.tiles = []
self.units = []
self.turnsTaken = 0
def get_setup(self):
raise NotImplementedError(
"Please Implement this method in a \"Strategy\" class")
def do_turn(self):
raise NotImplementedError(
"Please Implement this method in a \"Strategy\" class")
# Implement this in the "Strategy" class if you want to do something specific
# when a game ends
def game_over(self, result):
pass
# updates the game json. Called every turn
def update_game(self, game_json):
self.game = game_json
self.turnsTaken = self.game["turnsTaken"]
self.units = [Unit(unit_json) for unit_json in self.game["units"]]
self.tiles = [[Tile(tile_json) for tile_json in tile_json_list] for tile_json_list in self.game["tiles"]]
self.game_id = self.game["gameId"]
self.player_id = self.game["playerNum"]
"""
Given a player_id, returns the units for that team.
INPUT:
player_id: The id of the player to get units for
OUTPUT:
A list of Unit objects corresponding to that player_id
"""
def get_units_for_team(self, player_id):
units = []
for unit in self.game["units"]:
if unit["playerNum"] == player_id:
units.append(Unit(unit))
return units
"""
Gets my units
OUTPUT:
A list of Unit Objects corresponding to my units
"""
def get_my_units(self):
return self.get_units_for_team(self.player_id)
"""
Gets the units for the enemy player
OUTPUT:
A list of Unit Objects corresponding to the enemy's units
"""
def get_enemy_units(self):
if self.player_id == 1:
return self.get_units_for_team(2)
else:
return self.get_units_for_team(1)
"""
Returns the Tile at a given x and y
INPUT:
position: a tuple (x,y) specifying the desired x and y coordinates
OUTPUT:
The Tile Object corresponding to that position
"""
def get_tile(self, position):
tile_json = self.game["tiles"][position[0]][position[1]]
return Tile(tile_json)
"""
Returns the unit at a given position
INPUT:
position: a tuple (x,y) specifying the desired x and y coordinates
OUTPUT:
If there is a unit at that position, the Unit object at that position
If no unit is at that position, return None
"""
def get_unit_at(self, position):
unit_at_pos = None
for unit in self.game["units"]:
if unit["pos"]["x"] == position[0] and unit["pos"]["y"] == position[1]:
unit_at_pos = Unit(unit)
break
return unit_at_pos
"""
Get the shortest valid path from start to end position while avoiding tiles in tiles_to_avoid
INPUT:
start_position: a tuple (x,y) specifying the x and y coordinate of the start_position
end_position: a tuple (x,y) specifying the x and y coordinates of the end position
tiles_to_avoid: a list of tuples (x,y) specifying the x and y coordinates of tiles to avoid
OUTPUT:
A list of directions ('UP', 'DOWN', 'LEFT', or 'RIGHT') specifying how to get from the start_position to the end_position while avoiding the specified tiles
If no path exists, returns an None
"""
# Start and end position are tuples (x,y). tiles_to_avoid is a list of such tuples
def path_to(self, start_position, end_position, tiles_to_avoid=[]):
q = Queue()
q.put((start_position, []))
visited = [[False for i in range(len(self.game["tiles"]))] for j in range(len(self.game["tiles"]))]
while not q.empty():
position, directions = q.get()
if visited[position[1]][position[0]]:
continue
else:
visited[position[1]][position[0]] = True
if position == end_position:
return directions
left = (position[0] - 1, position[1])
if not ((left[0] < 0) or (left in tiles_to_avoid) or (self.get_tile(left).type != "BLANK")):
left_directions = copy.copy(directions)
left_directions.append("LEFT")
q.put((left, left_directions))
right = (position[0] + 1, position[1])
if not ((right[0] >= len(self.game["tiles"])) or (right in tiles_to_avoid) or (self.get_tile(right).type != "BLANK")):
right_directions = copy.copy(directions)
right_directions.append("RIGHT")
q.put((right, right_directions))
down = (position[0], position[1] - 1)
if not ((down[1] < 0) or (down in tiles_to_avoid) or (self.get_tile(down).type != "BLANK")):
down_directions = copy.copy(directions)
down_directions.append("DOWN")
q.put((down, down_directions))
up = (position[0], position[1] + 1)
if not ((up[1] >= len(self.game["tiles"][0])) or (up in tiles_to_avoid) or (self.get_tile(up).type != "BLANK")):
up_directions = copy.copy(directions)
up_directions.append("UP")
q.put((up, up_directions))
return None
"""
Gets the Unit for a specific unit_id
INPUT:
unit_id: The id of the desired unit
OUTPUT:
A Unit object corresponding the desired unit
"""
def get_unit(self, unit_id):
for unit in self.get_my_units():
if unit.id == unit_id:
return unit
for unit in self.get_enemy_units():
if unit.id == unit_id:
return unit
return None
"""
Given a unit_id and direction, returns where an attack in a certain direction would land on the map
INPUT:
unit_id: the id of the unit we want the attack pattern for
direction: Either 'UP', 'DOWN', 'LEFT', or 'RIGHT'. The direction we want to attack in
position (optional): a tuple (x,y) specifying the x and y coordinates of a position to attack from rather than the unit's current position.
OUTPUT:
A list of tuples of the form (pos, attack_damage)
pos: a Position Object
attack_damage: a positive value indicating the damage at that position
"""
def get_positions_of_attack_pattern(self, unit_id, direction, position = None):
unit = self.get_unit(unit_id)
attack_pattern = unit.attack
if direction == 'RIGHT':
attack_pattern = self.rotate_attack_pattern(attack_pattern)
elif direction == 'DOWN':
attack_pattern = self.rotate_attack_pattern(attack_pattern)
attack_pattern = self.rotate_attack_pattern(attack_pattern)
elif direction == 'LEFT':
attack_pattern = self.rotate_attack_pattern(attack_pattern)
attack_pattern = self.rotate_attack_pattern(attack_pattern)
attack_pattern = self.rotate_attack_pattern(attack_pattern)
elif direction != 'UP':
return None
attacks = []
for row in range(len(attack_pattern)):
for col in range(len(attack_pattern[row])):
attack = attack_pattern[row][col]
if attack == 0:
continue
# assume that unit is at the center of the 7 by 7 attack pattern
x_pos = unit.pos.x
y_pos = unit.pos.y
if position:
x_pos = position[0]
y_pos = position[1]
x_coordinate = x_pos + col - 3
y_coordinate = y_pos + row - 3
if x_coordinate >= 0 and x_coordinate < len(self.game["tiles"]) and y_coordinate >= 0 and y_coordinate < len(self.game["tiles"][0]):
attacks.append((Position({"x": x_coordinate, "y": y_coordinate}), attack))
return attacks
"""
Given an attack pattern, rotates it 90 degrees clockwise
INPUT:
attack: a 7x7 2d list indicating the attack pattern
OUTPUT:
The attack pattern rotated 90 degrees clockwise
"""
def rotate_attack_pattern(self, attack):
list_of_tuples = zip(*attack[::-1])
return [list(elem) for elem in list_of_tuples]