使用pygame实现2048

python 复制代码
import random
import sys

import pygame


GRID_SIZE = 4
TILE_SIZE = 105
GAP = 12
BOARD_SIZE = GRID_SIZE * TILE_SIZE + (GRID_SIZE + 1) * GAP
SIDE_PANEL = 230
WIDTH = BOARD_SIZE + SIDE_PANEL
HEIGHT = BOARD_SIZE
FPS = 60
MOVE_ANIMATION_MS = 120

BG_COLOR = (250, 248, 239)
BOARD_COLOR = (187, 173, 160)
EMPTY_TILE_COLOR = (205, 193, 180)
TEXT_DARK = (119, 110, 101)
TEXT_LIGHT = (249, 246, 242)
PANEL_TEXT = (90, 82, 74)
BUTTON_COLOR = (143, 122, 102)
BUTTON_HOVER = (124, 102, 82)
CHEAT_ON_COLOR = (76, 145, 112)
CHEAT_ON_HOVER = (58, 126, 95)
OVERLAY_COLOR = (250, 248, 239, 215)

TILE_COLORS = {
    2: (238, 228, 218),
    4: (237, 224, 200),
    8: (242, 177, 121),
    16: (245, 149, 99),
    32: (246, 124, 95),
    64: (246, 94, 59),
    128: (237, 207, 114),
    256: (237, 204, 97),
    512: (237, 200, 80),
    1024: (237, 197, 63),
    2048: (237, 194, 46),
}


def make_board():
    board = [[0 for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
    add_random_tile(board)
    add_random_tile(board)
    return board


def add_random_tile(board):
    empty_cells = [
        (row, col)
        for row in range(GRID_SIZE)
        for col in range(GRID_SIZE)
        if board[row][col] == 0
    ]
    if not empty_cells:
        return

    row, col = random.choice(empty_cells)
    board[row][col] = 4 if random.random() < 0.1 else 2


def get_merged_value(first, second, cheat_mode=False):
    if cheat_mode:
        return max(first, second) * 2
    if first == second:
        return first * 2
    return None


def compress_and_merge(line, cheat_mode=False):
    values = [value for value in line if value != 0]
    merged = []
    score_gain = 0
    index = 0

    while index < len(values):
        if index + 1 < len(values):
            new_value = get_merged_value(values[index], values[index + 1], cheat_mode)
        else:
            new_value = None

        if new_value is not None:
            merged.append(new_value)
            score_gain += new_value
            index += 2
        else:
            merged.append(values[index])
            index += 1

    merged.extend([0] * (GRID_SIZE - len(merged)))
    return merged, score_gain


def move_board(board, direction, cheat_mode=False):
    new_board = [row[:] for row in board]
    score_gain = 0

    if direction == "left":
        for row in range(GRID_SIZE):
            new_board[row], gained = compress_and_merge(new_board[row], cheat_mode)
            score_gain += gained
    elif direction == "right":
        for row in range(GRID_SIZE):
            merged, gained = compress_and_merge(list(reversed(new_board[row])), cheat_mode)
            new_board[row] = list(reversed(merged))
            score_gain += gained
    elif direction == "up":
        for col in range(GRID_SIZE):
            column = [new_board[row][col] for row in range(GRID_SIZE)]
            merged, gained = compress_and_merge(column, cheat_mode)
            score_gain += gained
            for row in range(GRID_SIZE):
                new_board[row][col] = merged[row]
    elif direction == "down":
        for col in range(GRID_SIZE):
            column = [new_board[row][col] for row in range(GRID_SIZE)]
            merged, gained = compress_and_merge(list(reversed(column)), cheat_mode)
            merged = list(reversed(merged))
            score_gain += gained
            for row in range(GRID_SIZE):
                new_board[row][col] = merged[row]

    moved = new_board != board
    return new_board, score_gain, moved


def collapse_line_with_animations(values, positions, cheat_mode=False):
    nonzero_tiles = [
        (index, value)
        for index, value in enumerate(values)
        if value != 0
    ]
    new_values = [0] * GRID_SIZE
    animations = []
    score_gain = 0
    source_index = 0
    target_index = 0

    while source_index < len(nonzero_tiles):
        start_slot, value = nonzero_tiles[source_index]
        start_pos = positions[start_slot]
        end_pos = positions[target_index]

        if source_index + 1 < len(nonzero_tiles):
            second_slot, second_value = nonzero_tiles[source_index + 1]
            new_value = get_merged_value(value, second_value, cheat_mode)
        else:
            second_slot = None
            new_value = None

        if new_value is not None:
            new_values[target_index] = new_value
            score_gain += new_value
            animations.append({"value": value, "start": start_pos, "end": end_pos})
            animations.append({"value": second_value, "start": positions[second_slot], "end": end_pos})
            source_index += 2
        else:
            new_values[target_index] = value
            animations.append({"value": value, "start": start_pos, "end": end_pos})
            source_index += 1

        target_index += 1

    return new_values, score_gain, animations


def move_board_with_animations(board, direction, cheat_mode=False):
    new_board = [[0 for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
    animations = []
    score_gain = 0

    if direction == "left":
        lines = [[(row, col) for col in range(GRID_SIZE)] for row in range(GRID_SIZE)]
    elif direction == "right":
        lines = [[(row, col) for col in range(GRID_SIZE - 1, -1, -1)] for row in range(GRID_SIZE)]
    elif direction == "up":
        lines = [[(row, col) for row in range(GRID_SIZE)] for col in range(GRID_SIZE)]
    else:
        lines = [[(row, col) for row in range(GRID_SIZE - 1, -1, -1)] for col in range(GRID_SIZE)]

    for positions in lines:
        values = [board[row][col] for row, col in positions]
        new_values, gained, line_animations = collapse_line_with_animations(
            values,
            positions,
            cheat_mode,
        )
        score_gain += gained
        animations.extend(line_animations)

        for index, value in enumerate(new_values):
            row, col = positions[index]
            new_board[row][col] = value

    moved = new_board != board
    return new_board, score_gain, moved, animations


def can_move(board, cheat_mode=False):
    if any(0 in row for row in board):
        return True

    for row in range(GRID_SIZE):
        for col in range(GRID_SIZE):
            value = board[row][col]
            if col + 1 < GRID_SIZE and (
                cheat_mode or value == board[row][col + 1]
            ):
                return True
            if row + 1 < GRID_SIZE and (
                cheat_mode or value == board[row + 1][col]
            ):
                return True
    return False


def has_won(board):
    return any(value >= 2048 for row in board for value in row)


def get_font(size, bold=False):
    return pygame.font.SysFont("arial", size, bold=bold)


def draw_text(surface, text, font, color, center):
    image = font.render(text, True, color)
    rect = image.get_rect(center=center)
    surface.blit(image, rect)


def ease_out_cubic(progress):
    return 1 - (1 - progress) ** 3


def draw_button(surface, rect, text, mouse_pos, active=False):
    base_color = CHEAT_ON_COLOR if active else BUTTON_COLOR
    hover_color = CHEAT_ON_HOVER if active else BUTTON_HOVER
    color = hover_color if rect.collidepoint(mouse_pos) else base_color
    pygame.draw.rect(surface, color, rect, border_radius=6)
    draw_text(surface, text, get_font(22, bold=True), TEXT_LIGHT, rect.center)


def tile_rect(row, col):
    x = GAP + col * (TILE_SIZE + GAP)
    y = GAP + row * (TILE_SIZE + GAP)
    return pygame.Rect(x, y, TILE_SIZE, TILE_SIZE)


def draw_tile(surface, value, rect):
    color = TILE_COLORS.get(value, (60, 58, 50))
    pygame.draw.rect(surface, color, rect, border_radius=6)

    font_size = 52 if value < 100 else 44 if value < 1000 else 36
    font = get_font(font_size, bold=True)
    text_color = TEXT_DARK if value in (2, 4) else TEXT_LIGHT
    draw_text(surface, str(value), font, text_color, rect.center)


def draw_board_background(surface):
    board_rect = pygame.Rect(0, 0, BOARD_SIZE, BOARD_SIZE)
    pygame.draw.rect(surface, BOARD_COLOR, board_rect, border_radius=8)

    for row in range(GRID_SIZE):
        for col in range(GRID_SIZE):
            rect = tile_rect(row, col)
            pygame.draw.rect(surface, EMPTY_TILE_COLOR, rect, border_radius=6)


def draw_board(surface, board):
    draw_board_background(surface)

    for row in range(GRID_SIZE):
        for col in range(GRID_SIZE):
            value = board[row][col]
            if value:
                draw_tile(surface, value, tile_rect(row, col))


def draw_moving_tiles(surface, animations, progress):
    eased = ease_out_cubic(progress)

    for animation in animations:
        start_row, start_col = animation["start"]
        end_row, end_col = animation["end"]
        start_rect = tile_rect(start_row, start_col)
        end_rect = tile_rect(end_row, end_col)
        x = start_rect.x + (end_rect.x - start_rect.x) * eased
        y = start_rect.y + (end_rect.y - start_rect.y) * eased
        rect = pygame.Rect(round(x), round(y), TILE_SIZE, TILE_SIZE)
        draw_tile(surface, animation["value"], rect)


def draw_panel(surface, score, best_score, mouse_pos, new_game_rect, cheat_rect, cheat_mode):
    panel_x = BOARD_SIZE
    pygame.draw.rect(surface, BG_COLOR, (panel_x, 0, SIDE_PANEL, HEIGHT))

    draw_text(surface, "2048", get_font(62, bold=True), PANEL_TEXT, (panel_x + 115, 62))

    score_rect = pygame.Rect(panel_x + 30, 112, 170, 64)
    pygame.draw.rect(surface, BOARD_COLOR, score_rect, border_radius=6)
    draw_text(surface, "SCORE", get_font(17, bold=True), TEXT_LIGHT, (score_rect.centerx, score_rect.y + 20))
    draw_text(surface, str(score), get_font(28, bold=True), TEXT_LIGHT, (score_rect.centerx, score_rect.y + 48))

    best_rect = pygame.Rect(panel_x + 30, 190, 170, 64)
    pygame.draw.rect(surface, BOARD_COLOR, best_rect, border_radius=6)
    draw_text(surface, "BEST", get_font(17, bold=True), TEXT_LIGHT, (best_rect.centerx, best_rect.y + 20))
    draw_text(surface, str(best_score), get_font(28, bold=True), TEXT_LIGHT, (best_rect.centerx, best_rect.y + 48))

    draw_button(surface, new_game_rect, "New Game", mouse_pos)
    draw_button(surface, cheat_rect, f"Cheat: {'ON' if cheat_mode else 'OFF'}", mouse_pos, cheat_mode)

    help_lines = ["Arrow keys / WASD", "R: restart", "Esc: quit"]
    for index, line in enumerate(help_lines):
        draw_text(surface, line, get_font(19), PANEL_TEXT, (panel_x + 115, 420 + index * 26))


def draw_overlay(surface, title, subtitle):
    overlay = pygame.Surface((BOARD_SIZE, BOARD_SIZE), pygame.SRCALPHA)
    overlay.fill(OVERLAY_COLOR)
    surface.blit(overlay, (0, 0))
    draw_text(surface, title, get_font(56, bold=True), PANEL_TEXT, (BOARD_SIZE // 2, BOARD_SIZE // 2 - 45))
    draw_text(surface, subtitle, get_font(25, bold=True), PANEL_TEXT, (BOARD_SIZE // 2, BOARD_SIZE // 2 + 15))


def main():
    pygame.init()
    pygame.display.set_caption("Pygame 2048")
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    clock = pygame.time.Clock()

    board = make_board()
    score = 0
    best_score = 0
    game_over = False
    game_won = False
    cheat_mode = False
    animation_start_ms = None
    animation_tiles = []
    pending_board = None
    pending_score_gain = 0
    new_game_rect = pygame.Rect(BOARD_SIZE + 30, 280, 170, 52)
    cheat_rect = pygame.Rect(BOARD_SIZE + 30, 344, 170, 52)

    key_to_direction = {
        pygame.K_LEFT: "left",
        pygame.K_RIGHT: "right",
        pygame.K_UP: "up",
        pygame.K_DOWN: "down",
    }
    letter_to_direction = {
        "a": "left",
        "d": "right",
        "w": "up",
        "s": "down",
    }

    while True:
        mouse_pos = pygame.mouse.get_pos()

        if animation_start_ms is not None:
            elapsed = pygame.time.get_ticks() - animation_start_ms
            if elapsed >= MOVE_ANIMATION_MS:
                board = pending_board
                score += pending_score_gain
                best_score = max(best_score, score)
                add_random_tile(board)
                game_won = has_won(board)
                game_over = not can_move(board, cheat_mode)
                animation_start_ms = None
                animation_tiles = []
                pending_board = None
                pending_score_gain = 0

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

            if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                if new_game_rect.collidepoint(event.pos):
                    board = make_board()
                    score = 0
                    game_over = False
                    game_won = False
                    animation_start_ms = None
                    animation_tiles = []
                    pending_board = None
                    pending_score_gain = 0
                elif cheat_rect.collidepoint(event.pos):
                    cheat_mode = not cheat_mode
                    if not game_won:
                        game_over = not can_move(board, cheat_mode)

            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    pygame.quit()
                    sys.exit()

                if event.key == pygame.K_r:
                    board = make_board()
                    score = 0
                    game_over = False
                    game_won = False
                    animation_start_ms = None
                    animation_tiles = []
                    pending_board = None
                    pending_score_gain = 0
                    continue

                direction = key_to_direction.get(event.key)
                if direction is None:
                    direction = letter_to_direction.get(event.unicode.lower())

                if (
                    direction is not None
                    and not game_over
                    and not game_won
                    and animation_start_ms is None
                ):
                    next_board, gained, moved, animations = move_board_with_animations(
                        board,
                        direction,
                        cheat_mode,
                    )
                    if moved:
                        animation_start_ms = pygame.time.get_ticks()
                        animation_tiles = animations
                        pending_board = next_board
                        pending_score_gain = gained

        screen.fill(BG_COLOR)
        if animation_start_ms is None:
            draw_board(screen, board)
        else:
            elapsed = pygame.time.get_ticks() - animation_start_ms
            progress = min(1, elapsed / MOVE_ANIMATION_MS)
            draw_board_background(screen)
            draw_moving_tiles(screen, animation_tiles, progress)

        draw_panel(
            screen,
            score,
            best_score,
            mouse_pos,
            new_game_rect,
            cheat_rect,
            cheat_mode,
        )

        if animation_start_ms is None and game_won:
            draw_overlay(screen, "You Win!", "Press R or New Game")
        elif animation_start_ms is None and game_over:
            draw_overlay(screen, "Game Over", "Press R or New Game")

        pygame.display.flip()
        clock.tick(FPS)


if __name__ == "__main__":
    main()

记得使用英文的键盘输入

我一直过不去 自己写了个作弊模式把cheat打开作弊

相关推荐
hh.h.8 小时前
CANN算子开发入门:从零开始写第一个Ascend C算子
c语言·开发语言·cann·c算子
AI科技星8 小时前
全域数学·第三部·数术几何部·平行网格卷 完整专著目录(含拓扑发展史+学科定位·终稿)
c语言·开发语言·网络·量子计算·agi
徐图图不糊涂8 小时前
搭建简易版的Rag系统
python·pycharm
SunnyDays10118 小时前
Java 读写 Excel 公式:从基础到高级的实战总结
java·开发语言·excel
wb043072018 小时前
Java 26
java·开发语言
白露与泡影8 小时前
JVM GC调优实战:从线上频繁Full GC到RT降低80%的全过程
java·开发语言·jvm
灰灰勇闯IT8 小时前
pyasc:用 Python 调用 CANN 的推理能力
开发语言·python
明月_清风9 小时前
FastAPI 从入门到实战:3 分钟构建高性能异步 API
后端·python·fastapi
笨拙的老猴子9 小时前
[特殊字符] Java GC机制详解:G1、ZGC、Shenandoah全面解析与版本演进对比
java·开发语言