它向南跑,向北绕圈,盘旋,随风绕行
并根据其回路回风;
所有的河流都流入大海-大海不会溢出,
到河流泛滥的地方,-那里继续泛滥;
传道书在1998年,开发了一个完全独特的应用程序,使您可以简化将抽象棋盘游戏(或拼图)的开发过程简化为一种小的文字描述语言的过程,这使
Lisp在某种程度上令人
回味 。 这个项目叫做
Zillions of Games 。 它在棋盘游戏爱好者中引起了轩然大波。 目前,使用该技术已创建了2,000多个应用程序。
很快就很清楚ZoG有很多缺点。 我已经在哈勃(Habr)上
写过有关此内容的信息,不再重复。 我只想说,开发人员没有考虑到大量现有游戏的功能,并且对一些重要的选项进行了硬编码,因此它们的更改变得非常成问题。 格雷格·施密特(Greg Schmidt)在2007年试图通过发布
Axiom开发套件来纠正这种情况,但是它与ZoG的紧密集成并不能解决所有问题。
卢迪计划指出了新领域,利用通用游戏“引擎”和
遗传算法来自动化新棋盘游戏的开发过程。 不幸的是,最初将这种方法设想为故意简化游戏机制和采用的AI级别。 关于该项目目标的讨论超出了本文的范围,但是毫无疑问,它的某些技术解决方案是我自己开发的起点。
我的目标是为抽象棋盘游戏的开发开发一种更加通用和用户友好的“引擎”。 近一年来,我一直在研究ZoG和Axiom的可能性,并了解了它们的局限性。 我认为我可以通过创建一个更通用且跨平台的解决方案来解决他们的问题。 关于这个项目的工作进展,我将报告。
开放性和模块化
ZoG的主要缺点可能是其关闭。 该产品在Windows单一平台下“一次又一次地”组装。 如果它是开源代码,则可以尝试在Linux,Android,iOS下移植它。另一个问题是它的整体性。
在ZoG中,有模块化的开始,允许连接到游戏DLL,包括AI的自定义实现。 Axiom更进一步,允许您以自动播放模式运行应用程序,而无需使用ZoG内核。 尽管此解决方案存在严重局限性(仅支持两个播放器支持应用程序),但本示例演示了模块化将如何有所帮助! 不能高估由两个机器人组织游戏(使用不同的AI设置)并收集大量游戏统计信息的机会。 但是,如果该产品是完全模块化的,那就更好了!
- 移动生成模块
- 移动执行模块
- 控制模块
- AI模块
- 可视化模块
描述游戏的所有工作都将由移动生成模块执行。 这是项目的“心脏”。 将与此功能无关的所有任务转移到其他模块将使其尽可能地简单。 您可以改进此模块,而无需考虑AI问题和用户交互。 您可以完全更改游戏描述的格式,也可以以ZoG,Axiom和Ludi的格式添加对描述的支持。 模块化是解决方案灵活性的基础!
移动执行模块是游戏状态的管理员。 有关当前游戏状态的信息将按需传输到所有其他模块。 出于以下原因,执行进度必须通过生成模块,生成模块的任务是根据模块执行来形成命令。 而且,基于游戏的描述,移动生成模块的任务是游戏空间的主要配置。
实际上,控制模块是应用程序本身。 它要求移动生成模块提供可能的移动列表,并更改游戏状态,并将选定的移动传递给移动执行模块。 可以连接控制模块来玩一个或多个AI机器人。 根据您的需要(可能不同)! 控制单元的类型由任务划分确定。 这可能是自动播放以收集游戏统计信息,游戏服务器(它可以控制多个状态存储,领导大量游戏会话)或用于离线游戏的单个应用程序。
连接AI的不同实现的能力将提高游戏质量。 可以理解,国际象棋和围棋的模块应使用不同的方法。 信息不完整的游戏和使用随机数据的游戏也需要采用单独的方法。 通用实施AI会在所有游戏中都表现不佳! 模块化连接AI将允许比较算法的“强度”,包括彼此之间的游戏模式。由于AI体系结构与游戏的存储状态是分开的,因此游戏机器人的一个实例可以支持无限数量同时进行游戏。
游戏过程的可视化也可能会有所不同。 首先想到的是2D和3D实现。 开发应用程序的平台也很重要。 不太明显的是,可视化可能是游戏的重要组成部分! 例如,在游戏
Surakarta中 ,如果没有适当的动作动画,则收片将是完全不明显的。
通常,模块化对于这样的项目来说似乎是个好主意,开放源代码将使希望参与该项目的每个人都能参与其中。 目前,我还没有设定自己的商业目的,但是我认为,如果需要的话,我将找到一种无需关闭源代码即可获利的方法。
游戏空间
在开始表演之前,您需要设置舞台。 木板不仅是排列碎片的地方。 除此之外,还可以确定棋子的移动方向(实际上是板位置之间的连接),游戏区域(例如,棋子转换区域),禁止区域等。 这是ZoG实现中棋盘定义的外观:
在ZoG中定义董事会(define Board-Definitions (image "images\Chess\SHaag\Chess8x8.bmp" "images\Chess\Chess8x8.bmp") (grid (start-rectangle 5 5 53 53) (dimensions ("a/b/c/d/e/f/g/h" (49 0))
您可能会注意到,除了游戏设置以外,还有与可视化相关的设置。 我坚信这些设置不属于此处。 在实现可视化模块时,可以使用多个设置,并且可能需要不同的设置。 此外,模拟游戏完全可以在没有任何可视化模块的情况下运行(例如Axiom中的自动播放)。 确实,由于Axiom用于可视化ZoG,因此该定义不包含任何多余的内容:
在Axiom中定义董事会 {board 8 8 {grid} board} {directions -1 0 {direction} n 1 0 {direction} s 0 1 {direction} e 0 -1 {direction} w -1 -1 {direction} nw 1 -1 {direction} sw -1 1 {direction} ne 1 1 {direction} se directions} {symmetries Black {symmetry} ns Black {symmetry} nw sw Black {symmetry} ne se symmetries}
不幸的是,Axiom还没有确定游戏区域的方法(游戏区域的位置必须在代码中手动确定)。 这不是Axiom的唯一简化。 此项目中板的定义不能包含多个网格,并且此网格必须是二维的。 这样定义的木板是一维数组,但是为了方便程序员,为每个空格都定义了同义词,如下所示:
与ZoG中更灵活的网格定义方案相比,这些限制非常令人不舒服(尤其是考虑到强制命名方案出于可视化目的而使用这些字段的事实)。 幸运的是,可以定义任意形状的板。 Axiom和ZoG都提供了机会,可以在板上逐个元素地确定每个位置,并可以确定任意位置对之间的链接。 使用这种方法,我们可以定义任何拓扑的电路板。 其唯一的缺点是描述的极端冗长和复杂。
除了将棋子放置在板上和储备中之外,系统还应能够存储单个棋子和棋盘上的空间的属性。 一个很好的例子,说明了需要在
国际象棋中使用“
castling ”规则的属性。 这是一个困难的举动,包括允许国王和车子同时移动,前提是这些棋子在执行此举之前均未移动。 可以使用一个属性来存储一个布尔标记,该布尔标记显示该块是否移动过。 字段属性也可以找到一些有趣的应用程序。
应该注意的是,属性不仅是变量,而且是游戏状态的一部分。 属性值可以通过执行回合(包括AI模块)来更改,并且应可用于所有后续回合,但不适用于游戏另一分支中执行的回合。 当前,ZoG支持存储片段的布尔属性。 不支持公理存储属性,但是您可以在板的定义中添加变量和数组的描述。 可以使用这些变量,例如捕获件数的计数器:
{board 5 18 {grid} {variable} WhitePieces {variable} BlackPieces board}
ZoG和Axiom的另一个局限性是规则,即板上的每个位置最多只能包含一块。 如果有任何一块完成移动到另一块所占据的位置,则先前占据该位置的块会自动被视为“已吃掉”。 该规则与“棋子”上棋规则非常吻合,简化了该游戏的描述,但使诸如“
bashni checkers ”和“
tavreli ”之类的游戏的实现复杂化。
在这些游戏中,棋子可以排列在“列”中。 这样的“列”可以作为一个整体一起移动。 经过一番思考,我决定最好不要放弃自动执行“国际象棋”捕获,而要改善移动棋子组的机制。 确实,对于“支柱”的实现,您可以随时添加另一个维度(这特别容易,只要可视化模块与移动生成模块和AI分离,那么您就可以使用任何逻辑用于将三维板呈现为二维可视化)。 支持该决定的另一个论点是,“高堆叠”的物品移动并不是唯一的团体旅行类型。 例如,在“
Pentago ”中,木板碎片可以与安装在其上的碎片一起旋转。
综上所述,我可以说,对于我的游戏框架,我决定采用ZoG,Axiom和Ludi所考虑的所有最佳方法,并添加我认为缺少的任何内容。
移动世代
移动生成类似于
非确定性编程 。 移动生成器的任务是根据请求提供当前位置所有可能移动的列表。 玩家或AI将从此列表中选择哪一个动作不是其功能。 让我们看看如何在ZoG中完成移动的生成。 举个例子,我们以移动生成宏为一个远距离片段(女王或主教)。 这是用于确定这些棋子的移动的方式:
(piece (name Bishop) (image White "images\Chess\SHaag\wbishop.bmp" "images\Chess\wbishop.bmp" Black "images\Chess\SHaag\bbishop.bmp" "images\Chess\bbishop.bmp") (moves (slide ne) (slide nw) (slide se) (slide sw) ) )
作为参数,将宏传递到板上的移动方向。 如果您不考虑在板上安装新零件的可能性,则移动的产生看起来很简单。 对于板上的每个零件,都会根据规则计算所有可能的移动。 然后魔术开始...
每个定义都可以将许多可能的动作添加到列表中! 使用命令add将移动添加到列表中(同时将每个移动部件放置在板上)。 我已经
写过关于这种体系结构解决方案非常差的文章。 形成移动的命令应与操纵棋子的命令分开(就像在Axiom中所做的那样)。 让我们看看宏的工作原理:
(define slide ( $1 (while empty? add $1 ) (verify not-friend?) add ))
首先,由一个单元沿给定方向执行位移,然后在一个循环中检查到达的空间中是否没有碎片,形成移动,然后沿相同方向前进至另一个单元。 如果您在这里停下来,那块棋子可以“滑过”空的牢房,但是您如何才能拿下敌人的棋子呢?
很简单! 运行了命令verify,验证了该字段未被友好块占用后,我们形成了另一个add命令,从而完成了移动。 如果在该单元格上放置了一块敌人,它将被自动抓取(就像在木板的一个空间上一样,一次最多只能有一个)。 如果该棋子是友好的,则移动的计算将通过命令verify终止(违反此命令中指定的条件将立即终止当前移动的计算)。
在ZoG和Axiom中,一个人只能移动自己的棋子(或者说,可以移动对手的棋子,但只有在计算一个棋子的棋子的方式中指定时)。 我发现这是一个非常不便的限制,因为在很多游戏中您都可以直接移动对手的棋子(例如,在“
Stavropol Checkers ”中)。 无论其隶属关系如何,所有零件的移动计算都将更加一致。 在确定移动的宏中,只需添加一张支票即可只移动自己的棋子:
(define slide ( (verify friend?) $1 (while empty? add $1 ) (verify not-friend?) add ))
重要的是执行包含多个“部分”动作的动作的能力。 在草稿的实现中,此功能用于执行“链式”捕获:
(define checker-jump ($1 (verify enemy?) capture $1 (verify empty?) (if (not-in-zone? promotion-zone) (add-partial jumptype) else (add-partial King jumptype) ) ) )
局部移动命令由add-partial构成(对于此命令以及add命令,存在移动的变化,并带有“变换”)。 这样的举动始终是更大的“综合”举动的一部分。 通常,为后续移动设置一个“模式”,继续应执行该模式。 因此,在检查程序中,捕获只能继续进行以下捕获,而不能进行“软”(非捕获)移动。
注意事项在ZoG中,部分移动的实施效果不佳。 尝试在一个周期中执行add-partial命令会导致错误。 结果,只能以以下非常尴尬的方式实现由检查王进行的捕获:
(define king-jump-1 ($1 (while empty? $1 ) (verify enemy?) capture $1 (verify empty?) (add-partial jumptype) ) ) (define king-jump-2 ($1 (while empty? $1 ) (verify enemy?) capture $1 (verify empty?) $1 (verify empty?) (add-partial jumptype) ) )
依此类推,直到King-jump-7! 让我提醒您,在大多数带有“远程”国王的棋子中,每次捕获后,国王都可以停在被捕获件之后连续的空白空间链的任何空间上。 顺便说一句,该游戏有一个变体,其中“链”式捕获规则的表述方式不同。 那就是我喜欢的棋子-每个人都可以找到自己喜欢的棋子。
这样的规则描述系统非常灵活,但是有时需要更复杂的逻辑。 例如,如果棋子在“部分”进程中不应重新通过先前遍历的字段,则使用与棋盘上位置相关联的标志是合乎逻辑的。 在访问了一个空间之后,我们设置了一个标志,因此随后不再去该空间:
(verify (not-position-flag? my-flag)) (set-position-flag my-flag true)
除了“位置”标志外,在ZoG中还可以使用全局标志。 这些功能不应与片段的属性混淆。 与后者不同,它们不属于游戏状态。 不幸的是,ZoG中的piece和flag属性只能是布尔值(在Axiom中甚至不支持属性)。 该限制使得难以执行与各种类型的计数相关联的操作。 例如,在
这个小难题中,我不得不使用“计数”块,用“叉子”夹住一对布尔标志(只要块多于一个,我就不需要确切的数字)来使用。
要解决的另一件事是在执行移动操作时缺少明确的“生命周期”。 在开始移动之前,所有标志都会自动重置,但是更容易识别初始化阶段。 我认为,在计算移动时,应分为以下几个阶段:
- 初始化变量并检查复合动作的前提条件
- 初始化变量并检查部分移动的前提条件
- 产生部分动作
- 检查部分移动的后置条件
- 生成,完成和检查复合移动的后置条件
- 检查游戏的终止条件
在完整的合成动作中,从第二步到第四步的步骤组可以重复很多次。 前提条件和后置条件的概念,我称为不变式,来自于Ludi项目。 稍后,我将向您详细介绍不变量的用法。
关于符号的重要性
从该位置生成所有可能的动作只是故事的一半。 为了控制游戏状态,需要对生成的动作进行紧凑的呈现。 为此,在ZoG中使用ZSG表示法。 以下是这种形式的国际象棋可能开始的说明:
1. Pawn e2 - e4 1. Pawn e7 - e5 2. Knight g1 - f3 2. Knight b8 - c6 3. Bishop f1 - c4 3. Knight g8 - f6 4. King e1 - g1 Rook h1 - f1 @ f1 0 0 @ g1 0 0 4. Pawn d7 - d5 5. Pawn e4 x d5 5. Knight f6 x d5
该脚本接近通常的
国际象棋符号,并且通常对用户友好。 只有怀特的第四步可能会引起一些混乱。 因此,在ZSG中,它看起来像
cast脚 。 在“ @”字符之前的动作描述部分非常清楚; 这是车和国王的同步运动,但是接下来呢? 因此,在ZSG中,似乎需要重新设置作品的属性,以防止重复铸造的可能性。
注意事项ZoG特别使用其ZSG注释来以一种玩家可以理解的形式显示游戏的进程。 在面板的右侧,可能会打开“移动列表”子窗口。 此列表可用于浏览记录的游戏。 该列表不是很方便,因为不支持其他游戏的分支树视图。 记录的转弯中与棋子属性变化相关的部分不会显示给用户。
以ZSG标记记录的移动应该包含足以正确更改游戏状态的完整信息。 如果丢失了有关属性更改的信息,则在根据此类记录的游戏中,移动可能会错误地重复(例如,玩家将有机会重新执行掷骰)。 不幸的是,在DLL扩展名(例如Axiom)中,扩展信息无法传输。
使用DLL扩展名时,ZoG在定位到选定的移动(例如,回滚移动)时被迫进行非常狡猾的操作。 从[每个]先前的位置[从游戏开始时开始],将生成所有可能的移动,然后,在该列表内,一个人必须使用[相应的] ZSG表示搜索移动。 生成的[每个动作的副作用]应用于[每个连续]游戏状态,因为可以执行未在该动作的ZSG表示中反映的副作用。
在过去的举动时进入游戏状态的唯一方法是,从游戏开始一直到棋盘的初始状态,所有举动的始终如一地应用,这使情况更加恶化。 在非常
复杂的情况下 ,这种导航不会很快发生。 ZSG标记的另一个缺点可以通过在
Go游戏中记录以下动作来说明:
1. White Stone G19 x A19 x B19 x C19 x D19 x E19 x F19
在这里,在位置G19,放置了一块白色的石头,捕获了一组黑色的石头。 由于必须在ZSG表演中提及与位置表现相关的所有部分,因此转弯记录可能看起来很长(在Go中,一滴下落最多可捕获360颗宝石)。 我可能
早些时候写道,这可能导致什么。 分配用于记录ZoG移动的缓冲区大小可能不够。 而且,如果由于某种原因,去除石头的顺序发生了变化(在游戏开发的过程中发生),则尝试从旧的捕获顺序进行移动的尝试将失败。
幸运的是,有一种简单的方法可以解决所有这些问题。 让我们看一下如何在ZRF中定义棋子的移动:
(piece (name Pawn) (image White "images\Chess\SHaag\wpawn.bmp" "images\Chess\wpawn.bmp" Black "images\Chess\SHaag\bpawn.bmp" "images\Chess\bpawn.bmp") (moves (Pawn-capture nw) (Pawn-capture ne) (Pawn-move) (En-Passant e) (En-Passant w) ) )
ZoG宏中定义的动作名称无法作为动作生成器访问。 但是,是什么阻止我们放弃宏并用名称来描述动作呢? 这是唱片下象棋的样子:
1. e2 - e4 Pawn-move 1. e7 - e5 Pawn-move 2. g1 - f3 leap2 n nw 2. b8 - c6 leap2 n ne 3. f1 - c4 slide nw 3. g8 - f6 leap2 n nw 4. e1 - g1 OO 4. d7 - d5 Pawn-move 5. e4 x d5 Pawn-capture nw 5. f6 x d5 leap2 w nw
注意事项精明的读者可能会注意到,在“黑”的举动中,我使用的指示与棋盘上的实际指示不符。 这与为黑色定义“对称”这一事实有关:
(symmetry Black (ns)(sn) (nw sw)(sw nw) (ne se)(se ne))
因此,粗略地说,白色是“北”,黑色是“南”,反之亦然。
这种记录的好处并不明显,但是它具有一个重要的优点。 所有动作均以统一的方式进行描述,并且这些描述不包含任何多余的内容(当然,动作的描述名称可以更具有“描述性”)。 在castling的描述中,人们设法摆脱了属性的更改和对rook move的描述(该描述不再依赖于move的实现细节)。 在围棋游戏中,这种记录的用途更加清晰:
1. G19 drop-to-empty White Stone
就是这样! 如果对手的石头是按照游戏规则拿走的,则无需在移动说明中列出所有石头。 只需指出位移的初始和最终空间(可能带有一个符号),正在执行的移动的名称以及传递给它的参数行即可。 当然,为了根据此描述执行移动,为了进行解码,必须访问移动生成模块,但是ZoG会这样做!
应该支持的另一种可能性出现在“部分”移动的功能中。 这是来自“
俄罗斯跳棋 ”的示例:
1. Checker g3 - f4 1. Checker f6 - g5 2. Checker e3 - d4 2. partial 2 Checker g5 - e3 = XChecker on f4 2. Checker e3 - c5 = XChecker on d4 x d4 x f4
在这里,黑人第二步在d4和f4取两张。 将这些片段初步“转换”为XChecker是此实现的功能,并且可以防止在同一步中重新获取“失败的”片段。 短语“部分2”描述了“综合”过程的开始,该过程由两个“部分”动作组成。 这种描述形式是不方便的,因为在产生第一动作时,可能不知道“部分”动作序列的长度。 这是新格式的说明:
1. g3 - f4 checker-shift nw 1. f6 - g5 checker-shift ne 2. e3 - d4 checker-shift nw 2. + g5 - e3 checker-jump nw 2. + e3 - c5 checker-jump sw 2. +
与片段“转换”相关的实现细节无关紧要。 棋子的捕捉也没有指定,因为在跳棋中,捕捉是棋子移动的“副作用”,而不是根据“国际象棋的原则”。部分进度将在开始时用符号“ +”编码的线。 单独的“ +”表示“复合动作”的完成(实际上,这是通常的“部分”动作,其中包含缺少的动作,空字符串)。
因此,通过使用命名规则来执行移动,人们已经设法创建了一种通用符号,完全满足了我们的要求。 当然,它与标准国际象棋或任何其他符号都没有关系,但是恰好碰巧国际象棋,西洋跳棋和其他游戏的传统符号也没有任何关系。 可视化模块可以始终将移动记录转换为特定游戏接受的更熟悉的形式。 转换也可以转换成某种通用形式,例如
SGF(智能游戏格式) 。
游戏的生命周期
除了有关在棋盘上放置棋子的信息外,转牌顺序也是游戏状态的重要组成部分,是游戏过程中的变量。 在最简单(也是最常见)的情况下,只需存储一点信息就可以了,但是ZoG提供了更多的机会来实现更复杂的情况。 以下是游戏
Splut的一系列动作描述
。 :
(players South West North East) (turn-order South West West repeat North North North East East East South South South West West West )
在此游戏中,每个玩家一次移动3步,但是如果您给第一个玩家从初始位置进行3步的机会,他将能够摧毁对手的一个棋子,这将给他带来一个优势。显着优势。 因此,第一个玩家只能做出一个动作(它有机会准备攻击对方玩家,但不能攻击他),第二个玩家应该做出两个动作(这也不足以攻击对方玩家)每个玩家总是走三步。
标签重复指示循环重复移动序列的开始。 如果未出现,则会循环重复整个描述。 ZoG不允许多次重复使用标签。 另一个重要功能是指定转单。 以下是游戏的回合顺序说明,其中每个玩家执行两次回合(第一步-移动棋子,第二步-捕捉对手的棋子):
(players White Black) (turn-order (White normal-move) (White capture-move) (Black normal-move) (Black capture-move) )
与移动别人的作品的描述相关的还有另一种功能,但是使用起来非常不便。 问题在于这样的描述没有其他选择。 如果说明中指出该移动应由敌方进行,则玩家必须执行该移动! 在ZoG中,不可能描述移动自己或他人的作品的选择。 如果游戏中需要这种功能(例如“
Stavropol Checkers ”),则必须使所有棋子保持中立(为此目的,创建一个不参与游戏的玩家)并为所有玩家确定机会移动中立的棋子。 上面我已经说过,默认情况下允许所有玩家移动任何棋子(他们自己的以及对手的棋子)的能力要容易得多,它们可以在棋步生成算法中添加必要的检查。
如您所见,ZoG提供的用于描述转弯顺序的选项范围非常有限。 Axiom也无法添加新功能,因为它(通常)在ZoG上运行。 在这方面,卢迪更穷。 为了最大程度地统一游戏规则(需要使用通用算法),在此项目中,所有描述功能都经过了故意简化,从而消除了整个游戏层。
“
Bao Swahili ”是具有复杂生命周期的游戏的一个很好的例子。在这个游戏中,两个阶段的执行规则明显不同。在游戏开始时,部分石头是“手中”。 “在每个玩家中。当手中还有石头时,将石头放入井中,一次放入一块石头。当手中的石头用尽时,游戏的第二阶段开始,分配插入不能说这个游戏不能用ZRF(ZoG的描述语言)来描述,但是由于ZoG的局限性,这种实现方式会造成极大的混乱(这肯定不是AI工作质量的最佳选择)。让我们看看这种游戏的描述在“理想世界”中的样子:
(players South North) (turn-order (turn-order (South pi-move) (North pi-move) ) (label phase-ii) (turn-order (South p-ii-move) (North p-ii-move) ) )
在此,每个转单列表确定其重复的移动顺序(通过移动执行方式进行区分)。 关键字标签定义了一个标签,在最近移动的生成过程中可以进行过渡。 您可能会注意到,在这里我们从隐含的假设出发,即这种过渡总是在第二个玩家移动之后发生(否则会违反移动顺序)。 如何在任意时间过渡到下一阶段?
(players South North) (turn-order (turn-order (South pi-move) (North pi-move) ) (turn-order (labels - phase-ii) (South p-ii-move) (labels phase-ii -) (North p-ii-move) ) )
在这里,标签包含在循环主体中,并包含两个名称。 标签列表中的标签名称按照播放器列表中播放器的转移顺序出现。 用于过渡的名称由哪个玩家进行了最后移动来确定。 如果是北方,它将过渡到第一个标签,否则,将过渡到第二个标签。 如果不使用标签中的任何名称,则相应的位置可以用破折号填充。
管理交替动作的一个重要方面是执行重复转弯的能力。 例如,在
Tables家族的游戏(如
Nard ,
Backgammon或
Ur)中 ,执行重复转弯的能力是游戏战术的重要组成部分。 在ZoG中,可以使用转弯来模仿此功能,但是这种方法会使游戏的说明变得更加复杂(尤其是在玩家更多的情况下)。 使用标签重复转弯会更合乎逻辑:
(players South North) (turn-order (label repeat) South (label repeat) North )
跳到标签重复处的游戏,玩家将再次玩自己的回合(最靠近回合列表中当前位置的标签将生效)。 我喜欢
Perl的隐式定义方法。 隐式生成控制结构可以大大简化游戏说明。 由于可以在许多游戏中使用重复的动作,因此标签会重复出现,并预示着任何回合的可能重复都是隐含的:
(players South North) (turn-order South North )
此外,由于回合的顺序与玩家构造中玩家的书面顺序完全一致,因此您可以自动生成整个回合顺序短语:
(players South North)
描述越容易编写,就越好。
易碎不变式
我在ZoG中不喜欢的主要内容可以用一个词表示-检验。 乍一看,这只是将游戏结束与队友关系联系起来的条件(在
国际象棋家族的游戏中很常见)。 las,仔细观察,简单性就表明自己具有欺骗性。 使用此关键字不仅意味着在每次移动后执行游戏完成检查,而且还向玩家强加了某些“行为”。
与普通的将
棋不同,该游戏的区别仅在于玩家人数。 不幸的是,这种差异足以使确定将死(以及与此“魔术”单词相关的所有事物)的工作不正确。 仅在与其中一位玩家有关的情况下才执行检验检查。 结果,国王可能会受到攻击,并被[即使不留在“检定”中时,也会被对手的回合所吞噬]! 这不是最优的,将反映在人工智能的工作中。
如果这个问题似乎微不足道,那么值得记住的是,联盟通常是在“一对一”的四人游戏中形成的。 在组成联盟的情况下,我们必须考虑对国王友好的碎片不会威胁到他! 因此,例如,两个友好的国王很可能居住在木板的相邻空间中。
如果一个玩家可能有几个国王,它将比以往更加复杂。 在“
Tamerlane国际象棋 ”中,皇家典当变成了王子(实际上是第二位国王)。 如果发生这种情况,您只能通过捕获第一个国王(两个国王中的一个)并交配第二个国王来获胜。 在这个游戏中,您甚至可以得到第三位国王,在“典当行”的改造上花费双倍! “受限制的”表达能力不足以充分描述这种情况。
另一个困难可能是交配的过程。 因此,在蒙古象棋(
Shatar )中,尝试配对的结果取决于棋子执行顺序“检查”的顺序。 结果可以证明是获胜或平局(例如通过棋子交配),甚至是失败(禁止马配偶,但您可以检查)。 就这一点而言,日本将棋是异国情调的东西。 在此游戏中,禁止给与掉下的典当配偶,但是您可以给与掉下的典当配给支票,并给与已移动的典当匹配的配偶。
注意事项还有一个重要的问题值得一提。 在某些游戏中,例如Rhythmomagic,可以有几种不同的方法来结束游戏。 最明显的获胜方式(包括破坏对手的棋子)也是最不受欢迎的。 为了取得更大的胜利,必须以某种方式在敌人的领土上摆弄自己的棋子。
人们应该在游戏描述的级别上区分胜利的类型(以及失败和平局),因为游戏结局的类型对玩家而言很重要。 另外,应该有可能为各种游戏结局分配数字优先级。 在同时满足多个完成条件时,应优先考虑具有最高优先级的条件。
显然,必须将对游戏结束进行验证的逻辑与对国王的下注进行检验的逻辑分开,这是
不变的规则 ,需要在每回合之后进行检查。 违反规则将导致无法执行移动(该移动已从可用移动列表中删除)。 因此,对“国王棋”的(简化)测试可能看起来像这样:
(verify (or (> (count (pieces my? (is-piece? King))) 1) (= (count (pieces my? (is-piece? King) is-attacked?)) 0) ) )
重要的是要理解,此测试应仅对自己的国王进行(我使用谓词my ?,因为谓词朋友“在支持联盟的情况下,不仅会为自己的棋子感到满意,而且也会对联盟的棋子感到满意。所有友善的玩家)。 可以接受的(如果有多个友好的国王,则是可取的)是敌人的国王在移动之后被自己的国王制止的情况。 这种情况应该是不可能的(除非有多个友好的国王)! 提供了检查此类规则的支持后,由将死者检查游戏是否完成变得微不足道。 如果没有可能的动作并且[唯一]国王处于检查中,则游戏结束[如果该国王属于倒数第二个幸存联盟的最后一个幸存玩家]:
(loss-condition (and (= (count moves) 0) (= (count (pieces my? (is-piece? King)) 1) (> (count (pieces my? (is-piece? King) is-attacked?)) 0) ) )
确定不变性的能力将在其他游戏中很有用,例如在
Checkers中 。 该家庭游戏实施中的最大困难与“多数规则”的实施有关。 在几乎所有的草稿游戏中,捕获都是强制性的。 同样,在该家族的大多数游戏中,单回合就可以完成“连锁抓取”的特征。 可能的话,检查员被抓获后,会继续拿其他物品。 在大多数游戏中,玩家需要进行链式捕获直到最后,但是该规则也有例外,例如
Fanorona 。
使用局部移动机制,实现“链捕获”非常简单。 此外,当人们施加一种条件,即必须在所有可能的选项中选择一条要捕获最大数量的链条时,就会出现困难。 在ZoG中,必须从头开始在“硬编码”级别实现此逻辑:
(option "maximal captures" true)
此设置适用于“
国际跳棋 ”,但在“
意大利跳棋 ”中,多数规则的制定方式有所不同。 在此版本的游戏中,如果有多个选项可捕获相同数量的捕获,则必须选择一个捕获较大数量的已转换棋子(国王)的选项。 ZoG的开发人员已提供了此功能。
您输入以下设置: (option "maximal captures" 2)
在这种设置下,不仅计数所捕获的碎片数,还统计其类型。不幸的是,并不是所有的事情都可以预见的。这是“古老的法国棋子”中“多数规则”的表达方式:如果通过一系列抓取可以用一个简单的人或一个国王来抓取相同数量的棋子,则玩家必须使用国王。但是,如果两种情况下的棋子数量相同,但是在一个棋子中有敌方国王(或更多),则玩家必须选择此选项,即使随后使用简单的棋子完成了捕获,但不是使用国王。
Of course, at the present time, almost no one plays this version of checkers, but its very existence clearly demonstrates the shortcomings of “hardcoded” implementation. Using the mechanism of invariants allows for all possible options for the “majority rule” in a universal manner. For the “
old French checkers ” implementation would be as follows:
(verify (>= capturing-count max-capturing-count) ) (if (> capturing-count max-capturing-count) (let max-capturing-count capturing-count) (let max-capturing-sum capturing-sum) (let max-attacking-value attacking-value) ) (verify (>= capturing-sum max-capturing-sum) ) (if (> capturing-sum max-capturing-sum) (let max-capturing-sum capturing-sum) (let max-attacking-value attacking-value) ) (verify (>= attacking-value max-attacking-value) ) (let max-attacking-value attacking-value)
Here, we assume that the rules for capture generation correctly fill [the following] local variables:
- capturing-count — total pieces captured
- capturing-sum — number of kings captured
- attacking-value — value of piece capturing
与这些变量中的每一个相关联的是一个值累加器,该值累加器存储在一个前缀为max的变量中。这三个检查是顺序执行的。任何违反验证条件的行为都会立即中断下一个转弯选项的生成(捕获内容不会存储在可能的转弯列表中)。由于执行的检查与变量值相关联,因此[仅测试当前的新捕获选项]还不够。每个测试都会生成一个与生成的捕获关联的“可弯曲规则”(可能会修改累积的最大值)。在任何累加器中进行每次更改后,都必须再次检查所有关联的规则[对于列表中的每个选项]。如果先前生成的期权违反了任何条件,则必须从可能的转向期权列表中删除该期权。结论
这是我2014年文章的翻译。从那时起,我进行了许多重新思考,Dagaz项目已成为现实,但是我几乎没有更改本文中的任何内容。这篇文章是由我的朋友霍华德·麦凯(Howard McCay)翻译的,我感谢他所做的工作。