为自闭症儿童定制的图像查看游戏
介绍
我的儿子纳特患有自闭症。他喜欢看熟悉的地方、事物和人物的照片。我们买了一台数码相机,这样我们就可以拍摄尽可能多的日常事物照片,而无需担心胶卷和冲洗的成本和麻烦。纳特的品味很难预测,所以我们可能会拍摄一个肯定会让他高兴的主题,但却发现他对它们根本没有兴趣。我们希望在拍照时能够广泛撒网,以确保捕捉到他会感兴趣的图像。
一旦有了相机,我们就拍摄了数百张照片,并教纳特如何在电脑上使用名为 ThumbsPlus 的现成图像查看器浏览它们。然而,在观看纳特翻阅照片时,我注意到了一些事情:一旦他熟悉了照片的顺序,他就会点击照片,好像点击的位置很重要。图像查看器并不在意:点击图片上的任何位置都会显示下一张图片。但是,如果纳特知道下一张照片是当前照片左侧的事物,他就会点击图像的左侧。如果他知道下一张照片是通过图片右侧的门,他就会点击门。他玩过许多儿童冒险游戏,例如 Pajama Sam,并且了解这种导航方式。即使在不使用该导航方式的程序中,他也开始像它使用了该导航方式一样使用它。
所以我想,为什么不做一款以他喜欢的方式工作的图像查看器呢?为什么不编写一个程序,让我能够从我们的生活中构建图片环境,并让纳特以他想要的方式在其中导航呢?

纳特世界中的 1500 多张图像之一。用户界面只是一组方向光标(靠近中心) 放大
选择 Python
我以前使用 Python 进行小型脚本项目,并且喜欢它快速启动的感觉。它将为我提供一个可塑的环境,用于试验功能,以查看纳特会发现什么有趣。虽然这是一个仅供我儿子使用的家庭项目,但它与更大、本应更严肃的项目存在相同的问题:需求不明确、客户不可预测以及开发人员资源有限。在选择开发工具时,我最关心的是我的生产力。由于这是“下班后”的工作,我的时间和注意力会分散,我希望能够专注于用户体验,而不是构建环境、内存管理等。
我寻找了现有的软件,并在 pygame (www.pygame.org) 上找到了许多有趣的示例。Pygame 提供了我需要的用于图像操作和显示管理的所有功能,并且那里有一些现有的项目提供了有用的示例。Pyzzle 几乎提供了我需要的东西,但在内部组织方面不如我希望的那么好,无法快速试验新功能。
实施
我使用现有示例来指导我对 pygame 的使用,为我的程序创建了一个新的框架。我的程序,我简单地命名为“纳特的世界”,将提供一个虚拟的世界供探索。它的工作方式将符合纳特的期望,提供一个类似于《神秘岛》等游戏的简单探索环境;点击屏幕左侧会向左转,点击中心会向前移动,依此类推。
最简单地说,整个环境只是一组节点,每个节点都有一个要显示的图像,以及一个要导航到的其他连接节点的列表。世界上的一个物理位置由多个节点表示,每个节点对应一个面朝的方向。这些节点合在一起称为“地点”。随着时间的推移,此模型被扩展以向环境中添加功能,但此简单模型使我得以启动。Python 为我构建 Node 类提供了清晰的面向对象基础,而 pygame 提供了显示图像、创建光标和收集输入的基元。
最大的挑战之一是在组织世界描述的方式之间做出决定。一方面,我可以为世界的结构创建一个描述性语言,并编写一个解释器来读取描述并构建游戏将运行的内存结构。这将是一项相当大的工作,并且需要我正式化世界中可能出现的所有结构。这也意味着在我获得纳特可以实际使用的东西之前会有更长的延迟。另一方面,我可以直接使用 Python 语句来直接构建结构。这将是繁琐且容易出错的。
作为一个方便的中间地带,我使用 Python 语句来构建世界,但大量使用了实用函数和类,为常见结构提供了快捷方式。例如,我的图像都存储在按日期命名的目录中,并且我必须为每个节点输入一个图像文件名。ImgShortcut 类使这变得简单。定义__call__方法允许我使用 Python 的语法来获得我想要的效果
class ImgShortcut: def __init__(self, fmt): self.fmt = fmt def __call__(self, arg): return self.fmt % (arg) nov4 = ImgShortcut(r'C:\img\vol3\20011104\dscf%04d.jpg') nov10 = ImgShortcut(r'C:\img\vol3\20011110\dscf%04d.jpg') >>> print nov4(17) c:\img\vol3\20011104\dscf0017.jpg
另一个示例是,世界上的许多地点共享相同的四节点结构:一个用于北视图,一个用于西视图,依此类推。使用 Python 的关键字参数语法,我能够编写一个函数来构建这种常见的地点类型
def nesw(name, **data): """ Make four nodes for n, e, w, s from a location. Keys: images: ni, ei, wi, si. destinations: n, e, w, s. """ # (code omitted)
第一个参数是新节点的基本名称(节点名称是通过附加“:n”、“:w”等来创建的)。其余参数由关键字指定:ni 是北节点的图像名称,n 是北方的节点;wi 是西节点的图像,w 是西方的节点,依此类推。现在,我可以使用简单的速记语法来创建一个地点
nesw('front43', ni = nov4(17), ei = nov4(18), e = 'allerton_hawthorne:e', si = nov10(381), s = 'hall:s', wi = nov4(16), w = 'markliesl:w' )
由于使用了关键字语法,我可以省略不适用于特定地点的参数。现在我有一种几乎是声明性的语法,它对于创建常见结构很方便,而无需为新的迷你语言编写解释器。而且,如果我遇到需要完整 Python 的不寻常情况,我可以随时使用它。
世界和支持它的程序并行增长。当我想到新的地点结构的想法时,我会编写新的实用函数(如 nesw())来轻松创建它们。我可以观看纳特玩游戏,看看他希望它做什么,并快速添加功能。Python 的许多功能(解释代码、面向对象、垃圾回收、列表和字典)都支持这种快速周转。
Node 类被扩展和子类化以创建新的节点类型。例如,我添加了一个 MenuNode 子类来处理屏幕上的目的地菜单。这允许点击汽车,然后选择您想开车去哪里。添加了节点之间的过渡,以使效果更加令人愉悦。点击立体声音响会产生一个要播放的歌曲菜单(当然,通过 pygame)。
结果
此时,源代码的总大小约为 1400 行代码,加上 1300 行描述世界的代码,目前有 1500 多个节点。
当我向朋友展示纳特的世界时,他们总是谈论如果我能为其他人提供类似的东西会多么棒。我考虑过编写一个 WorldBuilder 应用程序。它将允许非技术人员使用 GUI 来浏览图像,并将它们连接到节点世界中。这将是一个很好的构建项目,也是一个更好的拥有项目,因为我自己可以使用它来扩展纳特的世界。我不知道我是否会有时间和精力在业余时间构建这样的东西,但如果我这样做,我知道 Python 将是胜任这项工作的工具。
关于作者
Ned Batchelder 是马萨诸塞州布鲁克林的软件工程师。他的妻子和三个儿子都喜欢玩纳特的世界。他的网站是 https://www.nedbatchelder.com。