Skip to content

Instantly share code, notes, and snippets.

@tomowarkar
Created November 17, 2020 13:39
Show Gist options
  • Select an option

  • Save tomowarkar/bf37f911303a892226b23219b5043227 to your computer and use it in GitHub Desktop.

Select an option

Save tomowarkar/bf37f911303a892226b23219b5043227 to your computer and use it in GitHub Desktop.
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()
@tomowarkar
Copy link
Copy Markdown
Author

2048x4

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