1. 为什么250行代码是个值得挑战的目标
1.1 贪吃蛇这个项目到底在练什么
很多人看到“贪吃蛇”三个字的第一反应是:这不是编程入门第一课吗?有什么好讲的。但如果你真的动手从零写过一遍,就会发现这个看似简单的项目,实际上把游戏开发中最核心的几个问题全部串起来了——游戏循环、状态管理、碰撞检测、输入响应、渲染刷新,一个都不少。
250行代码这个数字很有意思。它刚好卡在一个微妙的区间:用50行可以写出来,但代码会挤成一团,逻辑耦合严重,改一个地方崩三个地方;用500行也能写,但会引入大量不必要的抽象,对于一个小项目来说属于过度设计。250行左右,恰好是结构清晰、职责分明、又不过度工程化的甜点区。
我自己的经验是,用Python写贪吃蛇,核心逻辑大概在180到220行之间,加上必要的注释和空行,250行是一个很舒服的体量。这个体量意味着你可以在一个下午写完,但写完之后回头看,每一行你都能说清楚它为什么在那里。
1.2 技术选型:为什么是Python加Pygame
Python做游戏开发,绕不开Pygame这个库。有人会问,为什么不用Pygame Zero或者Arcade?我的判断标准很简单:你要练的是什么。
Pygame Zero确实更简单,它帮你把游戏循环、事件处理都封装好了,你只需要写更新逻辑和绘制逻辑。但问题是,它把太多东西藏起来了,你写完一个游戏,对游戏循环怎么运转的、事件是怎么分发的、帧率是怎么控制的,依然是模糊的。Arcade则更现代,基于OpenGL,性能更好,但对于贪吃蛇这种2D网格游戏来说,属于杀鸡用牛刀。
Pygame的好处在于:它足够底层,你需要自己控制游戏循环、自己处理事件队列、自己管理帧率,这些恰恰是理解游戏开发最关键的环节。同时它又足够简单,API直观,不需要你懂图形学。对于250行这个体量的项目来说,Pygame是恰到好处的抽象层级。
安装也很直接:
pip install pygame就这一条命令,没有额外的依赖,没有复杂的配置。这也是我推荐用Pygame练手的原因之一——环境搭建的成本几乎为零,你可以把全部精力放在逻辑设计上。
1.3 最终成品长什么样
在动手之前,先明确目标。这个贪吃蛇最终会具备以下功能:
- 蛇在网格上移动,吃到食物后身体增长一格
- 撞墙或撞到自己身体则游戏结束
- 支持方向键控制,且不能180度掉头
- 分数实时显示在窗口标题栏
- 游戏结束后可以按R键重新开始
没有花哨的动画,没有音效,没有关卡设计。就是最纯粹的贪吃蛇。但每一个功能背后的实现细节,我都会讲清楚为什么这么做,以及不这么做会踩什么坑。
2. 核心架构拆解:250行怎么分配
2.1 整体代码结构规划
在写第一行代码之前,我习惯先把结构想清楚。250行不是随便堆的,每一块都有它的位置。我的分配方案是这样的:
| 模块 | 预估行数 | 职责 |
|---|---|---|
| 导入与常量定义 | 25行 | 窗口尺寸、颜色、方向、帧率 |
| 游戏状态初始化 | 30行 | 蛇的初始位置、食物生成、分数归零 |
| 事件处理 | 35行 | 键盘输入、退出、重开 |
| 游戏逻辑更新 | 60行 | 移动、吃食物、碰撞检测 |
| 渲染绘制 | 50行 | 网格、蛇、食物、分数 |
| 主循环与入口 | 30行 | 游戏循环、帧率控制、状态切换 |
| 注释与空行 | 20行 | 必要的说明和呼吸感 |
这个分配不是拍脑袋来的。事件处理和渲染绘制之所以行数接近,是因为它们都是“翻译层”——把外部输入翻译成内部状态,把内部状态翻译成视觉输出。而游戏逻辑更新是核心,行数最多,因为它包含了所有判断和状态变更。
注意:不要一开始就追求行数精确。先写出来,再优化。250行是结果,不是目标。如果你为了凑行数或者压行数而牺牲可读性,那就本末倒置了。
2.2 网格系统:为什么不用像素级移动
贪吃蛇的实现方式有两种:像素级连续移动和网格级离散移动。前者蛇的位置是浮点数,移动是平滑的;后者蛇的位置是整数格坐标,移动是一格一格跳的。
我选择网格系统,原因有三个:
第一,碰撞检测简单。网格系统下,判断蛇头是否撞墙,只需要比较坐标是否越界;判断是否撞到自己,只需要检查蛇头坐标是否在蛇身坐标列表中。如果用像素级,你需要做矩形碰撞检测,复杂度上升一个量级。
第二,食物生成简单。网格系统下,食物就是随机一个不在蛇身上的格子坐标。像素级的话,你需要确保食物不和蛇身重叠,还要处理食物被蛇身部分覆盖的情况。
第三,视觉风格统一。经典贪吃蛇就是格子风格的,蛇是一格一格的方块,食物是一个小方块。这种风格用网格系统实现最自然。
具体来说,我设定窗口为600x600像素,每个格子30x30像素,那么网格就是20x20。这个尺寸在大多数屏幕上显示都合适,不会太小看不清,也不会太大占满屏幕。
CELL_SIZE = 30 GRID_WIDTH = 20 GRID_HEIGHT = 20 WINDOW_WIDTH = CELL_SIZE * GRID_WIDTH WINDOW_HEIGHT = CELL_SIZE * GRID_HEIGHT2.3 蛇的数据结构选择
蛇用什么数据结构存?这是个小问题,但选错了后面会难受。
用列表(list)是最自然的选择。列表的头部是蛇头,尾部是蛇尾。每次移动时,在头部插入新坐标,如果没吃到食物就弹出尾部。这样蛇的移动就变成了两个操作:insert(0, new_head)和pop()。
snake = [(10, 10), (9, 10), (8, 10)] # 蛇头在第一个为什么不用队列(deque)?因为我们需要频繁访问蛇头(索引0)和蛇尾(索引-1),列表的随机访问是O(1),而deque虽然两端操作是O(1),但中间访问是O(n)。对于贪吃蛇这个体量,列表完全够用,而且更直观。
为什么不用集合(set)?因为蛇身是有顺序的,集合会丢失顺序信息,渲染的时候你就不知道哪节是哪节了。
实操心得:蛇的坐标用元组(tuple)而不是列表(list),因为元组是不可变的,可以作为字典的键,也避免了意外修改。这个习惯在后续做碰撞检测时会有帮助。
2.4 游戏循环的设计
游戏循环是整个程序的心脏。Pygame的游戏循环基本结构是这样的:
while running: handle_events() update_game() render() clock.tick(FPS)这个顺序很重要。先处理事件,因为玩家的输入需要尽快响应;然后更新游戏状态,因为渲染需要基于最新的状态;最后渲染,把当前帧画出来;最后控制帧率,避免CPU空转。
帧率我设定为10 FPS。为什么这么低?因为贪吃蛇是网格移动,如果帧率太高,蛇会移动得太快,玩家来不及反应。10 FPS意味着蛇每秒移动10格,在20x20的网格上,从一头走到另一头需要2秒,这个速度对大多数人来说是合适的。
如果你觉得太慢或太快,可以调整这个值。但注意,帧率不要超过15,否则蛇会快到看不清;也不要低于5,否则操作会有明显的延迟感。
3. 逐块实现与关键细节
3.1 初始化:蛇和食物的起点
初始化看起来简单,但有几个细节容易忽略。
蛇的初始位置我放在网格中间偏左,方向朝右。为什么不在正中间?因为如果蛇在正中间,食物生成在蛇右边的概率很高,蛇一出生就能吃到食物,缺少缓冲。放在偏左的位置,蛇有足够的空间向右移动。
snake = [(5, 10), (4, 10), (3, 10)] direction = (1, 0) # 向右食物生成需要保证不在蛇身上。最简单的做法是随机生成一个坐标,如果在蛇身上就重新生成。在20x20的网格上,蛇初始只有3节,冲突概率很低,循环几次就能找到合法位置。
def generate_food(): while True: pos = (random.randint(0, GRID_WIDTH-1), random.randint(0, GRID_HEIGHT-1)) if pos not in snake: return pos这里有个潜在问题:如果蛇长到占满整个网格,这个循环会死循环。虽然在实际游戏中几乎不可能发生,但严谨起见,可以加一个最大尝试次数,超过就返回None,游戏判定胜利。不过250行的版本里,我选择不加这个判断,因为蛇占满网格的概率极低,加了反而增加代码复杂度。
注意:
random.randint是闭区间,所以上限是GRID_WIDTH-1。如果你写成GRID_WIDTH,食物可能生成在网格外面,蛇永远吃不到。
3.2 事件处理:方向键与防掉头
事件处理的核心是键盘输入。Pygame用pygame.event.get()获取事件队列,然后遍历处理。
方向键的处理有个经典陷阱:不能180度掉头。如果蛇正在向右移动,玩家按下左键,蛇不能立刻反向,否则蛇头会撞到自己的脖子,游戏直接结束。这不是bug,是规则。
解决方案是维护一个“当前方向”和一个“待处理方向”。按键时只更新待处理方向,在游戏更新时再真正应用。应用前检查待处理方向是否与当前方向相反。
if event.key == pygame.K_UP and direction != (0, 1): next_direction = (0, -1) elif event.key == pygame.K_DOWN and direction != (0, -1): next_direction = (0, 1) elif event.key == pygame.K_LEFT and direction != (1, 0): next_direction = (-1, 0) elif event.key == pygame.K_RIGHT and direction != (-1, 0): next_direction = (1, 0)注意这里的判断条件:direction != (0, 1)表示当前不是向下,所以可以向上。如果当前是向下,按上键无效。这样就从逻辑上杜绝了掉头。
但这里还有个更隐蔽的问题:如果玩家在极短时间内连续按两个键,比如先按上再按左,而当前方向是右。按上时,next_direction变成上;按左时,检查direction != (1, 0),当前方向还是右,所以next_direction变成左。结果蛇直接向左,撞到自己。
这个问题的根源在于,按键时检查的是direction(当前方向),而不是next_direction(待处理方向)。正确的做法是检查next_direction:
if event.key == pygame.K_UP and next_direction != (0, 1): next_direction = (0, -1)这样连续按键时,第二次按键会基于第一次的结果判断,就不会出现掉头了。这个细节很多教程都不会讲,但实际玩的时候很容易触发。
3.3 移动逻辑:蛇怎么“走”
蛇的移动逻辑是整个游戏最核心的部分。每次更新时,根据当前方向计算新蛇头的位置:
head_x, head_y = snake[0] dir_x, dir_y = direction new_head = (head_x + dir_x, head_y + dir_y)然后判断新蛇头是否吃到食物:
if new_head == food: snake.insert(0, new_head) food = generate_food() score += 1 else: snake.insert(0, new_head) snake.pop()吃到食物时,只插入新头,不弹出尾部,蛇就长了一格。没吃到时,插入新头并弹出尾部,蛇的长度不变,但整体向前移动了一格。
这个逻辑看起来简单,但有一个容易忽略的点:先插入再判断还是先判断再插入。上面的写法是先计算新头,判断是否吃到食物,然后统一插入。这样写的好处是逻辑清晰,不会出现“插入了但没吃到,又忘了弹出”的情况。
实操心得:
snake.insert(0, new_head)在列表头部插入是O(n)操作,因为要移动所有元素。对于贪吃蛇这个长度(最多几百节),性能完全不是问题。如果你真的在意性能,可以用collections.deque,但代码会稍微复杂一点。250行的版本里,列表足够了。
3.4 碰撞检测:什么时候游戏结束
碰撞检测分两种:撞墙和撞自己。
撞墙的判断很简单,新蛇头的坐标超出网格范围就是撞墙:
if not (0 <= new_head[0] < GRID_WIDTH and 0 <= new_head[1] < GRID_HEIGHT): game_over = True撞自己的判断是,新蛇头坐标在蛇身列表中。但这里有个细节:蛇尾要不要算?因为蛇移动时,尾部会弹出一格,所以新蛇头的位置如果恰好是当前蛇尾的位置,实际上蛇尾已经移走了,不应该算撞到自己。
if new_head in snake[:-1]: # 排除蛇尾 game_over = True用snake[:-1]排除最后一个元素(蛇尾)。但注意,如果蛇吃到了食物,尾部不会弹出,这时候蛇尾还在,应该算碰撞。所以更严谨的写法是:
if new_head in snake: if new_head != snake[-1] or ate_food: game_over = True不过在实际游戏中,蛇头撞到蛇尾的情况极其罕见,因为蛇尾在移动。为了代码简洁,很多实现直接判断new_head in snake,包括蛇尾。这样做的后果是,在某些极端情况下会误判,但概率极低,不影响游戏体验。我个人的选择是排除蛇尾,因为逻辑上更正确。
3.5 渲染:把状态画出来
渲染部分用Pygame的绘图API,主要画三样东西:蛇、食物、分数。
蛇的每一节画一个填充矩形,蛇头用不同的颜色区分:
for i, (x, y) in enumerate(snake): color = HEAD_COLOR if i == 0 else BODY_COLOR rect = pygame.Rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE) pygame.draw.rect(screen, color, rect)食物画一个圆形或者圆角矩形,视觉上和蛇区分开:
food_rect = pygame.Rect(food[0] * CELL_SIZE, food[1] * CELL_SIZE, CELL_SIZE, CELL_SIZE) pygame.draw.ellipse(screen, FOOD_COLOR, food_rect)分数显示在窗口标题栏,用pygame.display.set_caption:
pygame.display.set_caption(f"贪吃蛇 - 分数: {score}")为什么不用字体渲染在窗口内?因为加载字体需要额外的代码,而且中文字体在不同系统上路径不同,容易出问题。标题栏显示分数虽然不够炫酷,但胜在简单可靠,零依赖。
注意:
pygame.draw.rect的坐标是左上角坐标,不是中心坐标。网格坐标乘以CELL_SIZE得到像素坐标,这个转换不要搞反了。
3.6 游戏结束与重开
游戏结束后,屏幕上显示“Game Over”和最终分数,按R键重新开始。
重开的逻辑是重置所有状态变量:
snake = [(5, 10), (4, 10), (3, 10)] direction = (1, 0) next_direction = (1, 0) food = generate_food() score = 0 game_over = False这里有个容易犯的错误:忘记重置next_direction。如果游戏结束时next_direction是某个方向,重开后蛇会立刻朝那个方向移动,而不是初始的向右。所以next_direction必须和direction一起重置。
4. 实操中踩过的坑与排查技巧
4.1 蛇移动速度不均匀
这是新手最常遇到的问题:蛇有时候移动得快,有时候移动得慢。原因通常是帧率控制没做好。
Pygame的clock.tick(FPS)会确保循环以指定帧率运行,但它控制的是最大帧率,不是固定帧率。如果某一帧的处理时间超过了1/FPS秒,实际帧率就会下降。
对于贪吃蛇来说,这通常不是问题,因为每帧的计算量很小。但如果你在渲染时做了大量绘制操作,或者电脑性能很差,就可能出现卡顿。
解决方案是把游戏逻辑更新和渲染分离。游戏逻辑以固定频率更新(比如每秒10次),渲染以尽可能高的频率运行。这样即使渲染卡顿,游戏逻辑也不会受影响。
不过对于250行的版本,我选择不引入这个复杂度。clock.tick(10)在绝大多数情况下都能稳定运行。如果你真的遇到了速度不均匀的问题,可以先检查是不是有其他程序占用了CPU。
4.2 按键响应延迟
有时候按了方向键,蛇要过一会儿才转向。这不是代码bug,是游戏循环的固有延迟。
在10 FPS下,每帧间隔100毫秒。如果你在帧刚开始时按键,要等100毫秒后下一帧才会处理。这个延迟在快速游戏中是致命的,但在贪吃蛇里可以接受。
如果你觉得延迟明显,可以提高帧率到15或20。但代价是蛇移动更快,游戏难度上升。这是一个权衡,没有标准答案。
实操心得:我试过在事件处理里直接更新方向,而不是等到下一帧。这样做确实能减少延迟,但会引入新的问题——如果一帧内收到多个方向键事件,蛇可能会连续转向,导致撞到自己。所以还是老老实实在更新阶段处理方向比较稳妥。
4.3 食物生成在蛇身上
前面提到过,食物生成时如果随机到了蛇身上,需要重新生成。但有一种情况容易忽略:蛇吃食物后,新蛇头的位置就是食物原来的位置。如果你在蛇移动之前就生成了新食物,新食物可能生成在新蛇头的位置上。
正确的顺序是:先移动蛇,再生成新食物。这样新食物生成时,蛇头已经在新的位置了,generate_food里的pos not in snake检查会排除掉蛇头的位置。
# 正确的顺序 snake.insert(0, new_head) if ate_food: food = generate_food() # 此时snake已经包含新蛇头4.4 窗口关闭无响应
有时候点击窗口的关闭按钮,程序没反应。这是因为没有处理pygame.QUIT事件。
for event in pygame.event.get(): if event.type == pygame.QUIT: running = False这行代码必须放在事件处理的最前面,否则如果事件队列里有其他事件,可能会先处理其他事件,导致关闭延迟。
另外,如果程序卡在某个循环里(比如generate_food的死循环),事件队列不会被处理,窗口就会无响应。所以generate_food一定要有退出条件,哪怕只是一个最大尝试次数。
4.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| 蛇移动速度不均匀 | 帧率不稳定 | 检查CPU占用,降低渲染复杂度 |
| 按键无响应 | 事件队列未处理 | 确认pygame.event.get()在循环中调用 |
| 蛇撞到自己但没结束 | 碰撞检测排除了蛇尾 | 检查new_head in snake的判断逻辑 |
| 食物生成在蛇身上 | 生成顺序错误 | 确保先移动蛇再生成食物 |
| 重开后蛇方向不对 | next_direction未重置 | 检查重开逻辑是否重置所有状态 |
| 窗口无法关闭 | 未处理QUIT事件 | 在事件循环中添加QUIT处理 |
| 蛇移动太快/太慢 | 帧率设置不当 | 调整clock.tick()的参数 |
| 分数不更新 | 分数变量未递增 | 检查吃到食物后的score += 1 |
4.6 代码组织的小技巧
250行代码虽然不多,但如果组织不好,读起来也会很累。我的建议是:
把功能相关的代码放在一起。比如所有和食物相关的操作(生成、绘制、碰撞)放在相邻的位置。这样你改食物逻辑时,不需要在文件里跳来跳去。
用空行分隔逻辑块。不要写成一整块,适当用空行分隔不同的逻辑段落。Python对空行不敏感,但人眼对空行很敏感。
常量全部大写并放在文件开头。颜色、尺寸、帧率这些常量,集中定义在文件顶部。这样调整参数时只需要改一个地方。
函数不要太多也不要太少。250行的项目,我建议3到5个函数。太少会导致主循环臃肿,太多会导致函数调用链过长。我的分配是:generate_food、handle_events、update_game、render,加上主函数,一共5个。
5. 从能跑到好用:几个提升体验的细节
5.1 蛇身的渐变效果
默认的蛇身是纯色,看起来比较单调。可以给蛇身加一个渐变效果,从蛇头的深色逐渐过渡到蛇尾的浅色。
实现方式是根据蛇身的索引计算颜色插值:
for i, (x, y) in enumerate(snake): ratio = i / len(snake) color = ( int(HEAD_COLOR[0] * (1 - ratio) + TAIL_COLOR[0] * ratio), int(HEAD_COLOR[1] * (1 - ratio) + TAIL_COLOR[1] * ratio), int(HEAD_COLOR[2] * (1 - ratio) + TAIL_COLOR[2] * ratio), ) # 绘制...这个效果只增加了不到10行代码,但视觉提升很明显。蛇看起来更有层次感,也更容易分辨蛇头和蛇尾。
5.2 食物的小动画
食物可以做一个简单的呼吸效果,大小随时间轻微变化。用pygame.time.get_ticks()获取当前时间,然后计算一个缩放因子:
ticks = pygame.time.get_ticks() scale = 0.8 + 0.2 * abs((ticks % 1000) / 500 - 1)这样食物会以1秒为周期,在0.8到1.0之间缩放。虽然是个很小的细节,但会让游戏看起来更精致。
注意:这个动画依赖于帧率,如果帧率不稳定,动画会看起来不流畅。不过对于贪吃蛇这种低帧率游戏,影响不大。
5.3 游戏结束的视觉反馈
游戏结束时,不要只是显示文字,可以给蛇身加一个闪烁效果,或者把整个屏幕变暗。
最简单的做法是画一个半透明的黑色遮罩:
overlay = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT)) overlay.set_alpha(128) overlay.fill((0, 0, 0)) screen.blit(overlay, (0, 0))然后在遮罩上画“Game Over”文字。这样游戏结束的视觉冲击力更强,玩家能立刻意识到游戏结束了。
5.4 防止按键过快导致的问题
前面提到过连续按键导致掉头的问题,解决方案是检查next_direction而不是direction。但还有一种情况:玩家按住方向键不放,Pygame会连续触发按键事件。
对于贪吃蛇来说,这通常不是问题,因为方向键的重复触发只是不断设置同一个方向。但如果你做了“按空格加速”之类的功能,就需要处理按键重复。
Pygame的按键事件有pygame.KEYDOWN和pygame.KEYUP两种。如果你需要检测“按住”状态,可以用pygame.key.get_pressed():
keys = pygame.key.get_pressed() if keys[pygame.K_SPACE]: # 加速逻辑但注意,get_pressed()返回的是当前所有按键的状态,需要在每一帧都调用。对于250行的版本,我不建议加入加速功能,因为它会引入额外的状态管理复杂度。
5.5 窗口图标的设置
默认的Pygame窗口图标是一个灰色的Pygame logo,看起来不够专业。可以自己画一个简单的蛇形图标:
icon = pygame.Surface((32, 32)) icon.fill((0, 0, 0)) pygame.draw.rect(icon, (0, 255, 0), (4, 12, 8, 8)) pygame.draw.rect(icon, (0, 200, 0), (12, 12, 8, 8)) pygame.draw.rect(icon, (0, 150, 0), (20, 12, 8, 8)) pygame.display.set_icon(icon)这段代码画了三个绿色方块,代表蛇的三节身体。虽然简单,但比默认图标好得多。
6. 250行之外的扩展思路
6.1 加入音效
Pygame支持音效播放,用pygame.mixer模块。吃到食物时播放一个短促的“叮”声,游戏结束时播放一个低沉的“咚”声。
eat_sound = pygame.mixer.Sound("eat.wav") eat_sound.play()音效文件需要自己准备,或者用代码生成简单的波形。不过音效文件会增加项目的文件数量,如果你追求单文件运行,可以跳过这个扩展。
6.2 加入障碍物
在网格上随机放置一些障碍物,蛇撞到障碍物也会游戏结束。这个扩展只需要增加一个obstacles列表,在碰撞检测时多判断一次。
但障碍物的生成需要保证不会把蛇围死,否则游戏无法进行。简单的做法是障碍物只生成在网格的边缘区域,或者生成后检查蛇是否还有通路。对于250行的版本,我建议障碍物固定位置,不要随机生成,避免复杂的通路检测。
6.3 加入双人模式
两个玩家分别用WASD和方向键控制两条蛇,谁先撞墙、撞自己或撞对方,谁就输。
这个扩展的复杂度主要在于碰撞检测:蛇头撞到对方蛇身、两条蛇头相撞、两条蛇同时吃到食物等。代码量会增加大约100行,超出250行的范围。但如果你已经写完了基础版本,可以尝试这个扩展,它对理解游戏状态管理很有帮助。
6.4 加入关卡系统
每吃到一个食物,蛇的速度增加一点,或者网格上出现新的障碍物。这样游戏会越来越难,增加挑战性。
速度增加的实现很简单,把clock.tick(FPS)中的FPS改成一个变量,吃到食物时递增:
FPS = min(FPS + 0.5, 20) # 最高20帧但注意,帧率变化会影响游戏手感,需要仔细调整递增的幅度。
6.5 用面向对象重构
250行的过程式代码已经足够清晰,但如果你想练习面向对象设计,可以把蛇、食物、游戏分别封装成类。
class Snake: def __init__(self): self.body = [(5, 10), (4, 10), (3, 10)] self.direction = (1, 0) def move(self): # 移动逻辑 def check_collision(self): # 碰撞检测面向对象的好处是职责更清晰,扩展更方便。但代价是代码量会增加,可能超过250行。所以这是一个取舍:你要的是简洁,还是可扩展性。
我个人在实际操作中的体会是,250行的过程式版本更适合学习和快速原型开发,而面向对象版本更适合作为项目的基础架构。如果你打算继续扩展这个游戏,建议先写过程式版本,理解清楚逻辑后,再用面向对象重构一遍。这样你对两种范式都会有更深的体会。
最后再分享一个小技巧:写完之后,把代码放一放,第二天再回来看。你会发现很多当时觉得没问题的地方,其实有更好的写法。代码review自己的代码,是提升最快的方式。