一、 简介
本文将细致地介绍用C#来实现游戏Reversi的完整过程。游戏界面如下图所示。
二、 背景 我最开始写这个程序是为了作为学习C#和.NET编程的一个练习。Reversi-或Othello一是一个相当有趣且相当流行的游戏,它仅要求几个基本元素和简单的游戏规则。所以,它是学习一个新的编程环境的良好选择。
该程序的第一个版本是一个可玩的游戏,但是缺乏一些计算机平板游戏的常规特性,例如撤消移动的能力。因此,在又学习了.NET编程的一些技巧后,我又对该游戏进行了改进。修改后的游戏在原先的图形和人工智能方面增加了一些新特性并作了性能上的改进。
三、 使用代码 你只要编译源文件并运行结果可执行文件Reversi.exe,即可开始玩这个游戏。使用菜单或工具栏,你可以进行多方面的选择和设置。你不妨试着在游戏中间缩放窗户,改变颜色或交换边界来观察所发生的情况。
你可能注意,在你退出该游戏时,该程序将创造一文件Reversi.xml。这个文件被使用于保存多方面设置-例如游戏选项,窗户大小和位置以及进行玩家统计-它们在游戏重新开始时被重载。
四、 帮助文件 本文还提供一个Windows帮助文件,包括完整的源代码。你可以在文档的"help files"子目录找到它。为了使这个帮助文件可应用于该程序,只需简单地把文件Reversi.chm复制到可执行文件Reversi.exe所在位置即可。当然,没有它你也可以运行此游戏,但是如果点击"Help Topics"选项将显示一个错误的话-说明该游戏主程序不能发现帮助文件。
所有用于创建这个帮助文件的源文件包括在那个子目录下。你可以使用微软的HTML Help Workshop对之进行编辑并且重新编译它。
五、 兴趣点 相应的源文件已经被很好的注释过了,读者可以很容易的看懂。现在让我们分析一下本游戏中几个有趣的方面。
(一) 游戏AI
本游戏的一个很有意思的地方是计算游戏玩家的移动,所以值得讨论。本游戏使用一标准的"最小最大向前看"算法来确定玩家的最佳移动。Alpha-beta pruning被使用于改进向前搜索的效率。如果你不熟悉"最小最大向前看"算法和/或alpha-beta pruning,你可以用Google搜索来找到大量的相关信息和示例。
当然,在游戏中可能存在的太多的移动顺序将导致一个相当费时的向前搜索-要生成所有可能的移动组合需要花太长的时间。这里的例外是在游戏的结束时-此时仅剩下很少的几个方格-大约十或二十个。此时,可以进行全部的搜索并且这时可能找到玩家的最佳移动结果。
但是在大多数情况中,向前搜索深度必须被限定到一个数目(这基于游戏的难度设置)。因此,对于每一系列可能的行动和反向移动搜索,必须计算最后的游戏平板以决定哪个玩家最有机会赢得游戏。该计算是通过使用下列标准来计算一个等级:
·输掉-让你的对手没有合法的移动可以迫使他输掉这一回合,从而使你更有利于能够在一行中再次(或多次)移动。
·可移动性-这是一种测算-你可以做出多少次合法的行动从而留给你的对手多少次合法的行动。类似于输掉,其思想是,减少你的对手的选择,从而最大化你自己的选择。
·边界-一个边界圆盘是邻近一个空的方格的地方。一般地,拥有大量的边界圆盘,会给你的对手在随后的回合中更多的可移动性。相反,拥有较少的边界圆盘意味着你的对手将在后面有较少的可移动性。这种得分反应了你的边界圆盘相对于你的对手的边界圆盘数。
·稳定性-角圆盘是稳定的,它们永不会被翼侧包围。随着游戏的进展,另外的圆盘也将变为稳定的。这种得分反应了你的稳定圆盘数相对于你的对手的稳定圆盘数。
·得分-这是在平板上你的圆盘数相对于你的对手的圆盘数之差。
不同的权值分别被赋给这里的每一种得分(这再次依赖于游戏的当前难度设置)。通过每一种标准得分乘以它的相应权值,然后把这些值加在一起,一个平板即被赋予一个等级。一个大的负数等级代表一个平板有利于黑棋,而一个大的正数等级代表一个平板有利于白棋。因此,对于一个可能的移动集合,计算机将为当前选定颜色一方选择最可能导致最高等级的那个移动。
一个常数maxRank被用于一场游戏的结束。它被设置为System.Int32.MaxValue-64。这可以确保任何会导致游戏结束的移动将总是比其它移动具有更高的等级(负数或正数)。
从系统的最大整数值减去64允许我们把最后的得分添加到等级上,这样赢得10个圆盘将比赢得2个圆盘具有高的等级。这可以使得计算机玩家在赢了时最大化自己的得分(或在输了时最小化对手玩家的得分)。
当前的实现还不能匹配更好的AI玩家,但是如果它和一个小人对手(至少,这个小人对手)玩得话,已经比较难了。同样,如果你用Google搜索一下,会找到许多描述此游戏策略和AI方法的资源。
(二) 游戏部件
1. 平板类
Board类描述了一个游戏平板。它使用一个二维数组来跟踪每个平板方格的内容,它可以是定义在类中的下列的常数值之一:
·Black=-1
·Empty=0
·White=1
该类提供了两个构造器。一个用于创建一个新的空的平板,而另一个创建一个已存在平板的拷贝。
它提供象MakeMove()这样的公共方法-这个方法把一个圆盘添加到平板上,并能翻动任何可翼侧包围的对手圆盘。例如,IsValidMove()可被用来确定是否一个给定的移动对于一个给定玩家是有效的。如果该给定玩家不能作任何合法的移动,HasAnyValidMove()将返回false。
另外,它还为每一个玩家跟踪圆盘的数目-该数目被用于机器的移动AI例程。这些数目包括圆盘总数、边界圆盘数和每种颜色的安全圆盘(或未翻动的圆盘)数。
2. 移动结构
在主要的ReversiForm类中,定义了一对结构用于存储游戏移动。这两个结构都包含了一个行与列索引对以相应于一个特别的平板方格。
ComputerMove结构用于计算机AI。除了移动位置之外,它还有一个等级成员。这是被用于跟踪一次移动的好或坏-这是在向前搜索过程中决定的。
MoveRecord结构用于存储在游戏中每次移动的信息。为了允许移动的撤消/重做特性,建立了一个数组来跟踪每一轮游戏中的该平板。一次移动记录包含一个描述这次特定移动之前的游戏平板,还有用来指示哪一个玩家将做下次移动的值。针对每个玩家的每次移动建立一个相应的数组以允许游戏复位到在移动过程中的任一点的状态。
RestoreGameAt()方法实现把游戏复位到一个特定的移动数字。尽管它潜在地允许游戏可以恢复到当前移动历史中的任何移动;但是,主表单程序中的菜单和工具条选项目前仅提供了一次移动的撤消/重做或所有移动的撤消/重做。一种将来的增加可能是允许用户点击移动列表中的项来把游戏恢复相应的移动数字。
(三) 图形和用户接口
1. 游戏平板
平板上的方格被一个叫SquareControl的用户控件所描述。对于每个方格都有一个这种控件显示于游戏平板上。该控件包含信息-用于显示方格和它的内容(空的或一个黑的或白的圆盘),包括圆盘动画和任何高亮。
2. 显示圆盘
每一个圆盘被动态绘制。其基本形状是一个圆-具有某种高亮和一个阴影来给它一个伪装的3D外观。这些形状被按比例缩放,依赖于方形控制的当前尺寸。通过以这种方式对其着色,代之使用静态的图像,平板可以被动态地调整大小以匹配表单窗口的大小。
在ReversiForm内部控制的方格控件的Click事件允许用户一次移动到一个特定的方格(假定它是一个合法的移动)。同样,当这些选项激活时,MouseMove和MouseLeave事件被控制通过有效的移动或预览一次移动来更新平板显示。
3. 移动动画
圆盘反转动画是通过使用一个定义在SquareControl类中的计数器并伴随一个System.Windows.Forms.Timer定时器实现的。基本上,这是一个被操作系统所控制的线程-它周期性地引发一个你的表单应用程序能够响应的事件。
在做一次移动后,如果移动动画选项处于活动状态,每个受影响的方格控制把它的计数器初始化并且激活定时器。在每次时钟滴答响时,主表单的AnimateMove()方法被调用(见下面)。这个方法更新方格计数器并且重画它们的显示。该动画基本上包含把圆盘形状从一个圆改变成一个更扁的椭圆,然后又变回到一个完整的圆,只是以相反的颜色罢了。这个动画的光滑度和速度依赖于初始的计数值的大小(由常数SquareControl.AnimationStart所设置)和时钟多长时间滴答响一次(由主表单中的常数animationTimerInterval所设置)。