本文基于Windows环境开发,适合Python新手
HlloGitHub推出的《讲解开源项目》系列,本期介绍Python练手级项目——贪吃蛇!
原本想推荐一个贪吃蛇的开源项目:python-consol-snak,但由于该项目最近一次更新是8年前,而且在运行的时候出现了诸多问题。索性我就动手用Python重新写了一个贪吃蛇游戏。
下面我们就一起用Python实现一个简单有趣的命令行贪吃蛇小游戏
本文包含设计和讲解,整体分为两个部分:第一部分是关于Python命令行图形化库curss接着是snak相关代码。
一、初识curssPython已经内置了curss库,但是对于Windows操作系统我们需要安装一个补丁以进行适配。
Windows下安装补全包:
pipinstallwindows-curss
curss是一个应用广泛的图形函数库,可以在终端内绘制简单的用户界面。
在这里我们只进行简单的介绍,只学习贪吃蛇需要的功能
如果您已经接触过curss,请跳过此部分内容。
1.1简单使用Python内置了curss库,其使用方法非常简单,以下脚本可以显示出当前按键对应编号:
您也可以尝试把nodlay(Tru)改为nodlay(Fals)后再次运行,这时候程序会阻塞在stdscr.gtch()只有当您按下按键后才会继续执行。
1.2整点花样您也许会觉得上面的例子太菜了,随便用几个print都能达到相同的效果,现在我们来整点花样以实现一些使用普通输出无法达到的效果。
1.2.1新建一个子窗口说再多的话也不如一张图来的实际:
如果我们想要实现图中Gamovr!窗口,可以使用nwwin方法:
除了curss.nwwin新建一个独立的窗口,我们还能在任意窗口上使用subwin或者subpad方法新建子窗口,例如stdscr.subwin、stdscr.subpad、nw_win.subwin、nw_win.subpad等等,其使用方法与本节中创建的nw_win或者stdscr没有区别,只是新建窗口使用独立的缓存区,而子窗口和父窗口共享缓存区。
如果某个窗口会在使用后删除,最好使用nwwin方法新建独立窗口,以防止删除子窗口造成父窗口的缓存内容出现问题。
1.2.2上点颜色白与黑的搭配看久了也会显得单调,curss提供了内置颜色可以让我们自定义前后背景。
在使用彩色模式之前我们需要先使用使用curss.start_corlor()进行初始化操作:
需要注意的是,0号位置颜色是默认黑白配色,无法修改
1.2.给点细节在此部分最后的最后,我们来说说如何给文字加一点文字效果:
二、贪吃蛇前面说了这么多,现在终于到了我们的主菜。在这部分,我将一步步教给大家如何从零开始做出一个简单却又不失细节的贪吃蛇。
2.1设计对于一个项目来讲,相比于尽快动手写下第一行代码不如先花点时间进行一些必要的设计,毕竟结构决定功能,一个项目没有一个良好的结构是没有前途的。
snak将贪吃蛇这个游戏分为了三大块:
界面:负责显示相关的所有工作游戏流程控制:判断游戏输赢、游戏初始化等蛇和食物:移动自身、判断是否死亡、是否被吃等每一块都被做成了单独的对象,通过相互配合实现游戏。下面让我们来分别看看应该如何实现。
2.2蛇语者对于贪吃蛇游戏里面的蛇来讲,它可以做的事情有三种:移动,死亡(吃到自己,撞墙)和吃东西
围绕着这三个功能,我们可以首先写出一个简陋的蛇,其类图如图所示:
这个蛇可以检查自己是不是死亡,是不是吃了东西,以及更新自己的位置信息。
其中,body和last_body是列表,分别存储当前蛇身坐标和上一步蛇身坐标,默认列表第一个元素是蛇头。dirction是当前行进方向,window_siz是蛇可以活动的区域大小。
rst方法用于重置蛇的状态,它与__init__共同负责蛇的初始化工作:
Position是我自定义的类,只有x,y两个属性,存储一个坐标点
在最开始我们可能只是模糊的感觉应该有这几个属性,但是对于其中的内容和初始化方法又不完全清楚,这是正常的。我们需要做的就是继续实现需要的功能,在实践中添加和完善最初的构想。
之后,我们从继续上到下实现,对照类图,我们接下来应该实现一下updat_snak_pos即更新蛇的位置,这部分非常简单:
其实last_body可以只记录最后一次修改的身体,这里我偷了个懒
在这里有一个细节,如果我们是第一次写这个函数,为了让蛇头能够正确地按照玩家操作移动,我们需要知道蛇头元素在x,y方向上各移动了多少。
最简单的方法是直接一串if-lif,判断方向再相加:
但是这样的问题在于,如果我们的需求更改(比如我现在说蛇可以一次走两个格子,或者吃了特殊道具x,y方向上走的距离不一样等等)直接修改这样的代码会让人很痛苦。
所以在这里更好的解决办法是使用一个dis_incrmnt_factor存储蛇再x和y上各移动多少,并且新建一个函数gt_dis_inc_factor进行判断:
当然了,这么做或许有点多余,但是努力做到一个函数只做一件事情能帮助化简我们的代码,降低写出又臭又长还难调试代码的可能性。
解决了移动问题,下一步就是考虑贪吃蛇如何吃到食物了,在这里我们用chck_at_food和at_food两个函数完成:
在这里,foods是一个存储着所有食物位置信息的列表,每次蛇体移动后都会调用chck_at_food函数检查是不是吃到了某一个食物。
可以发现,检查是不是「吃到」和「吃下去」这两个动作我分为了两个函数,以做到每个函数「一心一意」方便后期修改。
现在,我们的蛇已经能跑能吃了。但是作为一只能照顾自己的贪吃蛇,我们还需要能够判断当前自身状态,比如最基本的我需要知道我刚刚是不是咬到自己了,只需要看看蛇头是不是移动到了身体里面:
dfchck_at_slf(slf)-bool:
rturnslf.body[0]inslf.body[1:]#判断蛇头是不是和身体重合
或者我想知道是不是跑得太快而撞了墙:
这些功能都是简单得不能再简单了,但是要相信自己,就是这么简单的几行代码就能实现一个听你指挥能做出复杂动作的蛇
GitHub完整代码:AnthonySun/asy_gams
2.命令行?画板!上一节中我们实现了游戏里的第一位角色:蛇。为了将它显示出来我们现在需要将我们的命令行改造成一块「画板」。
在动手之前我们同样思考:我们需要画哪些东西在我们的命令行上?直接上类图:
是不是觉得有些眼花缭乱以至于感觉无从下手?其实Graphic类方法虽多但是大多数方法只是执行一个特定的功能而已,而且每次更新游戏只需要调用draw_gam方法即可:
遵循从上到下设计,从下到上实现的原则
可以看出draw_gam实际上已经完成了Graphic的所有功能。
再往下深入,我们可以发现类似draw_foods、draw_snak_body实现基本一样,都是遍历坐标列表然后直接在相应位置上添加字符即可:
将其分开实现也是为了保持代码干净易懂以及方便后期修改。draw_hlp、draw_fps、draw_livs_and_scors也是分别打印了不同文字信息,没有任何新的花样。
updat_fps实现了帧率的估算以及调节等待时间稳定帧率:
draw_mssag_window则实现了绘制胜利、失败的画面:
这样,我们就实现了游戏动画的显示!
2.4控制!到目前为止,我们实现了游戏内容绘制以及游戏角色实现,本节我们来学习snak的最后一个内容:控制。
老规矩,敲代码之前我们应该先想一想:如果要写一个control类,它应该都包含哪些方法呢?
仔细思考也不难想到:应该有一个循环,只要没输或者没赢就一直进行游戏,每轮应该更新画面、蛇移动方向等等。这就是我们的start:
只要我们写出了start对于剩下的结构也就能轻松地实现,比如读取按键控制就是最基本的比较数字是不是一样大:
更新蛇的状态时只需要判断是不是死亡、胜利、吃到东西就可:
2.5直接使用为了让这个包能够直接使用pythonsnak就能直接开始游戏,我们来看一下__main__.py:
importgam
g=gam.Gam()
g.start()
g.quit()
当我们尝试直接运行一个包时,Python从__main__.py中开始执行,对于我们写好的代码,只需三行即可开始游戏!
三、结尾到这里如何编写一个贪吃蛇游戏就结束啦!实际上编写一个小游戏不难,对于新手来讲难点在于如何去组织程序的结构。我所实现的只是其中的一种方法,每个人对于游戏结构理解不同所写出的代码也会不同。但无论怎样,我们都应该遵循一个目标:尽量遵循代码规范,养成良好的风格。这样不仅利于别人阅读你的代码,也利于自己排查bug、增加新的功能。
最后,感谢您的阅读。这里是HlloGitHub分享GitHub上有趣、入门级的开源项目。您的每个点赞、留言、分享都是对我们最大的鼓励!
-END-
HlloGitHub分享GitHub上有趣、入门级的开源项目。