-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgame.py
More file actions
217 lines (170 loc) · 6.05 KB
/
Copy pathgame.py
File metadata and controls
217 lines (170 loc) · 6.05 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
from enum import IntEnum
from random import Random
from typing import List, Optional, Tuple
from submission.snake import BaseAgent
BOARD_SIZE = 10
SPAWNING_CELLS = [(0, 0), (0, 1), (0, 2), (0, 3)]
class Action(IntEnum):
UP = 0
RIGHT = 1
DOWN = 2
LEFT = 3
class GameOver(ValueError):
pass
class CellState(IntEnum):
EMPTY = 0
TAIL = 1
HEAD = 2
FRUIT = 3
class Board:
def __init__(self, size, seed=None):
"""Initialise the board.
Args:
size (int): The size of the board.
seed (int, optional): The seed for the random number generator. Defaults to None.
"""
self.rng = Random(seed)
self.size = size
self.direction = Action.RIGHT
self.snake = [(0, 0), (0, 1), (0, 2), (0, 3)] # tail to head
self.fruit = None
self._clear_board()
def _clear_board(self):
"""Clear the board."""
self.board = [
[CellState.EMPTY for j in range(self.size)] for i in range(self.size)
]
def spawn_snake(self, cells: List[Tuple[int, int]]) -> None:
"""Spawn the snake.
Args:
cells (List[Tuple[int, int]]): The cells that make up the snake.
"""
self.snake = cells[:]
self.board[cells[-1][0]][cells[-1][1]] = CellState.HEAD
for cell in cells[:-1]:
self.board[cell[0]][cell[1]] = CellState.TAIL
def spawn_fruit(self) -> Tuple[int, int]:
"""Spawn a fruit.
Returns:
Tuple[int, int]: The coordinates of the fruit.
"""
possible_cells = [
(i, j)
for i in range(self.size)
for j in range(self.size)
if (i, j) not in self.snake
]
if not possible_cells:
raise GameOver
cell = self.rng.choice(possible_cells)
self.board[cell[0]][cell[1]] = CellState.FRUIT
self.fruit = cell
return self.fruit
def is_alive(self) -> bool:
"""Check if the snake is alive.
Returns:
bool: True if the snake is alive, False otherwise.
"""
head = self.snake[-1]
snake_body = self.snake[:-1]
return head not in snake_body
def _wrap_cell(self, point: Tuple[int, int]) -> Tuple[int, int]:
"""Wrap the cell.
Args:
point (Tuple[int, int]): The cell to wrap.
Returns:
Tuple[int, int]: The wrapped cell.
"""
return (point[0] % self.size, point[1] % self.size)
def get_direction(self, direction: Action) -> Action:
"""Get the direction.
Args:
direction (Action): The direction.
Returns:
Action: The direction.
"""
# Check if impossible direction
if self.direction == Action.UP and direction == Action.DOWN:
return self.direction
elif self.direction == Action.DOWN and direction == Action.UP:
return self.direction
elif self.direction == Action.RIGHT and direction == Action.LEFT:
return self.direction
elif self.direction == Action.LEFT and direction == Action.RIGHT:
return self.direction
return direction
def _get_new_head_position(self, direction: Action) -> Tuple[int, int]:
"""Get the new head position.
Args:
direction (Action): The direction.
Returns:
Tuple[int, int]: The new head position.
"""
self.direction = self.get_direction(direction)
# Determine head coords
x, y = self.snake[-1][:]
# Calc new head coords
if self.direction == Action.UP:
x -= 1
elif self.direction == Action.DOWN:
x += 1
elif self.direction == Action.RIGHT:
y += 1
elif self.direction == Action.LEFT:
y -= 1
# Wrap the board cell
return self._wrap_cell((x, y))
def move(
self, direction: Action
) -> Tuple[Tuple[int, int], Optional[Tuple[int, int]]]:
"""Moves the snake in a particular direction one step.
Args:
direction (Action): The direction.
Raises:
GameOver: The snake tried to move into itself.
Returns:
Tuple[int, int]: The new head location.
Optional[Tuple[int, int]]: The location of the fruit if it was eaten, None otherwise.
"""
head = self._get_new_head_position(direction)
is_eating_fruit = self.fruit == head
if not is_eating_fruit:
self.board[self.snake[0][0]][self.snake[0][1]] = CellState.EMPTY
self.snake.pop(0)
self.board[self.snake[-1][0]][self.snake[-1][1]] = CellState.TAIL
self.board[head[0]][head[1]] = CellState.HEAD
self.snake.append(head)
if not self.is_alive():
raise GameOver(
f"Snake died at {self.snake[-1]} and length {len(self.snake)}."
)
if is_eating_fruit:
return head, self.spawn_fruit()
return head, None
class SnakeGame:
def __init__(self, agent: BaseAgent, seed: Optional[int] = None) -> None:
self.agent = agent
self.board = Board(BOARD_SIZE, seed)
def initialise(self) -> Tuple[List[Tuple[int, int]], Tuple[int, int]]:
"""Initialise the game.
Returns:
Tuple[List[Tuple[int, int]], Tuple[int, int]]: The coorinates of the snake and fruit.
"""
self.board.spawn_snake(SPAWNING_CELLS)
fruit = self.board.spawn_fruit()
return SPAWNING_CELLS, fruit
async def _request_move(self) -> Action:
"""Request a move from the agent.
Returns:
Action: The direction to move in.
"""
self.agent.update(self.board.direction, self.board.snake)
return await self.agent.make_move(self.board.board)
async def run(self):
"""Plays a game of snake."""
try:
while True:
direction = await self._request_move()
yield self.board.move(direction)
except GameOver:
pass