习题 48: 更复杂的用户输入?

你的游戏可能一路跑得很爽,不过你处理用户输入的方式肯定让你不胜其烦了。每一个房间都需要一套自己的语句,而且只有用户完全输入正确后才能执行。你需要一个设备,它可以允许用户以各种方式输入语汇。例如下面的机种表述都应该被支持才对:

  • open door
  • open the door
  • go THROUGH the door
  • punch bear
  • Punch The Bear in the FACE

也就是说,如果用户的输入和常用英语很接近也应该是可以的,而你的游戏要识别出它们的意思。为了达到这个目的,我们将写一个模组专门做这件事情。这个模组里边会有若干个类,它们互相配合,接受用户输入,并且将用户输入转换成你的游戏可以识别的命令。

英语的简单格式是这个样子的:

  • 单词由空格隔开。
  • 句子由单词组成。
  • 语法控制句子的含义。

所以最好的开始方式是先搞定如何得到用户输入的词汇,并且判断出它们是什么。

我们的游戏语汇?

我在游戏里创建了下面这些语汇:

  • 表示方向: north, south, east, west, down, up, left, right, back.
  • 动词: go, stop, kill, eat.
  • 修饰词: the, in, of, from, at, it
  • 名词: door, bear, princess, cabinet.
  • 数词: 由 0-9 构成的数字。

说到名词,我们会碰到一个小问题,那就是不一样的房间会用到不一样的一组名词,不过让我们先挑一小组出来写程序,以后再做改进把。

如何断句?

我们已经有了词汇表,为了分析句子的意思,接下来我们需要找到一个断句的方法。我们对于句子的定义是“空格隔开的单词”,所以只要这样就可以了:

stuff = raw_input('> ')
words = stuff.split()

目前做到这样就可以了,不过这招在相当一段时间内都不会有问题。

语汇元组?

一旦我们知道了如何将句子转化成词汇列表,剩下的就是逐一检查这些词汇,看它们是什么类型。为了达到这个目的,我们将用到一个非常好使的 Python 数据结构,叫做”元组(tuple)”。元组其实就是一个不能修改的列表。创建它的方法和创建列表差不多,成员之间需要用逗号隔开,不过方括号要换成圆括号 ()

first_word = ('direction', 'north')
second_word = ('verb', 'go')
sentence = [first_word, second_word]

这样我们就创建了一个 (TYPE, WORD) 组,让你识别出单词,并且对它执行指令。

这只是一个例子,不过最后做出来的样子也差不多。你接受用户输入,用 split 将其分隔成单词列表,然后分析这些单词,识别它们的类型,最后重新组成一个句子。

扫描输入?

现在你要写的是词汇扫描器。这个扫描器会将用户的输入字符串当做参数,然后返回由多个 (TOKEN, WORD) 组成的一个列表,这个列表实现类似句子的功能。如果一个单词不在预定的词汇表中,那它返回时 WORD 应该还在,但 TOKEN 应该设置成一个专门的错误标记。这个错误标记将告诉用户哪里出错了。

有趣的地方来了。我不会告诉你这些该怎样做,但我会写一个“单元测试(unit test)”,而你要把扫描器写出来,并保证单元测试能够正常通过。

“异常”和数字?

有一件小事情我会先帮帮你,时时彩计划软件公式:那就是数字转换。为了做到这一点,我们会作一点弊,使用“异常(exceptions)”来做。“异常”指的是你运行某个函数时得到的错误。你的函数在碰到错误时,就会“提出(raise)”一个“异常”,然后你就要去处理(handle)这个异常。假如你在Python 里写了这些东西:

~/projects/simplegame $ python
Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> int("hell")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'hell'
>>

这个 ValueError 就是 int() 函数抛出的一个异常。因为你给 int() 的参数不是一个数字。 int() 函数其实也可以返回一个值来告诉你它碰到了错误,不过由于它只能返回整数值,所以很难做到这一点。它不能返回 -1,因为这也是一个数字。 int() 没有纠结在它“究竟应该返回什么”上面,而是提出了一个叫做 ValueError 的异常,然后你只要处理这个异常就可以了。

处理异常的方法是使用 tryexcept 这两个关键字:

def convert_number(s):
    try:
        return int(s)
    except ValueError:
        return None

你把要试着运行的代码放到 try 的区段里,再将出错后要运行的代码放到 except 区段里。在这里,我们要试着调用 int() 去处理某个可能是数字的东西,如果中间出了错,我们就抓到这个错误,然后返回 None。

在你写的扫描器里面,你应该使用这个函数来测试某个东西是不是数字。做完这个检查,你就可以声明这个单词是一个错误单词了。

你应该测试的东西?

这里是你应该使用的测试文件 tests/lexicon_tests.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from nose.tools import *
from ex48 import lexicon


def test_directions():
    assert_equal(lexicon.scan("north"), [('direction', 'north')])
    result = lexicon.scan("north south east")
    assert_equal(result, [('direction', 'north'),
                          ('direction', 'south'),
                          ('direction', 'east')])

def test_verbs():
    assert_equal(lexicon.scan("go"), [('verb', 'go')])
    result = lexicon.scan("go kill eat")
    assert_equal(result, [('verb', 'go'),
                          ('verb', 'kill'),
                          ('verb', 'eat')])


def test_stops():
    assert_equal(lexicon.scan("the"), [('stop', 'the')])
    result = lexicon.scan("the in of")
    assert_equal(result, [('stop', 'the'),
                          ('stop', 'in'),
                          ('stop', 'of')])


def test_nouns():
    assert_equal(lexicon.scan("bear"), [('noun', 'bear')])
    result = lexicon.scan("bear princess")
    assert_equal(result, [('noun', 'bear'),
                          ('noun', 'princess')])

def test_numbers():
    assert_equal(lexicon.scan("1234"), [('number', 1234)])
    result = lexicon.scan("3 91234")
    assert_equal(result, [('number', 3),
                          ('number', 91234)])


def test_errors():
    assert_equal(lexicon.scan("ASDFADFASDF"), [('error', 'ASDFADFASDF')])
    result = lexicon.scan("bear IAS princess")
    assert_equal(result, [('noun', 'bear'),
                          ('error', 'IAS'),
                          ('noun', 'princess')])

记住你要使用你的项目骨架来创建新项目,将这个测试用例写下来(不许复制粘贴!),然后编写你的扫描器,直至所有的测试都能通过。注意细节并确认结果一切工作良好。

设计的技巧?

集中一次实现一个测试项目,尽量保持项目简单,只要把你的 lexicon.py 词汇表中所有的单词放那里就可以了。不要修改输入的单词表,不过你需要创建自己的新列表,里边包含你的语汇元组。另外,记得使用 in 关键字来检查这些语汇列表,以确认某个单词是否在你的语汇表中。

加分习题?

  1. 改进单元测试,让它覆盖到更多的语汇。
  2. 向语汇列表添加更多的语汇,并且更新单元测试代码。
  3. 让你的扫描器能够识别任意大小写的词汇。更新你的单元测试以确认其功能。
  4. 找出另外一种转换为数字的方法。
  5. 我的解决方案用了 37 行代码,你的是更长还是更短呢?

Project Versions

Table Of Contents

Previous topic

习题 47: 自动化测试

Next topic

习题 49: 创建句子

This Page

经纬平台登录jw886 彩票开奖结果 黄金时时彩软件下载 重庆时时彩规则有哪些 吉林时时彩官网
重庆时时彩赚钱技巧 内蒙古11选5遗漏查询 做赌博平台注意什么 重庆时时彩黑彩网站 时时彩大小单双软件
时时彩平台注册就送 忆君时时彩软件 cnc娱乐 重庆时时彩5中一技巧 时时彩手机版平台
时时彩全国快开视频 红树林平台 时时彩骗局 江西时时彩10分钟更新 时时彩手机版平台
曾道人图库网 甘肃十一选五开奖结果 2元彩票 2017福建11选5多久派奖 26选5开奖公告
香港六合彩官网 广东11选5 云南11选5开奖结果85期 广西快乐十分非凡走趋 浙江彩票快乐12走势图
安徽11选5任2最大遗漏 福建快3如何跟住豹子号?贵州快3预测号码?加藤鹰金手指最快记录?福建快3万能走势图 黑龙江22选5折线图 快乐8走势图 时时彩计划
黑龙江11选5走势图软件 2017麻将馆规定出台了 北京赛车正规官网 甘肃快3预测软件群?快3开奖结果?甘肃快3走势图今天?甘肃快3遗漏统计 云南十一选五开奖直播