-
Notifications
You must be signed in to change notification settings - Fork 0
/
Board.py
250 lines (189 loc) · 7.6 KB
/
Board.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
from exceptions import MoveError, Win
class GridIterator:
"""Custom Iterator for iterating over the rows of a grid."""
def __init__(self, board):
self._board = board
self._current_row = 0
def __iter__(self):
return self
def __next__(self):
if self._current_row >= Board.SIZE:
raise StopIteration
row_result = list(iter(self._board))[self._current_row]
self._current_row += 1
return row_result
class Board:
"""
Santorini Board that holds the workers and grid of Cells of buildings.
SIZE (int): The size of the board (5).
_grid (list): The 5x5 grid representing the board.
_state (Memento): The state of the board.
"""
SIZE = 5
def __init__(self):
self._state = None
self._grid = Grid()
self._score = ScoreCalculator(self._state, self._grid)
@property
def state(self):
return self._state
def _update_state(self, memento):
# Uses and Supplies (Score Class) Deep Copy of the Workers
self._state = memento.get_state()
self._score = ScoreCalculator(self._state, self._grid)
self._grid = memento.get_grid()
def check_win(self, player):
"""Checks if a player has won. Input: player"""
for worker in player.workers:
x = worker.position
y = self.get_cell(x).level
if y == 3:
raise Win
return False
def occupied(self, position):
worker = self._state.get_worker_by_position(position)
return worker.symbol if worker else None
def validate(self, type, new, direction):
"""Validates a move or build action. Input: action type, positions, direction (for error)"""
if not (0 <= new[0] < self.SIZE and 0 <= new[1] < self.SIZE):
raise MoveError(type, direction)
return True
def get_ring(self, pos):
"""Returns a ring of positions around a given position."""
ans = []
for y in range(max(0, pos[0] - 1), min(5, pos[0] + 2)):
for x in range(max(0, pos[1] - 1), min(5, pos[1] + 2)):
if (y, x) == pos:
continue
ans.append((y, x))
return ans
def __iter__(self):
return GridIterator(self._grid)
def build(self, position):
"""Builds a structure (+1) at the specified position on the board."""
cell = self._grid.get_cell(position)
cell.upgrade()
def score(self, workers_positions, color):
"""Calculates the score (cell score, heuristic position score, distance score)."""
total_distance = self._score.total_distance_score(color)
curr_cell_score = self._score.total_cell_score(workers_positions)
curr_pos_score = self._score.height_score(workers_positions)
return (curr_cell_score, curr_pos_score, total_distance)
def check_score(self, symbol, color, new_positions):
"""Check score by moving worker to calculate heuristic optimal moves."""
workers_positions = self._state.get_workers_pos_by_symbol(symbol)
curr_worker = self._state.get_worker_by_symbol(symbol)
previous_position = curr_worker.position
curr_worker.position = new_positions
try:
return self._score.score(workers_positions, color)
finally:
curr_worker.position = previous_position
def get_cell(self, pos):
"""Get the cell at the specified position."""
return self._grid.get_cell(pos)
def __getitem__(self, key):
return self._grid[key]
def __repr__(self):
output = ""
for i, row in enumerate(self):
output += "+--" * Board.SIZE + "+\n"
row_result = ""
for j, cell in enumerate(row):
height = cell.level
worker_symbol = self.occupied((i, j))
row_result += f"|{height}" + (worker_symbol if worker_symbol else " ")
output += row_result + "|\n"
output += "+--" * Board.SIZE + "+"
return output
@property
def grid(self):
return self._grid
@grid.setter
def grid(self, grid):
self._grid = grid
class ScoreCalculator:
"""Class for calculating scores based on board and player information."""
def __init__(self, state, grid):
self._state = state
self._grid = grid
def check_win(self, player):
"""Checks if a player has won."""
for worker in player.workers:
x = worker.position
y = self._state.get_cell(x).level
if y == 3:
raise Win
return False
def total_cell_score(self, workers_positions):
"""Calculates the total cell score for given worker positions."""
result = 0
for position in workers_positions:
result += self._grid.get_cell(position).level
return result
def height_score(self, workers_positions):
"""Calculates the height score for given player workers list."""
score = 0
for position in workers_positions:
score += 2 - max(abs(position[0] - 2), abs(position[1] - 2))
return score
def chebyshev_distance(self, position1, position2):
"""Calculates Chebyshev distance between two positions."""
return max(abs(position1[0] - position2[0]), abs(position1[1] - position2[1]))
def total_distance_score(self, color):
"""Calculates the total distance score for a given player color."""
minimize = 0
result = 0
if color == "white":
workers1 = self._state.blue_workers
workers2 = self._state.white_workers
else:
workers1 = self._state.white_workers
workers2 = self._state.blue_workers
for worker1 in workers1:
minimize = 99
for worker2 in workers2:
minimize = min(
self.chebyshev_distance(worker1.position, worker2.position),
minimize,
)
result += minimize
return 8 - result
def score(self, workers_positions, color):
"""Calculates the score (cell score, heuristic position score, distance score)."""
total_distance = self.total_distance_score(color)
curr_cell_score = self.total_cell_score(workers_positions)
curr_pos_score = self.height_score(workers_positions)
return (curr_cell_score, curr_pos_score, total_distance)
def check_score(self, symbol, color, new_positions):
"""Check score by moving worker to calculate heuristic optimal moves."""
workers_positions = self._state.get_workers_pos_by_symbol(symbol)
curr_worker = self._state.get_worker_by_symbol(symbol)
previous_position = curr_worker.position
curr_worker.position = new_positions
try:
return self.score(workers_positions, color)
finally:
curr_worker.position = previous_position
class Grid:
def __init__(self):
self._grid = [[Cell() for _ in range(Board.SIZE)] for _ in range(Board.SIZE)]
def get_cell(self, pos):
"""Get the cell at the specified position."""
return self._grid[pos[0]][pos[1]]
def __iter__(self):
return iter(self._grid)
class Cell:
"""
Board Cell holding a buildings.
_level (int): The level of the cell.
"""
def __init__(self, level=0) -> None:
self._level = level
def upgrade(self):
"""Upgrades the cell level if it is below the maximum."""
if self._level < 4:
self._level += 1
@property
def level(self):
return self._level