-
Notifications
You must be signed in to change notification settings - Fork 1
/
grid.py
158 lines (134 loc) · 6.81 KB
/
grid.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
import random
import numpy as np
class Grid:
"""Creates a grid of 'rows' by 'columns'"""
def __init__(self, rows, columns):
self.r_size = rows
self.c_size = columns
# initialize the empty grid as a dict, where keys are tuples of (row_index, column_index)
self.grid = {(r, c): 0 for r in range(self.r_size) for c in range(self.c_size)}
self.directions = {
"right": (self.r_size, self.get_row_coords, self.move_fwd),
"down": (self.c_size, self.get_column_coords, self.move_fwd),
"left": (self.r_size, self.get_row_coords, self.move_bwrd),
"up": (self.c_size, self.get_column_coords, self.move_bwrd)}
self.score: int = 0
self.still_playing = True
def to_numpy(self):
"""Returns the grid values as a 2d numpy array."""
return np.array([v for v in self.grid.values()]).reshape((self.r_size, self.c_size))
def update_grid(self):
"""Updates grid with some values. (used in debugging)"""
self.grid[(0, 0)] = 16
self.grid[(0, 1)] = 8
self.grid[(0, 2)] = 4
self.grid[(0, 3)] = 0
self.grid[(1, 0)] = 8
self.grid[(1, 1)] = 2
self.grid[(1, 2)] = 0
self.grid[(1, 3)] = 0
self.grid[(2, 0)] = 4
self.grid[(2, 1)] = 0
self.grid[(2, 2)] = 0
self.grid[(2, 3)] = 0
self.grid[(3, 0)] = 0
self.grid[(3, 1)] = 2
self.grid[(3, 2)] = 0
self.grid[(3, 3)] = 0
def get_empty(self):
"""Returns a list of coordinates of empty tiles."""
return [k for k, v in self.grid.items() if v == 0]
def spawn(self):
"""Inserts value 2 or 4 at random empty slots on the grid. If no empty tiles, the game is over."""
empty_coords = self.get_empty()
self.grid[random.choice(empty_coords)] = random.choices([2, 4], [0.8, 0.2])[0]
def get_column_coords(self, column_index):
"""Returns a list of coordinates representing a column on the grid."""
return [(r, c) for (r, c) in self.grid.keys() if c == column_index]
def get_row_coords(self, row_index):
"""Returns a list of coordinates representing a row on the grid."""
return [(r, c) for (r, c) in self.grid.keys() if r == row_index]
def move_fwd(self, line):
"""Iterates from right to left over a given row or column, to move or add to the next available position.
Start with penultimate position. For each non-zero tile, move the value all the way to the right,
until another non-zero value or the end of the line is encountered.
If the value encountered is the same, add to it, if it hasn't already been added into.
Else, place the value immediately before it, and keep iterating over the rest of the line.
eg.:
[0, 2, 0, 0] -> [0, 0, 0, 2]
[0, 2, 0, 2] -> [0, 0, 0, 4]
[2, 2, 0, 2] -> [0, 0, 2, 4]
[2, 2, 2, 2] -> [0, 0, 4, 4]
"""
curr, last = len(line) - 2, len(line) - 1
while curr >= 0:
if self.grid[line[curr]] == 0 and self.grid[line[last]] == 0: # 0 -> 0
curr -= 1
elif self.grid[line[curr]] == 0 and self.grid[line[last]] != 0: # 0 -> x
curr -= 1
elif self.grid[line[curr]] != 0 and self.grid[line[last]] == 0: # x -> 0
self.grid[line[curr]], self.grid[line[last]] = 0, self.grid[line[curr]]
curr -= 1
elif self.grid[line[curr]] == self.grid[line[last]]: # x -> x
self.score += self.grid[line[curr]]
self.grid[line[curr]], self.grid[line[last]] = 0, self.grid[line[curr]] * 2
curr -= 1
last -= 1
else: # x -> y
self.grid[line[curr]], self.grid[line[last - 1]] = 0, self.grid[line[curr]]
curr -= 1
last -= 1
def move_bwrd(self, line):
"""Iterates from left to right over a given row or column, to move or add to the previous available position.
Start with the second position. For each non-zero tile, move the value all the way to the left, until another
non-zero value or the beginning of the line is encountered.
If the value encountered is the same, add to it, if it hasn't already been added into.
Else, place the value immediately after it, and keep iterating over the rest of the line.
eg.:
[0, 2, 0, 0] -> [2, 0, 0, 0]
[0, 2, 0, 2] -> [4, 0, 0, 0]
[2, 2, 0, 2] -> [4, 2, 0, 0]
[2, 2, 2, 2] -> [4, 4, 0, 0]
"""
prev, curr = 0, 1
while curr < len(line):
if self.grid[line[prev]] == 0 and self.grid[line[curr]] == 0: # 0 <- 0
curr += 1
elif self.grid[line[prev]] == 0 and self.grid[line[curr]] != 0: # 0 <- x
self.grid[line[prev]], self.grid[line[curr]] = self.grid[line[curr]], 0
curr += 1
elif self.grid[line[prev]] != 0 and self.grid[line[curr]] == 0: # x <- 0
curr += 1
elif self.grid[line[prev]] == self.grid[line[curr]]: # x <- x
self.score += self.grid[line[curr]]
self.grid[line[prev]], self.grid[line[curr]] = self.grid[line[curr]] * 2, 0
curr += 1
prev += 1
else: # x <- y
self.grid[line[curr]], self.grid[line[prev + 1]] = 0, self.grid[line[curr]]
curr += 1
prev += 1
def slide(self, direction):
"""Slides in the given direction ('right', 'down', 'left', 'up') and returns True if move generated any change.
If 'right' -> applies 'move_fwd()' to each row.
If 'down' -> applies 'move_fwd()' to each column.
If 'left' -> applies 'move_bwrd()' to each row.
If 'up' -> applies 'move_bwrd()' to each column.
"""
prior = self.grid.copy()
for i in range(self.directions[direction][0]):
line = self.directions[direction][1](i)
self.directions[direction][2](line)
return self.grid != prior
def no_equal_neigbours(self, coord):
"""Checks if given coordinate has no equal neighbours, i.e. no adjacent equal values column-wise or row-wise."""
r, c = coord
neighbours_coord = [(r - 1, c) if r - 1 >= 0 else None,
(r + 1, c) if r + 1 < self.r_size else None,
(r, c - 1) if c - 1 >= 0 else None,
(r, c + 1) if c + 1 < self.c_size else None]
neighbours = [self.grid[t] for t in neighbours_coord if t]
return self.grid[coord] not in neighbours
def game_over(self):
"""Checks the entire board for no empty tiles and no equal neighbours."""
return all(self.no_equal_neigbours(k) for k in self.grid.keys()) and not self.get_empty()