Created
November 17, 2020 13:39
-
-
Save tomowarkar/bf37f911303a892226b23219b5043227 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import subprocess as sp | |
| from copy import deepcopy | |
| from random import choice | |
| from typing import List, Tuple | |
| from pynput.keyboard import Key, Listener | |
| Matrix = List[List[int]] | |
| SQR_N = 4 | |
| rangeN = range(SQR_N) | |
| CELL_WIDTH = 8 | |
| CELL_PDY = 1 | |
| FIELD_HEIGHT = (CELL_PDY * 2 + 1) * SQR_N + 4 | |
| FIELD_WIDTH = CELL_WIDTH * SQR_N | |
| BIN_N = 14 | |
| NUM_COLORS = { | |
| str(1 << i): f"\033[38;2;{(BIN_N-i)<<3};{(BIN_N-i)<<4};255m{1<<i}\033[0m" | |
| for i in range(1, BIN_N) | |
| } | |
| class Dir4: | |
| """上下左右移動の情報を保持する | |
| """ | |
| left = {"name": "Left", "id": 0, "dir": (0, -1)} | |
| right = {"name": "Right", "id": 1, "dir": (0, 1)} | |
| up = {"name": "Up", "id": 2, "dir": (-1, 0)} | |
| down = {"name": "Down", "id": 3, "dir": (1, 0)} | |
| dir4 = [left, right, up, down] | |
| class Game: | |
| def __init__(self): | |
| self.dirs = Dir4() | |
| self.game_start() | |
| def game_start(self): | |
| sp.run("clear", shell=True) | |
| self.matrix = [[0] * SQR_N for _ in rangeN] | |
| self.matrix = add_new_tile(self.matrix, 2) | |
| self.matrix = add_new_tile(self.matrix, 2) | |
| self.score = 0 | |
| self.moves = 0 | |
| self.movable_dirs = movable_dir(self.matrix) | |
| self.draw_matrix() | |
| self.restart = False | |
| with Listener(on_press=self.key_press, on_release=self.key_release) as listener: | |
| listener.join() | |
| if self.restart: | |
| self.game_start() | |
| else: | |
| sp.run("clear", shell=True) | |
| def update_matrix(self, f): | |
| self.matrix, s = f(self.matrix) | |
| self.score += s | |
| self.moves += 1 | |
| self.matrix = add_new_tile(self.matrix, choice([2] * 9 + [4])) | |
| self.clear_matrix() | |
| self.draw_matrix() | |
| self.movable_dirs = movable_dir(self.matrix) | |
| if not any(self.movable_dirs): | |
| self.game_over() | |
| def key_press(self, key): | |
| if key in {Key.left}: | |
| d = self.dirs.left | |
| if not self.movable_dirs[d["id"]]: | |
| return | |
| self.update_matrix(move_left) | |
| elif key in {Key.right}: | |
| d = self.dirs.right | |
| if not self.movable_dirs[d["id"]]: | |
| return | |
| self.update_matrix(move_right) | |
| elif key in {Key.up}: | |
| d = self.dirs.up | |
| if not self.movable_dirs[d["id"]]: | |
| return | |
| self.update_matrix(move_up) | |
| elif key in {Key.down}: | |
| d = self.dirs.down | |
| if not self.movable_dirs[d["id"]]: | |
| return | |
| self.update_matrix(move_down) | |
| elif key in {Key.space}: | |
| self.restart = True | |
| return False | |
| def key_release(self, key): | |
| if key in {Key.esc}: | |
| return False | |
| def clear_matrix(self): | |
| print(f"\x1b[{FIELD_HEIGHT}A", end="") | |
| def game_over(self): | |
| self.clear_matrix() | |
| row = FIELD_HEIGHT // 2 | |
| for i in range(FIELD_HEIGHT): | |
| if i == row - 1: | |
| text = "GAME OVER!" | |
| elif i == row: | |
| text = f"Result: {self.score} points" | |
| elif i == row + 1: | |
| text = "restart: space" | |
| elif i == row + 2: | |
| text = "exit: esc" | |
| else: | |
| text = "" | |
| print(format(text, f"^{FIELD_WIDTH}")) | |
| def draw_matrix(self): | |
| text = "" | |
| for i in rangeN: | |
| text += "\n" * CELL_PDY | |
| for j in rangeN: | |
| value = str(self.matrix[i][j]) | |
| fstr = format(value, f"^{CELL_WIDTH}") | |
| if value == "0": | |
| text += fstr | |
| else: | |
| text += fstr.replace(value, NUM_COLORS[value]) | |
| text += "\n" + "\n" * CELL_PDY | |
| text += format(f"Moves: {self.moves}, Score: {self.score}", f"<{FIELD_WIDTH}") | |
| text += "\n" | |
| text += format(f"exit: esc, restart: spc", f"<{FIELD_WIDTH}") | |
| text += "\n" | |
| text += format(f"move: ↑ ↓ → ←", f"<{FIELD_WIDTH}") | |
| print(text) | |
| def stack(matrix: Matrix) -> Matrix: | |
| """行列の列の0以外の各要素を左につめる | |
| Args: | |
| matrix (Matrix): N x M matrix. | |
| Returns: | |
| Matrix: stacked matrix. | |
| Example: | |
| >>> matrix = [[0, 1], [2, 2]] | |
| >>> stack(matrix) | |
| [[1, 0], [2, 2]] | |
| """ | |
| new_matrix = [[0] * len(i) for i in matrix] | |
| for i, row in enumerate(matrix): | |
| pos = 0 | |
| for j, cell in enumerate(row): | |
| if cell != 0: | |
| new_matrix[i][pos] = matrix[i][j] | |
| pos += 1 | |
| return new_matrix | |
| def combine(matrix: Matrix) -> Tuple[Matrix, int]: | |
| """行列の列の0以外の各要素について隣り合う2つの要素の値が同じ場合に結合する | |
| Args: | |
| matrix (Matrix): N x M matrix. | |
| Returns: | |
| Tuple[Matrix, int]: combined matrix and sum of combined value. | |
| Example: | |
| >>> matrix = [[0, 1], [2, 2]] | |
| >>> combine(matrix) | |
| ([[0, 1], [4, 0]], 4) | |
| """ | |
| new_matrix = deepcopy(matrix) | |
| score = 0 | |
| for i, row in enumerate(new_matrix): | |
| for j, cell in enumerate(row[:-1]): | |
| if cell == 0: | |
| continue | |
| if cell != new_matrix[i][j + 1]: | |
| continue | |
| new_matrix[i][j] *= 2 | |
| new_matrix[i][j + 1] = 0 | |
| score += new_matrix[i][j] | |
| return new_matrix, score | |
| def reverse(matrix: Matrix) -> Matrix: | |
| """列の左右反転 | |
| Args: | |
| matrix (Matrix): N x M matrix. | |
| Returns: | |
| Matrix: reversed matrix | |
| Example: | |
| >>> matrix = [[0, 1], [2, 2]] | |
| >>> reverse(matrix) | |
| [[1, 0], [2, 2]] | |
| """ | |
| new_matrix = [] | |
| for i, row in enumerate(matrix): | |
| new_matrix.append(matrix[i][::-1]) | |
| return new_matrix | |
| def transpose(matrix: Matrix) -> Matrix: | |
| """Transpose of a matrix. | |
| Args: | |
| matrix (Matrix): N x M matrix. | |
| Returns: | |
| Matrix: M x N matrix. | |
| Example: | |
| >>> matrix = [[1, 2], [3, 4], [5, 6]] | |
| >>> transpose(matrix) | |
| [[1, 3, 5], [2, 4, 6]] | |
| """ | |
| new_matrix = [[0] * len(matrix) for _ in matrix[0]] | |
| for i, row in enumerate(new_matrix): | |
| for j, _ in enumerate(row): | |
| new_matrix[i][j] = matrix[j][i] | |
| return new_matrix | |
| def movable_dir(matrix: Matrix) -> List[bool]: | |
| """移動可能な方向を探索する | |
| 各要素において上下左右の値が0もしくは元の要素と等しい場合に移動可能とする | |
| Args: | |
| matrix (Matrix): N x M matrix. | |
| Returns: | |
| List[bool]: [left, right, up, down] | |
| Example: | |
| >>> matrix = [[1, 1], [3, 0]] | |
| >>> movable_dir(matrix) | |
| [True, True, False, True] | |
| """ | |
| h, w = len(matrix), len(matrix[0]) | |
| flags = [False] * 4 | |
| for i, row in enumerate(matrix): | |
| for j, cell in enumerate(row): | |
| if cell == 0: | |
| continue | |
| for idx, (dy, dx) in map(lambda x: (x["id"], x["dir"]), Dir4.dir4): | |
| ny, nx = i + dy, j + dx | |
| if nx < 0 or ny < 0 or nx >= w or ny >= h: | |
| continue | |
| if matrix[ny][nx] == cell: | |
| flags[idx] = True | |
| if matrix[ny][nx] == 0: | |
| flags[idx] = True | |
| if all(flags): | |
| return flags | |
| return flags | |
| def move_left(matrix: Matrix) -> Tuple[Matrix, int]: | |
| """2048 game において 行列が左移動をした結果とスコアを返す | |
| Args: | |
| matrix (Matrix): N x M matrix. | |
| Returns: | |
| Tuple[Matrix, int]: left moved matrix and score | |
| Example: | |
| >>> matrix = [[1, 1], [3, 0]] | |
| >>> move_left(matrix) | |
| ([[2, 0], [3, 0]], 2) | |
| """ | |
| new_matrix = stack(matrix) | |
| new_matrix, score = combine(new_matrix) | |
| new_matrix = stack(new_matrix) | |
| return new_matrix, score | |
| def move_right(matrix: Matrix) -> Tuple[Matrix, int]: | |
| """2048 game において 行列が右移動をした結果とスコアを返す | |
| Args: | |
| matrix (Matrix): N x M matrix. | |
| Returns: | |
| Tuple[Matrix, int]: right moved matrix and score | |
| Example: | |
| >>> matrix = [[1, 1], [3, 0]] | |
| >>> move_right(matrix) | |
| ([[0, 2], [0, 3]], 2) | |
| """ | |
| new_matrix = reverse(matrix) | |
| new_matrix, score = move_left(new_matrix) | |
| new_matrix = reverse(new_matrix) | |
| return new_matrix, score | |
| def move_up(matrix: Matrix) -> Tuple[Matrix, int]: | |
| """2048 game において 行列が上移動をした結果とスコアを返す | |
| Args: | |
| matrix (Matrix): N x M matrix. | |
| Returns: | |
| Tuple[Matrix, int]: up moved matrix and score | |
| Example: | |
| >>> matrix = [[1, 1], [3, 0]] | |
| >>> move_right(matrix) | |
| ([[1, 1], [3, 0]], 0) | |
| """ | |
| new_matrix = transpose(matrix) | |
| new_matrix, score = move_left(new_matrix) | |
| new_matrix = transpose(new_matrix) | |
| return new_matrix, score | |
| def move_down(matrix: Matrix) -> Tuple[Matrix, int]: | |
| """2048 game において 行列が下移動をした結果とスコアを返す | |
| Args: | |
| matrix (Matrix): N x M matrix. | |
| Returns: | |
| Tuple[Matrix, int]: down moved matrix and score | |
| Example: | |
| >>> matrix = [[1, 1], [3, 0]] | |
| >>> move_right(matrix) | |
| ([[1, 0], [3, 1]], 0) | |
| """ | |
| new_matrix = transpose(matrix) | |
| new_matrix = reverse(new_matrix) | |
| new_matrix, score = move_left(new_matrix) | |
| new_matrix = reverse(new_matrix) | |
| new_matrix = transpose(new_matrix) | |
| return new_matrix, score | |
| def add_new_tile(matrix: Matrix, n: int) -> Matrix: | |
| """行列の 0要素の中からランダムに1要素を選び, nを代入する. | |
| Args: | |
| matrix (Matrix): N x M matrix. | |
| n (int): insert number | |
| Returns: | |
| Matrix: N x M matrix. | |
| Example: | |
| >>> matrix = [[1, 1], [3, 0]] | |
| >>> add_new_tile(matrix, 2) | |
| [[1, 1], [3, 2]] | |
| """ | |
| empty_cells: List[Tuple[int, int]] = [] | |
| for i, row in enumerate(matrix): | |
| for j, cell in enumerate(row): | |
| if cell == 0: | |
| empty_cells.append((i, j)) | |
| if empty_cells == []: | |
| return matrix | |
| row, col = choice(empty_cells) | |
| matrix[row][col] = n | |
| return matrix | |
| if __name__ == "__main__": | |
| Game() |
Author
tomowarkar
commented
Nov 17, 2020

Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment