贪吃蛇的PyGame实现 Snake game with PyGame
一个期末作业的贴,模仿诺基亚3310上的贪吃蛇游戏,有基于指令行的菜单,和基于图形界面的的双人和单人模式。这个程序使用第三方PyGame库,采用GUI界面,并且有像素化的风格,是个看似简单,但却满是学问的小游戏,希望对大家初入PyGame有所帮助。
A final project, in imitation of the snake game on Nokia 3310 with help of pygame. Simple but not easy-to-make game.
组分 Subjects
- 菜单 menu
- 单人模式 single player mode
- 双人模式 multi player mode
要求 Requirements
- 主菜单 menu:
- 有基本的用户界面 basic user interface in terminal
- 允许用户在界面下进行模式选择 allow player to choose mode
- 程序会根据用户选择执行相应的源代码 run code according to player input
- 单人模式
- 有用户界面 basic user interface in window
- 蛇会动(看似简单)the snake moves(harder than you think)
- 蛇会根据用户输入改变方向 snake changes firection with player input
- 蛇吃树莓后增加长度 snake grow longer after eating raspberry
- 树莓的位置会随机生成 raspberry regenerate after eaten
- 多人模式
- 有2.的这些功能 all functions as single mode
- 两条蛇,一红一绿。一个玩家用WASD控制,另外一个用上下左右控制 one player control with WASD while the other with arrow keys
- 蛇死了之后,不结束游戏,重新生成蛇 snake regenerate after death, instead of game over
- 有多个树莓,避免双龙戏一珠 multiple raspberries
游戏主菜单 PySnakeMenu
1.导入必要的库和文件 import modules and files
这里,导入sys是为确保程序中的退出等功能可以正常运行。
Imports sys to ensure program functions like quit run normally
也导入了另外两个文件,是游戏的两个模式的源代码。至于为甚要在这里导入,笔者稍后解释是为什么。
Other two files imported are source code of the two modes.
Will explain later why import here.
import sys, pygame, PySnakeSingle, PySnakeMulti
2.显示用户界面 Print out welcome interface
这里是主程序的开始。打印出一个欢迎界面和一个选项列表。
Main function starts here.
这丑丑的蛇和树莓就不过多解释了,唯一值得一提的是用到了罕见的上划线符号“ ̄”。
There are nothing special about the ugly snake and raspberry, but the use of “ ̄”.
选项列表列出了单人模式,双人模式和退出。用户输入的数据会在下一步用到。
The single, multi mode of the game and the quit are listed as options.
def main():
print('================================================================================================')
print('Welcome to PySnake, a Python-based snake-eating-raspberry game with help of the Pygame module')
print('================================================================================================')
print(' _= ̄=_ ')
print(' // \\\ _**_ ')
print(' // \\\ / ̄O-_ // \\')
print(' // \\\ /// _=--< || ||')
print(' // \\\ // --= ̄ \\ //')
print(' // \\\ //  ̄ ')
print('-==/ =__= ')
print('===================================================== ')
print('Please select game mode:\n [1]Single player mode\n [2]Multi player mode\n [3]Quit\n')
3.提取用户输入并判断
这里是菜单程序的精华。先提取用户输入,再判断输入并进行相应的操作(即运行相应的程序(函数))
The essense of PySnake Menu. Here the user input is taken and program will be ran accordingly.
一、先用input()函数提取输入,外嵌套的int()转换成整数
First, get input with input() and convert to int with int().
二、下面的判断语法根据用户输入执行程序,the boolean expression judges user input and ran functions.
如果输入:
1,就是单人模式 Input 1 will start single mode
2,就是双人 Input 2 will start multi mode
3,就会退出,刚才导入的sys就是在用在这里:sys.exit(),这句作用是退出当前程序 Input 3 and the program will quit, with the help of sys.exit()
从一个python文件运行另外一个python文件也是一种学问 Running one python file from another
方法一,import
将该文件视作头文件导入,例如导入了file.py:
import as module
import file
运行file中的main是就用:
and run functions in that file
file.main()
这种方法最为高效,稳定,安全
efficient, stable and safe
方法二,exec
顾名思义,直接运行,
run with python function dedicated to run another file
python2里:execfile('file.py')
python3里:exec(open('file.py').read())
这种方式危险,不稳定,而且慢一丢丢,建议尽可能不用
dangerous and hacky, use with caution
方法三,os.system
这种方法调取系统指令行运行,
run with system command line
os.system('python file.py')
是没办法的办法
desperate way
注:来自StackOverflow,如何从一个python程序运行另外一个 How can I make one python file run another? [duplicate]
这里,我们用方法一,先前导入的py文件就派上用场了,
Here we use way 1
这里如果提示导入失败,在同一文件夹下建立一个叫__init__.py的空文件可以修复,两条下划线哦
if cannot import, put a empty file called __init__.py in the same directory, which tells python interperator it is okay to include
选择1时,就运行单人模式代码中的主程序;
Chosen 1, run single mode.
选择2时,就运行双人模式代码中的主程序;
Chosen 2, run multi mode.
选择3,退出。
Chosen 3, quit.
如果输入123之外的数,会提示再次输入。
Chosen something else, will be prompt to input again.
while True:
select=int(input())
if select==1:
PySnakeSingle.singlemain()
elif select==2:
PySnakeMulti.multimain()
elif select==3:
print('Thank you for using.')
sys.exit()
else:
print('Please select again.')
最后,用这个执行上述的代码
And run the main function above.
if __name__=='__main__':
main()
菜单就这样大功告成了!接下来讲一下单人模式。
So much for the Menu! Lets talk about single-player mode next.
单人模式 Single-player Mode
大致思路:
Rough outline:
导入 Import
- pygame和pygame.local,保证游戏中的各功能 ensure game functions
- sys:有关程序运行过程 ensure program basic functions
- time:确定游戏运行速度 control game speed
- random:本程序中,树莓的位置是随机生成的,randomly generate raspberry locations
import pygame, sys, time, random
from pygame.locals import *
设置常量 Set consts
这个游戏里有一些常量,最常见的就是颜色,显示区域尺寸等。单人模式这里仅设置一些颜色。
Set some constant values, only colors in this case.
#define color
GRAY=pygame.Color(130,130,130)
RED=pygame.Color(255,0,0)
BLACK=pygame.Color(0,0,0)
WHITE=pygame.Color(255,255,255)
顺便一提,双人模式下会利用到一些数据结构,比如说类。
By the way, there will be Class in teh multi mode
class Snake:
class Raspberry:
但在这里只有一条蛇和一个果子,暂时不用。
, which is not applicable here with 1 snake and 1 raspberry
主函数 Main function
初始化 Initialization and visualization
首先,我们需要初始化pygame,并设置一个运行速度pygame.time.Clock()
,稍后给它赋值,来规定游戏的帧率。还要设置游戏的界面playSurface
以及命名窗口名称PySnake;
Initialize pygmae, set a framerate. Set game interface(an area) and set windows title PySnake.
其次,定义snake,确定它头的位置snakePosition
,以及身体的位置snakeBody
Initialize snake, its head location, and its body location"s".
最后,还要定义raspberry,确定它第一次出现时的“默认”位置,确定它在游戏开始时默认存在。
And initialize raspberry, and its original location, and its existence.
最后,用来储存键盘输入方向的direction
被置为“右”,用来储存实际控制方向的changeDirection
也储存为“右”。这确定了蛇的初始方向。
Finally, initialize snake’s original direction to be “R”, which is right.
def singlemain():
#initialize pygame
pygame.init()
fpsClock=pygame.time.Clock()
#visualize
playSurface=pygame.display.set_mode((640,480))
pygame.display.set_caption('PySnake')
#initialize variable
snakePosition=[100,100]#Snake head location
snakeBody=[[100,100],[80,100],[60,100]]#Snak ebody location
raspberryPosition=[300,300]#Location of raspberry
raspberryGenerated=1#Generate Raspberry
direction='R'#Direction at the beganning
changeDirection=direction
玩家控制方向 Player control of direction
1. 提取键盘输入 Get input from keyboard
这里会用到pygame中的event.get来提取键盘输入(用event.type实现)
Use event.get to get event.type
如果输入了ctrl+c,退出程序
If input ctrl+c, which is QUIT, quit the program
而如果输入合法的按键:
If valid input:
- K_UP或w
- K_DOWN或s
- K_LEFT或a
- K_RIGHT或d
按的键就会被储存起来:
The pressed key will be stored:
1->上 Up,即U
changeDirection = ‘U’
2->下 Down,即D
changeDirection = ‘D’
3->左 Left,即L
changeDirection = ‘L’
4->右 Right,即R
changeDirection = ‘R’
while True:
#detect python event
for event in pygame.event.get():
#get event
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
#detect keyboard event
if event.key == K_UP or event.key == ord('w') or event.key==ord('W'):
changeDirection = 'U'
if event.key == K_DOWN or event.key == ord('s') or event.key==ord('S'):
changeDirection = 'D'
if event.key == K_LEFT or event.key == ord('a') or event.key==ord('A'):
changeDirection = 'L'
if event.key == K_RIGHT or event.key == ord('d') or event.key==ord('D'):
changeDirection = 'R'
if event.key == K_ESCAPE:
pygame.event.post(pygame.event.Event(QUIT))
2. 判断是否反向 Check if opposite direction
这里好理解,如果蛇在向右走,那就无法转向180°直接向左;如果向下走,就无法转向直接向上。
Snake cannot take 180° turn.
这一步程序就是确认没有输入相反方向。
This step makes sure that does not happen.
原理就是看原始方向direction与输入方向changeDirection是否相反。
By checking if the input direction is opposite to original direction.
如果输入的方向合法,那方向代码就会存入direction,稍后用来改变蛇的方向。
If input is valid and not opposite, its direction will be stored, and later be used to changed snake direction.
#detect if opposite direction input
if changeDirection == 'U' and direction != 'D':
direction = changeDirection
if changeDirection == 'D' and direction != 'U':
direction = changeDirection
if changeDirection == 'L' and direction != 'R':
direction = changeDirection
if changeDirection == 'R' and direction != 'L':
direction = changeDirection
3. 相应地改变方向 Change direction accordingly
这里,就会相应改变蛇头snakePosition的坐标,
可以理解为蛇头snakePosition下一步会伸向的方向,蛇身snakeBody会在之后的步骤也沿该方向改变轨迹。
#change direction accordingly
if direction == 'U':
snakePosition[1] -= 20
if direction == 'D':
snakePosition[1] += 20
if direction == 'L':
snakePosition[0] -= 20
if direction == 'R':
snakePosition[0] += 20
4. 蛇的移动(和增长)Snake move and add length
蛇每次都会向前进方向加长一格,
Snake will increase toward its current direction.
然后,如果移动时吃掉了树莓(判断树莓被吃掉的算法稍后讲解),重新生成树莓(也稍后讲解)。
When eaten, regenerate raspberry, which we will talk about later.
如果没有吃掉,那就减少刚才增加的长度。
But if not eaten, undo the length increase just now.
蛇移动和改变方向的算法 Algorithm
1.蛇向某一个方向移动(比如向右)
Snake movign toward a direction (E/X right)
2.pygame.event.get提取用户输入,
Pygame event get user input
- 如果没有输入,那么原始的方向就不会改变,蛇会向原本方向插入一格蛇头snakePosition,并让蛇身snakeBody向蛇头延长;If not input, snake’s original direction remains. Snake will put its head toward its direction and let body extend to its head
- 如果有输入,蛇就会向输入的方向插入一格蛇头snakePosition
If there is input, snake head will progress toward input direction.
比如输入w或者上键,就会往当前蛇头(蛇身的第一格)的上方插入蛇头
并让蛇身snakeBody向蛇头延长;
…and its body will progress toward the head
- 然后,如果移动这一步的过程中吃掉了树莓,则不删减蛇长,蛇比原有增加一格;如果没有,尾部减掉一格。If eaten rspberry in this process, decrease will not happen after increase and snake will seem to grow. If not, it will increase and decrease length.
如果蛇头(蛇身的第一格)和树莓横纵坐标一致,及重合,则代表树莓被吃,记录树莓是否存在的raspberryGenerated置零;
If snake head is the same coordinate as the raspberry at a moment, raspberry is eaten, its existence will be 0 (eaten)
如果树莓没被吃,如上述,减少长度。
If not, length will be decreased.
#increment snake length
snakeBody.insert(0,list(snakePosition))
#check if raspberry gone
if snakePosition[0]==raspberryPosition[0] and snakePosition[1]==raspberryPosition[1]:
#eaten
raspberryGenerated = 0 #raspberry not exist
else:
#not eaten
snakeBody.pop() #increase snake length
5. 判断树莓是否被吃,以及重新生成树莓Check if raspberry eaten and shall be regenerate
raspberryGenerated记录树莓是否还在,当它为1时,树莓还在;为0时,树莓被吃。
If the existence is 0, the raspberry is eaten. If it is 1, raspberry is not.
如果被吃,random库中的random.randint()会再次生成一个坐标,并记录在raspberryPosition中,并把树莓的状态(raspberryGenerated)置为1(存在)。
If eaten, a random coordinate will be generated and stored and teh status of the raspberry will be 1 (exist).
#if eaten
if raspberryGenerated==0:
x=random.randint(2,30)
y=random.randint(2,22)
raspberryPosition=[int(x*20),int(y*20)]
raspberryGenerated=1
6. 如果蛇死了,Snake dead & GameOver
这里的几个数字是窗口的尺寸。判断蛇的位置超过他们,就是判断蛇是否出界,
The numbers here are the edge of windows. If snake goes beyond these edge, they are dead.
#if snake is dead
if snakePosition[0]>640 or snakePosition[0]<0:
gameOver(playSurface)
if snakePosition[1]>460 or snakePosition[1]<0:
gameOver(playSurface)
如果出界,Gameover函数就会运行
If snake is dead, GameOver function will be ran
规定字体,内容,在界面的位置,显示出来并刷新就可以了。
Initiate font, color, text content, position. Refresh to show
五秒之后,退出程序。
Five seconds later, quit the program
#game over screen
def gameOver(playSurface):
gameOverFont=pygame.font.SysFont('arial.ttf', 72)
gameOverSurface=gameOverFont.render("Game Over", True, GRAY)
gameOverRect=gameOverSurface.get_rect()
gameOverRect.midtop=(320,10)
playSurface.blit(gameOverSurface, gameOverRect)
pygame.display.flip()
time.sleep(5)
pygame.quit()
sys.exit()
绘制显示 draw Display
位于主程序末尾。这一部分也极为重要,它是游戏能显示出来的关键:
它绘制背景色,并利用pygame中的draw.rect按照蛇的snakeBody和树莓的raspberryPosition坐标以方块的形式把他们显示出来。
Pygame draw:
Draw the black background, the white snake body and the red raspberry.
#generate pygame display
playSurface.fill(BLACK)
for position in snakeBody:
pygame.draw.rect(playSurface,WHITE,Rect(position[0],position[1],20,20))
pygame.draw.rect(playSurface,RED,Rect(raspberryPosition[0],raspberryPosition[1],20,20))
#refresh pygame display
pygame.display.flip()
控制游戏帧率 Control frame rate
#lastbut not least, control speed of the game
fpsClock.tick(5)
单人模式可以运行了,
接下来是双人模式。
So much for the Single Mode.
双人模式 Multi-player Mode
双人模式,看似只是多了一条蛇和若干树莓,实际则更加复杂。
Looks like 1 more snake and 9 more raspberris, actually waymore complicated.
大体思路是这样的:
Rough outline:
猜到你们的表情了,我会一点一点讲解的。
Scary but I’d show how it is done.
写这段代码时已经是写“单人模式”后的一个月,部分算法已经和以前不同。比如接下来的类。
介于双人模式有十个树莓和两条蛇,所以用类来储存数据
类 Classes
双人游戏比较复杂,所以用类
Complicated that single mode so use Class.
🐍蛇类 Snake Class
蛇的类中有蛇本身的属性和有关蛇的函数。
Snake class have snake’s property and functions about teh snake, like control, move and regenerate.
蛇的属性有:
Property of snake includes:
- 颜色 color self.color,
- 控制方法 control method self.control,
- 当前的移动方向 current direction self.direction,
- 随机生成的蛇头位置 snake head self.position
- 蛇身 snake body self.body
函数:
functions:
这里有一个函数moveAndAdd
Here is a function that let the snake move and increase length. defined below.
是蛇向当前的移动方向self.direction移动并增长的函数,这里重复两遍,用于把蛇长增加到3,即初始长度。
Also used three times, so that length is 3.
'''Complicated than single user mode, so use Classes'''
'''Snake Class'''
class Snake:
def __init__(self, color, control):
self.color = color#color of teh snake
self.control = control #keyboard control of the snake
self.direction = 1 #choose the direction of snake at beginning
x = random.randint(2,52)#generate a start location, not too edgy
y = random.randint(2,34)
self.position = [int(x*20),int(y*20)]
self.body = [self.position]
self.moveAndAdd()
self.moveAndAdd()
#repeated twice to ensure length of the snake to be intially 3
蛇有关的函数有:
functions:
1.方向改变函数 Direction changing
此处逻辑比较复杂,稍后一起讲解
The logic is complicated here, will be explained later.
def changeDirection(self, pressKey):
directions = [-2,2,-1,1]
for direct, key in zip(directions, self.control):
if key == pressKey and direct + self.direction != 0:
self.direction = direct
2.控制蛇移动和增长的函数 Snake move and add length
按照方向代码放蛇头,向该方向伸长一格
Move snake head to next block in direction.
详情请见上方单人模式->主函数->玩家控制方向->3.相应的改变方向
如何生成的方向代码,会在下面详细解释
How the will be explained later.
#control the movement of the snake
def moveAndAdd(self):
# according to the keyboard
if self.direction == 1:
self.position[0] += 20
if self.direction == -1:
self.position[0] -= 20
if self.direction == -2:
self.position[1] -= 20
if self.direction == 2:
self.position[1] += 20
self.body.insert(0,list(self.position)) # insert one block at the typed location
3.减少蛇长的函数 Decrease snake length
减少上一步增加的蛇长,用于蛇移动了树莓没有被吃时。
When raspberry is not eaten, undo the length increase.
#decrease
def pop(self):
self.body.pop() # decrement one in teh end of the body
'''Python list method pop() removes and returns last object or obj from the list'''
4.显示蛇的函数 Display snake
详情请见单人模式->绘制显示。把这几步操作整合成函数,用时调用方便。
See Single-players Mode->draw Display
Integrate them as one function for later use.
def show(self, playSurface):
# snake body
for position in self.body[1:]:
pygame.draw.rect(playSurface,self.color,Rect(position[0],position[1],20,20))
# snake head
pygame.draw.rect(playSurface,self.color,Rect(self.position[0],self.position[1],20,20))
5.重新生成蛇 Regenerate snake
判断蛇是否撞墙了,如果撞了,重新生成。
Regenerate snake if it or they touch the wall
重新生成,其实是从蛇属性的部分摘来的,毕竟是生成一条属性相同的蛇
x = random.randrange(2,52)
y = random.randrange(2,34)
self.direction = random.choice([-2,2,-1,1])
self.position = [int(x*20),int(y*20)]
self.body = [self.position]
self.moveAndAdd()
self.moveAndAdd()
def reGenerateIfDead(self):
if self.position[0] > SCREEN_WIDTH-20 or self.position[0] < 0 or self.position[1] > SCREEN_HEIGHT-20 or self.position[1] < 0:
x = random.randrange(2,52)
y = random.randrange(2,34)
self.direction = random.choice([-2,2,-1,1])
self.position = [int(x*20),int(y*20)]
self.body = [self.position]
self.moveAndAdd()
self.moveAndAdd()
树莓类 Raspberry Class
单个树莓 Individual raspberry
单个树莓的颜色位置,以及是否还在(beEaten)。
Color, position, If already eaten.
'''Raspberry Class'''
class Raspberry:
def __init__(self, color, position):
self.color = color
self.position = position
def beEaten(self, snakePos):
if snakePos[0] == self.position[0] and snakePos[1] == self.position[1]:
return True
else:
return False
树莓群 Raspberries
属性 Property:
树莓群里的树莓有相同的属性“颜色”,有总数,当前的数目,以及记录所有树莓位置的列表(要这个干嘛?防止两个树莓被生成到同一个位置上)
Color(also red), total Number, Current Number,
a list recording their locations and status (Why? Don’t wanna generate two raspberries in one location)
class Raspberrys:
def __init__(self, color, totalNum):
self.color = color
self.totalNum = totalNum
self.curNum = 0
self.raspberrys = []
函数 Functions:
1.生成一个随机位置,把该位置加入列表,并把当前总树莓数+1
generate a random location and put it in list, marked as exist(1)
本函数在树莓初始总数>实际总数时运行
This function runs when Current number<total number, AKA one or more raspberries are eaten.
def generate(self):
while self.curNum < self.totalNum:
x = random.randrange(0,54)
y = random.randrange(0,36)
self.raspberrys.append(Raspberry(self.color, [int(x*20),int(y*20)]))
self.curNum = self.curNum + 1
2.判断有树莓被吃 beEaten,扫描列表中的树莓,扫描到状态为“被吃”的,就把总数-1
Judge if any raspberry eaten. Scan the list for a eaten raspberry, if yes, current number decrease by 1.
从for语句可以看出,有树莓被吃时运行
Run when raspberry eaten.
def beEaten(self, snakePos):
for raspberry in self.raspberrys:
if raspberry.beEaten(snakePos):
self.raspberrys.remove(raspberry)
self.curNum = self.curNum - 1
return True
return False
3.显示树莓
详情请见单人模式->绘制显示。把这几步操作整合成函数,用时调用方便。
See Single-players Mode->draw Display
def show(self, playSurface):
for raspberrys in self.raspberrys:
pygame.draw.rect(playSurface,self.color,Rect(raspberrys.position[0],raspberrys.position[1],20,20))
可以删除的函数GameOver is not useful here
蛇在这里死了之后可以重新生成,本功能是不需要的,但还是放在这里了。
def gameOver(playSurface):
gameOverFont=pygame.font.SysFont('arial.ttf', 72)
gameOverSurface=gameOverFont.render("Game Over", True, GREY)
gameOverRect=gameOverSurface.get_rect()
gameOverRect.midtop=(320,10)
playSurface.blit(gameOverSurface, gameOverRect)
pygame.display.flip()
time.sleep(5)
pygame.quit()
sys.exit()
主函数 Main Function
一系列的初始化与定义 A series of initalization, visualization and defination
首先和单人模式一样,初始化pygame,定义窗口的尺寸、名称。
Pygame initialization, just like the initialization of Single-player Mode
def multimain():
'''initialization'''
'''main'''
pygame.init()
fpsClock = pygame.time.Clock()
#visualize
playSurface=pygame.display.set_mode((SCREEN_WIDTH,SCREEN_HEIGHT))
pygame.display.set_caption('PySnake')
蛇这里就不太一样了,还记得蛇的类中的数据吗?
Snakes are defined in another way
def __init__(self, color, control)
with the help of class, defination can be done fast.
Also color can be comstomized.
所以这里分别定义两条蛇的控制方式control和颜色color
Control and color are defined here.
'''snake'''
control1 = [ord('w'),ord('s'),ord('a'),ord('d')]
snake1 = Snake(GREEN,control1)
control2 = [K_UP,K_DOWN,K_LEFT,K_RIGHT]
snake2 = Snake(RED,control2)
'''raspberry'''
raspberry = Raspberrys(YELLOW, BEAN_NUM)
raspberry.generate()
提取用户输入,判断是否反向,并相应改变方向 Get player input, if opposite or not, and change/not change direction
这里先说一下控制方向,这里的控制方向已经被重新设计了 The direction control here is completely redesigned.
这里的逻辑,前面提到过
但在这里实现方式不尽相同。
这次的逻辑大概是这样的:
Algorithm
1.先由主程序这一部分提取键盘输入
Get player input from main function
'''loop part'''
while True:
# detect pygame event
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)
2.检查是否输入相同方向
还记得前面changeDirection吗?(缩进有点问题)
Check if opposite direction.
def changeDirection(self, pressKey):
directions = [-2,2,-1,1]
for direct, key in zip(directions, self.control):
if key == pressKey and direct + self.direction != 0:
self.direction = direct
这里的算法大概是:
This algotithm links keyboard input and direction code together for check, which saves for 2 for booleans. Two conditions are Input Valid and Direction not Opposite
输入左键,程序搜索self.control是否有相应按键,有…
当原始方向是右,即与输入方向相反时,方向不改变
For instance, if input left, which exist in self.control but is opposite to original direction of right, the direction will not change.
输入上键,程序搜索self.control是否有相应按键,有…
当原始方向是右,即与输入方向不相反时,所有判断条件符合,与上对应的方向码-2打入当前方向direction
If input up, which exist in self.control and direction is not opposite to original direction of right, the direction will change to up.
3.方向即更改完毕 End of direction control
蛇的移动——有向增长… Snake move and add length(defined as function before)
根据上一步输入的的方向,向该方向添加一个格的长度,moveAndAdd请参见 类->🐍蛇类->2.控制蛇移动和增长的函数
See **Classes->Snake move and add length **
# moving and add of the snake
snake1.moveAndAdd()
snake2.moveAndAdd()
碰到树莓,保持原长;没有碰到,减去增加的…
Eat and remain, Not eat and decrease
和单人模式一致 Same as single user mode
# if raspberry eaten regenerate, if not, remove the length added to the snake
#snake 1
if raspberry.beEaten(snake1.position):
raspberry.generate()
else:
snake1.pop()
#snake 2
if raspberry.beEaten(snake2.position):
raspberry.generate()
else:
snake2.pop()
蛇的重生 Regenerate of snake
为避免A玩的正尽兴,B的蛇撞墙,GameOver,
蛇撞墙后会再生为原始长度3
Replacement of GameOver, Regenerate
请参见 类->🐍蛇类->5.重新生成蛇
See Class -> Snake Class -> Regenerate snake
# If snake dead regenerate
snake1.reGenerateIfDead()
snake2.reGenerateIfDead()
绘制显示&控制游戏帧率 Control frame rate
有了来自两个类的show,这里就不那么复杂了。
With the two classes, it won’t be so complicated as in Single Mode
#generate pygame display
playSurface.fill(BLACK)
raspberry.show(playSurface)
snake1.show(playSurface)
snake2.show(playSurface)
#refresh pygame display
pygame.display.flip()
双人模式就是这样
That is Multi-Player Mode, thank you for reading.
源代码集合 all the source code
__init__.py
没错,是空文件。是为了让python成功import。请务必放在和PySnakeMenu.py的同一目录下
PySnakeMenu.py
import sys, pygame, PySnakeSingle, PySnakeMulti
def main():
print('================================================================================================')
print('Welcome to PySnake, a Python-based snake-eating-raspberry game with help of the Pygame module')
print('================================================================================================')
print(' _= ̄=_ ')
print(' // \\\ _**_ ')
print(' // \\\ / ̄O-_ // \\')
print(' // \\\ /// _=--< || ||')
print(' // \\\ // --= ̄ \\ //')
print(' // \\\ //  ̄ ')
print('-==/ =__= ')
print('===================================================== ')
print('Please select game mode:\n [1]Single player mode\n [2]Multi player mode\n [3]Quit\n')
while True:
select=int(input())
if select==1:
PySnakeSingle.singlemain()
elif select==2:
PySnakeMulti.multimain()
elif select==3:
print('Thank you for using.')
sys.exit()
else:
print('Please select again.')
if __name__=='__main__':
main()
PySnakeSingle.py
import pygame, sys, time, random
from pygame.locals import *
#define color
GRAY=pygame.Color(130,130,130)
RED=pygame.Color(255,0,0)
BLACK=pygame.Color(0,0,0)
WHITE=pygame.Color(255,255,255)
#game over screen
def gameOver(playSurface):
gameOverFont=pygame.font.SysFont('arial.ttf', 72)
gameOverSurface=gameOverFont.render("Game Over", True, GRAY)
gameOverRect=gameOverSurface.get_rect()
gameOverRect.midtop=(320,10)
playSurface.blit(gameOverSurface, gameOverRect)
pygame.display.flip()
time.sleep(5)
pygame.quit()
sys.exit()
def singlemain():
#initialize pygame
pygame.init()
fpsClock=pygame.time.Clock()
#visualize
playSurface=pygame.display.set_mode((640,480))
pygame.display.set_caption('PySnake')
#initialize variable
snakePosition=[100,100]#Snake head location
snakeBody=[[100,100],[80,100],[60,100]]#Snak ebody location
raspberryPosition=[300,300]#Location of raspberry
raspberryGenerated=1#Generate Raspberry
direction='R'#Direction at the beganning
changeDirection=direction
while True:
#detect python event
for event in pygame.event.get():
#get event
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
#detect keyboard event
if event.key == K_UP or event.key == ord('w') or event.key==ord('W'):
changeDirection = 'U'
if event.key == K_DOWN or event.key == ord('s') or event.key==ord('S'):
changeDirection = 'D'
if event.key == K_LEFT or event.key == ord('a') or event.key==ord('A'):
changeDirection = 'L'
if event.key == K_RIGHT or event.key == ord('d') or event.key==ord('D'):
changeDirection = 'R'
if event.key == K_ESCAPE:
pygame.event.post(pygame.event.Event(QUIT))
#detect if opposite direction input
if changeDirection == 'U' and not direction == 'D':
direction = changeDirection
if changeDirection == 'D' and not direction == 'U':
direction = changeDirection
if changeDirection == 'L' and not direction == 'R':
direction = changeDirection
if changeDirection == 'R' and not direction == 'L':
direction = changeDirection
#change direction accordingly
if direction == 'U':
snakePosition[1] -= 20
if direction == 'D':
snakePosition[1] += 20
if direction == 'L':
snakePosition[0] -= 20
if direction == 'R':
snakePosition[0] += 20
#increment snake length
snakeBody.insert(0,list(snakePosition))
#check if raspberry gone
if snakePosition[0]==raspberryPosition[0] and snakePosition[1]==raspberryPosition[1]:
#eaten
raspberryGenerated = 0 #raspberry not exist
else:
#not eaten
snakeBody.pop() #increase snake length
#if eaten
if raspberryGenerated==0:
x=random.randint(2,30)
y=random.randint(2,22)
raspberryPosition=[int(x*20),int(y*20)]
raspberryGenerated=1
#generate pygame display
playSurface.fill(BLACK)
for position in snakeBody:
pygame.draw.rect(playSurface,WHITE,Rect(position[0],position[1],20,20))
pygame.draw.rect(playSurface,RED,Rect(raspberryPosition[0],raspberryPosition[1],20,20))
#refresh pygame display
pygame.display.flip()
#if snake is dead
if snakePosition[0]>640 or snakePosition[0]<0:
gameOver(playSurface)
if snakePosition[1]>460 or snakePosition[1]<0:
gameOver(playSurface)
#lastbut not least, control speed of the game
fpsClock.tick(5)
PySnakeMulti.py
import pygame, sys, time, random
from pygame.locals import *
'''Defination of consts'''
# define UI size
SCREEN_WIDTH = 1080
SCREEN_HEIGHT = 720
# define Raspberry Number
BEAN_NUM = 10
# define colors
RED = pygame.Color(255,0,0)
BLACK = pygame.Color(0,0,0)
GREY = pygame.Color(150,150,150)
GREEN = pygame.Color(0,255,0)
YELLOW = pygame.Color(255,255,0)
'''Complicated than single user mode, so use Classes'''
'''Snake Class'''
class Snake:
def __init__(self, color, control):
self.color = color#color of teh snake
self.control = control #keyboard control of the snake
self.direction = 1 #choose the direction of snake at beginning
x = random.randint(2,52)#generate a start location, not too edgy
y = random.randint(2,34)
self.position = [int(x*20),int(y*20)]
self.body = [self.position]
self.moveAndAdd()
self.moveAndAdd()
#repeated twice to ensure length of the snake to be intially 3
#change direction according to keyboard input
def changeDirection(self, pressKey):
directions = [-2,2,-1,1]
for direct, key in zip(directions, self.control):
if key == pressKey and direct + self.direction != 0:
self.direction = direct
#control the movement of the snake
def moveAndAdd(self):
# according to the keyboard
if self.direction == 1:
self.position[0] += 20
if self.direction == -1:
self.position[0] -= 20
if self.direction == -2:
self.position[1] -= 20
if self.direction == 2:
self.position[1] += 20
self.body.insert(0,list(self.position)) # insert one block at the typed location
#decrease
def pop(self):
self.body.pop() # decrement one in teh end of the body
'''Python list method pop() removes and returns last object or obj from the list'''
def show(self, playSurface):
# snake body
for position in self.body[1:]:
pygame.draw.rect(playSurface,self.color,Rect(position[0],position[1],20,20))
# snake head
pygame.draw.rect(playSurface,self.color,Rect(self.position[0],self.position[1],20,20))
def reGenerateIfDead(self):
if self.position[0] > SCREEN_WIDTH-20 or self.position[0] < 0 or self.position[1] > SCREEN_HEIGHT-20 or self.position[1] < 0:
x = random.randrange(2,52)
y = random.randrange(2,34)
self.direction = random.choice([-2,2,-1,1])
self.position = [int(x*20),int(y*20)]
self.body = [self.position]
self.moveAndAdd()
self.moveAndAdd()
'''Raspberry Class'''
class Raspberry:
def __init__(self, color, position):
self.color = color
self.position = position
def beEaten(self, snakePos):
if snakePos[0] == self.position[0] and snakePos[1] == self.position[1]:
return True
else:
return False
class Raspberrys:
def __init__(self, color, totalNum):
self.color = color
self.totalNum = totalNum
self.curNum = 0
self.raspberrys = []
def generate(self):
while self.curNum < self.totalNum:
x = random.randrange(0,54)
y = random.randrange(0,36)
self.raspberrys.append(Raspberry(self.color, [int(x*20),int(y*20)]))
self.curNum = self.curNum + 1
def beEaten(self, snakePos):
for raspberry in self.raspberrys:
if raspberry.beEaten(snakePos):
self.raspberrys.remove(raspberry)
self.curNum = self.curNum - 1
return True
return False
def show(self, playSurface):
for raspberrys in self.raspberrys:
pygame.draw.rect(playSurface,self.color,Rect(raspberrys.position[0],raspberrys.position[1],20,20))
def gameOver(playSurface):
gameOverFont=pygame.font.SysFont('arial.ttf', 72)
gameOverSurface=gameOverFont.render("Game Over", True, GREY)
gameOverRect=gameOverSurface.get_rect()
gameOverRect.midtop=(320,10)
playSurface.blit(gameOverSurface, gameOverRect)
pygame.display.flip()
time.sleep(5)
pygame.quit()
sys.exit()
def multimain():
'''initialization'''
'''main'''
pygame.init()
fpsClock = pygame.time.Clock()
#visualize
playSurface=pygame.display.set_mode((SCREEN_WIDTH,SCREEN_HEIGHT))
pygame.display.set_caption('PySnake')
'''snake'''
control1 = [ord('w'),ord('s'),ord('a'),ord('d')]
snake1 = Snake(GREEN,control1)
control2 = [K_UP,K_DOWN,K_LEFT,K_RIGHT]
snake2 = Snake(RED,control2)
'''raspberry'''
raspberry = Raspberrys(YELLOW, BEAN_NUM)
raspberry.generate()
'''loop part'''
while True:
# detect pygame event
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)
# moving and add of the snake
snake1.moveAndAdd()
snake2.moveAndAdd()
# if raspberry eaten regenerate, if not, remove the length added to the snake
#snake 1
if raspberry.beEaten(snake1.position):
raspberry.generate()
else:
snake1.pop()
#snake 2
if raspberry.beEaten(snake2.position):
raspberry.generate()
else:
snake2.pop()
#generate pygame display
playSurface.fill(BLACK)
raspberry.show(playSurface)
snake1.show(playSurface)
snake2.show(playSurface)
#refresh pygame display
pygame.display.flip()
# If snake dead regenerate
snake1.reGenerateIfDead()
snake2.reGenerateIfDead()
# lastbut not least, control speed of the game
fpsClock.tick(5)
结语与谢鸣
作为我的这个账号在CSDN的首帖,这是我2020年所做的第一个能拿得出手的Python项目。
My first post this year.
感谢为此项目提供宝贵灵感,思路与支持的Qiu2013和tyst08等大神,以及来自StackOverflow的一众外国码友们。
Thank you for those how inspired me in this project.