【Python小游戏】深度解析Pygame实现2048游戏的完整开发流程(有代码实现)

目录

第一章 游戏开发的前置准备与环境搭建

第二章 色彩系统与视觉设计的精妙之处

第三章 数据结构与游戏棋盘的状态管理

第四章 游戏逻辑核心:移动与合并算法的深度分析

第五章 游戏状态判定与结束条件的实现

第六章 用户交互与事件处理的完整流程

第七章 渲染系统与图形绘制的视觉呈现

第八章 游戏的初始化与完整的生命周期管理

结论


第一章 游戏开发的前置准备与环境搭建

在当今的游戏开发生态中,Pygame无疑是Python开发者入门游戏编程最友好的选择之一。相比于复杂的商业引擎如Unity或Unreal,Pygame提供了更为轻量级却足够强大的功能集合,使得开发者能够专注于游戏逻辑和算法的实现,而不必被复杂的框架所困扰。2048这款经典的益智游戏虽然规则简单,但其背后涉及的编程思想和算法设计却值得深入探讨。本文将通过详细分析一个完整的Pygame版本2048游戏实现,带您理解游戏开发中的核心概念、数据结构运用、用户交互处理以及性能优化等多个层面。

Pygame框架的核心价值在于它提供了跨平台的图形渲染、事件处理、声音管理等基础功能。对于初学者而言,Pygame的学习曲线相对平缓,API设计也相对直观,使得开发者能够快速上手。在搭建开发环境时,首先需要通过pip包管理工具安装Pygame:在命令行中执行pip install pygame命令即可完成安装。安装完成后,通过在Python脚本中执行import pygame并调用pygame.init()进行初始化,就可以开始Pygame的开发工作了。整个游戏的运行流程遵循一个标准的游戏循环模式:事件处理、逻辑更新、画面渲染三个阶段不断循环执行,直到游戏结束条件触发为止。

在我们要讨论的2048游戏实现中,首先建立的就是这样一个基础的Pygame框架。代码中通过pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))创建了一个400x500像素的游戏窗口,并通过pygame.display.set_caption("2048 游戏")为窗口设置了标题。这些基础的初始化工作虽然简洁,但却为后续的整个游戏运行奠定了基础。特别值得注意的是,代码对字体加载的处理展现了实际项目开发中应有的容错意识——首先尝试加载指定的中文字体文件(Windows系统中的simhei.ttf),若加载失败则回退到系统默认字体,并给出相应的警告提示。这种渐进式的降级策略保证了游戏在各种不同的系统环境中都能运行,避免因为字体问题导致程序崩溃。

第二章 色彩系统与视觉设计的精妙之处

游戏的视觉表现力在很大程度上决定了用户的游戏体验。2048游戏之所以在全球范围内获得广泛的接受和喜爱,不仅因为其独特的游戏机制,更在于其清爽简洁的视觉设计。代码中定义的色彩映射表(COLOR_MAP)反映了游戏设计者对于视觉呈现的深思熟虑。每个数字值都被赋予了一个独特的颜色,这不仅提供了视觉上的美感,更重要的是帮助玩家快速识别和区分棋盘上的不同方块。

让我们仔细观察这个色彩系统的设计逻辑。在代码中,我们可以看到一个详尽的字典结构定义了从0到2048的各个数值对应的RGB颜色值。其中,0代表空白格子,被设置为(204, 192, 179)的米黄色,这是一个中性温和的颜色,不会对视线造成过度刺激。随着数值的增加,颜色逐渐从浅色过渡到更深的色调和更强烈的彩度,2代表的浅米色(238, 228, 218)、4的淡棕色(237, 224, 200),一直到最高的2048对应的深黄色(237, 188, 39)。这个渐进式的色彩变化方案有着深远的心理学考虑:数值越高的方块呈现出越来越深、越来越饱和的颜色,这在视觉上直观地强化了玩家对于游戏进度和成就感的认知,每当合并出一个更大的数值时,随之而来的色彩变化为玩家提供了即时的视觉反馈。

从设计的角度看,这个色彩方案遵循了暖色系的选择,避免了冷色系可能带来的视觉疲劳。整个色板给人一种温暖、友好的感受,这与游戏的casual益智游戏定位相契合。此外,代码中对于超出预定义范围的数值(如果玩家通过某种方式产生了大于2048的数值)采用了BLACK(黑色)作为默认颜色。虽然在标准2048游戏中通常在玩家达到2048后就结束游戏,但这种防守性编程的做法确保了程序的健壮性。

第三章 数据结构与游戏棋盘的状态管理

2048游戏的核心在于对四乘四棋盘状态的管理和变化。代码中采用的是一个二维列表来表示游戏棋盘,这是最为直观和易于操作的数据结构选择。棋盘被初始化为board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)],其中BOARD_SIZE被设置为4,这产生了一个4x4的二维矩阵,每个单元格初始值都是0,代表空白。这个简洁的初始化方式充分体现了Python语言的简洁性和表达力。

在游戏的整个生命周期中,棋盘的状态是不断变化的。每当玩家进行一次有效的移动和合并操作后,棋盘就会进入一个新的状态。游戏状态管理涉及三个全局变量:board代表当前棋盘的配置,score记录当前的得分,game_over则是一个布尔标志用来标识游戏是否已经结束。这种使用全局变量的方式在小型项目中是可以接受的,虽然从软件工程的最佳实践来看,将这些状态封装在一个GameState类中会更加优雅,但对于这个规模的项目而言,全局变量的方式提供了足够的简洁性和可读性。

棋盘的每个格子位置都对应一个非负整数值。在游戏初始阶段,棋盘几乎完全为0,只有通过add_new_tile函数在随机的空白格子中添加新的方块(通常是2或4)。这个设计直接反映了原始2048游戏的规则:每个有效的移动后,棋盘的某个随机空位会自动生成一个新的方块。随着游戏的进行,棋盘上的非零格子逐渐增多,可用的空白位置逐渐减少,最终可能导致无法进行任何合法的移动,游戏结束。

游戏状态属性数据类型含义说明初始值
board二维列表4x4棋盘,存储各格子的数值全0矩阵
score整数累计得分0
game_over布尔值游戏是否结束False
BOARD_SIZE常量棋盘维度4

第四章 游戏逻辑核心:移动与合并算法的深度分析

2048游戏的玩法围绕四个方向的移动展开:上、下、左、右。玩家通过按下相应的方向键来移动所有方块,方块会向指定方向滑动,相同数值的相邻方块会合并成一个数值为两倍的方块。这看似简单的机制实际上涉及复杂的状态处理逻辑。代码中实现了四个独立的函数来分别处理四个方向的移动:move_left、move_right、move_up和move_down。虽然这四个函数的实现逻辑有明显的相似性(都涉及收集非零值、合并相同值、重新放置等步骤),但由于棋盘的二维特性,对行和列的处理方式不同,因此不得不分别实现。

以move_left函数为例,来深入理解移动和合并的算法流程。当玩家按下左方向键时,棋盘上的所有方块应该向左移动,直到遇到棋盘边界或另一个方块。对于每一行,函数首先执行row_data = [tile for tile in current_board[r] if tile != 0],这一行代码的作用是将该行中所有非零的方块提取出来形成一个新的列表,实际上这个操作完成了移动的第一步——将所有方块"压实"到一侧。接下来,函数进入一个关键的合并循环,遍历row_data中的每个元素,如果当前元素和下一个元素相等,就将两个元素合并为一个数值为两倍的元素,并同时累加得分。这里有一个重要的细节:合并时通过i += 2跳过了下一个元素,确保同一个方块不会在同一次移动中被多次合并,这是2048游戏的一个重要规则。

在move_right、move_up和move_down函数中,基本的逻辑是类似的,但需要根据移动方向做出相应的调整。move_right相比于move_left,需要对收集到的数据进行反转(reverse),以便从右向左进行处理,合并完成后再进行一次反转,最后将结果填充到新棋盘的右侧。move_up和move_down则是对列而不是行进行相同的操作,通过改变循环的维度顺序来实现。例如在move_up中,代码使用col_data = [current_board[r][c] for r in range(BOARD_SIZE) if current_board[r][c] != 0]来提取特定列的非零元素,然后进行相同的合并处理。

值得注意的是,所有移动函数都会返回两个值:新的棋盘状态new_board和一个布尔值moved,用来指示这次操作是否实际改变了棋盘状态。这个boolean返回值的设计非常聪慧,因为在2048游戏中,如果一次移动没有改变棋盘状态(例如玩家在所有方块都已经靠在左边时再次按下左键),那么不应该在棋盘中添加新的方块。moved标志的判定是通过对比操作前后的棋盘状态来实现的,如果两个状态完全相同,则moved为False。

这个算法设计中还有一个优雅的地方:所有的移动和合并操作都是在一个新的棋盘副本上执行的(通过new_board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)]创建),而不是直接修改原棋盘。这样做的好处是如果玩家的操作无效(例如已经无法继续移动),原棋盘状态不会受到影响,游戏可以立即响应并拒绝这个操作。这是一个非常重要的设计考量,提高了代码的安全性和可维护性。

第五章 游戏状态判定与结束条件的实现

游戏何时结束是一个重要的问题。在2048游戏中,游戏结束的条件是:棋盘上没有空白格子,且棋盘上没有任何相邻的相同数值方块可以合并。换句话说,如果棋盘已经完全填满,且玩家无论向哪个方向移动都无法改变棋盘的状态,那么游戏就应该结束。代码中通过can_move函数来判定游戏是否还能继续进行。

can_move函数的实现逻辑分为两个主要部分。第一部分非常简单:检查是否存在空白格子。通过调用get_empty_cells(current_board)获取所有空白格子的列表,如果列表非空,说明还有可用的位置用于放置新的方块,因此游戏必然可以继续进行。第二部分则针对棋盘已满的情况,此时需要检查是否存在可以合并的相邻方块。函数遍历棋盘的每个格子,检查该格子是否与其右侧邻居或下方邻居相等。之所以只需要检查右侧和下方,而不需要检查左侧和上方,是因为每对相邻的格子在检查中只需要被考虑一次。如果发现任何两个相邻的相同值,函数立即返回True,表示游戏还可以继续。只有当遍历了整个棋盘且没有发现任何可合并的相邻对时,函数才会返回False。

在主游戏循环中,每当玩家进行了一次有效的移动并添加了新的方块后,代码就会调用can_move(board)来检查游戏是否应该结束。如果返回值为False,全局变量game_over被设置为True,游戏进入结束状态。一旦game_over为True,游戏循环中处理玩家按键的代码就不再响应方向键,而是只响应R键用于重新开始游戏。这种状态转移的设计清晰且符合游戏的用户体验预期。

第六章 用户交互与事件处理的完整流程

Pygame通过事件队列来处理用户的各种输入。代码中的主游戏循环中包含了一个事件处理的部分,通过pygame.event.get()获取所有待处理的事件,然后根据事件的类型进行相应的处理。这个事件处理系统是Pygame框架的核心部分之一,它使得游戏能够及时响应用户的操作。

在我们的2048游戏实现中,主要处理两种事件类型:QUIT事件和KEYDOWN事件。QUIT事件是当用户关闭游戏窗口时触发的,此时游戏应该正常退出。代码通过设置running = False来打破主循环,并调用pygame.quit()sys.exit()来完整地清理资源并退出程序。这种处理方式确保了所有的Pygame资源都被正确释放,避免可能的资源泄漏。

对于KEYDOWN事件,代码的处理逻辑分为两种情况:游戏进行中和游戏已结束。当游戏进行中时(game_over为False),代码检查按下的是哪个方向键,然后调用对应的移动函数。这里的实现非常巧妙:首先定义了一些临时变量new_board = Nonemoved = False,然后根据按键来决定是否调用相应的移动函数。只有当moved为True时,才会更新全局的board变量并添加新的方块,然后检查游戏是否应该结束。当游戏已结束时(game_over为True),玩家按下的任何方向键都会被忽略,只有R键会触发reset_game()函数来重新开始游戏。

这个事件处理的设计充分考虑了游戏的各种状态,确保了合理的用户交互流程。玩家在游戏进行中不会看到游戏结束的界面和重新开始的提示,反之亦然。这种清晰的状态分离提高了游戏的易用性和沉浸感。

第七章 渲染系统与图形绘制的视觉呈现

游戏的视觉呈现是通过一系列绘制函数实现的。在每个游戏循环迭代中,代码都会调用draw_board、draw_score和可能的draw_game_over函数来更新屏幕显示。这些函数共同构成了Pygame的渲染管线,最终通过调用pygame.display.flip()来将绘制的内容显示到屏幕上。

draw_board函数负责绘制游戏棋盘的基本结构和所有的方块。函数首先调用screen.fill(GRAY)用灰色填充整个屏幕作为背景。然后,代码通过嵌套的循环遍历棋盘的每个格子,计算该格子在屏幕上的像素坐标。坐标的计算方式是x = MARGIN + c * (TILE_SIZE + MARGIN)y = SCORE_HEIGHT + MARGIN + r * (TILE_SIZE + MARGIN),其中MARGIN代表格子之间的间距,TILE_SIZE代表单个格子的大小(80像素),SCORE_HEIGHT代表屏幕顶部用于显示分数的区域高度(100像素)。这个计算方式确保了所有格子均匀分布在棋盘上,并且之间有适当的间距,增强了视觉的整洁性。

对于每个格子,代码首先根据格子中的数值从COLOR_MAP中查找对应的颜色,然后使用pygame.draw.rect()函数绘制一个填充了该颜色的矩形。如果格子的数值非零,代码还会在该矩形的中心绘制对应的数字。这里使用的是font.render()函数来创建文本图像,然后通过screen.blit()将其绘制到屏幕上。绘制位置通过计算矩形的中心坐标来实现,确保文字始终居中显示在格子中。

draw_score函数相对简单,它只需在屏幕的顶部显示当前的累计分数。代码使用score_font(相比于display_font更小的字体)来渲染分数文本,然后将其绘制在屏幕的上方中央。这个位置的选择既不会遮挡游戏棋盘,又足够醒目以让玩家清楚地看到自己的分数。

draw_game_over函数处理游戏结束时的显示。这个函数首先创建了一个半透明的黑色覆盖层,通过在SRCALPHA模式下创建表面并设置其颜色为(0, 0, 0, 180)(其中180代表透明度),然后将这个覆盖层绘制到整个游戏区域上。这样做的效果是将游戏棋盘变暗,使得浮在上面的游戏结束文字更加突出。然后代码使用game_over_font(最大的字体)绘制"游戏结束"文字,并在其下方使用较小的字体显示"按 R 重新开始"的提示。这个设计清晰地传达了游戏已结束以及如何继续的信息。

第八章 游戏的初始化与完整的生命周期管理

一个完整的游戏不仅包括核心的游戏逻辑,还包括初始化阶段、运行阶段和结束阶段的完整生命周期管理。在我们的2048实现中,这个生命周期通过reset_game函数和主游戏循环得到精妙的管理。

reset_game函数负责将游戏恢复到初始状态。它首先重置全局的board变量为一个全零的4x4矩阵,清零score分数,设置game_over标志为False。然后,函数两次调用add_new_tile函数,在棋盘的随机空白位置添加初始的两个方块。这个初始化过程与真实的2048游戏规则相符:新游戏开始时,棋盘上随机出现两个方块,这两个方块可能是2也可能是4,其中2出现的概率更高(代码中设置为90%的概率出现2,10%的概率出现4)。这个概率的设置是经过设计的,它保证了游戏在初期有足够的可操作空间,不会过于困难而劝退新玩家。

add_new_tile函数用于在任意时刻向棋盘中添加新的方块。函数首先调用get_empty_cells获取所有空白格子的坐标,然后从中随机选择一个,最后在该位置放置一个数值为2或4的方块。这个函数的实现非常直接,但其中包含了一个关键的细节:在调用random.choice([2, 2, 2, 4])时,通过在列表中重复放置2来实现加权概率。这是一个简洁而有效的技巧,避免了使用更复杂的概率计算方法。

主游戏循环是整个程序的心脏。代码通过while running:来维持一个持续的循环,在这个循环中不断进行事件处理、游戏状态更新和画面渲染。这个循环只有在用户关闭窗口时才会退出。整个循环的节奏是由Pygame的事件系统和显示更新驱动的。虽然代码中没有显式的帧率控制,但实际运行时Pygame会尽快更新显示,通常在现代计算机上会以很高的帧率运行(可能是每秒数百次迭代)。

从游戏设计的整体来看,这个2048的实现虽然相对简洁,但却完整地包含了一个可玩的游戏应有的所有要素。从初始化到用户交互,从游戏逻辑到视觉呈现,再到游戏结束和重新开始,每个环节都得到了妥当的处理。这个项目是一个很好的案例研究,展示了如何使用Pygame框架来创建一个功能完整的小游戏。对于初学者而言,通过深入研究和改进这个项目,可以学到许多关于游戏开发的基础知识和最佳实践。例如,可以尝试添加音效系统、实现得分的本地存储、为游戏添加难度级别、实现更多的视觉效果(如方块移动时的动画)或性能优化等功能,这些都是进一步提升游戏品质和学习游戏开发的好方向。

结论

通过对这个Pygame版本2048游戏的深入分析,我们可以看到,即使是看似简单的小游戏,其背后也隐藏着丰富的编程思想和设计考量。从数据结构的选择到算法的实现,从用户交互的处理到视觉效果的呈现,每一个细节都影响着游戏的最终质量。这个项目展现了Python语言的简洁性和表达力,同时也展示了Pygame框架在游戏开发中的实用价值。对于任何想要进入游戏开发领域的开发者,深入研究类似的项目代码是必不可少的。不仅要理解代码的每一行是做什么的,更要思考为什么要这样做,这样才能真正掌握游戏开发的核心概念,为今后开发更复杂、更有趣的游戏项目打下坚实的基础。


完整代码实现:

import pygame import random import sys # 初始化 Pygame pygame.init() # 屏幕尺寸 SCREEN_WIDTH = 400 SCREEN_HEIGHT = 500 screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption("2048 游戏") # 颜色定义 WHITE = (255, 255, 255) BLACK = (0, 0, 0) GRAY = (200, 200, 200) COLOR_MAP = { 0: (204, 192, 179), 2: (238, 228, 218), 4: (237, 224, 200), 8: (242, 177, 121), 16: (245, 149, 99), 32: (246, 124, 95), 64: (246, 94, 66), 128: (237, 200, 117), 256: (237, 197, 97), 512: (237, 194, 78), 1024: (237, 191, 59), 2048: (237, 188, 39), } # 字体设置 (尝试加载指定的中文字体文件) try: # 请将 'C:/Windows/Fonts/simhei.ttf' 替换为您系统中实际的中文字体文件路径 font_path = 'C:/Windows/Fonts/simhei.ttf' font = pygame.font.Font(font_path, 40) score_font = pygame.font.Font(font_path, 30) game_over_font = pygame.font.Font(font_path, 60) # 尝试渲染一个中文字符来验证字体是否支持中文 test_surface = font.render("测", True, BLACK) except pygame.error: print(f"错误:无法加载字体文件 '{font_path}'。请检查路径是否正确,或者尝试其他字体文件。") # 如果字体加载失败,回退到使用默认字体,并给出警告 try: print("将尝试使用系统默认字体。") font = pygame.font.Font(None, 40) score_font = pygame.font.Font(None, 30) game_over_font = pygame.font.Font(None, 60) font.render("你好", True, BLACK) # 再次尝试渲染中文 score_font.render("你好", True, BLACK) game_over_font.render("你好", True, BLACK) except pygame.error: print("错误:无法加载任何字体。请确保您的系统安装了可用的字体。") pygame.quit() sys.exit() else: print("警告:系统默认字体可能不支持中文,中文显示可能出现乱码。") except FileNotFoundError: # 捕获 FileNotFoundError 更加精确 print(f"错误:字体文件 '{font_path}' 未找到。请检查路径是否正确。") # 如果字体加载失败,回退到使用默认字体,并给出警告 try: print("将尝试使用系统默认字体。") font = pygame.font.Font(None, 40) score_font = pygame.font.Font(None, 30) game_over_font = pygame.font.Font(None, 60) font.render("你好", True, BLACK) # 再次尝试渲染中文 score_font.render("你好", True, BLACK) game_over_font.render("你好", True, BLACK) except pygame.error: print("错误:无法加载任何字体。请确保您的系统安装了可用的字体。") pygame.quit() sys.exit() else: print("警告:系统默认字体可能不支持中文,中文显示可能出现乱码。") # 游戏参数 BOARD_SIZE = 4 TILE_SIZE = 80 MARGIN = 10 BOARD_WIDTH = TILE_SIZE * BOARD_SIZE + MARGIN * (BOARD_SIZE + 1) BOARD_HEIGHT = TILE_SIZE * BOARD_SIZE + MARGIN * (BOARD_SIZE + 1) SCORE_HEIGHT = 100 GAME_WIDTH = BOARD_WIDTH GAME_HEIGHT = BOARD_HEIGHT + SCORE_HEIGHT # 游戏状态 board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)] score = 0 game_over = False # --- 辅助函数 --- def get_empty_cells(current_board): """获取棋盘上所有空白格的坐标列表""" empty_cells = [] for r in range(BOARD_SIZE): for c in range(BOARD_SIZE): if current_board[r][c] == 0: empty_cells.append((r, c)) return empty_cells def add_new_tile(current_board): """在棋盘的随机空白格添加一个新方块 (2 或 4)""" empty_cells = get_empty_cells(current_board) if empty_cells: r, c = random.choice(empty_cells) current_board[r][c] = random.choice([2, 2, 2, 4]) # 90% 2, 10% 4 def can_move(current_board): """判断是否还能进行移动或合并""" # 检查是否有空白格 if get_empty_cells(current_board): return True # 检查是否有相邻的相同数字可以合并 for r in range(BOARD_SIZE): for c in range(BOARD_SIZE): # 检查右侧 if c < BOARD_SIZE - 1 and current_board[r][c] == current_board[r][c + 1]: return True # 检查下方 if r < BOARD_SIZE - 1 and current_board[r][c] == current_board[r + 1][c]: return True return False def reset_game(): """重置游戏状态""" global board, score, game_over board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)] score = 0 game_over = False add_new_tile(board) add_new_tile(board) # --- 移动和合并逻辑 --- def move_left(current_board): """向左移动方块并合并""" global score moved = False new_board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)] for r in range(BOARD_SIZE): row_data = [tile for tile in current_board[r] if tile != 0] # 移除空白 merged_row = [] i = 0 while i < len(row_data): if i + 1 < len(row_data) and row_data[i] == row_data[i + 1]: merged_value = row_data[i] * 2 merged_row.append(merged_value) score += merged_value # 累加得分 i += 2 moved = True else: merged_row.append(row_data[i]) i += 1 # 将合并后的行填充回新棋盘 for c in range(len(merged_row)): new_board[r][c] = merged_row[c] # 检查是否有实际移动发生(即使没有合并,方块也可能移动到更左的位置) if not moved: for r in range(BOARD_SIZE): for c in range(BOARD_SIZE): if current_board[r][c] != new_board[r][c]: moved = True break if moved: break return new_board, moved def move_right(current_board): """向右移动方块并合并""" global score moved = False new_board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)] for r in range(BOARD_SIZE): row_data = [tile for tile in current_board[r] if tile != 0] # 移除空白 row_data.reverse() # 反转以便从右向左处理 merged_row = [] i = 0 while i < len(row_data): if i + 1 < len(row_data) and row_data[i] == row_data[i + 1]: merged_value = row_data[i] * 2 merged_row.append(merged_value) score += merged_value i += 2 moved = True else: merged_row.append(row_data[i]) i += 1 # 将合并后的行填充回新棋盘(需要再次反转并靠右对齐) merged_row.reverse() for c in range(len(merged_row)): new_board[r][BOARD_SIZE - 1 - c] = merged_row[c] # 检查是否有实际移动发生 if not moved: for r in range(BOARD_SIZE): for c in range(BOARD_SIZE): if current_board[r][c] != new_board[r][c]: moved = True break if moved: break return new_board, moved def move_up(current_board): """向上移动方块并合并""" global score moved = False new_board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)] for c in range(BOARD_SIZE): col_data = [current_board[r][c] for r in range(BOARD_SIZE) if current_board[r][c] != 0] # 移除空白 merged_col = [] i = 0 while i < len(col_data): if i + 1 < len(col_data) and col_data[i] == col_data[i + 1]: merged_value = col_data[i] * 2 merged_col.append(merged_value) score += merged_value i += 2 moved = True else: merged_col.append(col_data[i]) i += 1 # 将合并后的列填充回新棋盘 for r in range(len(merged_col)): new_board[r][c] = merged_col[r] # 检查是否有实际移动发生 if not moved: for r in range(BOARD_SIZE): for c in range(BOARD_SIZE): if current_board[r][c] != new_board[r][c]: moved = True break if moved: break return new_board, moved def move_down(current_board): """向下移动方块并合并""" global score moved = False new_board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)] for c in range(BOARD_SIZE): col_data = [current_board[r][c] for r in range(BOARD_SIZE) if current_board[r][c] != 0] # 移除空白 col_data.reverse() # 反转以便从下往上处理 merged_col = [] i = 0 while i < len(col_data): if i + 1 < len(col_data) and col_data[i] == col_data[i + 1]: merged_value = col_data[i] * 2 merged_col.append(merged_value) score += merged_value i += 2 moved = True else: merged_col.append(col_data[i]) i += 1 # 将合并后的列填充回新棋盘(需要再次反转并靠下对齐) merged_col.reverse() for r in range(len(merged_col)): new_board[BOARD_SIZE - 1 - r][c] = merged_col[r] # 检查是否有实际移动发生 if not moved: for r in range(BOARD_SIZE): for c in range(BOARD_SIZE): if current_board[r][c] != new_board[r][c]: moved = True break if moved: break return new_board, moved # --- 绘制函数 --- def draw_board(current_board): """绘制游戏棋盘和方块""" # 绘制背景 screen.fill(GRAY) # 绘制棋盘网格 for r in range(BOARD_SIZE): for c in range(BOARD_SIZE): x = MARGIN + c * (TILE_SIZE + MARGIN) y = SCORE_HEIGHT + MARGIN + r * (TILE_SIZE + MARGIN) tile_value = current_board[r][c] tile_color = COLOR_MAP.get(tile_value, BLACK) # 默认黑色 pygame.draw.rect(screen, tile_color, (x, y, TILE_SIZE, TILE_SIZE)) # 绘制数字 if tile_value != 0: text_surface = font.render(str(tile_value), True, BLACK) text_rect = text_surface.get_rect(center=(x + TILE_SIZE // 2, y + TILE_SIZE // 2)) screen.blit(text_surface, text_rect) def draw_score(): """绘制分数""" score_text = score_font.render(f"分数: {score}", True, BLACK) score_rect = score_text.get_rect(center=(SCREEN_WIDTH // 2, SCORE_HEIGHT // 2)) screen.blit(score_text, score_rect) def draw_game_over(): """绘制游戏结束画面""" overlay = pygame.Surface((GAME_WIDTH, GAME_HEIGHT), pygame.SRCALPHA) overlay.fill((0, 0, 0, 180)) # 半透明黑色背景 screen.blit(overlay, (0, 0)) game_over_text = game_over_font.render("游戏结束", True, WHITE) game_over_rect = game_over_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 50)) screen.blit(game_over_text, game_over_rect) restart_text = score_font.render("按 R 重新开始", True, WHITE) restart_rect = restart_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 20)) screen.blit(restart_text, restart_rect) # --- 初始化游戏 --- reset_game() # --- 主游戏循环 --- running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False pygame.quit() sys.exit() if not game_over: if event.type == pygame.KEYDOWN: new_board = None moved = False if event.key == pygame.K_LEFT: new_board, moved = move_left(board) elif event.key == pygame.K_RIGHT: new_board, moved = move_right(board) elif event.key == pygame.K_UP: new_board, moved = move_up(board) elif event.key == pygame.K_DOWN: new_board, moved = move_down(board) if moved: board = new_board add_new_tile(board) if not can_move(board): game_over = True else: # 如果游戏结束,处理重新开始的按键 if event.type == pygame.KEYDOWN: if event.key == pygame.K_r: reset_game() # 绘制 draw_board(board) draw_score() if game_over: draw_game_over() # 更新屏幕 pygame.display.flip()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1170217.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从手机 GPS 到厘米级定位:一辆卡丁车的“定位进化史” - 教程

从手机 GPS 到厘米级定位:一辆卡丁车的“定位进化史” - 教程2026-01-16 22:26 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !impor…

视觉空间问题突破的潜在方法探索

视觉空间问题突破的潜在方法探索 关键词:计算机视觉、空间理解、深度学习、3D重建、注意力机制、多模态学习、视觉推理 摘要:本文深入探讨了视觉空间问题解决的潜在方法,从计算机视觉的基础理论到前沿技术应用。我们将分析当前视觉空间理解面临的挑战,介绍核心算法原理,并…

简单一篇文章,讲一下 消息队列(Message Queue)是干什么的?

目录数据库 和 消息队列 的区别?那 消息队列 怎么工作呢?微服务架构是什么?同步通信的弊端? 一句话说完就是,消息队列 就是解决 微服务架构 的应用程序,各模块传递数据的杂七杂八的问题的! 本文完整版原文地址:…

电脑运行库合集-(微软/vc/游戏)运行库安装包文件

在使用电脑系统时经常会出现丢失找不到某些文件的情况&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编写的&#xff0c;所以这类软件的运行需要依赖微软Visual C运行库&#xff0c;比如像 QQ、迅雷、Adobe 软件等等&#xff0c;如果没有安装VC运行库或者安装…

基于深度学习的皮肤病检测系统(YOLOv8+YOLO数据集+UI界面+Python项目+模型)

一、项目介绍 摘要 本项目基于YOLOv8目标检测算法开发了一个皮肤病自动识别系统&#xff0c;专门用于检测和分类7种常见的皮肤病变。系统训练数据集包含681张训练图像、97张验证图像和195张测试图像&#xff0c;涵盖了Bowens Disease(鲍温病)、Basal Cell Carcinoma(基底细胞…

微软常用运行库合集32|64位文件下载

在使用电脑系统时经常会出现丢失找不到某些文件的情况&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编写的&#xff0c;所以这类软件的运行需要依赖微软Visual C运行库&#xff0c;比如像 QQ、迅雷、Adobe 软件等等&#xff0c;如果没有安装VC运行库或者安装…

S4 新语法记录(如+with、select内表、range、过滤器filter、分组loop…group)

1.EXSTS 注意&#xff1a;where后面 添加需要判断的字段 2. with 注意&#xff1a;with 后面的 内表名 可以有多个 &#xff0c;但是后面只能紧接一个sql&#xff1b;需要用到多次可以使用union关键字 连接 3.case… 需要as 别名 4.select内表 **注意&#xff1a;**内…

Postman接口测试极速入门指南

一、2026年Postman核心功能升级功能模块新特性测试效率增益智能填充AI自动补全URL/Headers40%断言生成根据历史响应自动推荐断言逻辑65%多环境治理可视化环境变量血缘分析50%二、三阶速成体系&#xff08;含企业级最佳实践&#xff09;阶段1&#xff1a;智能请求构建&#xff0…

基于深度学习的安检X光危险物检测系统(YOLOv8+YOLO数据集+UI界面+Python项目+模型)

一、项目介绍 摘要 本项目基于先进的YOLOv8目标检测算法&#xff0c;开发了一套专门用于安检X光图像的危险物品自动检测系统。系统能够识别18类常见危险物品&#xff0c;包括各类刀具&#xff08;Axe、Knife、Throwing Knife等&#xff09;、工具类物品&#xff08;Hammer、C…

全网最全本科生AI论文平台TOP9:毕业论文写作必备测评

全网最全本科生AI论文平台TOP9&#xff1a;毕业论文写作必备测评 2026年本科生AI论文平台测评&#xff1a;为何需要这份权威榜单&#xff1f; 随着人工智能技术在学术领域的不断渗透&#xff0c;越来越多的本科生开始借助AI工具提升论文写作效率。然而&#xff0c;面对市场上琳…

Anaconda3下载安装+使用教程全攻略:Python开发环境下载安装教程

新手学 Python 数据分析,是不是总被 “安装库失败”“版本冲突” 搞崩溃?Anaconda3 直接解决这些痛点 —— 作为**Python 数据科学集成工具包**,它打包了 Python 3.11 内核、180 + 常用科研库(NumPy/Pandas/Matplo…

基于深度学习的水稻病害检测系统(YOLOv8+YOLO数据集+UI界面+Python项目+模型)

一、项目介绍 摘要 本项目基于先进的YOLOv8目标检测算法&#xff0c;开发了一套专门针对水稻病害的智能识别系统。系统可准确检测并分类三种常见水稻病害&#xff1a;细菌性条斑病(Bacteria_Leaf_Blight)、褐斑病(Brown_Spot)和叶黑粉病(Leaf_smut)。项目采用大规模专业数据集…

Python 中安装和导入 DBSCAN 库步骤

你想知道在Python中具体如何安装和导入DBSCAN相关库,核心答案是:DBSCAN并非独立库,而是集成在`scikit-learn`(简称sklearn)这个Python机器学习核心库中,所以只需安装sklearn,就能调用DBSCAN模块。下面我会一步步…

学霸同款2026自考AI论文平台TOP10:选对工具轻松过关

学霸同款2026自考AI论文平台TOP10&#xff1a;选对工具轻松过关 2026年自考AI论文平台测评&#xff1a;选对工具&#xff0c;轻松通关 随着人工智能技术的不断进步&#xff0c;越来越多的自考生开始借助AI论文平台提升写作效率、优化内容质量。然而&#xff0c;面对市场上琳琅满…

强烈安利专科生必用TOP8 AI论文软件测评

强烈安利专科生必用TOP8 AI论文软件测评 一、不同维度核心推荐&#xff1a;8款AI工具各有所长 对于专科生来说&#xff0c;撰写论文是一项既重要又复杂的任务&#xff0c;从选题到初稿、再到查重和排版&#xff0c;每一个环节都可能成为挑战。而市面上的AI论文工具种类繁多&…

JAVA攻防-Shiro专题有key无利用链JRMP协议CC1链分析Transform执行链

知识点&#xff1a; 1、Java攻防-Shiro-有key无利用链&JRMP协议 2、Java攻防-Shiro-CC1链分析&Transform执行链 一、演示案例-Java攻防-Shiro-有key无利用链&JRMP协议 Shrio有key无链&#xff1a; JRMP指的是Java远程方法协议&#xff08;Java Remote Method Pro…

边缘智算新引擎 DPU 驱动的算力革新

2026年1月7日&#xff0c;工信部印发《工业互联网和人工智能融合赋能行动方案》&#xff0c;强化工业智能算力供给。加快工业互联网与通算中心、智算中心、超算中心融合应用&#xff0c;鼓励公共算力服务商向工业企业提供服务。引导工业企业加快边缘一体机、智能网关等设备部署…

从需求分析到精准匹配:解码专业红娘的“择偶系统设计”逻辑

作为一名长期与逻辑和系统打交道的技术人&#xff0c;你是否发现&#xff1a;调试代码比处理情感问题简单得多&#xff1f;今天我们从系统设计的角度&#xff0c;聊聊专业红娘如何帮你解决这个“非线性优化问题”。一、问题定义&#xff1a;为什么择偶需求比产品需求更难厘清&a…

2026最新MinGW64官网下载安装全攻略教程(含环境变量配置避坑,适合新手小白) - xiema

MinGW(Minimalist GNU for Windows)是Windows平台上非常受欢迎的 C/C++ 编译工具。 简单来说,MinGW 就是把 Linux 下大名鼎鼎的 GCC 编译器"移植"到了 Windows 系统,让你不用装虚拟机就能在 Win 上直接编…