Python实现双人对战贪吃蛇游戏完整代码解析
本文详细介绍如何使用Python和Pygame库开发一款支持双人对战的贪吃蛇游戏。文章从基础单人对战版本出发,逐步重构代码,引入蛇类与豆子类,实现双人独立控制、自定义颜色、蛇头高亮显示及多豆子随机生成等功能。通过面向对象的设计思路,展示了如何简化主循环逻辑并处理碰撞检测,最终提供完整可运行的代码示例,适合Python初学者进行游戏开发实践。
Python双人对战贪吃蛇游戏开发概述
本文分享了一个使用Python编写的双人对战版贪吃蛇游戏的具体实现代码,供读者参考学习。
起因是家中孩子想玩贪吃蛇并邀请我对战,由于一时找不到现成的双人游戏,我决定利用编程技能现场编写一个。这不仅满足了孩子的需求,也是一个向孩子展示程序员能力的绝佳机会,于是我开始着手开发。
基础版本代码实现与逻辑分析
首先介绍基础版本。
该基础版本无需从零开始,我在网上搜索到了相关共享代码,据称源自《Raspberry Pi 用户指南》,具体链接如下。代码如下:
#!/usr/bin/env python
import pygame,sys,time,random
from pygame.locals import *
# 定义颜色变量
redColour = pygame.Color(255,0,0)
blackColour = pygame.Color(0,0,0)
whiteColour = pygame.Color(255,255,255)
greyColour = pygame.Color(150,150,150)
# 定义gameOver函数
def gameOver(playSurface):
gameOverFont = pygame.font.Font('arial.ttf',72)
gameOverSurf = gameOverFont.render('Game Over', True, greyColour)
gameOverRect = gameOverSurf.get_rect()
gameOverRect.midtop = (320, 10)
playSurface.blit(gameOverSurf, gameOverRect)
pygame.display.flip()
time.sleep(5)
pygame.quit()
sys.exit()
# 定义main函数
def main():
# 初始化pygame
pygame.init()
fpsClock = pygame.time.Clock()
# 创建pygame显示层
playSurface = pygame.display.set_mode((640,480))
pygame.display.set_caption('Raspberry Snake')
# 初始化变量
snakePosition = [100,100]
snakeSegments = [[100,100],[80,100],[60,100]]
raspberryPosition = [300,300]
raspberrySpawned = 1
direction = 'right'
changeDirection = direction
while True:
# 检测例如按键等pygame事件
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
# 判断键盘事件
if event.key == K_RIGHT or event.key == ord('d'):
changeDirection = 'right'
if event.key == K_LEFT or event.key == ord('a'):
changeDirection = 'left'
if event.key == K_UP or event.key == ord('w'):
changeDirection = 'up'
if event.key == K_DOWN or event.key == ord('s'):
changeDirection = 'down'
if event.key == K_ESCAPE:
pygame.event.post(pygame.event.Event(QUIT))
# 判断是否输入了反方向
if changeDirection == 'right' and not direction == 'left':
direction = changeDirection
if changeDirection == 'left' and not direction == 'right':
direction = changeDirection
if changeDirection == 'up' and not direction == 'down':
direction = changeDirection
if changeDirection == 'down' and not direction == 'up':
direction = changeDirection
# 根据方向移动蛇头的坐标
if direction == 'right':
snakePosition[0] += 20
if direction == 'left':
snakePosition[0] -= 20
if direction == 'up':
snakePosition[1] -= 20
if direction == 'down':
snakePosition[1] += 20
# 增加蛇的长度
snakeSegments.insert(0,list(snakePosition))
# 判断是否吃掉了树莓
if snakePosition[0] == raspberryPosition[0] and snakePosition[1] == raspberryPosition[1]:
raspberrySpawned = 0
else:
snakeSegments.pop()
# 如果吃掉树莓,则重新生成树莓
if raspberrySpawned == 0:
x = random.randrange(1,32)
y = random.randrange(1,24)
raspberryPosition = [int(x*20),int(y*20)]
raspberrySpawned = 1
# 绘制pygame显示层
playSurface.fill(blackColour)
for position in snakeSegments:
pygame.draw.rect(playSurface,whiteColour,Rect(position[0],position[1],20,20))
pygame.draw.rect(playSurface,redColour,Rect(raspberryPosition[0], raspberryPosition[1],20,20))
# 刷新pygame显示层
pygame.display.flip()
# 判断是否死亡
if snakePosition[0] > 620 or snakePosition[0] < 0:
gameOver(playSurface)
if snakePosition[1] > 460 or snakePosition[1] < 0:
for snakeBody in snakeSegments[1:]:
if snakePosition[0] == snakeBody[0] and snakePosition[1] == snakeBody[1]:
gameOver(playSurface)
# 控制游戏速度
fpsClock.tick(5)
if __name__ == "__main__":
main()
这段基础代码实现了核心功能:主循环中首先检测按键事件,随后更新蛇的位置。当蛇吃到食物(原代码称为“树莓”,此处简化为“豆子”)时,蛇身长度增加并重新生成豆子,接着刷新屏幕显示,最后检测是否发生碰撞死亡。若判定死亡,则调用gameOver函数结束游戏。
然而,基础版本无法满足孩子的游戏需求。经过试玩,孩子提出了以下改进要求:
1、支持双人同时游戏,每人控制一条蛇,比较谁吃的豆子更多。 2、蛇死亡后不结束游戏,而是直接重新开始。 3、允许自定义蛇的颜色。 4、蛇头需要以不同颜色显示,以便区分。 5、增加豆子数量,一次性生成多个豆子供玩家自由食用。
面向对象重构:蛇类与豆子类设计
鉴于用户需求多样且可能持续变化,为了便于后续维护与扩展,我将蛇相关的操作封装为一个独立的类,定义如下。
蛇类定义。
class Snake:
def __init__(self, color, headColor, ctrlKeys):
self.color = color
self.headColor = headColor
self.ctrlKeys = ctrlKeys #按[上,下,左,右]的顺序
self.direction = random.choice([-2,2,-1,1]) # 方向[-2,2,-1,1]分别表示[上,下,左,右]
x = random.randrange(5,SCREEN_WIDTH/20-5)
y = random.randrange(5,SCREEN_HEIGHT/20-5)
self.headPos = [int(x*20),int(y*20)]
self.segments = [self.headPos]
self.moveAndAdd()
self.moveAndAdd()
def changeDirection(self, pressKey):
directions = [-2,2,-1,1]
for direct, key in zip(directions, self.ctrlKeys):
if key == pressKey and direct + self.direction != 0:
self.direction = direct
def moveAndAdd(self):
# 根据方向移动蛇头的坐标
if self.direction == 1:
self.headPos[0] += 20
if self.direction == -1:
self.headPos[0] -= 20
if self.direction == -2:
self.headPos[1] -= 20
if self.direction == 2:
self.headPos[1] += 20
self.segments.insert(0,list(self.headPos)) # 在蛇头插入一格
def pop(self):
self.segments.pop() # 在蛇尾减去一格
def show(self, playSurface):
# 画蛇身
for pos in self.segments[1:]:
pygame.draw.rect(playSurface,self.color,Rect(pos[0],pos[1],20,20))
# 画蛇头
pygame.draw.rect(playSurface,self.headColor,Rect(self.headPos[0],self.headPos[1],20,20))
def respawnIfDead(self):
if self.headPos[0] > SCREEN_WIDTH-20 or self.headPos[0] < 0 or self.headPos[1] > SCREEN_HEIGHT-20 or self.headPos[1] < 0:
x = random.randrange(5,SCREEN_WIDTH/20-5)
y = random.randrange(5,SCREEN_HEIGHT/20-5)
self.direction = random.choice([-2,2,-1,1])
self.headPos = [int(x*20),int(y*20)]
self.segments = [self.headPos]
self.moveAndAdd()
self.moveAndAdd()
该类的初始化函数接收三个参数:蛇身颜色、蛇头颜色以及控制按键。初始状态下,蛇身长度为3格,并随机生成在屏幕中央区域(避免生成在边缘导致玩家来不及反应即死亡)。初始化蛇对象的代码如下:
# 初始化蛇
ctrlKeys1 = [ord('w'),ord('s'),ord('a'),ord('d')]
ctrlKeys2 = [K_UP,K_DOWN,K_LEFT,K_RIGHT]
snake1 = Snake(GREEN,GREEN_HEAD,ctrlKeys1)
snake2 = Snake(RED,RED_HEAD,ctrlKeys2)
changeDirection函数用于改变蛇的移动方向,接收一个按键参数。self.direction属性记录当前移动方向,使用[-2, 2, -1, 1]分别代表[上,下,左,右],这种数值设定旨在简化代码逻辑。该函数根据按键值判断是否允许改变方向。需要注意的是,蛇不能反向移动,例如当蛇向上移动时,按下向下键无效。
moveAndAdd函数根据当前方向移动蛇头坐标,并在蛇头位置添加新的一段。pop函数则在蛇尾移除一段。这两个函数结合使用,实现了蛇的移动以及吃到豆子后蛇身增长一格的效果。
show函数负责在屏幕上绘制蛇。先绘制蛇身,再绘制蛇头,这样可以确保蛇头颜色不会被蛇身颜色覆盖,从而清晰区分蛇头。
respawnIfDead函数用于检测蛇是否死亡(目前判定为超出屏幕边界)。若死亡,则执行重生逻辑。重生后的蛇随机出现在屏幕中央区域,身体长度重置为3格。
为了满足“一次性生成多个豆子”的需求,并考虑到未来的扩展性,我同样将豆子封装为Bean类,并创建了管理多个豆子的Beans类,具体定义如下。
豆类定义。
class Bean:
def __init__(self, color, pos):
self.color = color
self.pos = pos
def beEaten(self, snakePos):
if snakePos[0] == self.pos[0] and snakePos[1] == self.pos[1]:
return True
else:
return False
class Beans:
def __init__(self, color, totalNum):
self.color = color
self.totalNum = totalNum
self.curNum = 0
self.beans = []
def generate(self):
while self.curNum < self.totalNum:
x = random.randrange(0,SCREEN_WIDTH/20)
y = random.randrange(0,SCREEN_HEIGHT/20)
self.beans.append(Bean(self.color, [int(x*20),int(y*20)]))
self.curNum = self.curNum + 1
def beEaten(self, snakePos):
for bean in self.beans:
if bean.beEaten(snakePos):
self.beans.remove(bean)
self.curNum = self.curNum - 1
return True
return False
def show(self, playSurface):
for bean in self.beans:
pygame.draw.rect(playSurface,self.color,Rect(bean.pos[0],bean.pos[1],20,20))
Bean类较为简单,初始化时需指定颜色和位置,并提供beEaten函数用于判断该豆子是否被蛇头吃到。
Beans类相对复杂,它包含totalNum个豆子实例。初始化时需指定豆子颜色和总数量。curNum属性用于记录当前屏幕上实际存在的豆子数量,因为部分豆子可能已被吃掉。generate函数负责生成豆子,在初始化或豆子被吃掉后调用,生成的豆子随机出现在屏幕有效范围内。beEaten函数遍历beans列表,判断是否有豆子被蛇头吃到;若有,则从列表中移除该豆子,并更新curNum以反映剩余数量。show函数负责将所有豆子绘制到屏幕上。 初始化Beans对象的代码如下:
# 初始化豆子
yellowBeans = Beans(YELLOW, BEAN_NUM)
yellowBeans.generate()
主循环逻辑简化与完整运行配置
在蛇类和豆子类均初始化完毕后,主循环的代码逻辑得以大幅简化,如下所示:
while True:
# 检测按键等pygame事件
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.event.post(pygame.event.Event(QUIT))
else:
snake1.changeDirection(event.key)
snake2.changeDirection(event.key)
# 根据方向移动蛇并增加蛇长度一格
snake1.moveAndAdd()
snake2.moveAndAdd()
# 如果豆子被吃掉,则重新生成豆子,否则蛇长度减少一格
if yellowBeans.beEaten(snake1.headPos):
yellowBeans.generate()
else:
snake1.pop()
if yellowBeans.beEaten(snake2.headPos):
yellowBeans.generate()
else:
snake2.pop()
# 绘制刷新
playSurface.fill(BLACK) # 绘制pygame显示层
yellowBeans.show(playSurface)
snake1.show(playSurface)
snake2.show(playSurface)
pygame.display.flip() # 刷新pygame显示层
# 若死亡则重生
snake1.respawnIfDead()
snake2.respawnIfDead()
# 控制游戏速度
fpsClock.tick(5)
当然,为了使程序能够正常运行,仍需进行Pygame的基础初始化,代码如下:
pygame.init()
fpsClock = pygame.time.Clock()
# 创建pygame显示层
playSurface = pygame.display.set_mode((SCREEN_WIDTH,SCREEN_HEIGHT))
pygame.display.set_caption('Snake Eat Beans')
最后,代码中涉及的一些固定常量定义如下:
# 定义界面大小
SCREEN_WIDTH = 1080
SCREEN_HEIGHT = 720
# 定义豆子数量
BEAN_NUM = 10
# 定义颜色变量
RED = pygame.Color(255,0,0)
RED_HEAD = pygame.Color(255,150,0)
BLACK = pygame.Color(0,0,0)
WHITE = pygame.Color(255,255,255)
GREY = pygame.Color(150,150,150)
GREEN = pygame.Color(0,255,0)
GREEN_HEAD = pygame.Color(100,200,200)
YELLOW = pygame.Color(255,255,0)
BLUE = pygame.Color(0,0,255)
至此,我可以愉快地与孩子一起进行双人贪吃蛇对战了。

更多关于Python游戏的精彩文章请点击查看以下专题:
Python俄罗斯方块游戏集合
Python经典小游戏汇总
Python微信跳一跳游戏集合
更多有趣的经典小游戏实现专题,分享给大家:
C++经典小游戏汇总
JavaScript经典游戏 玩不停
Java经典小游戏汇总
JavaScript经典小游戏汇总
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









