python 状态机框架_史上最详细的用python写2048小游戏教程(一):有限状态机

前面写了4篇文章,介绍了一个非常简单的python爬虫框架,这次来搞点不一样的,用python代码实现一个字符界面的1024,哦不不,是字符界面的2048。故事很长,所以我打算分成两篇来讲。

第二篇请戳这里:折腾君:史上最详细的用python写2048小游戏教程(二):大功告成​zhuanlan.zhihu.comv2-24fdb4ff83b431691f4bebd5f1843353_180x120.jpg

这部分的内容来自于我之前学习的体会和总结,代码也来自于网上搜集,网址放在文末,有兴趣的朋友一定要去看一下(不过肯定没有我写的这么详细啦,大牛嘛,自然都是点到为止,另外,代码有改动!)。我当初在看代码的时候也是花了好些功夫才搞明白,所以初学python的同学看第一遍有些疑惑不解都是很正常的,只要多加琢磨就一定会搞定这个小项目。

另外:如果有看不懂的地方,很有可能是我没讲清楚,请留言,我尽量解决~尽量保证初学者能够看懂

原版游戏地址如下:2048

运行环境:python2.7或python3

玩法很简单,搞不清楚的朋友赶紧去体验一把就什么都明白啦!

下面开始正文。

首先,要对一个很重要的概念进行介绍:有限状态机

顾名思义,有限状态机就是用来描述状态之间相互转化的一个编程模型。

有限状态机有五个要素:初始状态:有限状态机从此状态开始并接受输入

结束状态:有限状态机在此结束,不再接受输入

有限状态集合:该集合包含了一系列状态

有限字符输入集合:有限状态需要根据这些输入进行转化

状态转移函数:根据用户的输入,将某个状态转移至下一个状态

OK,概念就是这么简单,但是用python实现起来可就有点复杂,不过还好,用python写个2048还用不到多么高深的知识,只需要稍微了解一下就可以了。介绍完了有限状态机的概念,我们来看看2048这个小游戏可以抽象成什么有限状态机。一般而言,2048程序的运行流程图是这样的(WASD表示四个方向的移动,R为重新开始,Q为退出程序):

我们从状态机的角度入手,发现其实上面的图可以抽象为5个状态:

即:Init,Game,Win,Gameover,Exit(Gameover和Exit是不同的,Gameover是在屏幕上输出Gameover和得分等等,Exit就直接退出了程序)。

根据上图的描述,每个状态及其应该执行的函数可以写成下面的伪代码:

def init():

game_field.reset() #重置游戏棋盘

return 'Game' #Init下一步只能是Game

def game():

game_field.draw(game) #画出当前棋盘

action = get_user_input() #获取用户输入

if action == 'Restart':

return 'Init' #判断,重新开始游戏,回到Init状态

if action == 'Exit'

return 'Exit' #判断,退出游戏,回到Exit状态

if game_field.move(action) #根据用户的输入对棋盘进行变换

if game_field.is_win() #如果赢了,就返回Win的状态

return 'Win'

if game_field.is_gameover() #如果输了,就返回Gameover状态

return 'Gameover'

def win():

game_field.draw(win) #画出赢的状态,

action = get_user_input() #获取用户输入

if action == 'Restart': #判断状态

return 'Init'

if action == 'Exit':

return 'Exit'

def gameover():

game_field.draw(gameover) #画出输的状态,

action = get_user_input() #获取用户输入

if action == 'Restart': #判断状态

return 'Init'

if action == 'Exit':

return 'Exit'

'''上面的win()和gameover()只有一行不同,因此可以写在一起,写成not_game()的形式以not_game('Win')或not_game('Gameover')的方法来调用'''

def not_game(state):

game_field.draw(state) #画出赢或输的状态

action = get_user_input() #获取用户输入

if action == 'Restart': #判断状态

return 'Init'

if action == 'Exit':

return 'Exit'

每个状态有其对应的状态转移函数(上面列出的就是状态转移函数),当进入这个状态的时候就要调用状态转移函数,状态转移函数的返回值为下一个状态,这样就实现了状态之间的转化,我们用一个字典来把状态和其状态转移函数对应起来:

state_actions = {

#'状态名': 状态转移函数名,

'Init': init,

'Win': lambda: not_game('Win'),

'Gameover': lambda: not_game('Gameover'),

'Game': game

}

在这里呢,我们借助一个lambda函数把Win和Gameover统一写成了not_game()这个函数。

有朋友可能会问:问什么要把这四个函数放在一个字典里面?

答:这是为了方便在循环中的调用。另外需要注意的是,在state_action这个字典当中,'Init'对应的并不是函数,而是一个“函数名”,即将state_action["Init']指向了init()这个函数的地址,而函数并没有被执行,只有在代码执行到state_action["Init"]()的时候,才会真正执行init()函数定义中的内容lambda是个啥?

state_action["Win"] = lambda: not_game('Win')

就相当于:

def state_action["Win"]():

return not_game('Win')

问题结束。。。

这样,我们就能非常方便地通过如下的方式开启游戏的循环:

state = 'Init'

#状态机开始循环

while state != 'Exit':

state = state_actions[state]()

上面的代码中,先设定初始状态为'Init',然后进入循环,进入循环,先执行 :

state_actions['Init']()

#即执行init()这个函数

init()函数返回'Game',此时state的值为'Game',下一个循环再执行:

state_actions['Game']()

'''即执行def game():game_field.draw(game) #画出当前棋盘action = get_user_input() #获取用户输入if action == 'Restart':return 'Init' #判断,重新开始游戏,回到Init状态if action == 'Exit'return 'Exit' #判断,退出游戏,回到Exit状态if game_field.move(action) #根据用户的输入对棋盘进行变换if game_field.is_win() #如果赢了,就返回Win的状态return 'Win'if game_field.is_gameover() #如果输了,就返回Gameover状态return 'Gameover''''

就这样一直循环下去,直至赢或输,这样的设计是不是很巧妙?

介绍了这么多,希望讲得足够清楚,最后贴上游戏主体部分的代码(实际上和伪代码十分接近),具体细节的实现下次再谈:

def main(stdscr):

def init():

#重置游戏棋盘

game_field.reset()

return 'Game'

def not_game(state):

#画出 GameOver 或者 Win 的界面

game_field.draw(stdscr)

#读取用户输入得到action,判断是重启游戏还是结束游戏

action = game_field.get_user_action(stdscr)

responses = defaultdict(lambda: state) #默认是当前状态,没有行为就会一直在当前界面循环

responses['Restart'], responses['Exit'] = 'Init', 'Exit' #对应不同的行为转换到不同的状态

return responses[action]

def game():

#画出当前棋盘状态

game_field.draw(stdscr)

#读取用户输入得到action

action = game_field.get_user_action(stdscr)

if action == 'Restart':

return 'Init'

if action == 'Exit':

return 'Exit'

if game_field.move(action): # move successful

if game_field.is_win():

return 'Win'

if game_field.is_gameover():

return 'Gameover'

return 'Game'

state_actions = {

'Init': init,

'Win': lambda: not_game('Win'),

'Gameover': lambda: not_game('Gameover'),

'Game': game

}

curses.use_default_colors()

# 设置终结状态最大数值为 32,可以自行修改

game_field = GameField(win=32)

state = 'Init'

#状态机开始循环

while state != 'Exit':

state = state_actions[state]()

关于上面的代码块,你可能仍有问题:

stdscr是个啥?curses又是个啥?

简单说来,这是一个把终端变成互动界面的模块。

推荐阅读(不用全看懂,了解一下就行,下节再介绍):

另外,文中出现了较多的lambda表达式。lambda在很多时候对于精简代码有很大的帮助,希望初学者能够理解它的用途。

参考资料:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值