-
Notifications
You must be signed in to change notification settings - Fork 0
/
terminal_version.py
210 lines (177 loc) · 8.7 KB
/
terminal_version.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
import copy
import random
import time
class Board:
"""Initialise the board"""
def __init__(self, base, size, post_merge=False):
if base <= 1:
raise ValueError("The base must be greater than 1")
self.base = base
if size <= base:
raise ValueError("The grid dimensions must be greater than the base")
self.size = size
if post_merge: # variable that indicates whether adjacent identical tiles are merged together at the end
if base > 5:
raise ValueError("This feature only works for bases up to 5")
self.post_merge = post_merge
self.board = [[0] * size for _ in range(size)] # create empty board
self.score = 0 # initial score is zero
for _ in range(self.base):
self.add_new_tile()
"""Add a new tile to the board"""
def add_new_tile(self):
empty_tiles = [(row, col) for row in range(self.size) for col in range(self.size) if self.board[row][col] == 0]
if empty_tiles:
row, col = random.choice(empty_tiles) # selects the coordinates of a random empty tile
self.board[row][col] = self.base if random.random() < 0.9 else self.base ** 2
"""Compress the board, removing any empty tiles"""
def compress(self, row):
compressed_row = [num for num in row if num != 0]
compressed_row += [0] * (self.size - len(compressed_row)) # created for the left
return compressed_row
"""Merge the rows together"""
def merge(self, row):
for i in range(self.size - (self.base - 1)): # n instances of num need to be merged to create n * num
if row[i] != 0:
loop_broken = False # needed to keep track of whether any of the numbers don't match
for j in range(1, self.base):
if row[i + j] != row[i]:
loop_broken = True
break
if not loop_broken:
row[i] *= self.base
self.score += row[i]
for j in range(1, self.base): # clear the non-centre merged tiles
row[i + j] = 0
return row
"""Logic for moving the board to the left, upon which all of right, up and down are based"""
def move_left(self):
new_grid = []
valid_move = True # valid moves require the board to change state by the end
for row in self.board:
compressed_row = self.compress(row) # remove all empty tiles
# when moving to visualised version, this is where a visual update would be
merged_row = self.merge(compressed_row) # merge together tiles
# visual update 2
new_row = self.compress(merged_row) # remove any introduced empty tiles
# visual update 3
new_grid.append(new_row)
if self.board == new_grid:
valid_move = False
self.board = new_grid
return valid_move
def move_right(self):
self.board = [list(reversed(row)) for row in self.board] # reverse the board so it can be moved left
valid_move = self.move_left()
self.board = [list(reversed(row)) for row in self.board] # reverse back
return valid_move
def move_up(self):
self.board = [[self.board[j][i] for j in range(self.size)] for i in range(self.size)] # transpose
valid_move = self.move_left()
self.board = [[self.board[j][i] for j in range(self.size)] for i in range(self.size)] # transpose back
return valid_move
def move_down(self):
self.board = [[self.board[j][i] for j in range(self.size)] for i in range(self.size)] # transpose
valid_move = self.move_right()
self.board = [[self.board[j][i] for j in range(self.size)] for i in range(self.size)] # transpose back
return valid_move
"""Feature for base 3 onward, to merge together n adjacent tiles with the same value at the end"""
def merge_cells(self):
directions = [
(-1, 0), # above
(0, -1), # left
(1, 0), # below
(0, 1) # right
]
old_board = copy.deepcopy(self.board) # keeps track of whether there have been any changes
for row in range(self.size):
for col in range(self.size):
if self.board[row][col] != 0: # don't run for empty squares
same_value_locations = []
for y, x in directions:
new_row, new_col = row + y, col + x
if 0 <= new_row < self.size and 0 <= new_col < self.size: # accounts for edges of the board
if self.board[row][col] == self.board[new_row][new_col]:
same_value_locations.append((new_row, new_col))
if len(same_value_locations) == self.base - 1:
self.board[row][col] *= self.base
self.score += self.board[row][col]
for y_coord, x_coord in same_value_locations: # clear the non-centre merged tiles
self.board[y_coord][x_coord] = 0
break
if old_board != self.board: # shows the board update if the board has changed through merges
print("Merging...\n")
time.sleep(1)
self.display()
return old_board != self.board
"""Evaluates whether there are any empty tiles, or there are any valid moves on the board"""
def can_move(self):
for row in range(self.size):
for col in range(self.size):
if self.board[row][col] == 0: # empty tile present
return True
if row < self.size - (self.base - 1): # checks rows for valid moves if board is full
loop_broken = False
for i in range(1, self.base):
if self.board[row + i][col] != self.board[row][col]:
loop_broken = True
break
if not loop_broken:
return True
if col < self.size - (self.base - 1): # checks columns for valid moves if board is full
loop_broken = False
for j in range(1, self.base):
if self.board[row][col + j] != self.board[row][col]:
loop_broken = True
break
if not loop_broken:
return True
return False # no valid moves, i.e. game is over
"""Displays the board in the terminal"""
def display(self):
print(f'Score: {self.score}')
for row in self.board:
print(' '.join([f'{str(col):<5}' for col in row]))
print()
"""Game loop: terminal version"""
def play(self):
bindings = {
'w': self.move_up,
's': self.move_down,
'a': self.move_left,
'd': self.move_right
}
while True:
self.display()
if self.post_merge: # run the additional merging function if the toggle is on
while self.merge_cells(): # merge cells until there are no more changes to the board
pass
move = input("Enter a direction (w/a/s/d): ")
if move in bindings.keys():
valid_move = bindings[move]()
if valid_move:
if self.post_merge: # run the additional merging function if the toggle is on
while self.merge_cells(): # merge cells until there are no more changes to the board
pass
if not self.can_move(): # i.e. game over
self.display()
print("Game over!")
break
self.add_new_tile() # adds a new tile to the board
else:
print("Invalid move - move provided must change ths state of the board")
time.sleep(2)
print("\n---\n") # divides terminal between moves
else:
print("Invalid move - only w/a/s/d allowed")
time.sleep(2)
game6561 = Board(3, 5, True)
game6561.score = 17217
game6561.board = [
[2187, 2187, 0, 2187, 0],
[0, 27, 243, 27, 3],
[0, 0, 3, 0, 27],
[0, 0, 0, 3, 0],
[0, 0, 0, 0, 0]
]
game6561.play()