• 软件测试技术
  • 软件测试博客
  • 软件测试视频
  • 开源软件测试技术
  • 软件测试论坛
  • 软件测试沙龙
  • 软件测试资料下载
  • 软件测试杂志
  • 软件测试人才招聘
    暂时没有公告

字号: | 推荐给好友 上一篇 | 下一篇

一个C#写的翻转棋游戏

发布: 2007-7-01 20:40 | 作者: admin | 来源: | 查看: 33次 | 进入软件测试论坛讨论

领测软件测试网

前言

这是一个用C#写的翻转棋游戏。

背景

 

我原来写这个程序只是为了常规上学习C#和.NET编程而做的一个练习。翻转棋,大家都知道,是一个比较流行而且比较有趣的游戏,原理和规则都很简单。这使得它成为学习一种新的语言或工具时的一个很好的练习选择。

 

  开始时的练习虽然可以用来当作比赛的游戏,但还是缺少一些电脑棋盘游戏一般的特征,比如撤消走过的步骤,因此,积累了一些.NET的经验以后,就将它升级了。升级后增加了一些新的功能,而且改进了原来的图形界面和游戏AI。

 

使用代码

 

编译源文件并运行Reversi.exe就可以开始玩游戏了。你可以通过菜单和工具栏进行设置,以实现不同选项下的玩法。如在游戏过程中调整窗口大小,改变颜色和电脑对换角色等。

 

当你退出游戏时,你会发现程序建立了一个叫Reversi.xml的文件。这个文件是用来保存各种设置比如窗口大小、位置和玩家的统计数据等,当你再次运行游戏时,它会将这些设置重新加载进来。

 

帮助文件

 

帮助文件跟源代码放在一起,你可以在help files子文件夹中找到。为了使帮助文件可用,只需将Reversi.chm文件拷贝到Reversi.exe所在目录即可。你可以不这样而直接运行程序,但这样当你点击Help Topics时,就会出现一个错误,说文件找不到。

 

所有用于建立help file的源文件都放在那个子目录中,你可以编辑并用重新编译它。

 

游戏 AI

 

代码中比较精华的部分是关于计算电脑和玩家的移动步数部分,因此值得探讨一下。程序使用了标准的min-max look-ahead算法来决定电脑最好走哪步。Alpha-beta 裁剪是用来提高look-ahead搜索效率的,如果你不熟悉min-max算法和alpha-beta裁剪,你可以到Google上搜索一下,会有很多信息和例子。

 

当然,在游戏中有太多的走法,这需要穷举法,它会花很长时间列举出所有可能的走法。一个例外是在游戏要结束时,在10或12格周围剩下很少的方格。在这里,要做一个完全的搜索,才最有可能找出最好的移动路径。

 

但是大多数情况下,look-ahead搜索的深度要限定在一定的数量上,这要根据玩家的难易级别设置。所以对于每一个移动路径和移动步数,游戏棋盘都必须要测算一下来决定哪个玩家最有可能赢得比赛。这个测算是通过利用下列标准计算行列来实现的:

  • forfeit - 让你的对手没有合法的移动步骤,从而迫使他丧失一个回合,使你有两次或更多次的移动次数,来奠定你的优势。
  • mobility - 这是一个衡量你和对手的合法移动次数比的标准。跟forfeit相似,目的就是减少你对手的可选择放棋子的位置,而最大限度的增加你自己的合法放棋子位置。
  • frontier – 一个frontier disc 是棋盘上一个空的方格。一般来说,存在越多的frontier disc就意味着对手在下一回合会有更大的灵活性。相反,存在越少的frontier disc,就回越大限度的限制对手的灵活性。这个得分将反映出你和对手的frontier disc数量比。
  • stability – 边角的棋子是固定的,因为永远不会出界。 这样一来, 倒不如让其他棋子先成为固定的好,这个得分将反映出你和对手的固定的棋子之比。
  •  
  • score – 这个简单,就是指你和对手棋盘上的棋子数量之比。

得分增加的幅度也是根据玩家设置的游戏难易级别来定的。有一个RmaxRank常量来代表游戏到了最后阶段。它的值被设为RSystem.Int32.MaxValue-64.这可以保证在游戏的最后任何一个移动都比平时有比较高的级别。

 

现在这个游戏虽然敌不上高智商的玩家,但对一些一般的对手,它会走的很漂亮。另外,在 上我们可以找到很多游戏策略。

 

游戏的组成

 

Board

Board 类描述了游戏的棋盘。 它用一个二维数组来记录每一个棋盘方格的状态,它只能是下面的在类中已经定义好的常量之一:

  • Black = -1
  • Empty = 0
  • White = 1

程序中共有两个构造函数,一个用来创建一个新的、空的棋盘,另一个用来保存游戏退出时的棋盘状态。

 

  MakeMove()方法是public的,用来增加棋盘上的一个方格,IsValidMove()用来验证玩家走的是否合法。如果玩家无路可走的时候,HasAnyValidMove()就会返回false。

 

  还要记录每个玩家的方格数量,包括总的方格数量、边界的方格数量和不同颜色的不会被攻击的棋子的数量。

 

移动结构

 

  在主类ReversiForm中,有两个结构,用来存储移动的步,两个结构中都包含了一个行索引和一个列索引,分别与棋盘方格相对应。

 

  ComputerMove结构是为计算机AI的,有一个rank成员变量存储移动的位置。用来记录移动步的好坏程度,这是在look-ahead搜索过程中决定的。

 

MoveRecord结构是用来存储游戏期间每个移动步骤的信息的。允许撤消行为,这样的一个结构数组用来记录每个回合的棋盘状态。一个移动记录包含了移动之前棋盘状态,还有一个值来记录下一步该哪个玩家走。这样的一个结构数组就可以记录每个玩家的移动情况,允许游戏重新设回原来的任何一个时间的状态。

 

RestoreGameAt()方法来处理游戏从哪个地方从新开始,这意味着可以将游戏重新设回原来的任何一个时间的状态。而游戏界面上的菜单和工具栏中只提供撤消一步的功能,将来的版本可能会增加一个列表,列出走过的每个时间点,玩家可以通过点击列表中的时间点来一下子返回到某个历史状态。

 

图形用户界面

 

游戏棋盘

 

    SquareControl控件用来描述棋盘上的方格,控件包含了方格的信息和它的状态(empty or a black or white disc)包含带亮圈的活动棋子。

 

显示棋子

 

  每个棋子都是动态画上去的,基本的形状是一个圆行带有阴影的3D效果,棋子的大小是根据棋盘上的方格大小决定的。通过这种方式代替静态图象,棋盘也可以动态调整大小以适应窗口的尺寸。

 

  Click事件是在ReversiForm中处理的,它允许用户走一步棋(假设是合法移动)。同样,MouseMove 和 MouseLeave 事件是用来更新棋盘显示的,进而用亮色标出哪些方格是可以走棋子的。

 

移动的动作

 

  棋子的翻转动作是利用SquareControl 类中的记数器和 System.Windows.Forms.Timer来完成的。因为,这是一个操作系统控制的线程,它可以定期的触发事件来等待你的程序去响应。

 

  当记时器触发事件的时候,AnimateMove()方法将被响应,来更新方格数量并重画。这个动作主要包含改变棋子形状(从正圆形到扁圆形),然后再以相反的颜色回到正圆形。翻转的平滑程度和速度取决于初始值的大小(由常量SquareControl.AnimationStart设定)和记时器的频率(由常量 animationTimerInterval设定)。

 

玩游戏

 

下列变量用来处理游戏的Play:

 

// Game parameters.

private GameState gameState;

private int       currentColor;

private int       moveNumber;

 

moveNumber应该不用说明了,currentColor表示现在轮到哪个玩家了(黑或白),gameState 是以下枚举类型中的值:

 

// Defines the game states.

private enum GameState

{

    GameOver,        // The game is over (also used for the initial state).

    InMoveAnimation, // A move has been made and the animation is active.

    InPlayerMove,    // Waiting for the user to make a move.

    InComputerMove,  // Waiting for the computer to make a move.

    MoveCompleted    // A move has been completed

                     // (including the animation, if active).

}

 

  大多数游戏的Play都是事件驱动的,因此gameState允许不同的事件handler决定适当的动作。比如说,当用户点击棋盘上的一个方格时,SquareControl_Click()被调用,如果游戏状态是InPlayerMove,则棋子将被放在那个方格中。但是,如果游戏正处于其它状态,就是说没有到用户可以操作的回合,则点击将被取消。

 

  同样,如果用户点击工具栏中的"Undo Move"按钮,我们想检查一下游戏的状态,看看在撤消时都做了什么。比如,如果游戏状态是InMoveAnimation,活动的timer将暂停,而方格控件启动记数器。如果状态是InComputerMove,程序将会另起一个线程来进行look-ahead搜索。

 

程序流程

 

  下面的图说明了游戏过程中的流程:

 

  StartTurn() 将在游戏之初被调用,它负责评测游戏的形式并为下一步移动做些设置。

 

  它首先检查当前玩家的移动是否合法,如果不合法,它将切换到对方玩家并检查是否有合法移动。当双方都没有合法移动时,按规则,游戏就会结束。

 

  另外,它也为当前玩家做些设置,如果当前玩家是用户,它就退出,玩家就可以通过点击鼠标来移动棋子。如果当前玩家是计算机,它将启动一个look-ahead搜索去找到最好的移动步骤。

 

用一个工作线程

 

  因为look-ahead搜索是一个深度计算,所以它要在一个工作线程中完成。如果不是这样,那么在进行给计算机搜索最好走法时,主窗口将会不响应而死在那里。因此,StartTurn() 用来创建一个工作线程并且执行CalculateComputerMove()方法开始搜索。

 

  在游戏棋盘对象中的Lock用来防止紊乱情况的发生。一个例子,MakeComputerMove() 和 UndoMove()方法都可以改变棋盘,如果用了Lock,则当一个方法即将执行,而另一个方法正在更新棋盘时,这个方法就必须等待,直到另一个方法更新完毕后并将Lock释放后才能执行。

 

  当搜索到一个好的移动时,CalculateComputerMove() 方法就会做个回调来执行MakeComputerMove(),这个方法将得到一个lock并调用MakeMove()执行移动。

 

执行移动

 

MakeMove() 方法更新棋盘,并将棋子放在指定位置。它也做一些移动记录的维护和消除方格的亮色工作。

 

然后,如果animate选项被关闭,它就会调用EndMove()方法来切换currentColor并且通过调用StartTurn()来启动下一回合。

 

但是,如果animate选项打开,它就会设置成活动的,而不去做初始话工作,并启动animation timer。前面说过,timer会每隔几毫秒就去触发一个事件调AnimateMove()方法来更新显示并记数。最后,记数要结束的时候,AnimateMove()方法就会调用EndMove()来完成移动。

 

将来的升级

 

  在计算机玩家的AI方面还有很大的升级空间,look-ahead算法还可以进一步扩展。另一个可以改进的地方是存储look-ahead树,这可以让搜索深度更深,因为程序每次都不会走同一个步骤。

 

程序升级纪录

  • August 1, 2003 - version 1.0
    • 初始版本
  • September 16, 2005 - version 2.0
    • 改进了图形显示
    • 增加了无限次数的撤消
    • 改进了计算机玩家 AI.
    • 解决了线程问题
    • 扩展了帮助文件

 

文章原文链接地址:


延伸阅读

文章来源于领测软件测试网 https://www.ltesting.net/


关于领测软件测试网 | 领测软件测试网合作伙伴 | 广告服务 | 投稿指南 | 联系我们 | 网站地图 | 友情链接
版权所有(C) 2003-2010 TestAge(领测软件测试网)|领测国际科技(北京)有限公司|软件测试工程师培训网 All Rights Reserved
北京市海淀区中关村南大街9号北京理工科技大厦1402室 京ICP备2023014753号-2
技术支持和业务联系:info@testage.com.cn 电话:010-51297073

软件测试 | 领测国际ISTQBISTQB官网TMMiTMMi认证国际软件测试工程师认证领测软件测试网