def __init__(self, width: int, min_tile: int, max_tile: int): self.score = 0 self.min_tile = min_tile self.max_tile = max_tile self.mat = MutSqMat(width)
def test_eq(self): mat = MutSqMat(4) mat[Vec2(3, 3)] = 4 mat[Vec2(1, 1)] = 2 mat_ref = MutSqMat(4) mat_ref[Vec2(3, 3)] = 4 mat_ref[Vec2(1, 1)] = 2 self.assertEqual(mat_ref, mat)
def test_rotate_3(self): mat = MutSqMat(4) mat[Vec2(1, 1)] = 16 mat.rotate(3) mat_ref = MutSqMat(4) mat_ref[Vec2(2, 1)] = 16 self.assertEqual(mat_ref, mat)
def test_set_get_row(self): mat = MutSqMat(4) mat.set_row(0, [8, 32, 64, 64]) mat.set_row(1, [2, 4, 8, 16]) mat.set_row(2, [4, 2, 2, 128]) self.assertEqual([2, 4, 8, 16], mat.get_row(1))
def test_hash(self): mat = MutSqMat(4) mat[Vec2(2, 3)] = 32 mat_ref = mat.copy() mat_other = MutSqMat(4) mat_other[Vec2(1, 0)] = 2 hash_mat = hash(mat) hash_mat_ref = hash(mat_ref) hash_mat_other = hash(mat_other) self.assertEqual(hash_mat_ref, hash_mat) self.assertNotEqual(hash_mat_other, hash_mat)
def test_set_get(self): mat = MutSqMat(4) mat[Vec2(1, 1)] = 8 mat[Vec2(2, 3)] = 2 self.assertEqual(8, mat[Vec2(1, 1)]) self.assertEqual(2, mat[Vec2(2, 3)])
def test_contains(self): mat = MutSqMat(4) self.assertNotIn(64, mat) mat[Vec2(0, 2)] = 64 self.assertIn(64, mat)
def test_copy(self): mat = MutSqMat(4) mat.set_row(0, [16, None, 8, 4]) mat.set_row(2, [2, 2, None, None]) mat_copy = mat.copy() self.assertEqual(mat, mat_copy) self.assertFalse(mat_copy is mat)
def test_init(self): mat = MutSqMat(4) self.assertEqual(mat.width, 4)
def test_init_items(self): mat = MutSqMat(4, [1 for _ in range(4 * 4)]) self.assertEqual(4, mat.width) self.assertEqual([1 for _ in range(4 * 4)], mat._items)
class Game: """ Square game grid with maximum and minimum tile values and tracked score. """ def __init__(self, width: int, min_tile: int, max_tile: int): self.score = 0 self.min_tile = min_tile self.max_tile = max_tile self.mat = MutSqMat(width) @property def width(self) -> int: return self.mat.width def left(self) -> bool: changed = False for y in range(self.width): row = self.mat.get_row(y) old = row.copy() self._move_left(row) self.mat.set_row(y, row) if old != row: changed = True return changed def right(self) -> bool: self.mat.flip_vertical() changed = self.left() self.mat.flip_vertical() return changed def up(self) -> bool: self.mat.rotate(1) changed = self.left() self.mat.rotate(3) return changed def down(self) -> bool: self.mat.rotate(3) changed = self.left() self.mat.rotate(1) return changed def stuck(self) -> bool: """ Determines whether the game is stuck (i.e. no action can be performed). """ if None in self.mat: return False for x in range(self.width): for y in range(self.width): value = self.mat[Vec2(x, y)] # Does any tile have an adjacent tile with the same value? if 0 < x and value == self.mat[Vec2(x - 1, y)] \ or 0 < y and value == self.mat[Vec2(x, y - 1)] \ or x < self.width - 1 and value == self.mat[Vec2(x + 1, y)] \ or y < self.width - 1 and value == self.mat[Vec2(x, y + 1)]: return False return True def won(self) -> bool: """ Determines whether the game has been won by producing the largest tile. """ return self.max_tile in self.mat def full(self) -> bool: """ Determines whether the game grid is full of tiles. """ return None not in self.mat def place_two(self): """ Place a 2-valued tile in a random location. :raises ValueError When the grid is in a full state """ if self.full(): raise ValueError('Cannot place tile since game grid is full') empty_indices = [] for i, v in enumerate(self.mat._items): if v is None: empty_indices.append(i) choice = random.choice(empty_indices) self.mat._items[choice] = 2 def _move_left(self, line: List[Optional[int]]): """ Processes a line of tiles using the rules defined by the game. """ for curr_idx, current_value in enumerate(line): # Iterate through all following indices for follow_idx in range(curr_idx + 1, len(line)): follow_value = line[follow_idx] if follow_value is not None: if current_value is None: current_value = follow_value line[curr_idx] = follow_value line[follow_idx] = None continue # Merge when values are the same if follow_value == current_value: current_value *= 2 self.score += current_value line[curr_idx] = current_value line[follow_idx] = None break # Shift non-mergeable else: line[follow_idx] = None line[curr_idx + 1] = follow_value break