本节书摘来自异步社区《html5 canvas开发详解(第2版)》一书中的第1章,第1.10节,作者: 【美】steve fulton , jeff fulton 更多章节内容可以访问云栖社区“异步社区”公众号查看。
现在来快速看一下另一个广泛提及的“hello world!”类型的应用程序示例——“猜字母”游戏。本章通过这个示例来说明用javascript编写canvas程序比用canvas api多了哪些工作量。
图 1-4所示的游戏中,玩家要做的是猜出计算机从字母表中随即抽取的字母。游戏会记录玩家已经猜了多少次,并列出已经猜过的字母,同时告诉玩家需要往哪个方向猜(往z方向猜还是往a方向猜)。

1.10.1 游戏如何工作
这个游戏的基本结构与“hello world!”的设置相同——canvasapp()是主函数,所有其他函数都定义为局部函数。这里使用drawscreen()函数在画布上显示文本。不过,本示例中也包括了其他一些函数,后面的章节会继续描述。
1.10.2 “猜字母”游戏的变量
这里是游戏中将要用到的所有变量的列表,它们都在canvasapp()中定义并初始化。因此,它们的作用域都被限定在本地定义的封装函数内。
guesses:这个变量保存玩家按键的次数,次数越少说明他玩得越好。
message:这个变量用来向玩家提示游戏的玩法。
letters:这个数组保存字母表中的每一个字母,一方面用来随机挑选一个游戏的秘密字母,另一方面也用来计算字母在字母表中的相对位置。
today:这个变量保存当前日期,仅用于在屏幕上显示,并无其他目的。
lettertoguess:这个变量保存当前被猜的游戏秘密字母。
higherorlower:这个变量存储文本 “higher”或“lower”,具体取决于最后一次猜的字母与秘密字母的位置关系。如果秘密字母离“a”更近,则程序给出“lower”提示;如果离“z”更近,则程序给出“higher”提示。
lettersguessed:这个数组保存玩家已经猜过的字母集合。程序将把这些字母显示在屏幕上,提示玩家他已经猜过什么字母。
gameover:这个变量在玩家获胜前都设为false,程序能通过它知道何时在屏幕上显示“you win”信息,玩家赢了之后就不用再猜了。
代码如下所示。
1.10.3 initgame()函数
initgame()函数为玩家初始化游戏。以下是两段重要的代码。第一段代码从字母数组中找出一个随机字母,然后将其储存在lettertoguess变量中。
var letterindex = math.floor(math.random()* letters.length);
lettertoguess = letters[letterindex];
第二段代码为dom的window对象添加了一个事件监听器,以“监听”键盘的keydown事件。当某个键被按下时,将调用eventkeypressed事件处理函数检测按下的字母。
以下是函数的全部代码。
1.10.4 eventkeypressed()函数
当玩家按下一个键时将调用此函数,这个函数包含了游戏中的大部分操作。javascript中的每个事件处理函数都会传递event对象。该对象中包含发生事件的相关信息,这里使用e参数表示该对象。
首先检测gameover变量是否为false,如果是false,则继续检测玩家按下的是哪个键。以下代码实现了该功能:第一行代码从事件中获得按键值,并将其转换为一个字母表中的字母,用于与lettertoguess中存储的字母进行比较。
下一行代码将这个字母转换为小写字母。如果玩家不小心打开大写锁定键也可以检测大写字母。
接下来,增加guesses变量的计数,用于显示猜测次数。然后,使用array.push()方法将字母添加到lettersguessed数组。
检测游戏的当前状态,给予玩家反馈。首先,测试letterpressed与lettertoguess是否相同,如果相同玩家就赢了。
如果玩家没赢,程序需要分别获得lettertoguess以及letterpressed在数组letters中的索引。下面将用这些数值计算是应该显示“higher”还是“lower”,或者显示“that is not a letter.”(这不是一个字母)。为此,这里使用数组的indexof()方法获得每个字母的对应索引。由于数组是按字母顺序排列的,因此判断显示哪条信息会非常容易。
现在来进行检测。首先,如果guessindex小于0,意味着indexof()返回了−1,也就是说,按键不是一个字母,那么就显示一条错误信息。
剩下的测试就简单了。如果guessindex大于letterindex,就把higherorlower文本设为“lower”。反之,若guessindex小于letterindex,就把higherorlower文本设为“higher”。
最后,调用drawscreen()在屏幕上进行绘制。
1.10.5 drawscreen()函数
下面开始编写drawscreen()函数。之前已经学习过其中的大部分代码了——代码几乎与“hello world!”中的代码相同。例如,这里使用canvas文本api在屏幕上绘制多个变量。仅需要设置一次context.textbaseline = 'top',就可以对所有显示的文本生效。另外,还可以使用context.fillstyle改变颜色,使用ontext.font改变字体。
这里最有趣的事就是显示lettersguessed数组的内容。在画布上,数组将显示为一组用逗号分隔的字符串,例如:
为了输出这个字符串,只需使用lettersguessed数组的tostring()方法,即可以使用逗号间隔的方式打印出玩家猜到的数组。
接下来,还需检测gameover变量。如果结果为真,程序在屏幕上使用大字号(40px)显示文本“you got it!”(你胜利了)。这样,用户就知道自己获胜了。
以下是函数的完整代码。
1.10.6 导出canvas到图像
之前,本章简要讨论了canvas对象的todataurl()属性。这里将使用这个属性让用户能够随时创建一个游戏画面的图像,类似基于canvas制作的游戏中的屏幕捕捉功能。
此处需要在html页面上创建一个按钮,用户单击该按钮就可以获得屏幕捕捉的图像。下面将这个按钮添加到< form >中,然后赋予其编号createimagedata。
在init()函数中,通过document对象的getelementbyid()方法获得了这个表单元素的参考。然后,使用createimagedatapressed()方法为按钮的“单击”事件设置一个事件处理器。
在canvasapp()函数中,定义createimagedatapressed()函数作为事件处理器。这个函数调用window.open(),并将canvas.todataurl()方法的返回数值传送给窗口。由于这个数据表单是一个有效的.png,因此图像会在一个新窗口中显示。
1.10.7 最终的游戏代码
读者可以在本书分发的代码包中的ch1ex4.html文件中找到“猜字母”的最终游戏代码。