许多程序员入门的第一个项目都会选择贪吃蛇。用贪吃蛇来练手比较合适。因为贪吃蛇的算法并不难,但绝对够有启发作用,也比较经典。相对来说它很容易就能实现。对于新手来说,拿贪吃蛇来作为练习是很有必要的。
各个语言都会有许多共性,但实现的算法基本都差不多。这里就用Python为例,分析一下算法实现,并附上相应代码。不足之处还请指正。
python是一门面向对象的程序语言,我们在编写一个项目时要首先根据需要对其进行分类,模块化处理。
分析得到,需要定义Snake类、Food类、Game类,分别代表蛇、食物、游戏规则。然后对类定义成员方法,比如Snake类需要进行拐弯、增加、减少等操作。
//Author:xiaobei
import random
class Food(object):
def __init__(self,r_pos,color,state):
self.r_pos = r_pos
self.color = color
self.state = state
def update(self,snake,sound_effect):
if self.r_pos in snake.body:
self.state = 1
else:
self.state = 0
if self.state == 1:
self.r_pos = [random.randrange(30,720,30),random.randrange(30,540,30)]
self.color = random.choice([(255,255,0),(255,0,255),(0,255,255)])
snake.length+=1
sound_effect.play()
//Author:xiaobei
Snake(object):
def __init__(self,length,body,direction,direction_change,head_pos,end):
self.length = length
self.body = body
self.direction = "RIGHT"
self.direction_change = "RIGHT"
self.head_pos = head_pos
self.end = False
#控制蛇的爬行
def move(self):
if self.length > len(self.body):
self.body.append([0,0])
for i in range(self.length-1,0,-1):
self.body[i][0] = self.body[i-1][0] #这里多重列表互相赋值不能整体,因为最外层代表首地址,即首元素,因此要分开赋值
self.body[i][1] = self.body[i-1][1]
if self.direction == "LEFT" :
self.body[0][0]-=30
elif self.direction == "RIGHT":
self.body[0][0]+=30
elif self.direction == "UP":
self.body[0][1]-=30
else:
if self.direction == "DOWN":
self.body[0][1]+=30
#控制不让贪吃蛇溢出
if self.head_pos[0]>850:
self.end = True
elif self.head_pos[1] > 650:
self.end = True
elif self.head_pos[0] < -30:
self.end = True
else:
if self.head_pos[1]<-30:
self.end = True
def turn(self):
if self.direction_change == "LEFT" and self.direction != "RIGHT":
self.direction = self.direction_change
elif self.direction_change == "RIGHT" and self.direction != "LEFT":
self.direction = self.direction_change
elif self.direction_change == "UP" and self.direction != "DOWN":
self.direction = self.direction_change
else :
if self.direction_change == "DOWN" and self.direction != "UP":
self.direction = self.direction_change
这里偷了一个懒,没有定义Game类,我把游戏规则掺进了主程序main()中。
用pygame产生一个界面,我们把这个界面当做一个坐标网格,蛇身是一个坐标列表。用蛇头坐标作为蛇的行进方向,控制蛇身整体的方向。难点在于如何使蛇身在转弯的时候,使蛇身每一部分逐个而不是整体转向呢?
for i in range(self.length-1,0,-1):
self.body[i][0] = self.body[i-1][0] #这里多重列表互相赋值不能整体,因为最外层代表首地址,即首元素,因此要分开赋值
self.body[i][1] = self.body[i-1][1]
这里我们采用一个循环,从尾部开始,只有蛇头在变动,后面的所有部分都在被蛇头牵连着动。假设有n个结点,nn-1 = nn,……,n2 = n1 .依次移动,形成一个拐弯效果。
食物刷新位置是在,蛇吃到食物之后才激发。所以重点在于如何判断是否吃到食物。这里有一个易错点:容易使食物坐标和蛇头的坐标永不吻合的的情况。这是因为蛇的坐标间隔以及食物刷新间隔位置,不是以同一递增/递减公差q进行的。
def update(self,snake,sound_effect):
if self.r_pos in snake.body:
self.state = 1
else:
self.state = 0
if self.state == 1:
self.r_pos = [random.randrange(30,720,30),random.randrange(30,540,30)]
self.color = random.choice([(255,255,0),(255,0,255),(0,255,255)])
snake.length+=1
sound_effect.play()
在此基础上,当蛇头坐标与食物坐标吻合,便激发食物刷新位置。
游戏结束条件无非两种:一是蛇头与自身重合,二十蛇头触到边界。这两种方法同样是通过坐标判断来实的。
if snake.head_pos in snake.body[1:] or snake.end:
screen.blit(text2Image,(200,200))
pygame.mixer.music.stop()
sound_end.play()
pygame.display.update()
time.sleep(5)
pygame.quit()
sys.exit()
界面实现依靠pygame第三方库来执行,pygame库的安装可以用pip命令行来实现,在控制台进行。
pip install -U pygame
参数-U是安装game为最新版。
pygame的使用请参考《Python 游戏开发—Pygame 快速入门》
主要代码如下:
//Author:xiaobei
import pygame
import os
import sys
import time
import random
from pygame.locals import *
from Snake import Snake
from Food import Food
pygame.init()
#混音器初始化
pygame.mixer.init()
#加载音效
sound_effect = pygame.mixer.Sound("C:\\Windows\\media\\Windows Proximity Notification.wav")
sound_end = pygame.mixer.Sound("C:\\Windows\\media\\Ring07.wav")
pygame.mixer.music.load("C:\\Windows\\media\\flourish.mid")
pygame.mixer.music.set_volume(0.5)
sound_effect.set_volume(1)
pygame.mixer.music.play(-1) #-1控制无限循环
#设置游戏时钟
clock = pygame.time.Clock()
#设置主窗体相对位置、大小,标题、背景颜色
os.environ["SDL_VIDEO_WINDOW_POS"] = "%d,%d"%(50,50)
screen = pygame.display.set_mode((820,620))
screen.fill([255,235,100])
pygame.display.set_caption("贪吃蛇小游戏")
#设置文本
pygame.font.init
text1 = pygame.font.Font(None,35)
text1Image = text1.render("SCORE:",True,[100,125,255])
text2 = pygame.font.Font(None,120)
text2Image = text2.render("Gameover",True,[100,125,255])
text3 = pygame.font.Font(None,35)
text4 = pygame.font.Font(None,35)
screen.blit(text1Image,(5,5))
#创建贪吃蛇对象、食物等对象
snake = Snake(length = 3,body = [[120,90],[90,90],[60,90]],direction = "RIGHT",direction_change ="RIGHT", head_pos = [120,100],end = False)
food = Food(r_pos = [120,420],color = (100,255,100),state = 0)
score = 0
#进入循环
while True:
#设置帧率刷新速度
clock.tick(5)
#设置窗口便于刷新
screen = pygame.display.set_mode((820,620))
screen.fill([255,235,100])
#绘制文本“分数”
score = len(snake.body)-3
screen.blit(text1Image,(5,5))
text3Image = text3.render("{}".format(score),True,[100,125,255])
screen.blit(text3Image,(120,5))
#绘制文本“时间”
text4Image = text4.render("time: {} seconds".format(int(time.clock())),True,[100,125,255])
screen.blit(text4Image,(200,5))
#绘制蛇身、食物
food_circle = pygame.draw.rect(screen,food.color,(food.r_pos[0],food.r_pos[1],28,28),0)
for i in range(snake.length):
snake_rent = pygame.draw.rect(screen,(120,255,205),(snake.body[i][0],snake.body[i][1],28,28),0)
#设置退出条件
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#获取键盘事件
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
snake.direction_change = "LEFT"
elif event.key == pygame.K_RIGHT:
snake.direction_change = "RIGHT"
elif event.key == pygame.K_UP:
snake.direction_change = "UP"
else:
if event.key == pygame.K_DOWN:
snake.direction_change = "DOWN"
#控制蛇的转向
snake.head_pos = snake.body[0]
food.update(snake,sound_effect)
snake.move()
snake.turn()
#游戏结束
if snake.head_pos in snake.body[1:] or snake.end:
screen.blit(text2Image,(200,200))
pygame.mixer.music.stop()
sound_end.play()
pygame.display.update()
time.sleep(5)
pygame.quit()
sys.exit()
#刷新界面
pygame.display.update()
我把每个类模块独立为单个文件,把它们放在同一项目文件夹下,然后在主程序中调用它们,主程序也是一个文件,直接运行主文件即可运行程序。(不好意思,这里又偷了一个懒,嘿嘿!)
运行界面如下:
主程序中有背景音乐和音效的绝对路径引用,读者可自行忽略删改。恭喜,到这里一个贪吃蛇小程序大功告成!
不当之处,还望指正,谢谢。