Last active
June 30, 2021 22:36
-
-
Save shiracamus/d691300f25c6cbfca1058992e7b7130f 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 random | |
import pygame | |
from enum import Enum | |
# COLOR | |
BLACK = (0, 0, 0) | |
WHITE = (255, 255, 255) | |
FLORALWHITE = (255, 250, 240) | |
LIGHTGRAY = (211, 211, 211) | |
GRAY = (128, 128, 128) | |
RED = (255, 0, 0) | |
GREEN = (0, 128, 0) | |
BLUE = (0, 0, 255) | |
ORANGE = (255, 165, 0) | |
COLORS = ((204, 192, 179), (238, 228, 218), (238, 224, 198), # 2, 4, 8 | |
(243, 177, 116), (243, 177, 116), (248, 149, 90), # 16, 32, 64 | |
(249, 94, 50), (239, 207, 108), (239, 207, 99), # 128, 256, 512, | |
(239, 203, 82), (239, 199, 57), (239, 195, 41), # 1024, 2048, 4096 | |
(95, 218, 147)) # 8192 | |
class Command(Enum): | |
NOP = 0 | |
START = 1 | |
UP = 2 | |
DOWN = 3 | |
LEFT = 4 | |
RIGHT = 5 | |
UNDO = 6 | |
RESTART = 7 | |
QUIT = 8 | |
class Board: | |
def __init__(self, size=4): | |
"""縦・横がsizeマスのゲーム盤を作成する""" | |
self.SIZE = size | |
self._RANGE = range(size) | |
up_left = range(1, size) | |
down_right = range(0, size - 1)[::-1] | |
self._SLIDE = { # (dx, dy, range_x, range_y) | |
Command.UP: (0, -1, self._RANGE, up_left), | |
Command.DOWN: (0, +1, self._RANGE, down_right), | |
Command.LEFT: (-1, 0, up_left, self._RANGE), | |
Command.RIGHT: (+1, 0, down_right, self._RANGE), | |
} | |
self.reset() | |
def reset(self): | |
"""初期状態にリセットする""" | |
self._cells = [[0 for x in self._RANGE] for y in self._RANGE] | |
self._last = self._cells | |
def __iter__(self): | |
"""盤データ(列)のイテレータを返す""" | |
return iter(map(tuple, self._cells)) | |
def save(self): | |
""" 1つ前の状態に戻せるように現在の状態を保存する""" | |
self._last = [row.copy() for row in self._cells] | |
def load(self): | |
""" 1つ前の状態に戻す""" | |
self._cells = self._last | |
def _empties(self): | |
"""空いているマスの座標を返す""" | |
return [(x, y) | |
for y in self._RANGE | |
for x in self._RANGE | |
if self._cells[y][x] == 0] | |
def random_put_2(self): | |
"""空いているマスのひとつをランダムに選んで2を置く""" | |
x, y = random.choice(self._empties()) | |
self._cells[y][x] = 2 | |
def _numbers(self, command): | |
"""数字があるマスの座標とcommand方向の座標(x, y, cx, cy)を返す""" | |
if command not in self._SLIDE: | |
return [] | |
dx, dy, rx, ry = self._SLIDE[command] | |
return [(x, y, x + dx, y + dy) | |
for y in ry | |
for x in rx | |
if self._cells[y][x] != 0] | |
def can_slide(self, command): | |
"""command方向にスライド可能ならTrueを返す""" | |
# 数字があるマスのcommand方向が0か同じ数字ならスライドできる | |
return any(self._cells[cy][cx] in (0, self._cells[y][x]) | |
for x, y, cx, cy in self._numbers(command)) | |
def slide(self, command): | |
"""command方向に数字をスライドさせる""" | |
sliding = True | |
while sliding: | |
sliding = False | |
for x, y, cx, cy in self._numbers(command): | |
if self._cells[cy][cx] == 0: | |
self._cells[cy][cx] = self._cells[y][x] | |
self._cells[y][x] = 0 | |
sliding = True | |
def merge(self, command): | |
"""command方向に同じ数字が並んでいたら数字を合算して得点を返す""" | |
score = 0 | |
for x, y, cx, cy in self._numbers(command): | |
if self._cells[cy][cx] == self._cells[y][x]: | |
self._cells[cy][cx] *= 2 | |
self._cells[y][x] = 0 | |
score += self._cells[cy][cx] | |
return score | |
class Game: | |
_SLIDE = Command.UP, Command.DOWN, Command.LEFT, Command.RIGHT | |
def __init__(self): | |
self.board = Board() | |
self.high_score = 0 | |
self._reset() | |
def _reset(self): | |
self.board.reset() | |
self.message = "" | |
self.score = 0 | |
self.tick = 0 | |
self._undo_score = 0 | |
self._stage = self._title | |
def operate(self, command): | |
self.tick += 1 | |
self._stage(command) | |
def _title(self, command): | |
self.message = "push [SPACE] to start game!" | |
if command == Command.START: | |
self._start() | |
def _start(self): | |
self._reset() | |
self._stage = self._play | |
def _play(self, command): | |
self.message = "W - UP S - DOWN A - LEFT D - RIGHT" | |
if self.tick == 1: | |
self.board.random_put_2() | |
elif self.tick <= 10: | |
pass | |
elif command == Command.RESTART: | |
self._start() | |
elif command == Command.UNDO: | |
self.score = self._undo_score | |
self.board.load() | |
elif self.board.can_slide(command): | |
self._undo_score = self.score | |
self.board.save() | |
self.board.slide(command) | |
self.score += self.board.merge(command) | |
self.board.slide(command) | |
if self.high_score < self.score: | |
self.high_score = self.score | |
self.tick = 0 | |
elif all(not self.board.can_slide(command) for command in self._SLIDE): | |
self._stage = self._over | |
self.tick = 0 | |
def _over(self, command): | |
self.message = "GAME OVER!" | |
if self.tick > 120: | |
self._stage = self._title | |
class View: | |
def __init__(self, screen): | |
self._screen = screen | |
def clear(self, color): | |
self._screen.fill(color) | |
def draw_rect(self, rect, color, width=0): | |
pygame.draw.rect(self._screen, color, rect, width) | |
def draw_text(self, text, x, y, size, color): | |
render = pygame.font.Font(None, size).render(text, True, color) | |
pos = (x - render.get_width() // 2, y - render.get_height() // 2) | |
self._screen.blit(render, pos) | |
class BoardView(View): | |
X = 23 | |
Y = 253 | |
SIZE = 488 | |
def draw(self, board): | |
cell_size = self.SIZE // board.SIZE | |
for y, row in enumerate(board): | |
for x, number in enumerate(row): | |
cell_x = self.X + x * cell_size | |
cell_y = self.Y + y * cell_size | |
rect = (cell_x, cell_y, cell_size, cell_size) | |
if number == 0: | |
self.draw_rect(rect, LIGHTGRAY) | |
else: | |
index = (number >> 2).bit_length() % len(COLORS) | |
self.draw_rect(rect, COLORS[index]) | |
number_x = cell_x + cell_size // 2 | |
number_y = cell_y + cell_size // 2 | |
self.draw_text(str(number), number_x, number_y, 76, WHITE) | |
self.draw_rect(rect, GRAY, 5) | |
class GameView(View): | |
WIDTH = 538 | |
HEIGHT = 768 | |
RESTART = pygame.Rect(23, 215, 138, 30) | |
UNDO = pygame.Rect(442, 215, 92, 30) | |
def __init__(self, screen): | |
super().__init__(screen) | |
self._board = BoardView(screen) | |
def draw(self, game): | |
self.clear(FLORALWHITE) | |
self._board.draw(game.board) | |
# blink message | |
if game.tick % 50 < 40: | |
self.draw_text(game.message, self.WIDTH // 2, 176, 38, BLACK) | |
# 2048 | |
self.draw_text("2", 38, 61, 76, RED) | |
self.draw_text("0", 69, 61, 76, GREEN) | |
self.draw_text("4", 99, 61, 76, ORANGE) | |
self.draw_text("8", 130, 61, 76, BLUE) | |
# SCORE | |
self.draw_rect((184, 23, 153, 76), GRAY) | |
self.draw_text("SCORE", 261, 42, 30, LIGHTGRAY) | |
self.draw_text(str(game.score), 261, 76, 30, WHITE) | |
# HIGH SCORE | |
self.draw_rect((360, 23, 153, 76), GRAY) | |
self.draw_text("HIGH SCORE", 437, 42, 30, LIGHTGRAY) | |
self.draw_text(str(game.high_score), 437, 76, 30, WHITE) | |
# RESTART / UNDO | |
self.draw_text("RESTART", 92, 230, 38, ORANGE) | |
self.draw_text("UNDO", 468, 230, 38, ORANGE) | |
class Controller: | |
KEYS = {pygame.K_SPACE: Command.START, | |
pygame.K_w: Command.UP, | |
pygame.K_s: Command.DOWN, | |
pygame.K_a: Command.LEFT, | |
pygame.K_d: Command.RIGHT} | |
@classmethod | |
def command(cls): | |
"""ウィンドウ操作、キー操作、マウス操作をCommandで返す""" | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
return Command.QUIT | |
pressed = pygame.key.get_pressed() | |
for key, command in cls.KEYS.items(): | |
if pressed[key]: | |
return command | |
pressed_1, pressed_2, pressed_3 = pygame.mouse.get_pressed() | |
if pressed_1: | |
x, y = pygame.mouse.get_pos() | |
if GameView.UNDO.collidepoint(x, y): | |
return Command.UNDO | |
if GameView.RESTART.collidepoint(x, y): | |
return Command.RESTART | |
return Command.NOP | |
def main(): | |
pygame.init() | |
pygame.display.set_caption("2048") | |
screen = pygame.display.set_mode((GameView.WIDTH, GameView.HEIGHT)) | |
clock = pygame.time.Clock() | |
game = Game() | |
view = GameView(screen) | |
while (command := Controller.command()) != Command.QUIT: | |
game.operate(command) | |
view.draw(game) | |
pygame.display.update() | |
clock.tick(30) | |
pygame.quit() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment