象棋和五子棋AI的开发(二)

二、基类

这是两种棋子算法共有的部分。


 
 
  1. public abstract class IGame< T>
  2. {
  3. /// <summary>
  4. /// 伪无限大
  5. /// </summary>
  6. protected const int MAX_VALUE = 100000000;
  7. /// <summary>
  8. /// 伪无限小
  9. /// </summary>
  10. protected const int MIN_VALUE = -100000000;
  11. protected int _row;
  12. /// <summary>
  13. /// 获得行数
  14. /// </summary>
  15. public int Row
  16. {
  17. get
  18. {
  19. return _row;
  20. }
  21. }
  22. protected int _col;
  23. /// <summary>
  24. /// 获得列数
  25. /// </summary>
  26. public int Col
  27. {
  28. get
  29. {
  30. return _col;
  31. }
  32. }
  33. protected ChessColor wincolor;
  34. /// <summary>
  35. /// 胜利一方,必须在GameOver为true时调用
  36. /// </summary>
  37. public ChessColor WinColor
  38. {
  39. get
  40. {
  41. return wincolor;
  42. }
  43. }
  44. protected Chess[,] board;
  45. /// <summary>
  46. /// 刚刚移动的棋子颜色
  47. /// </summary>
  48. public ChessColor MoveColor;
  49. /// <summary>
  50. /// 剩余棋子数
  51. /// </summary>
  52. public int RemainChess;
  53. /// <summary>
  54. /// 算法深度
  55. /// </summary>
  56. protected int AlgorithmDepth;
  57. /// <summary>
  58. /// 最优走步
  59. /// </summary>
  60. protected T BestStep;
  61. /// <summary>
  62. /// 历史走步分数
  63. /// </summary>
  64. protected Dictionary< int, int> history_score;
  65. /// <summary>
  66. /// 获得棋子
  67. /// </summary>
  68. public Chess Chess(int row, int col)
  69. {
  70. return board[row, col];
  71. }
  72. /// <summary>
  73. /// 初始化
  74. /// </summary>
  75. public abstract void Init();
  76. /// <summary>
  77. /// 是否游戏结束
  78. /// </summary>
  79. /// <param name="pre_steps">历史走步</param>
  80. public abstract bool GameOver(T[] pre_steps);
  81. /// <summary>
  82. /// 移动一步
  83. /// </summary>
  84. /// <returns>是否移动成功</returns>
  85. public abstract bool Move(T step);
  86. /// <summary>
  87. /// 产生所有可以移动的步
  88. /// </summary>
  89. /// <param name="c">要移动的颜色</param>
  90. /// <returns>所以可移动走步列表</returns>
  91. protected abstract List<T> GenerateAllMove(ChessColor c);
  92. #region 算法
  93. /// <summary>
  94. /// 计算最优走步
  95. /// </summary>
  96. /// <param name="c">要移动的颜色</param>
  97. /// <param name="max_depth">最深深度</param>
  98. /// <returns>最优走步</returns>
  99. public T CalcBestMove(ChessColor c, int max_depth, bool clear_history)
  100. {
  101. if (clear_history)
  102. {
  103. history_score = new Dictionary< int, int>();
  104. }
  105. else
  106. {
  107. if (history_score == null)
  108. {
  109. history_score = new Dictionary< int, int>();
  110. }
  111. }
  112. AlgorithmDepth = max_depth;
  113. AlphaBeta(c, AlgorithmDepth, MIN_VALUE, MAX_VALUE, null);
  114. return BestStep;
  115. }
  116. /// <summary>
  117. /// Alpha-Beta剪枝算法
  118. /// </summary>
  119. /// <param name="c">棋子颜色</param>
  120. /// <param name="depth">深度</param>
  121. /// <param name="alpha">alpha</param>
  122. /// <param name="beta">beta</param>
  123. /// <param name="pre_steps">历史走步</param>
  124. /// <returns>当前节点的最高值</returns>
  125. protected int AlphaBeta(ChessColor c, int depth, int alpha, int beta, T[] pre_steps)
  126. {
  127. if (depth == 0 || GameOver(pre_steps))
  128. {
  129. return Evaluation(c, pre_steps);
  130. }
  131. List<T> steps = GenerateAllMove(c);
  132. SortMove(steps);
  133. T current_best_step = default(T);
  134. foreach (T step in steps)
  135. {
  136. object obj = MakeMove(step);
  137. T[] new_steps = null;
  138. if (pre_steps == null)
  139. {
  140. new_steps = new T[ 1];
  141. }
  142. else
  143. {
  144. new_steps = new T[pre_steps.Length + 1];
  145. Array.Copy(pre_steps, new_steps, pre_steps.Length);
  146. }
  147. new_steps[new_steps.Length - 1] = step;
  148. int score = -AlphaBeta(SwitchColor(c), depth - 1, -beta, -alpha, new_steps);
  149. UnmakeMove(step, obj);
  150. if (score > alpha)
  151. {
  152. alpha = score;
  153. if (depth == AlgorithmDepth)
  154. {
  155. BestStep = step;
  156. }
  157. current_best_step = step;
  158. if (alpha >= beta)
  159. {
  160. break;
  161. }
  162. }
  163. }
  164. if (current_best_step != null)
  165. {
  166. int from_chess = GetStepNum(current_best_step);
  167. if (history_score.ContainsKey(from_chess))
  168. {
  169. history_score[from_chess] += ( 1 << depth);
  170. }
  171. else
  172. {
  173. history_score.Add(from_chess, ( 1 << depth));
  174. }
  175. }
  176. return alpha;
  177. }
  178. /// <summary>
  179. /// 进走步进行排序
  180. /// </summary>
  181. protected abstract void SortMove(List<T> steps);
  182. /// <summary>
  183. /// 估值
  184. /// </summary>
  185. /// <param name="c">要估值的颜色</param>
  186. /// <param name="pre_steps">历史走步</param>
  187. /// <returns>分数</returns>
  188. protected abstract int Evaluation(ChessColor c, T[] pre_steps);
  189. /// <summary>
  190. /// 虚拟走一步
  191. /// </summary>
  192. protected abstract object MakeMove(T step);
  193. /// <summary>
  194. /// 撤销走一步
  195. /// </summary>
  196. protected abstract void UnmakeMove(T step, object obj);
  197. /// <summary>
  198. /// 相反颜色
  199. /// </summary>
  200. protected abstract ChessColor SwitchColor(ChessColor c);
  201. /// <summary>
  202. /// 把走步转成一个数
  203. /// </summary>
  204. protected abstract int GetStepNum(T step);
  205. #endregion
  206. }

在基类里面,或者需要我们注意的只有CalcBestMove和AlphaBeta两个函数。这两个函数在《PC游戏编程(人机博弈)》一书中都有详细讲解,我这里只说明不一样的地方。

在说明历史启发这一算法时,《PC游戏编程(人机博弈)》认为只需要标记起始位置和结束位置。但我认为起始位置是什么棋子也是有必要记录的。历史记录存放在一个字典里面,在象棋里面,键值的计算如下:


 
 
  1. protected override int GetStepNum(Step step)
  2. {
  3. return step.ToRow * 100000 + step.ToCol * 10000 + step.FromRow * 1000 + step.FromCol * 100 + ( int)board[step.FromRow, step.FromCol].color * 10 + ( int)board[step.FromRow, step.FromCol].type;
  4. }

而在五子棋里面,键值计算如下:


 
 
  1. protected override int GetStepNum(Put step)
  2. {
  3. return step.ToRow * 1000 + step.ToCol * 10 + ( int)step.Color;
  4. }

我们可以看到,象棋的键值可能性明显更多,而且随着游戏的进展,某些移动位置的优势参考价值已不大。所以在象棋里面,每一次计算最优走步,历史启发数据都全部清空。

但五子棋就不一样了。五子棋的键值明显只有225种,而且越到后面会越少。所以我们在整个游戏里面,都不清空五子棋的历史启发数据。

在AlphaBeta函数里面,我加入了对整个走步过程的记录,这主要在打印路线和五子棋估值的时候使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
扩展功能: a. 首先满足网友的”口味”, 重新设计了所有旗子, 选择新的旗盘背景, 换了一个更清爽的面. (界面配色并不是件很容易的事情, 这样的棋类游戏长时间容易使眼睛疲劳, 首先要做到选择的色彩 不刺激眼睛,其实大部分色彩都比较刺激眼睛,尤其是纯三基色(红/黄/蓝), 还要使界面做得漂亮). b. 增加”回放” 功能. 当下完旗子时,可以重新回味一下, 刚杀完的一盘旗,可以寻找不足和重新感受 一下胜利的喜悦! 这个功能比较复杂! d. 又看了一下电脑走旗, 感觉确实比较难处理, 没有高人指点写这个算法确实比较难, 应该比以前聪明 了一些, 但是还是比较笨, 打算有空去找个现在的电脑走旗组件替换上, 自己的电脑走旗算法慢慢研 究(当时是因为实在找不到现在的组件, 自己写了个较笨的,如果哪位朋友能够提供组件,在次深表感谢!!!). e. 扩展走旗的步数容量, 有些网友, 对战的都是高手, 产生数组越界, 这次从 200 扩展到了500, 当然 您还可以扩展到更大,因为源代码已经开放). f. 增加图像缓存功能. g. 解决 .net 从framework 1.0到framework 2.0升级出现的程式升级逻辑问题及一个小bug. h. 本来我只是想把这个程式放到Blog上, 供爱好c#的网友学习,一起交流一下, 没想到反应那么的强烈! 经常收到网友的反馈邮件, 从下载量看,不到一年仅从我的下载空间(不算网友转载下载次数)就有近 万五千次. 所以又重新看懂已经基本忘记的代码,修复了bug,并扩展了以上功能. 有可能还会增加一些功能! 另外,在此对给我提交建议和bug的朋友表示感谢!!! (开发语言: C#语言) 来自:http://community.csdn.net/Expert/topic/5237/5237003.xml?temp=.4600031
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值