在过去的几年中,数独游戏悄然传入美国然后风靡全美,这与它在其发源国日本的兴起如出一辙。数独的玩法简单,类似于纵横字谜但是它使用的是数字(纵横字迷使用的是格子),并且不需借助任何外部的线索。该游戏提供了一个填了一些数字的格子,如图 1 所示。
图 1. 数独游戏示例
游戏目的是使用数字 1-9 将面板填满,使每一行、第一列和每个 3 x 3 的方框(图中用线划出的部分)里都没有重复的数字,如图 2. 所示。
图 2. 数独迷题的一种解答
要解答数独有许多种方法,其中大部分不在本文的范围之内,但基本的原理都是利用排除法决定每个数字的去向。(参阅 参考资料 部分以获得一些有用的信息。)就像广告中描述的那样,解题并不需要用到数学运算
本文将构建一个基本的表格,它将用于显示游戏并能使用选择列表输入答案。使用 XPath 能确定迷题是否被解开。第 2 部分 将对游戏进行改进使其具有载入和保存的功能,有可能的话还将调整一下界面。
首先,游戏的建立需要一些符合要求的数据。我们采用的方法是创建一个内在的数据 “表格”。换句话说,该表格中的一 “行” 数据,即 r(行)元素的内容,将映射到游戏面板中的一行。每个数字存在于各自的方框内,九个数字组成一行。九行便构成了这个游戏。最终的结构类似于图 3。
图 3. 数据结构
注意,应该使行和列尽量与其在面板上显示出来的效果接近。在本例中,使用 0 条目对应于用户还未填数字的空白方框。XForms 是这样工作的,当用户在表单上的方框中填入数字时,该数据将被添加到 XML 文档中。因此,可以通过精心构造出的 XPath 语句来检查文档的结构,以确定迷题是否已被解答出来。
首先我们需要创建基本页面。
基本页面应包含如下几个元素,即真实页面结构(包括名称空间等)和 XForms 模型(如清单 1 所示)。
清单 1. 基本页面
<?xml version="1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xforms="http://www.w3.org/2002/xforms"
xmlns:s="http://www.example.com/sudoku"
xmlns:b="http://www.example.com/board">
<head>
<title>Sudoku</title>
<xforms:model>
<xforms:instance id="content">
<s:game>
<s:row><s:box>6</s:box><s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>9</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>1</s:box></s:row>
<s:row><s:box>0</s:box><s:box>8</s:box>
<s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>0</s:box><s:box>7</s:box><s:box>2</s:box>
<s:box>5</s:box></s:row>
<s:row><s:box>0</s:box><s:box>7</s:box>
<s:box>0</s:box><s:box>3</s:box><s:box>8</s:box>
<s:box>0</s:box><s:box>9</s:box><s:box>0</s:box>
<s:box>0</s:box></s:row>
<s:row><s:box>4</s:box><s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box><s:box>6</s:box>
<s:box>1</s:box><s:box>8</s:box><s:box>0</s:box>
<s:box>0</s:box></s:row>
<s:row><s:box>9</s:box><s:box>6</s:box>
<s:box>0</s:box><s:box>0</s:box><s:box>5</s:box>
<s:box>0</s:box><s:box>0</s:box><s:box>1</s:box>
<s:box>2</s:box></s:row>
<s:row><s:box>0</s:box><s:box>0</s:box>
<s:box>8</s:box><s:box>7</s:box><s:box>2</s:box>
<s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>3</s:box></s:row>
<s:row><s:box>0</s:box><s:box>0</s:box>
<s:box>6</s:box><s:box>0</s:box><s:box>3</s:box>
<s:box>2</s:box><s:box>0</s:box><s:box>7</s:box>
<s:box>0</s:box></s:row>
<s:row><s:box>1</s:box><s:box>3</s:box>
<s:box>2</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box><s:box>6</s:box>
<s:box>0</s:box></s:row>
<s:row><s:box>7</s:box><s:box>0</s:box>
<s:box>0</s:box><s:box>6</s:box><s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>4</s:box></s:row>
</s:game>
</xforms:instance>
<xforms:submission id="submitgame" action="" method="post"/>
</xforms:model>
</head>
<body>
<img src="images/showlayout.gif" style="float:left;height: 64px; width: 64px;" />
<h1 align="center">Sudoku</h1>
<br clear="left" />
<div>
</div>
</body>
</html>
|
代码中包含了一些稍后将使用到的名称空间。这可以使程序的创建稍微简单一点。还需注意,数据是以它自己的实例形式被创建的,使 XML 结构更具可读性(当然,名称完全是任意的)。其中还包含了一幅小图片,它提醒用户他们希望实现的目标。这样便得到了一个简单的页面(如图 4 所示)。
图 4. 空白页面
现在向这个页面添加一些控件。
创建游戏面板,需要九行下拉列表,每个列表包含有 1 到 9 的数字和一个空白符用以表示 0。幸运的是,这很容易。最简单的方法就是另外创建一个实例用于提供模板(如清单 2 所示)。
清单 2. 使用模板
...
</s:game>
</xforms:instance>
<xforms:instance id="templates">
<b:template>
<b:entry><b:sendvalue>0</b:sendvalue><b:display></b:display>
</b:entry>
<b:entry><b:sendvalue>1</b:sendvalue><b:display>1</b:display>
</b:entry>
<b:entry><b:sendvalue>2</b:sendvalue><b:display>2</b:display>
</b:entry>
<b:entry><b:sendvalue>3</b:sendvalue><b:display>3</b:display>
</b:entry>
<b:entry><b:sendvalue>4</b:sendvalue><b:display>4</b:display>
</b:entry>
<b:entry><b:sendvalue>5</b:sendvalue><b:display>5</b:display>
</b:entry>
<b:entry><b:sendvalue>6</b:sendvalue><b:display>6</b:display>
</b:entry>
<b:entry><b:sendvalue>7</b:sendvalue><b:display>7</b:display>
</b:entry>
<b:entry><b:sendvalue>8</b:sendvalue><b:display>8</b:display>
</b:entry>
<b:entry><b:sendvalue>9</b:sendvalue><b:display>9</b:display>
</b:entry>
</b:template>
</xforms:instance>
<xforms:submission id="submitgame" action="" method="post"/>
</xforms:model>
<style type="text/css">
div > * {display: inline;}
</style>
</head>
<body>
<img src="images/showlayout.gif" style="float:left;height: 64px; width: 64px;" />
<h1 align="center">Sudoku</h1>
<br clear="left" />
<div>
<xforms:repeat id="gamerow" nodeset="instance('content')/s:row">
<xforms:repeat id="gamebox" nodeset="s:box">
<xforms:select1>
<xforms:itemset nodeset="instance('templates')/b:entry">
<xforms:label ref="b:display"/>
<xforms:value ref="b:sendvalue"/>
</xforms:itemset>
</xforms:select1>
</xforms:repeat>
</xforms:repeat>
</div>
</body>
</html>
|
有别于显示效果,模板表示的是单一的一行,其中每个 “方框” 都有一个插入到主游戏实例中的值和另一个显示的值。对于游戏中的每一行,我们遍历这一行中的每个方框。通过显示模板中的每个条目,为这一行中的每个方框都创建一个选择列表。稍微修改一下层叠样式表(设置 div 的内容以内联的形式显示,而不是各自一行)便可得到基本的游戏面板(如图 5 所示)。
图 5. 基本游戏面板
然后,根据需要为每一个列表设置初始值(如果有的话)。
由于这些列表已经被组织好了,设置初始值非常的简单(如清单 3 所示)。
清单 3. 设置初始值
...
<xforms:repeat id="gamebox" nodeset="s:box">
<xforms:select1 ref=".">
<xforms:itemset nodeset="instance('templates')/b:entry">
<xforms:label ref="b:display"/>
<xforms:value ref="b:sendvalue"/>
</xforms:itemset>
</xforms:select1>
</xforms:repeat>
...
|
表单为每个方框元素都显示了一个选择列表,所以只需要使该列表引用当前方框元素即可。图 6 显示了结果。
图 6. 设置初始值
然而,请注意,当您觉得原始值使用并不方便,可以简单地更改原始值。当然,这样也破坏了游戏设计的初衷。
为了不让用户轻易地更改原始值,我们应该将这些已提供好的值设置为 “只读”。此时,看上去最简单的方法就是告诉表单不允许用户更改任何非 “0” 的数字。但是考虑到一旦用户开始游戏以后,他们将会把那些原来是 0 的数字改为其他值,显然不让用户更改自己选取的值是不可行的。
要解决这个问题,需要在实例中添加一些数据(如清单 4 所示)。
清单 4. 在实例中添加信息
...
<s:game>
<s:row><s:box ro="yes">6</s:box><s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box ro="yes">9</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box ro="yes">1</s:box></s:row>
<s:row><s:box>0</s:box><s:box ro="yes">8</s:box>
<s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>0</s:box><s:box ro="yes">7</s:box><s:box
ro="yes">2</s:box>
<s:box ro="yes">5</s:box></s:row>
...
</s:game>
</xforms:instance>
<xforms:bind nodeset="//s:row//s:box[@ro='yes']" readonly="true()" />
<xforms:instance id="templates">
...
<style type="text/css">
div > * {display: inline;}
*:read-only { color: red }
</style>
...
|
通过在原始值中添加属性可以使用一个绑定条件指定应该为只读的值。换句话说,用户将不能更改这些选择列表的值。
在理想条件下,可以使用 “pseudo classes” 为这些值设定样式,这样用户就能轻易地认出哪些数字是他们自己选择的以及哪些数字是最初就已提供的。我在页面中添加了这段代码,但不幸地是 Firefox 并不支持它。
重新载入页面,我们会发现空白值可以更改,但是不能更改原始值。
面板已经做好了,但是如何才能知道游戏完成的进度呢?这可以通过计分来实现,该方法很简单,即找出已经解出的行。
要确定某一行是否已经被解出,只需确定它是否包含所有九个数字的实例。由于只有九个位置,所以也保证了没有任何重复的数字。使用一个简单的 XPath 语句便可达到此目的。
首先,要知道游戏中总共有多少行,可以使用 XPath 语句:count(/s:game/s:row)。
使用该语句得到的值是 9。然而,得到所有的行数并没有太大的作用,我们需要的是所有满足特定条件的行。比方说,可以选择含有值为 1 的方框的行:count(/s:game/s:row[s:box = '1'])。
在这些行中,只需那些同时还包含了值为 2 的方框的行:count(/s:game/s:row[s:box = '1'][s:box = '2'])。
其它数字以此类推。把这些条件合在一起就能够检查所有的九个值,这样便可以反映出游戏的完成情况(如清单 5 所示)。
清单 5. 检查正确的行数
...
<s:row><s:box ro="yes">7</s:box><s:box>0</s:box>
<s:box>0</s:box><s:box ro="yes">6</s:box><s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box ro="yes">4</s:box></s:row>
<s:correctRows>no</s:correctRows>
</s:game>
</xforms:instance>
<xforms:bind nodeset="//s:row//s:box[@ro='yes']" readonly="true()" />
<xforms:bind nodeset="//s:correctRows" calculate=
"count(/s:game/s:row[s:box = '1'][s:box =
'2'][s:box = '3']
[s:box = '4'][s:box = '5'][s:box = '6'][s:box = '7'][s:box = '8']
[s:box = '9'])" />
<xforms:instance id="templates">
...
</xforms:repeat>
</div>
Correct Rows: <xforms:output ref="//s:correctRows" /><br />
</body>
</html>
|
通过在实例中添加元素,可以轻易地了解游戏的完成进度,因为每次实例中的值更新时,页面也将随之更新。如图 7 所示。
图 7. 查找正确的行数
第二行包含了所有的九个数字,所以我们能看到 correctRows 的值为 1。
创建一个检查正确列的 XPath 表达式将更复杂一点。不像查找满足多重条件的单一行,查找单一列需要查找行中每一数值。比方说,只有当其中某一行的第一列的值为 1 时,才可以计算根元素:count(/s:game[s:row[s:box[1]='1']])
换句话说,当且仅当某一行满足了其第一个方框元素的值为 1 的条件时,游戏元素才会计数。哪一行满足条件无关紧要,只要值 1 出现在第一个位置就行了。类似地,只有同时当数字 2 也出现在第一个位置时,游戏元素才会计数:count(/s:game[s:row[s:box[1]='1']][s:row[s:box[1]='2']])
其他条件以此类推。不幸的是,该语句只检查第一列的正确性,也就是说每一列都需要一个新的 count() 函数,不过这样并不算太糟糕(如清单 6 所示)。
清单 6. 查找正确的列
...
<xforms:bind nodeset="//s:correctRows" calculate=
"count(/s:game/s:row[s:box = '1'][s:box = '2'][s:box = '3']
[s:box = '4'][s:box = '5'][s:box = '6'][s:box = '7'][s:box = '8']
[s:box = '9']) +
count(/s:game[s:row[s:box[1]='1']][s:row[s:box[1]='2']]
[s:row[s:box[1]='3']][s:row[s:box[1]='4']][s:row[s:box[1]='5']]
s:row[s:box[1]='6']][s:row[s:box[1]='7']][s:row[s:box[1]='8']]
[s:row[s:box[1]='9']]) +
count(/s:game[s:row[s:box[2]='1']][s:row[s:box[2]='2']]
[s:row[s:box[2]='3']][s:row[s:box[2]='4']][s:row[s:box[2]='5']]
[s:row[s:box[2]='6']][s:row[s:box[2]='7']][s:row[s:box[2]='8']]
[s:row[s:box[2]='9']]) +
count(/s:game[s:row[s:box[3]='1']][s:row[s:box[3]='2']]
[s:row[s:box[3]='3']][s:row[s:box[3]='4']][s:row[s:box[3]='5']]
[s:row[s:box[3]='6']][s:row[s:box[3]='7']][s:row[s:box[3]='8']]
[s:row[s:box[3]='9']]) +
count(/s:game[s:row[s:box[4]='1']][s:row[s:box[4]='2']]
[s:row[s:box[4]='3']][s:row[s:box[4]='4']][s:row[s:box[4]='5']]
[s:row[s:box[4]='6']][s:row[s:box[4]='7']][s:row[s:box[4]='8']]
[s:row[s:box[4]='9']]) +
count(/s:game[s:row[s:box[5]='1']][s:row[s:box[5]='2']]
[s:row[s:box[5]='3']][s:row[s:box[5]='4']][s:row[s:box[5]='5']]
[s:row[s:box[5]='6']][s:row[s:box[5]='7']][s:row[s:box[5]='8']]
[s:row[s:box[5]='9']]) +
count(/s:game[s:row[s:box[6]='1']][s:row[s:box[6]='2']]
[s:row[s:box[6]='3']][s:row[s:box[6]='4']][s:row[s:box[6]='5']]
[s:row[s:box[6]='6']][s:row[s:box[6]='7']][s:row[s:box[6]='8']]
[s:row[s:box[6]='9']]) +
count(/s:game[s:row[s:box[7]='1']][s:row[s:box[7]='2']]
[s:row[s:box[7]='3']][s:row[s:box[7]='4']][s:row[s:box[7]='5']]
[s:row[s:box[7]='6']][s:row[s:box[7]='7']][s:row[s:box[7]='8']]
[s:row[s:box[7]='9']]) +
count(/s:game[s:row[s:box[8]='1']][s:row[s:box[8]='2']]
[s:row[s:box[8]='3']][s:row[s:box[8]='4']][s:row[s:box[8]='5']]
[s:row[s:box[8]='6']][s:row[s:box[8]='7']][s:row[s:box[8]='8']]
[s:row[s:box[8]='9']]) +
count(/s:game[s:row[s:box[9]='1']][s:row[s:box[9]='2']]
[s:row[s:box[9]='3']][s:row[s:box[9]='4']][s:row[s:box[9]='5']]
[s:row[s:box[9]='6']][s:row[s:box[9]='7']][s:row[s:box[9]='8']]
[s:row[s:box[9]='9']])" />
<xforms:instance id="templates">
...
|
此时刷新页面,我们会发现当解答出一列后,“正确的列” 的值也会随之更新。如图 8 中的第三列。
图 8. 正确解答一行
在这种情况下,当完成了所有的列和行时,“正确行” 的值应该为 18(9 行和 9 列)。但此时还不算完成。
这并不是太糟糕,但确实还需再做一步。我们知道,除了行和列之外,每个数字也只能在 图 1 所示的 3 x 3 方框内出现一次。不幸的是,并没有简单的 XPath 语句能轻松地确定是否所有的数字都出现了,因为构造的任何语句都必须指定特定的方框和特定的数字。所幸地是,我们还有另一个选择。
我们能做的便是将每个正方形方块中的元素映射到一行数据上,这一行数据是专门为此目的创建的。比方说,以清单 7 所示的 XML 元素为例。
清单 7. 方框元素
<s:square><s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box><s:box>0</s:box></s:square>
|
九个方框元素正好对应于一行。尝试将方框映射到如下对等体(如清单 8 所示)。
清单 8. 方框的对等体
<s:square><s:box>[1, 1]</s:box><s:box>
[1, 2]</s:box><s:box>[1, 3]</s:box>
<s:box>[2, 1]</s:box><s:box>
[2, 2]</s:box><s:box>[2, 3]</s:box>
<s:box>[3, 1]</s:box><s:box>
[3, 2]</s:box><s:box>[3, 3]</s:box></s:square>
|
能看到数据对应的很好,并且使用一条简单的 XPath 语句就可以确定有多少方块是正确的(如清单 9 所示)。
清单 9. 用于确定正确方块的数量的 XPath 语句
count(/s:game/s:square[s:box = '1'][s:box = '2']
[s:box = '3'][s:box = '4'][s:box = '5'][s:box = '6']
[s:box = '7'][s:box = '8'][s:box = '9'])
|
请注意,该语句几乎与用于确定原始行的语句相同。下一步,需要在现有游戏中添加所有的这些信息(如清单 10 所示)。
清单 10. 向游戏添加映射实例
...
<s:row><s:box ro="yes">7</s:box><s:box>0</s:box>
<s:box>0</s:box><s:box ro="yes">6</s:box><s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box ro="yes">4</s:box></s:row>
<s:square><s:box>0</s:box><s:box>0</s:box><s:box>0
</s:box><s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box></s:square>
<s:square><s:box>0</s:box><s:box>0</s:box><s:box>0
</s:box><s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box></s:square>
<s:square><s:box>0</s:box><s:box>0</s:box><s:box>0
</s:box><s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box></s:square>
<s:square><s:box>0</s:box><s:box>0</s:box><s:box>0
</s:box><s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box></s:square>
<s:square><s:box>0</s:box><s:box>0</s:box><s:box>0
</s:box><s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box></s:square>
<s:square><s:box>0</s:box><s:box>0</s:box><s:box>0
</s:box><s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box></s:square>
<s:square><s:box>0</s:box><s:box>0</s:box><s:box>0
</s:box><s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box></s:square>
<s:square><s:box>0</s:box><s:box>0</s:box><s:box>0
</s:box><s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box></s:square>
<s:square><s:box>0</s:box><s:box>0</s:box><s:box>0
</s:box><s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
<s:box>0</s:box>
<s:box>0</s:box><s:box>0</s:box></s:square>
<s:correctRows>no</s:correctRows>
</s:game>
</xforms:instance>
<xforms:bind nodeset="//s:row//s:box[@ro='yes']" readonly="true()" />
<xforms:bind nodeset="//s:correctRows" calculate=
"count(/s:game/s:row[s:box = '1'][s:box = '2'][s:box = '3']
[s:box = '4'][s:box = '5'][s:box = '6'][s:box = '7'][s:box = '8']
[s:box = '9']) +
count(/s:game[s:row[s:box[1]='1']][s:row[s:box[1]='2']]
[s:row[s:box[1]='3']][s:row[s:box[1]='4']][s:row[s:box[1]='5']]
[s:row[s:box[1]='6']][s:row[s:box[1]='7']][s:row[s:box[1]='8']]
[s:row[s:box[1]='9']]) +
...
[s:row[s:box[9]='9']]) +
count(/s:game/s:square[s:box = '1'][s:box = '2']
[s:box = '3'][s:box = '4'][s:box = '5'][s:box = '6']
[s:box = '7'][s:box = '8'][s:box = '9'])" />
<xforms:instance id="templates">
...
|
当然,仅仅添加实例并没有真正起什么作用,完成好一个方块后,“正确行” 的值并没有改变即可证明。要使它发挥作用,还必须完成映射。
XForms 内建了许多不同的方法用于设置一个元素的值等于另一元素。但是在本例中,我们采用了直接赋值的方法。我们将创建一个按钮,用户单击它时便会显式设置每一个元素的值(如清单 11 所示)。
清单 11. 设置值
...
Correct Rows: <xforms:output ref="//s:correctRows" /><br />
<xforms:trigger style="display:block">
<xforms:label>Check it!</xforms:label>
<xforms:action ev:event="DOMActivate">
<xforms:setvalue ref="/s:game/s:square[1]/s:box[1]"
value="/s:game/s:row[1]/s:box[1]" />
<xforms:setvalue ref="/s:game/s:square[1]/s:box[2]"
value="/s:game/s:row[1]/s:box[2]" />
<xforms:setvalue ref="/s:game/s:square[1]/s:box[3]"
value="/s:game/s:row[1]/s:box[3]" />
<xforms:setvalue ref="/s:game/s:square[1]/s:box[4]"
value="/s:game/s:row[2]/s:box[1]" />
<xforms:setvalue ref="/s:game/s:square[1]/s:box[5]"
value="/s:game/s:row[2]/s:box[2]" />
<xforms:setvalue ref="/s:game/s:square[1]/s:box[6]"
value="/s:game/s:row[2]/s:box[3]" />
<xforms:setvalue ref="/s:game/s:square[1]/s:box[7]"
value="/s:game/s:row[3]/s:box[1]" />
<xforms:setvalue ref="/s:game/s:square[1]/s:box[8]"
value="/s:game/s:row[3]/s:box[2]" />
<xforms:setvalue ref="/s:game/s:square[1]/s:box[9]"
value="/s:game/s:row[3]/s:box[3]" />
<xforms:setvalue ref="/s:game/s:square[2]/s:box[1]"
value="/s:game/s:row[1]/s:box[4]" />
<xforms:setvalue ref="/s:game/s:square[2]/s:box[2]"
value="/s:game/s:row[1]/s:box[5]" />
<xforms:setvalue ref="/s:game/s:square[2]/s:box[3]"
value="/s:game/s:row[1]/s:box[6]" />
<xforms:setvalue ref="/s:game/s:square[2]/s:box[4]"
value="/s:game/s:row[2]/s:box[4]" />
<xforms:setvalue ref="/s:game/s:square[2]/s:box[5]"
value="/s:game/s:row[2]/s:box[5]" />
<xforms:setvalue ref="/s:game/s:square[2]/s:box[6]"
value="/s:game/s:row[2]/s:box[6]" />
<xforms:setvalue ref="/s:game/s:square[2]/s:box[7]"
value="/s:game/s:row[3]/s:box[4]" />
<xforms:setvalue ref="/s:game/s:square[2]/s:box[8]"
value="/s:game/s:row[3]/s:box[5]" />
<xforms:setvalue ref="/s:game/s:square[2]/s:box[9]"
value="/s:game/s:row[3]/s:box[6]" />
<xforms:setvalue ref="/s:game/s:square[3]/s:box[1]"
value="/s:game/s:row[1]/s:box[7]" />
<xforms:setvalue ref="/s:game/s:square[3]/s:box[2]"
value="/s:game/s:row[1]/s:box[8]" />
<xforms:setvalue ref="/s:game/s:square[3]/s:box[3]"
value="/s:game/s:row[1]/s:box[9]" />
<xforms:setvalue ref="/s:game/s:square[3]/s:box[4]"
value="/s:game/s:row[2]/s:box[7]" />
<xforms:setvalue ref="/s:game/s:square[3]/s:box[5]"
value="/s:game/s:row[2]/s:box[8]" />
<xforms:setvalue ref="/s:game/s:square[3]/s:box[6]"
value="/s:game/s:row[2]/s:box[9]" />
<xforms:setvalue ref="/s:game/s:square[3]/s:box[7]"
value="/s:game/s:row[3]/s:box[7]" />
<xforms:setvalue ref="/s:game/s:square[3]/s:box[8]"
value="/s:game/s:row[3]/s:box[8]" />
<xforms:setvalue ref="/s:game/s:square[3]/s:box[9]"
value="/s:game/s:row[3]/s:box[9]" />
<xforms:setvalue ref="/s:game/s:square[4]/s:box[1]"
value="/s:game/s:row[4]/s:box[4]" />
<xforms:setvalue ref="/s:game/s:square[4]/s:box[2]"
value="/s:game/s:row[4]/s:box[5]" />
<xforms:setvalue ref="/s:game/s:square[4]/s:box[3]"
value="/s:game/s:row[4]/s:box[6]" />
<xforms:setvalue ref="/s:game/s:square[4]/s:box[4]"
value="/s:game/s:row[5]/s:box[4]" />
<xforms:setvalue ref="/s:game/s:square[4]/s:box[5]"
value="/s:game/s:row[5]/s:box[5]" />
<xforms:setvalue ref="/s:game/s:square[4]/s:box[6]"
value="/s:game/s:row[5]/s:box[6]" />
<xforms:setvalue ref="/s:game/s:square[4]/s:box[7]"
value="/s:game/s:row[6]/s:box[4]" />
<xforms:setvalue ref="/s:game/s:square[4]/s:box[8]"
value="/s:game/s:row[6]/s:box[5]" />
<xforms:setvalue ref="/s:game/s:square[4]/s:box[9]"
value="/s:game/s:row[6]/s:box[6]" />
<xforms:setvalue ref="/s:game/s:square[5]/s:box[1]"
value="/s:game/s:row[4]/s:box[1]" />
<xforms:setvalue ref="/s:game/s:square[5]/s:box[2]"
value="/s:game/s:row[4]/s:box[2]" />
<xforms:setvalue ref="/s:game/s:square[5]/s:box[3]"
value="/s:game/s:row[4]/s:box[3]" />
<xforms:setvalue ref="/s:game/s:square[5]/s:box[4]"
value="/s:game/s:row[5]/s:box[1]" />
<xforms:setvalue ref="/s:game/s:square[5]/s:box[5]"
value="/s:game/s:row[5]/s:box[2]" />
<xforms:setvalue ref="/s:game/s:square[5]/s:box[6]"
value="/s:game/s:row[5]/s:box[3]" />
<xforms:setvalue ref="/s:game/s:square[5]/s:box[7]"
value="/s:game/s:row[6]/s:box[1]" />
<xforms:setvalue ref="/s:game/s:square[5]/s:box[8]"
value="/s:game/s:row[6]/s:box[2]" />
<xforms:setvalue ref="/s:game/s:square[5]/s:box[9]"
value="/s:game/s:row[6]/s:box[3]" />
<xforms:setvalue ref="/s:game/s:square[6]/s:box[1]"
value="/s:game/s:row[4]/s:box[7]" />
<xforms:setvalue ref="/s:game/s:square[6]/s:box[2]"
value="/s:game/s:row[4]/s:box[8]" />
<xforms:setvalue ref="/s:game/s:square[6]/s:box[3]"
value="/s:game/s:row[4]/s:box[9]" />
<xforms:setvalue ref="/s:game/s:square[6]/s:box[4]"
value="/s:game/s:row[5]/s:box[7]" />
<xforms:setvalue ref="/s:game/s:square[6]/s:box[5]"
value="/s:game/s:row[5]/s:box[8]" />
<xforms:setvalue ref="/s:game/s:square[6]/s:box[6]"
value="/s:game/s:row[5]/s:box[9]" />
<xforms:setvalue ref="/s:game/s:square[6]/s:box[7]"
value="/s:game/s:row[6]/s:box[7]" />
<xforms:setvalue ref="/s:game/s:square[6]/s:box[8]"
value="/s:game/s:row[6]/s:box[8]" />
<xforms:setvalue ref="/s:game/s:square[6]/s:box[9]"
value="/s:game/s:row[6]/s:box[9]" />
<xforms:setvalue ref="/s:game/s:square[7]/s:box[1]"
value="/s:game/s:row[7]/s:box[1]" />
<xforms:setvalue ref="/s:game/s:square[7]/s:box[2]"
value="/s:game/s:row[7]/s:box[2]" />
<xforms:setvalue ref="/s:game/s:square[7]/s:box[3]"
value="/s:game/s:row[7]/s:box[3]" />
<xforms:setvalue ref="/s:game/s:square[7]/s:box[4]"
value="/s:game/s:row[8]/s:box[1]" />
<xforms:setvalue ref="/s:game/s:square[7]/s:box[5]"
value="/s:game/s:row[8]/s:box[2]" />
<xforms:setvalue ref="/s:game/s:square[7]/s:box[6]"
value="/s:game/s:row[8]/s:box[3]" />
<xforms:setvalue ref="/s:game/s:square[7]/s:box[7]"
value="/s:game/s:row[9]/s:box[1]" />
<xforms:setvalue ref="/s:game/s:square[7]/s:box[8]"
value="/s:game/s:row[9]/s:box[2]" />
<xforms:setvalue ref="/s:game/s:square[7]/s:box[9]"
value="/s:game/s:row[9]/s:box[3]" />
<xforms:setvalue ref="/s:game/s:square[8]/s:box[1]"
value="/s:game/s:row[7]/s:box[4]" />
<xforms:setvalue ref="/s:game/s:square[8]/s:box[2]"
value="/s:game/s:row[7]/s:box[5]" />
<xforms:setvalue ref="/s:game/s:square[8]/s:box[3]"
value="/s:game/s:row[7]/s:box[6]" />
<xforms:setvalue ref="/s:game/s:square[8]/s:box[4]"
value="/s:game/s:row[8]/s:box[4]" />
<xforms:setvalue ref="/s:game/s:square[8]/s:box[5]"
value="/s:game/s:row[8]/s:box[5]" />
<xforms:setvalue ref="/s:game/s:square[8]/s:box[6]"
value="/s:game/s:row[8]/s:box[6]" />
<xforms:setvalue ref="/s:game/s:square[8]/s:box[7]"
value="/s:game/s:row[9]/s:box[4]" />
<xforms:setvalue ref="/s:game/s:square[8]/s:box[8]"
value="/s:game/s:row[9]/s:box[5]" />
<xforms:setvalue ref="/s:game/s:square[8]/s:box[9]"
value="/s:game/s:row[9]/s:box[6]" />
<xforms:setvalue ref="/s:game/s:square[9]/s:box[1]"
value="/s:game/s:row[7]/s:box[7]" />
<xforms:setvalue ref="/s:game/s:square[9]/s:box[2]"
value="/s:game/s:row[7]/s:box[8]" />
<xforms:setvalue ref="/s:game/s:square[9]/s:box[3]"
value="/s:game/s:row[7]/s:box[9]" />
<xforms:setvalue ref="/s:game/s:square[9]/s:box[4]"
value="/s:game/s:row[8]/s:box[7]" />
<xforms:setvalue ref="/s:game/s:square[9]/s:box[5]"
value="/s:game/s:row[8]/s:box[8]" />
<xforms:setvalue ref="/s:game/s:square[9]/s:box[6]"
value="/s:game/s:row[8]/s:box[9]" />
<xforms:setvalue ref="/s:game/s:square[9]/s:box[7]"
value="/s:game/s:row[9]/s:box[7]" />
<xforms:setvalue ref="/s:game/s:square[9]/s:box[8]"
value="/s:game/s:row[9]/s:box[8]" />
<xforms:setvalue ref="/s:game/s:square[9]/s:box[9]"
value="/s:game/s:row[9]/s:box[9]" />
</xforms:action>
</xforms:trigger>
</body>
</html>
|
此时当用户单击 Check it! 按钮时,所有的值将被设置好,并触发更新 “正确行” 的计数。通过重新载入页面然后再完成一个方块可看到这一变化。只有在单击这个按钮之后 “正确行” 的值才会发生变化,如图 9 (单击之前)和图 10 (单击之后)所示。
图 9. 单击按钮前
图 10 显示了单击按钮之后 “正确行” 的值。
图 10. 单击按钮后
所以,加上要完成的九个方块,游戏完成的条件必须是 “正确行” 的值等于 27。
最后,还需添加提交按钮。为了使这个按钮只有在游戏准备好提交时出现,所以需要为它设置相关性(如清单 12 所示)。
清单 12. 设置提交按钮的相关性
...
<xforms:instance id="content">
<s:game>
<!-- Test set of correct data -->
<s:row><s:box>1</s:box><s:box>4</s:box><s:box>7</s:box>
<s:box>2</s:box><s:box>5</s:box><s:box>8</s:box><s:box>3
</s:box>
<s:box>6</s:box><s:box>9</s:box></s:row>
<s:row><s:box>2</s:box><s:box>5</s:box><s:box>8</s:box>
<s:box>3</s:box><s:box>6</s:box><s:box>9</s:box><s:box>1
</s:box>
<s:box>4</s:box><s:box>7</s:box></s:row>
<s:row><s:box>3</s:box><s:box>6</s:box><s:box>9</s:box>
<s:box>1</s:box><s:box>4</s:box><s:box>7</s:box><s:box>2
</s:box>
<s:box>5</s:box><s:box>8</s:box></s:row>
<s:row><s:box>4</s:box><s:box>7</s:box><s:box>1</s:box>
<s:box>5</s:box><s:box>8</s:box><s:box>2</s:box><s:box>6
</s:box>
<s:box>9</s:box><s:box>3</s:box></s:row>
<s:row><s:box>5</s:box><s:box>8</s:box><s:box>2</s:box>
<s:box>6</s:box><s:box>9</s:box><s:box>3</s:box><s:box>4
</s:box>
<s:box>7</s:box><s:box>1</s:box></s:row>
<s:row><s:box>6</s:box><s:box>9</s:box><s:box>3</s:box>
<s:box>4</s:box><s:box>7</s:box><s:box>1</s:box><s:box>5
</s:box>
<s:box>8</s:box><s:box>2</s:box></s:row>
<s:row><s:box>7</s:box><s:box>1</s:box><s:box>4</s:box>
<s:box>8</s:box><s:box>2</s:box><s:box>5</s:box><s:box>9
</s:box>
<s:box>3</s:box><s:box>6</s:box></s:row>
<s:row><s:box>8</s:box><s:box>2</s:box><s:box>5</s:box>
<s:box>9</s:box><s:box>3</s:box><s:box>6</s:box><s:box>7
</s:box>
<s:box>1</s:box><s:box>4</s:box></s:row>
<s:row><s:box>9</s:box><s:box>3</s:box><s:box>6</s:box>
<s:box>7</s:box><s:box>1</s:box><s:box>4</s:box><s:box>8
</s:box>
<s:box>2</s:box><s:box>5</s:box></s:row>
<!-- <s:row><s:box ro="yes">6</s:box><s:box>0</s:box>
...
<s:box ro="yes">4</s:box></s:row> -->
<s:square><s:box>0</s:box><s:box>0</s:box><s:box>0</s:box>
...
<s:box>0</s:box><s:box>0</s:box></s:square>
<s:submitButtonElement>Submit</s:submitButtonElement>
<s:correctRows>no</s:correctRows>
</s:game>
</xforms:instance>
<xforms:bind nodeset="//s:row//s:box[@ro='yes']" readonly="true()" />
<xforms:bind nodeset="//s:submitButtonElement"
relevant="/s:game/s:correctRows = 27" />
<xforms:bind nodeset="//s:correctRows" calculate=
"count(/s:game/s:row[s:box = '1'][s:box = '2'][s:box = '3']
[s:box = '4'][s:box = '5'][s:box = '6'][s:box = '7'][s:box = '8']
[s:box = '9']) +
...
</xforms:action>
</xforms:trigger>
<xforms:submit ref="/s:game/s:submitButtonElement" submission="submitgame">
<xforms:label>Submit</xforms:label>
</xforms:submit>
</body>
</html>
|
提交按钮被添加到了面板的底部,并引用了一个新的元素,即 submitButtonElement 元素。还添加了一条绑定语句,因此只有当 correctRows 元素等于 27 时,该元素才会出现。要测试这一点,我们临时添加了一个 “正确的” 数据设置。当第一次载入该页面时,所有的行和列都是完成好的(如图 11 所示)。
图 11. 载入正确数据
这时一旦单击 Check it! 按钮,correctRows 的值将更新为 27,并且将出现提交按钮,如图 12 所示。
图 12. 提交按钮出现
此刻,还不知道将数据提交到哪里。这点我们将在 第 2 部分 中完成。
由于数独是基于特定的数据模式,因此可以使用 XPath 表达式计算用户在某一特定游戏中完成的进度。通过这些模式还能轻易地生成游戏面板,供用户进行游戏。在第一部分中,我们创建了表单和游戏的机制;在第二部分中,我们将探究数据端、载入和保存游戏以及执行其他方面的增强。
| 描述 | 名字 | 大小 | 下载方法 |
|---|---|---|---|
| 第 1 部分示例代码 | sudoku1_source.zip | 3KB | HTTP |
学习
- 您可以参阅本文在 developerWorks 全球网站上的 英文原文。
- 您可以参阅本系列的 第 2 部分。
-
Sudoku.com 为初学者介绍了数独游戏的玩法。http://www.sudoku.org.uk 上有 Michael Mepham 为 Daily Telegraph 和 Los Angeles Times 提供的迷题并介绍了如何解答更具挑战性的迷题。如果想在阅读第 2 部分之前通过实践解决更多问题,WebSudoku 上有不同难度的迷题可供线上游戏。
-
了解 XForms,请阅读 XForms 简介,第 1 部分:新的 Web 表单标准 (developerWorks,2006 年 9 月)。
-
从 IBM developerWorks 中国网站 XML 专区 学习更多有关 XForms 的知识。
-
学习 XForms 提交事件,请阅读 XForms 技巧:使用表单提交事件 (developerWorks,2006 年 11 月)。
-
学习如何在 Java(developerWorks,2006 年 10 月)、Perl(developerWorks,2006 年 10 月)和 PHP (developerWorks,2006 年 10 月)中接收 XForms 数据。
-
IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 及相关技术的开发人员。
-
XML 技术文档库:developerWorks XML 专区提供了大量技术文章、提示、教程、标准以及 IBM 红皮书。
- 访问 XForms.org,包含了大量与 XForms 有关的信息。
-
developerWorks 技术活动 和 网络广播
:随时关注技术的最新进展。
-
通过 developerWorks 中国网站 XML 专区 了解 XML 的各个方面。
获得产品和技术
-
Python Sudoku 能生成和解答数独迷题。在第二部分中我们将使用该引擎生成新迷题。
-
W3C 提供了 XForms 推荐标准。
-
查阅 XPath 推荐标准 了解更多有关 XPath 的信息。
-
获取 MozzIE,一种开放源码控件,使您能在 IE 中呈现 XForms。
讨论
- 参与论坛讨论。
-
developerWorks
blog:加入 developerWorks 社区。