Successfully reported this slideshow.

人机对弈编程概述

2,783 views

Published on

放一个刚工作时给同事做技术报告的 PPT。

Published in: Technology

人机对弈编程概述

  1. 1. 人机对弈编程概述 赖勇浩 2005.12.28
  2. 2. 抽象原理 初始状态 衍生状态 状态评估 作出决策 走法生成 搜索算法 棋盘表示
  3. 3. 1) 棋盘表示 <ul><li>棋盘表示就是让计算机知道当前棋局状态的状态表示法 . </li></ul><ul><li>各种棋盘表示技术 : </li></ul><ul><li>一 9*10 的二维数组表示法 </li></ul><ul><li>二 90 的一维数组表示法 </li></ul><ul><li>三 13*14 的一维数组表示法 </li></ul><ul><li>四 位棋盘 (96bit) 和折叠位棋盘 </li></ul><ul><li>五 16*16 的一维数组表示法 </li></ul><ul><li>六 棋盘 - 棋子数组和位行位列数组 </li></ul>
  4. 4. 一 9*10的二维数组表示法 <ul><li>最直观的棋盘表示法 </li></ul><ul><li>速度慢 , 不能设计特殊计算 </li></ul><ul><li>走法生成例子 : </li></ul><ul><li>马在 34 </li></ul><ul><li>可走位 </li></ul><ul><li>X = 4±2 or 4±1 </li></ul><ul><li>Y = 3±2 or 3±1 </li></ul><ul><li>然后将不在棋盘范围的坐标 逐个 去除 </li></ul>98 97 96 95 94 93 92 91 90 88 87 86 85 84 83 82 81 80 78 77 76 75 74 73 72 71 70 68 67 66 65 64 63 62 61 60 58 57 56 55 54 53 52 51 50 48 47 46 45 44 43 42 41 40 38 37 36 35 34 33 32 31 30 28 27 26 25 24 23 22 21 20 18 17 16 15 14 13 12 11 10 08 07 06 05 04 03 02 01 00
  5. 5. 二 90的一维数组表示法 <ul><li>速度慢 </li></ul><ul><li>走法生成例子 : </li></ul><ul><li>马在 39 位 </li></ul><ul><li>可走位 </li></ul><ul><li>M = 39 + INC </li></ul><ul><li>INC = {-19,-17,-11,-7,7,11,17,19} </li></ul><ul><li>然后将不在棋盘范围的坐标 逐个 去除 </li></ul>89 88 87 86 85 84 83 82 81 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
  6. 6. 三 13*14的一维数组表示法 181 180 179 178 177 176 175 174 173 172 171 170 169 168 167 166 165 164 163 162 161 160 159 158 157 156 155 154 153 152 151 150 149 148 147 146 145 144 143 142 141 140 139 138 137 136 135 134 133 132 131 130 129 128 127 126 125 124 123 122 121 120 119 118 117 116 115 114 113 112 111 110 109 108 107 106 105 104 103 102 101 100 99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
  7. 7. 三 13*14的一维数组表示法 <ul><li>速度快 </li></ul><ul><li>走法生成例子 : </li></ul><ul><li>马在 83 位 </li></ul><ul><li>可走位 </li></ul><ul><li>M = 83 + INC </li></ul><ul><li>INC = {-27,-25,-15,-11,11,15,25,27} </li></ul><ul><li>判断是否在棋盘范围 : Borad[M] != Edge </li></ul>
  8. 8. 五 16*16的一维数组表示法
  9. 9. <ul><li>16*16的一维数组表示法 </li></ul><ul><li>速度更快 </li></ul><ul><li>走法生成例子 : </li></ul><ul><li>车在 0x88 位 </li></ul><ul><li>可走位 : </li></ul><ul><li>M = 0x88 +0x01, 0x88 –0x01 </li></ul><ul><li>= 0x88 +0x10, 0x88 –0x10 </li></ul>
  10. 10. 六 棋盘-棋子数组和位行位列 <ul><li>struct CChessPosition </li></ul><ul><li>{ </li></ul><ul><li>unsigned long Squares[256]; </li></ul><ul><li>unsigned long Pieces[32]; </li></ul><ul><li>} </li></ul><ul><li>BitRank,BitFile </li></ul>
  11. 11. 2) 走法生成 <ul><li>走法生成就是扩展状态的手段 , 使计算机能从一个状态进入到另一个状态 .( 扩展状态空间 ) </li></ul><ul><li>与棋盘表示法最大关联 , 走法生成器一般取决于使用了什么样的棋盘表示法 . </li></ul><ul><li>生成的走法放在走法数组里 , 定义为 : </li></ul><ul><li>ChessMove cmList[uiMaxPly][uiMaxMoveCount]; </li></ul>
  12. 12. 3) 搜索算法 <ul><li>复杂度 : O(b^n) </li></ul><ul><li>b 为分枝因子 , 即平均情况下可选择的走法数目 , 国际象棋大约 b = 38, 中国象棋大约 b = 42. </li></ul><ul><li>n 为搜索层数 , 当前商业软件一般为 12 左右 ( 中局复杂局面 ), 少子残局可达到 30. 一般认为采用商业级评估函数 ,n >= 14 的时候可以和人类特级大师抗衡 . </li></ul>
  13. 13. 3.1 Min-Max <ul><li>一个故事(打赌赢了) </li></ul>
  14. 14. 3.1 Min-Max <ul><li>int MinMax(int depth) { </li></ul><ul><li>  if (SideToMove() == WHITE) {   </li></ul><ul><li>// 白方是 “ 最大 ” 者 </li></ul><ul><li>   return Max(depth); </li></ul><ul><li>  } else {             </li></ul><ul><li>// 黑方是 “ 最小 ” 者 </li></ul><ul><li>   return Min(depth); </li></ul><ul><li>  } </li></ul><ul><li>} </li></ul><ul><li>  </li></ul><ul><li>int Max(int depth) { </li></ul><ul><li>  int best = -INFINITY; </li></ul><ul><li>  if (depth <= 0) { </li></ul><ul><li>   return Evaluate(); </li></ul><ul><li>} </li></ul><ul><li>  GenerateLegalMoves(); </li></ul><ul><li>  while (MovesLeft()) { </li></ul><ul><li>   MakeNextMove(); </li></ul><ul><li>   val = Min(depth - 1); </li></ul><ul><li>   UnmakeMove(); </li></ul><ul><li>   if (val > best) { </li></ul>    best = val;    }   }   return best; }   int Min(int depth) {   int best = INFINITY;   // 注意这里不同于 “ 最大 ” 算法   if (depth <= 0) {    return Evaluate();   }   GenerateLegalMoves();   while (MovesLeft()) {    MakeNextMove();    val = Max(depth - 1);    UnmakeMove();    if (val < best) {   // 注意这里不同于 “ 最大 ” 算法     best = val;    }   }   return best; }
  15. 15. 3.2 NegaMax <ul><li>int NegaMax(int depth) { </li></ul><ul><li>  int best = -INFINITY; </li></ul><ul><li>  if (depth <= 0) { </li></ul><ul><li>   return Evaluate(); </li></ul><ul><li>  } </li></ul><ul><li>  GenerateLegalMoves(); </li></ul><ul><li>  while (MovesLeft()) { </li></ul><ul><li>   MakeNextMove(); </li></ul><ul><li>   val = -NegaMax(depth - 1); // 注意这里有个负号。 </li></ul><ul><li>   UnmakeMove(); </li></ul><ul><li>   if (val > best) { </li></ul><ul><li>    best = val; </li></ul><ul><li>   } </li></ul><ul><li>  } </li></ul><ul><li>  return best; </li></ul><ul><li>} </li></ul>
  16. 16. 3.3 Alpha-Beta <ul><li>一个故事 ( 打赌又赢了 ) </li></ul><ul><li>算法 </li></ul><ul><li>int AlphaBeta (int depth , int alpha, int beta ) { </li></ul><ul><li>  if (depth == 0) { </li></ul><ul><li>   return Evaluate(); </li></ul><ul><li>  } </li></ul><ul><li>  GenerateLegalMoves(); </li></ul><ul><li>  while (MovesLeft()) { </li></ul><ul><li>   MakeNextMove(); </li></ul><ul><li>   val = - AlphaBeta (depth - 1 , -beta, -alpha ); </li></ul><ul><li>   UnmakeMove(); </li></ul><ul><li>   if (val >= beta) { </li></ul><ul><li>    return beta; </li></ul><ul><li>   } </li></ul>   if (val > alpha) {     alpha = val;    }   }   return alpha; }
  17. 17. 3.3 不得不说的问题 <ul><li>效果 : </li></ul><ul><li>算法复杂度从 O(b^n) 减小到 O(b^n/2). </li></ul><ul><li>相同时间下可以加深一倍搜索层次 </li></ul><ul><li>现实和理想差别巨大 </li></ul><ul><li>原因 :Alpha-Beta 对节点顺序敏感 </li></ul><ul><li>解决 : 对节点依重要性排序 </li></ul><ul><li>新矛盾 : 不对节点搜索 , 就得不到它的重要性 . 而 排序又要在搜索之前 . </li></ul>
  18. 18. 3.4.1 Transposition Table <ul><li>一个事实 </li></ul><ul><li>实现 : </li></ul><ul><li>#define hashfEXACT 0 </li></ul><ul><li>#define hashfALPHA 1 </li></ul><ul><li>#define hashfBETA 2 </li></ul><ul><li>typedef struct tagHASHE { </li></ul><ul><li>  U64 key; </li></ul><ul><li>  int depth; </li></ul><ul><li>  int flags; </li></ul><ul><li>  int value; </li></ul><ul><li>  MOVE best; </li></ul><ul><li>} HASHE; </li></ul><ul><li>表的大小 </li></ul><ul><li>解决冲突 </li></ul><ul><li>不稳定性问题 </li></ul><ul><li>冲突覆盖 </li></ul><ul><li>无法记录到达结点的线路 </li></ul><ul><li>改善着法顺序 </li></ul><ul><li>长将检测 </li></ul><ul><li>支持置换的开局库和残局库 </li></ul>
  19. 19. 3.4.2 Zobrist Hash <ul><li>U64 Piece[uiType][uiColor][uiPos] </li></ul><ul><li>uiType 是棋子的类型 </li></ul><ul><li>uiColor 是棋子的颜色 </li></ul><ul><li>uiPos 是棋子的位置 ( 坐标 ) </li></ul><ul><li>U64 Piece[uiNum][uiPos] </li></ul><ul><li>uiNum 是棋子的序号 ,0-31 </li></ul><ul><li>uiPos 是棋子的位置 ( 坐标 ) </li></ul>
  20. 20. 3.4.2 Zobrist Hash <ul><li>初始化 : </li></ul><ul><li>U64 rand64(void) { </li></ul><ul><ul><li>  return rand() ^ ((U64)rand() << 15) ^ ((U64)rand() << 30) ^ ((U64)rand() << 45) ^ ((U64)rand() << 60); </li></ul></ul><ul><ul><li>} </li></ul></ul><ul><li>初始化整个棋盘的 Hash: </li></ul><ul><li>for(uiNum = 0; uiNum<32;++uiNum) </li></ul><ul><li>{BoardHash ^= Piece[uiNum][Sqr[uiNum]];} </li></ul><ul><li>中间计算 </li></ul><ul><li>BoardHash ^= Piece[uiNum][uiOldPos]; </li></ul><ul><li>BoardHash ^= Piece[uiNum][uiNewPos]; </li></ul>
  21. 21. 3.5 Iterative Deepening <ul><li>通过的 Transposition Table 的 辅助 , 我 们可以 记 录结 点 的当前 最 优 孩 子, 到 深 度 Depth+1 的 时 候 , 就 可以 先 搜索深 度 Depth 的 最 优 孩 子, 因为 深 度 Depth 的 最 优 孩 子 往往 也是深 度 Depth+1 的 最 优 孩 子 或 者 是个较优的 估 计, 这样 的 话 , 产 生 剪枝 的机 会就更 大,搜索的 节 点 大大地 减 少 。 并 且 由 于 每增 加一 层 搜索深 度 ,其以指数 时间增 加, 相 对来 说 ,深 度为 Depth 的搜索 树 节 点 数 相 对 Depth+1 的搜索 树 节 点 数 几乎 可以 忽略 。 </li></ul>
  22. 22. 3.6 Principal Variation Search <ul><li>思想 : </li></ul><ul><li>就是当第一次迭代搜索时找到最好的值,那么 Alpha-Beta 搜索的效率最高。对着法列表进行排序,或者把最好的着法保存到散列表中,这些技术可能让第一个着法成为最佳着法。如果真是如此,我们就可以假设其他着法不可能是好的着法,从而对它们快速地搜索过去。因此 PVS 对第一个搜索使用正常的窗口,而后续搜索使用零宽度的窗口,来对每个后续着法和第一个着法作比较。只有当零窗口搜索失败后才去做正常的搜索。 </li></ul><ul><li>好处 : </li></ul><ul><li>搜索树的大多数结点都以零宽度的窗口搜索, </li></ul>
  23. 23. 3.6 Principal Variation Search <ul><li>int alphabeta(int depth, int alpha, int beta) { </li></ul><ul><li>  move bestmove;int current; </li></ul><ul><li>  if ( 棋局结束 || depth <= 0) { </li></ul><ul><li>   return eval(); </li></ul><ul><li>  } </li></ul><ul><li>  move m = 第一个着法 ; </li></ul><ul><li> 执行着法 m; </li></ul><ul><li>  current = -alphabeta(depth - 1, -beta, -alpha); </li></ul><ul><li> 撤消着法 m; </li></ul><ul><li>  for ( 其余的每个着法 m) { </li></ul><ul><li>  执行着法 m; </li></ul><ul><li>   score = -alphabeta(depth - 1, -alpha - 1, -alpha); </li></ul><ul><li>   if (score > alpha && score < beta) { </li></ul><ul><li>    score = -alphabeta(depth - 1, -beta, -alpha); </li></ul><ul><li>   } </li></ul>   撤消着法 m;    if (score >= current) {     current = score;     bestmove = m;     if (score >= alpha) {      alpha = score;     }     if (score >= beta) {      break;     }    }   }   return current; }
  24. 24. 3.7 Null-Move Forward Pruning <ul><li>一个事实:很多时候什么都不做比做要好. </li></ul><ul><li>给对手一个机会攻击自己,结果会如何? </li></ul><ul><li>不是万灵药: </li></ul><ul><li>有时候你不能什么都不做,因为做比不做好. </li></ul><ul><li>你不是世界最强的,可能给对手机会,他就击倒你! </li></ul><ul><li>对手也给你一个先出手的机会,怎么办? </li></ul>
  25. 25. 3.8 Null-Move Forward Pruning <ul><li>#define R 2 </li></ul><ul><li>int AlphaBeta(int depth, </li></ul><ul><li> int alpha, int beta) { </li></ul><ul><li>  if (depth == 0) { </li></ul><ul><li>   return Evaluate(); </li></ul><ul><li>  } </li></ul><ul><li>  MakeNullMove(); </li></ul><ul><li>  val = -AlphaBeta(depth - 1 - R, </li></ul><ul><li>-beta, -beta + 1); </li></ul><ul><li>  UnmakeNullMove(); </li></ul><ul><li>  if (val >= beta) { </li></ul><ul><li>   return beta; </li></ul><ul><li>  } </li></ul><ul><li>  GenerateLegalMoves(); </li></ul><ul><li>  while (MovesLeft()) { </li></ul><ul><li>   MakeNextMove(); </li></ul>   val = -AlphaBeta(depth - 1, -beta, -alpha);    UnmakeMove();    if (val >= beta) {     return beta;    }    if (val > alpha) {     alpha = val;    }   }   return alpha; }
  26. 26. 3.9 Quiescence Search <ul><li>我们的目光总是太短浅 </li></ul><ul><li>在前面我给你们看的伪代码中,每一步棋都搜索到一个固定的深度,这个深度被称为 “ 水平线 ” ( Horizon ) 。对于看到水平线以内会发生的威胁,这个方法非常有效,但是它显然不能检查到水平线以后的威胁 ,就无法作出防御,而且只是简单地忽略那些遥远的威胁。这种现象称为 “ 水平线效应 ” (Horizon Effect) 。 </li></ul><ul><li>增强我们的目光 </li></ul><ul><li>“ 静态搜索” (Quiescence Search) 的思想是,到达主搜索的水平线后,用一个搜索只展开吃子 ( 有时是吃子加将军 ) 的着法。 </li></ul>
  27. 27. 3.9 Quiescence Search <ul><li>// 主 Alpha-Beta 搜索中, </li></ul><ul><li>// 原来出现 “ eval()” 的地方现在调用这个函数 </li></ul><ul><li>quiesce(int alpha, int beta) { </li></ul><ul><li>  int score = eval(); </li></ul><ul><li>  if (score >= beta) { </li></ul><ul><li>   return score; </li></ul><ul><li>  } </li></ul><ul><li>产生吃子着法 </li></ul><ul><li>  for ( 每个吃子着法 m) { </li></ul><ul><li>  执行着法 m; </li></ul><ul><li>   score = -quiesce(-beta,-alpha); </li></ul><ul><li>  撤消着法 m; </li></ul>   if (score >= alpha) {     alpha = score;     if (score >= beta) {      break;     }    } }   return score; }
  28. 28. 3.10 其它搜索算法 <ul><li>六脉神剑 :History Heuristics </li></ul><ul><li>一个着法 , 可能总是好 / 坏着法 . 好着法比如中炮 , 比如卧槽马 , 比如肋车 , 比如鬼卒 . 坏着法比如窝心马 , 比如羊角士 . </li></ul><ul><li>葵花宝典 :Multi Prob Cut </li></ul><ul><li>一个可以将搜索层数提高一个数量级的算法 , 曾将仅能搜索 10 层左右的黑白棋程序提升到可搜索 20 多层 . 但此算法参数很多 , 难以调试 , 要求有非常客观准确的估值函数 , 开发人员必须有很强的棋类知识 . 是目前战胜人类的唯一看得见的道路 , 但还没有人能够将它移植到国际象棋和中国象棋上 . </li></ul>
  29. 29. 3.11 我们可以做到怎么样? -75% Null-Move -25% Iterative Deepening -50% Transposition Table -10% PVS -35% 位行位列 效果 技术名称
  30. 30. 4) 其它话题1 <ul><li>循环检测 </li></ul><ul><li>估值函数 </li></ul><ul><li>开局库和残局库 </li></ul><ul><li>自我学习 </li></ul><ul><li>还有一些东西 : </li></ul><ul><li>MTD(f) 算法 </li></ul><ul><li>单步延伸 </li></ul>
  31. 31. 4) 其它话题2 <ul><li>1.int 并不总是 32 位的 , 如果你想要一个始终 32 位的 整型变量 , 请使用 long </li></ul><ul><li>2. 无符号整型比有符号的要快 </li></ul><ul><li>3. 移位要注意有符号和无符号的差别 </li></ul><ul><li>4. 不要在象棋程序里使用乘法和除法 </li></ul><ul><li>5. 尽量使用位操作 , 不要使用浮点数 , 如果开发 32 位系统上的象棋程序 , 尽可能用 unsigned long </li></ul><ul><li>6. 尽量设计不使用循环变量的循环 , 多用点内存 问题不大 </li></ul><ul><li>7. 你可以做的事不要叫电脑做 </li></ul><ul><li>8. 把一切都准备好 </li></ul>
  32. 32. 结束语 <ul><li>我有一个梦想… </li></ul><ul><li>这是一个非常吸引人的课题 </li></ul><ul><li>还有很多技术没有讲…… </li></ul><ul><li>大家一起学习 , 我的 popo:laiyonghao </li></ul><ul><li>谢谢大家 ! </li></ul>

×