-
Notifications
You must be signed in to change notification settings - Fork 0
/
board.py
332 lines (289 loc) · 11.2 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
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
"""
FILE: board.py
DESCRIPTION: a 'Board' class used for a 'snake' game.
"""
###############################################################################
# Imports #
###############################################################################
import game_utils
from typing import Optional, List, Tuple
from board_cell import BoardCell
from snake import Snake
from wall import Wall
from game_display import GameDisplay
###############################################################################
# Class & Inner Functions #
###############################################################################
class Board:
"""
A 'SnakeGame' class used for a 'snake' game
"""
def __init__(self, is_debug: bool = False) -> None:
self.width: int = game_utils.size.width
self.height: int = game_utils.size.height
self.__key_clicked: Optional[str] = None
self.__walls: List[Wall] = []
self.__apples: List[BoardCell] = []
self.__rounds: int = 0
self.__score: int = 0
self.is_debug: bool = is_debug
self.is_over: bool = False
self.snake = Snake(self.width // 2, self.height // 2, length=3)
# region get & set methods
# region property: rounds
def get_rounds(self) -> int:
"""
Returns the board's current round
"""
return self.__rounds
def add_round(self) -> None:
"""
Adds to the total rounds
"""
self.__rounds += 1
# endregion property: rounds
# region property: score
def get_score(self) -> int:
"""
Returns the board's current score
"""
return self.__score
def increase_score(self, value: int) -> None:
"""
Adds to the total score
"""
self.__score += value
# endregion property: score
# endregion get & set methods
# region action methods
def read_key(self, key_clicked: Optional[str]) -> None:
"""
Reads the key pressed by the user
:param key_clicked: the key pressed by the user
:return: None
"""
self.__key_clicked = key_clicked
def update_moving_objects(self) -> None:
"""
Updates all moving objects in the game
"""
# region update walls
if self.__rounds % 2 == 0:
for wall in self.__walls:
wall.move()
self.remove_walls()
# endregion update walls
# region update snake
if not self.is_debug:
self.snake.update_direction(self.__key_clicked)
self.snake.move()
# endregion update snake
def draw_list_of_board_cells(self, list_of_board_cells: List[BoardCell],
gd: GameDisplay) -> None:
"""
Draws all BoardCell(s) from a given list
:param list_of_board_cells: a list of BoardCell(s)
:param gd: a GameDisplay
:return: None
"""
for cell in list_of_board_cells:
cell_row, cell_col = cell.get_location()
# verify in board boundaries
if self.is_coord_in_board_boundaries((cell_row, cell_col)):
cell_color = cell.get_color()
gd.draw_cell(cell_row, cell_col, cell_color)
def draw_board(self, gd: GameDisplay) -> None:
"""
Draws the board
:param gd: a GameDisplay
:return: None
"""
# draws apples before walls, so if a wall ran into an apple,
# the wall will be drawn on top of it
# apples
self.draw_list_of_board_cells(self.__apples, gd)
# snake
if not self.is_debug:
self.draw_list_of_board_cells(self.snake.get_snake_cells(), gd)
# walls
for wall in self.__walls:
self.draw_list_of_board_cells(wall.get_wall_cells(), gd)
# endregion action methods
# region comparion methods
def is_coord_in_board_boundaries(self, coordinate: Tuple[int, int]) -> \
bool:
"""
Checks whther the given coordinate is in the board boundaries
:param coordinate: a coordinate of a board cell
:return: True if the coordinate is in the board boundaries else False
"""
if not (0 <= coordinate[0] < self.width):
return False
if not (0 <= coordinate[1] < self.height):
return False
return True
# endregion comparion methods
# region game objects methods
# region walls
def get_walls(self) -> List[Wall]:
"""
Returns a list of all the walls on the board
"""
return self.__walls
def is_wall_valid_to_place(self, wall: Wall) -> bool:
"""
Checks whether all the wall's cells are currently empty on the
board, therefore it is valid to place
:param wall: a new wall candidate
:return: True if all cells are empty, False otherwise
"""
for cell in wall.wall_cells:
# snake position comparions
if not self.is_debug:
if cell.get_location() in \
self.snake.get_snake_cells_locations():
return False
# existing walls comparions
for existing_wall in self.__walls:
if cell.get_location() in \
existing_wall.get_wall_cells_locations():
return False
# existing apples comparions
for existing_apple in self.__apples:
if cell.get_location() == existing_apple.get_location():
return False
return True
def add_wall(self) -> bool:
"""
Adds a wall to the board
:return: True if the wall was added, False otherwise
"""
new_wall_col, new_wall_row, new_wall_direction = \
game_utils.get_random_wall_data()
# verify conditions
# - 1: in board's boundaries
wall_in_boundaries = \
self.is_coord_in_board_boundaries((new_wall_col, new_wall_row))
if not wall_in_boundaries:
return False
# - 2: is valid to place
new_wall = Wall(new_wall_col, new_wall_row, new_wall_direction)
if not self.is_wall_valid_to_place(new_wall):
return False
# add wall
self.__walls.append(new_wall)
return True
def should_remove_wall(self, wall: Wall) -> bool:
"""
Checks if the given wall is outside the board's boundaries,
therefore should be removed
:param wall: a wall candidate
:return: True if the wall should be removed, False otherwise
"""
body_index = len(wall.wall_cells) // 2
tail_index = 0
wall_body_in_board_boundaries = self.is_coord_in_board_boundaries(
wall.wall_cells[tail_index].get_location())
wall_tail_in_board_boundaries = self.is_coord_in_board_boundaries(
wall.wall_cells[body_index].get_location())
return not wall_body_in_board_boundaries and \
not wall_tail_in_board_boundaries
def remove_walls(self) -> None:
"""
Deletes all walls which should be removed
"""
for index in range(len(self.__walls) - 1, -1, -1):
if self.should_remove_wall(self.__walls[index]):
self.__walls.pop(index)
# endregion walls
# region apples
def get_apples(self) -> List[BoardCell]:
"""
Returns a list of all the apples on the board
"""
return self.__apples
def is_apple_valid_to_place(self, apple: BoardCell) -> bool:
"""
Checks whether a given coordinate is currently empty on the board,
therefore it is valid to place
:param apple: the new apple candidate
:return: True if the cell are empty, False otherwise
"""
# snake position comparions
coordinate = apple.get_location()
if coordinate in self.snake.get_snake_cells_locations():
return False
# existing walls comparions
for existing_wall in self.__walls:
if coordinate in existing_wall.get_wall_cells_locations():
return False
# existing apples comparions
for existing_apple in self.__apples:
if coordinate == existing_apple.get_location():
return False
return True
def add_apple(self) -> bool:
"""
Adds an apple to the game
:return: True if apple was added, False otherwise
"""
new_apple_col, new_apple_row = game_utils.get_random_apple_data()
# verify conditions
# - 1: in board's boundaries
apple_in_boundaries = self.is_coord_in_board_boundaries((
new_apple_col, new_apple_row))
if not apple_in_boundaries:
return False
# - 2: is valid to place
new_apple = BoardCell(new_apple_col, new_apple_row, color="green")
if not self.is_apple_valid_to_place(new_apple):
return False
# add apple
self.__apples.append(new_apple)
return True
def remove_apple(self, apple: BoardCell) -> bool:
"""
Removes the apple in a given coordinate from the board
:param apple: the apple to remove
:return: True if succeeded to find and remove the apple,
False otherwise
"""
for board_apple in self.__apples:
if apple == board_apple:
self.__apples.remove(apple)
return True
return False
# endregion apples
# region snake
def get_snake_cells_length(self) -> int:
"""
Returns a list of all the snake cells on the board
"""
return len(self.snake.get_snake_cells())
def cut_snake_tail(self, cutting_coordinate: Tuple[int, int]) -> None:
"""
Cuts the tail of the snake
:param cutting_coordinate: the coordinate of the tail to cut
:return: None
"""
self.snake.cut_tail(cutting_coordinate)
def is_snake_out_boundaries(self) -> bool:
"""
Checks whther the snake collided with the board
:return: True if the snake is out of boundaries, False otherwise
"""
for cell in self.snake.get_snake_cells():
if not self.is_coord_in_board_boundaries(cell.get_location()):
return True
return False
def is_snake_tangled(self) -> bool:
"""
Checks whether the snake is tangled (ate itself)
:return: True if the snake not tangled, False otherwise
"""
return self.snake.is_tangled()
# endregion snake
# endregion game objects methods
if __name__ == "__main__":
print("This script is part of the 'Snake' board game.\nYou should run:\n"
"> python game_display.py [optional arguments|--help]")