-
Notifications
You must be signed in to change notification settings - Fork 2
/
snake.py
125 lines (100 loc) · 3.91 KB
/
snake.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
import numpy as np
from numpy import random as rand
HEAD = 3
SNAKE = 2
FOOD = 1
BG = 0
class Snake:
"""A snake simulation."""
size = 8
markhead = False
walled = True
minsnakelen = 5
directions = 4 # Number of allowed directions (1: up, 2: down, 3: left, 4: right)
def __init__(self, markhead=markhead, walled=walled):
"""
Initializes a random square board of size size. Generates one snake and one food position.
:param markhead: can be either True or False depending on if the head of the snake should be marked
:param walled: a walled board does not allow for the snake to cross the borders
"""
self.markhead = markhead
self.walled = walled
self.size = Snake.size
self.steps = 0
self.highscore = 0
self.board = np.ones((self.size, self.size)) * BG
r, c = rand.randint(1, self.size - 1), rand.randint(1, self.size - 1)
self.body = [np.ravel_multi_index((r, c), self.board.shape)]
self.board.flat[self.body[0]] = SNAKE if not self.markhead else HEAD
self.board.flat[rand.choice(np.flatnonzero(self.board == BG))] = FOOD
self.dir = np.random.randint(0, Snake.directions)
def step(self, direction=None):
"""Moves the snake into the specified direction.
Appends a new snake marker in front and removes the last,
if the snake would be longer than its maximum length.
If food is consumed, the maximum length is increased by 1 and
a new food is spawned.
Checks if the snake wins or bites itself.
Args:
direction: The direction to move to as int.
1: up, 2: down, 3: left, 4: right
Returns:
1 if winning
-1 if losing
0 else
"""
if direction is None:
direction = self.dir
direction = abs(direction) % 4
if direction ^ 1 != self.dir:
self.dir = direction
if self.markhead:
self.board[self.head] = SNAKE
r, c = self.head
if self.dir & 2: # horizontal
c = (c + (self.dir & 1) * 2 - 1)
if self.walled and (c < 0 or c >= self.size):
return -1
c = c % self.size
else: # vertical
r = (r + (self.dir & 1) * 2 - 1)
if self.walled and (r < 0 or r >= self.size):
return -1
r = r % self.size
head = np.ravel_multi_index((r, c), self.board.shape)
if self.board.flat[head] == FOOD: # scored
self.highscore += 1
# check win condition
new_location = np.flatnonzero(self.board == BG)
if len(new_location) == 0:
return 1
food = rand.choice(new_location)
self.board.flat[food] = FOOD
if len(self.body) > (self.highscore + self.minsnakelen): # cut tail
self.board.flat[self.body.pop(0)] = BG
if self.board.flat[head] == SNAKE: # died
return -1
self.steps += 1
self.body.append(head)
self.board.flat[head] = SNAKE if not self.markhead else HEAD
return 0
@property
def head(self, rc=True):
"""Returns the current head position of the snake.
Args:
rc: True to return row and column (2D) coordinates.
Return:
2D coordinates if rc is True, otherwise 1D coordinate
"""
head = self.body[-1]
return np.unravel_index(head, self.board.shape) if rc else head
@property
def food(self, rc=True):
"""Returns the position of the current food item.
Args:
rc: True to return row and column (2D) coordinates.
Return:
2D coordinates if rc is True, otherwise 1D coordinate
"""
food = np.flatnonzero(self.body == FOOD)[0]
return np.unravel_index(food, self.board.shape) if rc else food