非程序猿上手python系列-实战贪吃蛇(五)


哎,实在是想不出有什么好的实战项目。如果你们有什么思路,可以提供提供,我们的目标是解决生活中,工作中遇到的问题。一起边学边写代码。

想不到也得继续呀。那就来个增加趣味性的pygame项目-贪吃蛇

方案一: 搜索 “python3 贪吃蛇” 有很多案例,直接拷贝。或者看别人代码如何写的。
方案二: 跟着我思路,一步一步的探索,我对pygame 也不熟悉。

认识pygame

我在网上搜 python 游戏,有很多项目都用到了pygame
那就试试这个吧

首先,肯定是去官网看看https://www.pygame.org/docs/
当然,如果不想看英文,搜素下也能看到中文版本的介绍,和案例。

安装pygame

python 解释器给我们提供了很多强大又好用的模块,我们可以不需要额外的安装就能import使用。
但是也有很多库是需要我们自己安装的。pygame就是其中之一
pip 是python的安装包工具。如果你是安装了python2 python3两个版本,那么pip3 才是对应python3的安装包工具。

python3 -m pip install -U pygame --user
# 或者
pip3 install pygame

容我先看教程学点东西

http://blog.sina.com.cn/s/blog_71c2d34a0101r1gy.html
这里有个翻译,还不错。

分析怎么做

先看看我想做的效果图

然后进行拆解:

  1. 我需要一个游戏窗口
  2. 我需要一条蛇(白色的块)
    2.1 这条蛇能自己往前移动
    2.2 这条蛇能接受键盘控制上下左右动
    2.3 蛇吃到食物会变长
    2.4 当蛇吃到自己的时候,游戏结束
  3. 我需要一个食物(红色的块)
    3.1 这个食物随机出现位置
    3.2 这个食物不会动
    3.3 这个食物全屏只能出现一个
    3.4 这个食物不能出现在蛇身上
    3.5 当食物没办法生成(满屏都是蛇)的时候,游戏结束

我发现边讲解边写代码还是有点困难,篇幅会打很多。所以我打算用git 管理

git 是一个版本控制的工具。你需要安装它. 点击查看安装教程

# 下载代码
git clone https://github.com/tfhappy/pygame_snake.git 
# 进入我的项目
cd pygame_snake
# 查看有哪些步骤
git tag
# 选择并查看某个步骤的代码, 这里以"1_官方案例" 为例子
git checkout 1_官方案例

git checkout {tag_name} 之后,当前项目下你的代码就会变成制定版本的代码。
这样你就可以一边看着步骤,一边查看代码是如何变更的了。

步骤

1_官方案例

# 在项目的路径下
git checkout 1_官方案例
# 你会发现你的文件夹子下只有3个文件
.
├── README.md
├── demo.py
└── intro_ball.gif

这是https://pygame.org/docs/tut/PygameIntro.html展示的代码.
能够帮你快速的了解pygame

2_创建一个窗口

# 在项目的目录下
git checkout 2_创建一个窗口
# 你会发现项目下多了个文件
.
├── README.md
├── demo.py
├── intro_ball.gif
└── snake.py
# 运行snake.py 查看效果
python3 snake.py

查看snake.py的代码,许多内容我都已写到注释中了

如果你想看每个步骤和上一步的差别是什么,可以用git show 命令
如:

# 显示 1_官方案例 到 2_创建一个窗口 做了哪些改动
git shwo 2_创建一个窗口

一开始你可能看得不是很习惯,没关系,你可以选择直接看代码。

知识点

  1. 元组(tuple)
    元组也是一种数据类型,和列表很像,用()包围

    a = (1,2,3) # 第一种赋值方法
    b = 1,2,3 # 可以不带括号
    print(type(a)) # type方法查看一个对象的类型
    print(type(b)) # 打印结果: <class 'tuple'>
    print(a == b) # True
    # 元组内的值不可修改,如
    a[0] = 10 # TypeError: 'tuple' object does not support item assignment
    
  2. 连续赋值

    a = b = 0 # 把0赋值给a和b
    a = b = c = d = 0 # 把0赋值给a,b,c,d
    

    连续赋值的写法能让我们更快的开发

  3. 退出进程
    我们知道代码执行完毕后,程序会退出。但是有些情况下,我们希望提前退出。那么就可以用sys模块下的exit方法。sys 是一个内置模块,不需要安装

    import sys
    sys.exit()
    
  4. 一行实现if语句

    a = 0
    if a == 0:
    	print(a)
    
    # 等价写法
    a = 0
    if a == 0: print(a)
    

    一般用于只执行一条语句

  5. time.sleep()
    time 包也是内置的包

    import time
    # sleep 接受一个数字类型的参数, 单位s
    # 功能:让程序休眠一段时间(什么都不干)
    time.sleep(1)  # 休眠1s
    

3_填充黑色背景

如何查看代码,不再描述

知识点:

  1. RGB 颜色
    颜色对照表

29行代码修正:

# 你看到的代码
screen.fill((0,0,0))
# 应该为
screen.fill(black)

这里black 也是(0, 0, 0), 所以是等效的。发现有点晚了,只能补个说明到这里了。

4_画蛇头

  1. pygame.Color(r, g, b, a=255)
    pygame 封装的一个类。这样我可以对颜色做更多的操作
    可以接收4个参数,我们只用了前三个。第4个参数控制透明度,值域:[0,255]
    扩展阅读
  2. pygame.Rect
    创建一个矩形
    扩展阅读

5_画食物

画食物和画蛇头没什么太大的区别
但是我们的食物的位置应该是随机的
random 是一个内置的包,提供了很多随机数生成的方法
randint 只是其中一个方法,随机生成一个整数

import random
num = random.randint(0,2) # 随机生成一个整数, 整数值的范围: 0<=num<=2

现在每次运行代码,你应该能看到每次食物都在不同的位置
可能你会想到这样实现有点问题,先别急,我们慢慢来

6_让蛇头动起来

ps: 从这里开始,为了代码看起来简洁,我删掉了一些多余的注释。

先思考
我们现在蛇头是在右上角,那我们设定初始的运动方向为右。
那如何动起来呢?
看看现在的实现

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT: sys.exit()

    screen.fill(black)  # 填充黑色背景

    pygame.draw.rect(screen, snake_head_color, snake_head)  # 画蛇头
    pygame.draw.rect(screen, food_color, food)  # 画食物

    pygame.display.flip()  # 刷新屏幕
    time.sleep(1)

我们把刷新屏幕放到了一个无限循环中,也就是说,每次循环就会刷新一次屏幕。
如果我们画蛇头的时候,位置是变的,那么是不是就可以动起来了?
talk is cheap,show you the code

over ~ 蛇动起来了,你可以调整到你喜欢的速度哦

6_让蛇头动起来_2

# 每次循环加上一个移动单位, 生成新的位置
left = snake_head_pos[0] + move_to[0]
top = snake_head_pos[1] + move_to[1]
snake_head_pos = (left, top)
snake_head = pygame.Rect(snake_head_pos, snake_head_size)  # 重新创建一个蛇头
pygame.draw.rect(screen, snake_head_color, snake_head)  # 画蛇头

这是上一个步骤的逻辑。
move_to 的值一直是(3,0)
所以每次刷新,snake_head_pos 都会在横坐标+3

我们还可以这样写,让代码更为简洁

 # 每次循环加上一个移动单位, 生成新的位置
snake_head_pos = tuple(map(lambda x, y: x + y, snake_head_pos, move_to))
snake_head = pygame.Rect(snake_head_pos, snake_head_size)  # 重新创建一个蛇头
pygame.draw.rect(screen, snake_head_color, snake_head)  # 画蛇头

效果是一样的。
这里涉及了几个新概念:

  1. 类型强转
    类似函数调用,函数名用类型的名称。
    作用是强行转换参数的类型,并返回。如果无法强转,则会报错

    # 强转为字符串
    a = str(10)
    print(a, type(a)) #  '10' <class 'str'>
    b = str([1,2,3])
    print(b, type(b)) # '[1, 2, 3]' <class 'str'>
    # 强转为整数
    c = int('10') 
    print(c, type(c) # 10 <class 'int'>
    d = int('abc') # 报错, 无法强转    
    

    其他类型也是这般,上面的代码中我们强行转换为tuple 类型, 也就是元组类型

  2. map 函数
    map 函数是python内置的函数
    使用方法: https://www.runoob.com/python/python-func-map.html
    大概是第一个参数是一个函数,其他参数为函数使用的参数
    返回的是一个可迭代对象。
    凡是能用for遍历的都叫可迭代对象。
    map函数返回的值,不能像列表一样用[index]取值 , 所以我们在上面强转为tuple

  3. lambda
    匿名函数, 经常和map连用
    使用方法: https://www.runoob.com/python3/python3-function.html

    map 和 lambda 一开始可能比较不好理解,不要求掌握,大概知道那么一回事就好

嗯,到这,我们属于优化了一次。
但是这还不是我单独列一步骤的目的。
其实,像移动一个矩形这种事。pygame 早就帮我们做好了,上面的代码你可以认为是帮助你理解移动的原理。
我们看下官方文档,看看Rect 给我们提供了什么方法http://www.pygame.org/docs/ref/rect.html

嗯,假装你看到了,我们其实可以用move方法,所以代码再次改造

# 每次循环加上一个移动单位, 生成新的位置
snake_head = snake_head.move(move_to)
pygame.draw.rect(screen, snake_head_color, snake_head)  # 画蛇头

ps: 别忘了git checkout 查看完整代码

7_控制蛇头方向

上一步,我们实现了让蛇头自己动。但是,只有一个方向。而且超出边界后也没有结束游戏。
我们先来解决控制的问题
我们希望能够使用键盘来控制蛇头的运动方向,也就是说,程序需要知道你是什么时候按下键盘的。
https://www.pygame.org/docs/ref/key.html
看看官方教程的例子

for event in pygame.event.get():
    if event.type == pygame.KEYDOWN or event.type == pygame.KEYUP:
        if event.mod == pygame.KMOD_NONE:
            print('No modifier keys were in a pressed state when this '
                  'event occurred.')
        else:
            if event.mod & pygame.KMOD_LSHIFT:
                print('Left shift was in a pressed state when this event '
                      'occurred.')
            if event.mod & pygame.KMOD_RSHIFT:
                print('Right shift was in a pressed state when this event '
                      'occurred.')
            if event.mod & pygame.KMOD_SHIFT:
                print('Left shift or right shift or both were in a '
                      'pressed state when this event occurred.')

第一行有没有觉得很熟悉
然后我们继续读
event 这里代表了事件
event.type 翻译过来就是事件的类型
event.type == pygame.KEYDOWN 这里看着就是 如果事件的类型等于一个什么东西,那么pygame.KEYDOWN是一个值。如果你想象力够丰富,应该能看出来 KEYDOWN 就是按键按下去的意思;KEYUP 就是案件起来
这合起来就是, 如果事件的类型为按键按下或者按键弹起。

好了,大概就是这样读代码的。我就不啰嗦了
直接改造成我们需要的。

8_碰到边界退出游戏

退出游戏后我们希望界面能停留,而且显示"game over" 的字样,而不是直接退出程序
如何显示字体:https://www.pygame.org/docs/ref/font.html#pygame.font.Font
(需要一步一步的看每个方法有什么功能,返回值是什么)

另外,因为退出游戏的步骤会经常使用,我们封装成了game_over函数

9_吃

  1. 蛇头移动
  2. 蛇身体移动
  3. 判断蛇头和食物是否有碰撞
    3.1. 如果有,则身体变长,生成新的食物
  4. 判断蛇头和身体是否有碰撞
    2.1. 如果有,游戏结束

存在问题:

  1. 因为我们不是一个一个像素移动的,所以有可能发生在过程中吃到食物/身体。
  2. 同理,身体也不是一个一个像素移动的,如何才能让身体紧跟身体?

解决方案:
每次只移动一个像素, 但是移动speed个像素后,才刷新一次屏幕
如果这个方案效率不行,再考虑别的方案。

10_部分优化

食物的生成:
如果随机生成的位置,在蛇身上怎么办?
如果简单的判断如果在蛇身上,就再次随机生成,那么,当蛇的身体很长的时候,落在蛇身上的概率是很大的
另外,如果已经没有空间生成了,应该游戏结束。
控制速度:
目前的方案控制速度没有和食物生成的位置关联起来,导致你可能永远吃不到食物


项目后期可能还有改动,可以在github查看。这里不再介绍。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值