Final Project 期末项目: PySnake

贪吃蛇的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

  1. 菜单 menu
  2. 单人模式 single player mode
  3. 双人模式 multi player mode

要求 Requirements

  1. 主菜单 menu:
    • 有基本的用户界面 basic user interface in terminal
    • 允许用户在界面下进行模式选择 allow player to choose mode
    • 程序会根据用户选择执行相应的源代码 run code according to player input
  2. 单人模式
    • 有用户界面 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
  3. 多人模式
    • 有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

注:来自*,如何从一个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:
Final Project 期末项目: PySnake

导入 Import

  1. pygame和pygame.local,保证游戏中的各功能 ensure game functions
  2. sys:有关程序运行过程 ensure program basic functions
  3. time:确定游戏运行速度 control game speed
  4. 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:

  1. K_UPw
  2. K_DOWNs
  3. K_LEFTa
  4. K_RIGHTd

按的键就会被储存起来:
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.

Final Project 期末项目: PySnake
如果蛇头(蛇身的第一格)和树莓横纵坐标一致,及重合,则代表树莓被吃,记录树莓是否存在的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:
Final Project 期末项目: PySnake
猜到你们的表情了,我会一点一点讲解的。
Scary but I’d show how it is done.

写这段代码时已经是写“单人模式”后的一个月,部分算法已经和以前不同。比如接下来的类。
介于双人模式有十个树莓和两条蛇,所以用来储存数据

类 Classes

双人游戏比较复杂,所以用类
Complicated that single mode so use Class.

上一篇:贪吃蛇小游戏


下一篇:android活动切换方向