乐趣区

关于python:python3pygame实现的2048非常完整的代码

前几天写了一个 2048 程序,是基于 python3+pygame 实现的,对于初学 python 的同学来说应该是很好的练手我的项目,当初将源码分享给大家,增加了清晰的正文,置信大家能看的很明确

运行成果如下:

游戏完结后的成果如下:

残缺代码如下,如果须要下载素材(图片、字体等能够到 https://www.itprojects.cn/detail.html?example_id=ad099a8cf24e15979a89d2d2bcaa4ca4 进行下载)

import random
import sys
from collections import Iterable
from functools import reduce

import pygame

# 屏幕尺寸
WIDTH, HEIGHT = (650, 370)
# 背景色彩
BG_COLOR = '#92877d'
# 棋盘须要的数据
MARGIN_SIZE = 10  # 距离大小
BLOCK_SIZE = 80  # 棋子地位大小


def draw_tips(screen):
    """显示提示信息"""
    # 显示 "分数:"
    tips_img = pygame.image.load("resources/images/tips.png")
    screen.blit(tips_img, (375, 200))


def get_score(chess_nums_temp):
    """计算以后棋盘的总分数"""

    def sum_all(x, y):
        if isinstance(x, Iterable):
            return sum(x) + sum(y)
        return x + sum(y)

    return reduce(sum_all, chess_nums_temp)


def draw_score(screen, score):
    """显示分数"""
    # 显示数字
    font_size_big = 60
    font_color = (0, 255, 255)
    font_big = pygame.font.Font("resources/font/Gabriola.ttf", font_size_big)
    score = font_big.render(str(score), True, font_color)
    screen.blit(score, (470, 25))
    # 显示 "分数:"
    score_img = pygame.image.load("resources/images/score.png")
    screen.blit(score_img, (370, 30))


def show_game_over(screen):
    font_size_big = 60
    font_size_small = 30
    font_color = (255, 255, 255)
    font_big = pygame.font.Font("resources/font/Gabriola.ttf", font_size_big)
    font_small = pygame.font.Font("resources/font/Gabriola.ttf", font_size_small)
    surface = screen.convert_alpha()
    surface.fill((127, 255, 212, 2))
    text = font_big.render('Game Over!', True, font_color)
    text_rect = text.get_rect()
    text_rect.centerx, text_rect.centery = WIDTH / 2, HEIGHT / 2 - 50
    surface.blit(text, text_rect)
    button_width, button_height = 100, 40
    button_start_x_left = WIDTH / 2 - button_width - 20
    button_start_x_right = WIDTH / 2 + 20
    button_start_y = HEIGHT / 2 - button_height / 2 + 20
    pygame.draw.rect(surface, (0, 255, 255), (button_start_x_left, button_start_y, button_width, button_height))
    text_restart = font_small.render('Restart', True, font_color)
    text_restart_rect = text_restart.get_rect()
    text_restart_rect.centerx, text_restart_rect.centery = button_start_x_left + button_width / 2, button_start_y + button_height / 2
    surface.blit(text_restart, text_restart_rect)
    pygame.draw.rect(surface, (0, 255, 255), (button_start_x_right, button_start_y, button_width, button_height))
    text_quit = font_small.render('Quit', True, font_color)
    text_quit_rect = text_quit.get_rect()
    text_quit_rect.centerx, text_quit_rect.centery = button_start_x_right + button_width / 2, button_start_y + button_height / 2
    surface.blit(text_quit, text_quit_rect)
    clock = pygame.time.Clock()
    while True:
        screen.blit(surface, (0, 0))
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.MOUSEBUTTONDOWN and event.button:
                if text_quit_rect.collidepoint(pygame.mouse.get_pos()):
                    sys.exit()
                if text_restart_rect.collidepoint(pygame.mouse.get_pos()):
                    return True
        pygame.display.update()
        clock.tick(60)


def judge_game_over(field):
    """只有有 1 个方向能够挪动,那么游戏就没完结"""
    return not any([judge_move_left(field), judge_move_right(field), judge_move_up(field), judge_move_down(field)])


def judge_move_up(chess_nums_temp):
    # 对棋盘的数字进行「行与列转置」,即原来在第 2 行第 3 列变为第 3 行第 2 列
    # zip: 实现
    # *chess_nums_temp 对列表进行拆包
    chess_nums_temp = [list(row) for row in zip(*chess_nums_temp)]
    return judge_move_left(chess_nums_temp)


def judge_move_down(chess_nums_temp):
    """逻辑:判断是否向下挪动, 也就是对于元素进行转置, 判断转置后的棋盘是否向右挪动"""
    # 1.「行与列转置」chess_nums_temp = [list(row) for row in zip(*chess_nums_temp)]
    # 2. 判断是否能够向右挪动
    return judge_move_right(chess_nums_temp)


def judge_move_left(chess_nums_temp):
    # 只有棋盘的任意一行能够向左挪动,就返回 True
    for row in chess_nums_temp:
        for i in range(3):  # 每一行判断 3 次
            # 如果判断的右边的数为 0,左边的数不为 0,则阐明能够向左挪动;if row[i] == 0 and row[i + 1] != 0:
                return True
            elif row[i] != 0 and row[i + 1] == row[i]:
                # 如果判断的右边的数不为 0,且左右 2 个数相等,则阐明能够向左挪动;return True
    return False


def judge_move_right(chess_nums_temp):
    # 对棋盘的每一行元素进行反转,此时就能够用向左的函数进行判断了
    return judge_move_left([row[::-1] for row in chess_nums_temp])


def move_left(chess_nums_temp):
    for i, row in enumerate(chess_nums_temp):
        # 1. 把这一行的非 0 数字向前放,把 0 向后放。例如之前是 [0, 2, 2, 2]-->[2, 2, 2, 0]
        row = sorted(row, key=lambda x: 1 if x == 0 else 0)

        # 2. 顺次循环判断两个数是否相等,如果相等 第一个 *2 第二个数为 0。例如 [2, 2, 2, 0]-->[4, 0, 2, 0]
        for index in range(3):
            if row[index] == row[index + 1]:
                row[index] *= 2
                row[index + 1] = 0

        # 3. 将合并之后的空隙移除,即非 0 靠左,0 靠右。例如 [4, 0, 2, 0]-->[4, 2, 0, 0]
        row = sorted(row, key=lambda x: 1 if x == 0 else 0)
        # 4. 更新数字列表,因为这一行曾经是操作之后的了
        chess_nums_temp[i] = row
    return chess_nums_temp


def move_right(chess_nums_temp):
    # 先翻翻转
    chess_nums_temp = [row[::-1] for row in chess_nums_temp]
    # 而后在调用像左挪动的性能
    move_left(chess_nums_temp)
    # 最初再次翻转,实现之前的样子
    return [row[::-1] for row in chess_nums_temp]


def move_up(chess_nums_temp):
    # "行与列转置"
    chess_nums_temp = [list(row) for row in zip(*chess_nums_temp)]
    # 向左挪动
    chess_nums_temp = move_left(chess_nums_temp)
    # 再次 "行与列转置" 从而实现还原
    return [list(row) for row in zip(*chess_nums_temp)]


def move_down(chess_nums_temp):
    # "行与列转置"
    chess_nums_temp = [list(row) for row in zip(*chess_nums_temp)]
    # 向右挪动
    chess_nums_temp = move_right(chess_nums_temp)
    # 再次 "行与列转置" 从而实现还原
    return [list(row) for row in zip(*chess_nums_temp)]


def move(chess_nums_temp, direction):
    """依据方向挪动数字"""
    # 存储判断各个方向是否可挪动对应的函数
    judge_move_func_dict = {
        'left': judge_move_left,
        'right': judge_move_right,
        'up': judge_move_up,
        'down': judge_move_down
    }
    # 存储各个方向挪动的函数
    move_func_dict = {
        'left': move_left,
        'right': move_right,
        'up': move_up,
        'down': move_down
    }

    # 调用对应的函数,判断是否能够朝这个方向挪动
    ret = judge_move_func_dict[direction](chess_nums_temp)
    print("%s 方向是否能够挪动:" % direction, ret)
    if ret:
        chess_nums_temp = move_func_dict[direction](chess_nums_temp)
        create_random_num(chess_nums_temp)

    # 返回列表,如果更新了就是新的,如果没有更新就是之前的那个
    return chess_nums_temp


def get_num_color(num):
    """
    依据以后要显示的数字,提取出背景色以及字体色彩
    对应的数字:[方格背景色彩, 方格里的字体色彩]
    """
    color_dict = {2: ['#eee4da', '#776e65'], 4: ['#ede0c8', '#776e65'], 8: ['#f2b179', '#f9f6f2'],
        16: ['#f59563', '#f9f6f2'], 32: ['#f67c5f', '#f9f6f2'], 64: ['#f65e3b', '#f9f6f2'],
        128: ['#edcf72', '#f9f6f2'], 256: ['#edcc61', '#f9f6f2'], 512: ['#edc850', '#f9f6f2'],
        1024: ['#edc53f', '#f9f6f2'], 2048: ['#edc22e', '#f9f6f2'], 4096: ['#eee4da', '#776e65'],
        8192: ['#edc22e', '#f9f6f2'], 16384: ['#f2b179', '#776e65'], 32768: ['#f59563', '#776e65'],
        65536: ['#f67c5f', '#f9f6f2'], 0: ['#9e948a', None]
    }
    return color_dict[num]


def create_random_num(nums_temp):
    """在棋盘中随机生成一个数字"""
    # 存储所有空地位
    positions = list()
    for row, line in enumerate(nums_temp):
        for col, num in enumerate(line):
            if num == 0:
                positions.append((row, col))

    # 随机从空地位列表中抽取一个,而后拆包
    row, col = random.choice(positions)
    nums_temp[row][col] = random.choice([2, 4, 2])  # 随机从 2 个 2,1 个 4 中抽取,这样抽到 2 的概率是 4 的 2 倍


def draw_nums(screen, chess_nums_temp):
    """显示棋盘上的数字"""
    # 筹备字体等
    font_size = BLOCK_SIZE - 10
    font = pygame.font.Font("./resources/font/Gabriola.ttf", font_size)
    # 遍历数字
    for i, line in enumerate(chess_nums_temp):
        for j, num in enumerate(line):
            if num != 0:
                # 计算显示地位(x 坐标、y 坐标)x = MARGIN_SIZE * (j + 1) + BLOCK_SIZE * j
                y = MARGIN_SIZE * (i + 1) + BLOCK_SIZE * i
                # 获取色彩
                font_color = pygame.Color(get_num_color(num)[1])
                # 显示数字
                text = font.render(str(num), True, font_color)
                text_rect = text.get_rect()
                text_rect.centerx, text_rect.centery = x + BLOCK_SIZE / 2, y + BLOCK_SIZE / 2
                # 用对应的数字背景色,从新绘制这个方块
                pygame.draw.rect(screen, pygame.Color(get_num_color(num)[0]), (x, y, BLOCK_SIZE, BLOCK_SIZE))
                screen.blit(text, text_rect)


def draw_chess_board(screen):
    """显示棋盘"""
    for i in range(4):
        for j in range(4):
            x = MARGIN_SIZE * (j + 1) + BLOCK_SIZE * j
            y = MARGIN_SIZE * (i + 1) + BLOCK_SIZE * i
            pygame.draw.rect(screen, pygame.Color('#f9f6f2'), (x, y, BLOCK_SIZE, BLOCK_SIZE))


def run(screen):
    # 定义列表,用来记录以后棋盘上的所有数字,如果某地位没有数字,则为 0
    chess_nums = [[0 for _ in range(4)] for _ in range(4)]
    # 随机生成一个数字
    create_random_num(chess_nums)
    create_random_num(chess_nums)
    # 记录以后的分数
    score = get_score(chess_nums)
    # 创立计时器(避免 while 循环过快,占用太多 CPU 的问题)clock = pygame.time.Clock()
    while True:
        # 事件检测(鼠标点击、键盘按下等)for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if event.key in [pygame.K_UP, pygame.K_DOWN, pygame.K_LEFT, pygame.K_RIGHT]:
                    direction = {pygame.K_UP: 'up', pygame.K_DOWN: 'down', pygame.K_LEFT: 'left', pygame.K_RIGHT: 'right'}[event.key]
                    print("按下了方向键:", direction)
                    chess_nums = move(chess_nums, direction)
                    if judge_game_over(chess_nums):
                        print("游戏完结....")
                        return
                    # 每按下方向键,就从新计算
                    score = get_score(chess_nums)

        # 显示背景色
        screen.fill(pygame.Color(BG_COLOR))

        # 显示棋盘
        draw_chess_board(screen)

        # 显示棋盘上的数字
        draw_nums(screen, chess_nums)

        # 显示分数
        draw_score(screen, score)

        # 显示操作提醒
        draw_tips(screen)

        # 刷新显示(此时窗口才会真正的显示)pygame.display.update()

        # FPS(每秒钟显示画面的次数)clock.tick(60)  # 通过肯定的延时,实现 1 秒钟可能循环 60 次


def main():
    # 游戏初始化
    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    while True:
        # 运行一次游戏
        run(screen)
        # 显示游戏完结,是否重来
        show_game_over(screen)


if __name__ == '__main__':
    main()
退出移动版