前一阵子去hackathon和队友体验了一把Amazon Echo特别爽。这个小玩意扩展能力很好,你可以通过它直接和你的电脑进行交互。这次我们使用python的Flask来通过Echosim操作2048,完成各个方向的滑动。
让我们开始吧!
工具准备
- Flask
- Flask-Ask
- EchoSim
- ngrok
- Python3
- Python 2048
修改2048的输入源
要使用Echo作为输入源,原理非常简单粗暴。只要把程序中的键盘输入源去掉->换成Echo输入源。具体来讲就是
- 使用'left','right'字符串来代替键盘按键
- 使用Echo生成'left,'right'字符串
让我们先来看看2048程序里面的两个文件,其中puzzle部分包含了键盘输入部分,分为下面几个部分
第一部分定义了2048以接受'wasd'字符的方式来操作
KEY_UP = "'w'" KEY_DOWN = "'s'" KEY_LEFT = "'a'" KEY_RIGHT = "'d'"
因为我们会对Echo说出具体方向,所以我们把它改成
KEY_UP = "up" KEY_DOWN = "down" KEY_LEFT = "left" KEY_RIGHT = "right"
现在我们要取消wasd与键盘上的wasd按键绑定,先删掉GameGrid类中init方法里面的
self.master.bind("<Key>", self.key_down)
接着将key_down方法中的
key = repr(event.char)
改成
key = event
由于程序使用了tkinter的GUI,内部又一个封闭的循环我们不能通过python的交互指令来操作2048,所以我们需要先禁用这个初始化以及自动循环的过程,后面我们手动初始化,来到GameGrid类中的init方法,删除下面几行
self.mainloop()
这个时候我们已经摆脱了了原来的操作方式,来python的交互界面试一下
1 2 3 4 5 | >>> from puzzle import * >>> gamegrid = Gamegrid() #操作 >>> gamegrid.key_down('up') >>> gamegrid.key_down('down') |
这个时候我们对puzzle.py的改造已经完成,我们需要一种类似input的方法来进行移动,接下来我们来使用Flask搭建和Echo沟通的桥梁
交互模型
修改了原游戏之前,我们先来看交互模型,这为我们以后使用Flask-Ask构建了框架。在交互模型中我们要定义Intent(意图),和不同表达所对应的意图。
Intent Schema
{ "intents": [ { "intent": "YesIntent" }, { "intent": "AnswerIntent", "slots": [ { "name": "action", "type": "LIST_OF_SIGNS" } ] } ] }
Amazon Echo 使用json格式来进行交互,上面这个交互模型主要包含了各个Intent,其中
- YesIntent: 当Echo询问是否开始游戏时,回答是时执行(启动游戏)
- AnswerIntent:启动游戏后,所有的指令都通过此条传输,此处定义Echo接收的变量名名称和类型,我们将使用slot来定义,名字为action,类型为自定义类型LIST_OF_SIGNS
Custom Slot Types
在这里我们定义LIST_OF_SIGNS为
up down left right
Sample Utterance
这里定义了识别方法,当你说yes/start/begin时会自动导向YesIntent,'{}'内是会被传输的变量
YesIntent start YesIntent begin YesIntent Yes AnswerIntent {action} AnswerIntent turn {action} AnswerIntent go {action}
使用Flask-Ask搭建服务
创建一个名为server.py的文件
初始化
引入我们的工具包,引入2048
1 2 3 | from flask import Flask from flask_ask import Ask,statement,question,session from puzzle import * |
对引入的内容进行初始化,因为游戏没有开始,我们设置状态为not_yet
1 2 3 4 | app = Flask(\_\_name\_\_) ask = Ask(app,"/") gamegrid=None status = 'not\_yet' |
欢迎界面
欢迎界面就是当你命令Alexa(Echo)启动程序后,Echo的反应。我们在这里使用装饰器@ask.launch,并用字符串来表达Alexa要说的话,用question来发问。
1 2 3 4 5 | @ask.launch def welcome(): welcome_msg = "welcome to 20 48,say yes to begin, say instruction for instruction " return question(welcome_msg) |
启动游戏
欢迎界面过后我们要说yes来启动游戏,@ask.intent('YesIntent')表示回答"YesIntent"后对应的操作,我们在这里初始化游戏(使用global表示游戏在主线程中,tkinter的GUI必须在主线程中),修改当前状态(避免在游戏过程中被重复执行)
1 2 3 4 5 6 7 8 9 10 11 | @ask.intent('YesIntent') def start_instruction(): global status global gamegrid if status == 'not_yet': gamegrid =GameGrid() ins_msg = "Please say the direction to move" status = 'in_game' return question(ins_msg) return question("already in game") |
操作
启动游戏后,自动进入操作模式。这个时候我们使用定义好的AnswerIntent来接收指令,将action通过装饰器作为字符串变量传入函数中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @ask.intent("AnswerIntent",convert ={'action':str}) def game(action): global direc global gamegrid if action in ['left','right','up','down']: try: gamegrid.key_down(action) except: pass return question('') return question('invalid action,please try again') |
如果action是有效的,我们的question是'',即没有反馈,当遇到非法操作时才报错
question和statement
每次通过ask.launch进入程序后,都有一个新的session,statement和question的作用都是将预先安排好的字符串作为语音反馈。但是有一个关键的区别:statement会结束掉当前Session(直接退出),而question会保留当前Session,这是我们循环的根本。
测试
启动服务
下载ngrok,并运行
-
Unix/Mac
./ngrok http 5000
-
Windows
ngrok.exe http 5000
接着同时运行你写好的server.py
Developer上创建新技能
登入你的Amazon Developer账户,点击Alexa ->Alexa Skill Kit -> Create a new skill进入设置页面
- Skill Information,为我们的程序起名字(启动口令),我们这里叫 twenty forty eight
"Alexa,Start Twenty forty eight"
-
Interaction Model,将我们的交互模型放入即可
-
Configuration,选择HTTPS,并将你的ngrok生成的https地址拷贝到上面
-
SSL Certificate下选择‘My development endpoint is a sub-domain of a domain that has a wildcard certificate from a certificate authority’一项后,点击next,你的测试状态就变为可用了
Echosim
进入Echosim.io,用你的amazon developer账户登入即可测试
课后拓展
有兴趣的朋友可以完成以下任务来巩固所学知识
- 添加一个StopIntent使其可以终止
- 判断输赢,并报出结果
- 通过口令重新启动游戏