Python实现双人对战贪吃蛇游戏完整代码解析

陆遥
陆遥
发布时间:2026-10-13 | 2人浏览过
原创

本文详细介绍如何使用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经典小游戏汇总

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表