/*! 负极大值搜索,与Minimax算法相比,算法描述更简洁一些 * 这种搜索拿到实战中是不可行的,只是理论上的一个实验,计算5层都要用42秒。 * \param depth 搜索深度 * \return 评估值 */ private int NegaMax(int depth) { /* 在着法生成中有将军的判断,这里就不需要再进行判断了。否则还要进行终止局面的判断。 * 是不是还有其它终止局面?现在还不得知。 */ if (depth == 0) { return(Evaluator.EvaluateAllWithSide(board)); } int bestScore = Evaluator.MIN_EVAL_VALUE + (MaxSearchDepth - depth); //这种写法可以搜索深度最短的杀着 // 着法生成中要进行将军的判断,也就是轮到红方走棋时,红方的走完后,帅不能被将军 // 在终止局面时,即被将死的局面时,这个countMove返回0 Move[] moveList = new Move[200]; int countMoves = MoveGenerator.GenAllMoveList(board, moveList); for (int i = 0; i < countMoves; i++) { board.MakeMove(moveList[i]); int score = -NegaMax(depth - 1); board.UnmakeMove(moveList[i]); bestScore = (score > bestScore) ? score : bestScore; } return(bestScore); }
/// <summary> /// 输出开局库中元素的散列分布图,用图示的办法来查看散列的效果 /// 2048行、2048列,2^11 * 2^11 * 4 = 16M 正好是16M个盘面情况 /// 每4个元素为一组,未占用是白色,占用1个是黄色,占用2个是绿色,全部占用(3个)是蓝色 /// 通过输出的位图可以看到散列效果还是不错的 /// </summary> //public void SaveHashPicture(string filename) //{ // using (System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(2048, 2048, System.Drawing.Imaging.PixelFormat.Format24bppRgb)) // { // fs.Seek(0, SeekOrigin.Begin); // for (int h = 0; h < 2048; h++) // for (int w = 0; w < 2048; w++) // { // int count = 0; // for (int i = 0; i < 4; i++) // { // //从文件中取出6个字节,前4个字节是zobrist值,后2字节是相同盘面统计数 // byte[] bytes6 = new byte[6]; // fs.Read(bytes6, 0, 6); // int zobristInOpening = BitConverter.ToInt32(bytes6, 0); //前4字节 // //countSameBoard = BitConverter.ToUInt16(bytes6, 4); //后2个字节 // if (zobristInOpening != 0) // { // ++count; // } // } // if (count == 0) // bmp.SetPixel(w, h, System.Drawing.Color.White); // else if (count == 1) // bmp.SetPixel(w, h, System.Drawing.Color.Yellow); // else if (count == 2) // bmp.SetPixel(w, h, System.Drawing.Color.Green); // else // bmp.SetPixel(w, h, System.Drawing.Color.Blue); // } // bmp.Save(filename, System.Drawing.Imaging.ImageFormat.Png); // } //} public Move FindBestMove(Board board) { Move maxUsedMove = new Move(0, 0, 0, 0); // 使用率最高的着法 int maxCount = 0; // 保存使用得最多的次数 // 在所有的可行的着法里查找一遍,可以发现这种办法效率有点低 Move[] movelist = new Move[200]; MoveGenerator.GenAllMoveList(board, movelist); foreach (Move m in movelist) { UInt16 countSameBoard; board.MakeMove(m); FindBoardHash(Zobrist.ZoristHash(board), out countSameBoard); //if (countSameBoard != 0) // Console.WriteLine(m + ": " + countSameBoard); if (countSameBoard > maxCount) { maxCount = countSameBoard; maxUsedMove = new Move(m); } board.UnmakeMove(m); // 要恢复原始的盘面 } return(maxUsedMove); }
/// <summary> /// 最简单的AlphaBeta剪枝搜索,没有用置换表。 /// 比MinMax算法要好多了,C++64位计算5层只需0.8秒,6层为6.8秒,7层为151秒。 /// C#版本5层要10秒 /// 把搜索到的最好的着法保存在BestMove中。 /// TODO: 如何能够结束计算??? /// </summary> /// <param name="depth">当前搜索深度</param> /// <param name="alpha">初始值可以设置为MIN_EVAL_VALUE</param> /// <param name="beta">初始值可以设置为MAX_EVAL_VALUE</param> /// <returns>评估值</returns> private int NegaAlphaBeta(int depth, int alpha, int beta) { /* 要进行被将死情况的检测 * if (board.BlackTurn && board.IsRedKingCheckmated()) * { * return int.MinValue; * } * else if (board.RedTurn && board.IsBlackKingCheckmated()) * { * return int.MaxValue; * } */ if (depth == 0) { return(Evaluator.EvaluateAllWithSide(board)); } Move[] moveList = new Move[200]; int countMove = MoveGenerator.GenAllMoveList(board, moveList); if (countMove == 0 && depth == 0) { bestMove = new Move(0, 0, 0, 0); } int bestScore = Evaluator.MIN_EVAL_VALUE + (MaxSearchDepth - depth); for (int i = 0; i < countMove; i++) { board.MakeMove(moveList[i]); int max_alpha_bestscore = (alpha > bestScore) ? alpha : bestScore; int score = -NegaAlphaBeta(depth - 1, -beta, -max_alpha_bestscore); board.UnmakeMove(moveList[i]); if (depth == 0 && engine != null) { engine.Output.WriteLine("info depth 0 score " + score + " pv " + moveList[i]); } if (score > bestScore) { bestScore = score; if (depth == 0) { bestMove = moveList[i]; } if (bestScore >= beta) { return(bestScore); } } } return(bestScore); }
/// <summary> /// 极小极大值搜索 /// 这种搜索拿到实战中是不可行的,只是理论上的一个实验,用C++语言计算5层都要用42秒。 /// </summary> /// <param name="depth">当前搜索深度</param> /// <param name="maxDepth">最大搜索深度</param> /// <returns>评估值</returns> private int MiniMax(int depth) { /* 要进行将死的判断 * if (board.BlackTurn && board.IsRedKingCheckmated()) * { * return int.MinValue; * } * else if (board.RedTurn && board.IsBlackKingCheckmated()) * { * return int.MaxValue; * } */ if (depth == 0) { return(Evaluator.EvaluateAll(board)); } int bestScore = board.IsRedTurn ? Evaluator.MIN_EVAL_VALUE + (MaxSearchDepth - depth) : Evaluator.MAX_EVAL_VALUE - (MaxSearchDepth - depth); Move[] moveList = new Move[200]; // 在终止局面时,即被将死的局面时,这个countMove返回0 int countMove = MoveGenerator.GenAllMoveList(board, moveList); for (int i = 0; i < countMove; i++) { board.MakeMove(moveList[i]); int score = MiniMax(depth - 1); board.UnmakeMove(moveList[i]); if (board.IsRedTurn) { bestScore = (score > bestScore) ? score : bestScore; } else { bestScore = (score < bestScore) ? score : bestScore; } } return(bestScore); }
/// <summary> /// 主要变例搜索 /// </summary> /// <param name="depth">最底部的叶子结点为第0层</param> /// <param name="alpha"></param> /// <param name="beta"></param> /// <returns></returns> private int PVS(int depth, int alpha, int beta) { bool haveFoundPV = false; ulong boardKey = board.ZobristKey; // Zobrist.ZoristHash(board); BoardHistory[Ply] = boardKey; if (IsRepetition(boardKey, Ply, BoardHistory, HalfmoveClock[Ply])) { bool redRepCheck = true; bool blackRepCheck = true; Board b = new Board(board); for (int d = 0; d < HalfmoveClock[Ply]; d++) { if (Ply - d == 0) { break; } // TODO: 逻辑不合理的代码:关于将军的判断应该放在Board类中 MoveGenerator g = new MoveGenerator(b); if (b.IsRedTurn) { if (redRepCheck && g.IsBlackKingSafe()) { redRepCheck = false; } } else { if (blackRepCheck && g.IsRedKingSafe()) { blackRepCheck = false; } } b.UnmakeMove(MoveHistory[Ply - d]); if (board.ZobristKey == b.ZobristKey) { break; } } if (redRepCheck && blackRepCheck) { return(0); //双方都长将,平局分数 } if (redRepCheck) { return(-30000 + 100); } if (blackRepCheck) { return(30000 - 100); } return(0); // 双方都是允许着法时 } // 探查置换表 NodeOfSearch entry = transpositionTable.Probe(boardKey); // (depth=2) a // / \ // (depth=1) entry<b> c 这里的b是已经搜索过的节点,已经保存在置换表中,entry.Depth = 1 // / \ \ // (depth=0) d e [f] (当前搜索的节点是f) depth = 0 // .................. // 假设b是以前搜索过的节点,已经保存在置换表中,entry.depth=1 // 当前搜索的节点是f,depth=0,如果f与b的局面相同,由于b的评估值是经过了更深层的搜索得到的, // 所以f可以直接用b的评估值,这个结果只会好,不会差,所以应该判断entry.Depth >= depth if (entry.NodeType != NodeOfSearch.EMPTY_NODES && entry.Depth > depth) { switch (entry.NodeType) { case NodeOfSearch.PV_NODES: return(entry.Score); case NodeOfSearch.CUT_NODES: // -------------\ /-----------------\ // 评估值的范围 | | 当前搜索窗口 | //--------------+---------+-----------------+-- // entry.Score <= alpha beta if (entry.Score <= alpha) // 剪枝! { return(alpha); } //-------------------------\ // 评估值的范围 | // /-----------------+---------------\ // | 当前搜| 索窗口 | //-------+-----------------+---------------+------- // alpha entry.Score < beta if (entry.Score < beta) //调整beta即可 { beta = entry.Score; } break; case NodeOfSearch.ALL_NODES: // /-----------------\ /------------- // | 当前搜索窗口 | |评估值的范围 //------+-----------------+------+-------------- // alpha beta <= entry.Score if (beta <= entry.Score) // 剪枝! { return(beta); } // /----------------------- // | 评估值的范围 // /-----------------+---------------\ // | 当前搜| 索窗口 | //-------+-----------------+---------------+------- // alpha < entry.Score beta if (alpha < entry.Score) // 此时只要调整alpha即可 { alpha = entry.Score; } break; } } // 到达叶子节点 if (depth == 0) { int valueLeaf = Evaluator.EvaluateAllWithSide(board); NodeOfSearch nodeLeaf = new NodeOfSearch(boardKey, depth, NodeOfSearch.PV_NODES, valueLeaf); transpositionTable.RecordHash(nodeLeaf); return(valueLeaf); } int nodeType = NodeOfSearch.ALL_NODES; Move[] moveList = new Move[200]; int countMove = MoveGenerator.GenAllMoveList(board, moveList); // 无着可走,说明是终止局面,即被将死 if (countMove == 0) { int scoreEndStatus = Evaluator.MIN_EVAL_VALUE + Ply; NodeOfSearch nodeEnd = new NodeOfSearch(boardKey, depth, NodeOfSearch.PV_NODES, scoreEndStatus); transpositionTable.RecordHash(nodeEnd); return(scoreEndStatus); } // 利用了置换表中的历史评估数据,进行着法排序 // 局面"9/4a4/3k5/3N5/3N5/r8/9/9/9/4K4 w" // 用迭代加深算法来测试效果:迭代加深计算到第8层 // DEBUG // 不排序时1.7秒,探查并对着法排序时:17秒,代价很大 // Release // 不排序时0.7秒,探查并对着法排序时:7秒 // if(depth == 0) // SortMovelist(moveList, countMove); Move bestMove = null; for (int i = 0; i < countMove; i++) { ++Ply; // 胜利局面中需要这个变量, -MAX + ply MoveHistory[Ply] = moveList[i]; HalfmoveClock[Ply] = moveList[i].Irreversible ? 0 : HalfmoveClock[Ply - 1] + 1; board.MakeMove(moveList[i]); int score; if (haveFoundPV) { score = -PVS(depth - 1, -alpha - 1, -alpha); if ((score > alpha) && (score < beta)) { // 检查失败 score = -PVS(depth - 1, -beta, -alpha); } } else { score = -PVS(depth - 1, -beta, -alpha); } board.UnmakeMove(moveList[i]); --Ply; if (score >= beta) { //PrintDebugInfo("发生剪枝!bestScore >= beta: " + bestScore + " >= " + beta); NodeOfSearch nodeBeta = new NodeOfSearch(boardKey, depth, NodeOfSearch.CUT_NODES, beta, moveList[i]); transpositionTable.RecordHash(nodeBeta); return(beta); } if (score > alpha) { // 这时只是记录alpha值的变化情况,并不写置换表 nodeType = NodeOfSearch.PV_NODES; alpha = score; bestMove = moveList[i]; //PrintDebugInfo("修改alpha: " + alpha); } if (engine != null && stopWatch.ElapsedMilliseconds > engine.timeLimit) { break; } } NodeOfSearch node = new NodeOfSearch(boardKey, depth, nodeType, alpha, bestMove); transpositionTable.RecordHash(node); return(alpha); }
/********************************************************** * 带置换表的Alphabeta搜索算法的示例代码供参考: * int AlphaBeta(int depth, int alpha, int beta) { * int hashf = hashfALPHA; * if ((val = ProbeHash(depth, alpha, beta)) != valUNKNOWN) { * // 【valUNKNOWN必须小于-INFINITY或大于INFINITY,否则会跟评价值混淆。】 * return val; * } * if (depth == 0) { * val = Evaluate(); * RecordHash(depth, val, hashfEXACT); * return val; * } * GenerateLegalMoves(); * while (MovesLeft()) { * MakeNextMove(); * val = -AlphaBeta(depth - 1, -beta, -alpha); * UnmakeMove(); * if (val >= beta) { * RecordHash(depth, beta, hashfBETA); * return beta; * } * if (val > alpha) { * hashf = hashfEXACT; * alpha = val; * } * } * RecordHash(depth, alpha, hashf); * return alpha; * } *****************************************************************/ /// <summary> /// /// </summary> /// <param name="depth">最底部的叶子结点为第0层</param> /// <param name="alpha"></param> /// <param name="beta"></param> /// <returns></returns> private int NegaAlphaBetaTT(int depth, int alpha, int beta) { ulong boardKey = board.ZobristKey; // Zobrist.ZoristHash(board); BoardHistory[Ply] = boardKey; if (IsRepetition(boardKey, Ply, BoardHistory, HalfmoveClock[Ply])) { bool redRepCheck = true; bool blackRepCheck = true; Board b = board; for (int d = 0; d < HalfmoveClock[Ply]; d++) { // TODO: 逻辑不合理的代码:关于将军的判断应该放在Board类中 MoveGenerator g = new MoveGenerator(b); if (b.IsRedTurn) { if (redRepCheck && g.IsBlackKingSafe()) { redRepCheck = false; } } else { if (blackRepCheck && g.IsRedKingSafe()) { blackRepCheck = false; } } b.UnmakeMove(MoveHistory[Ply - d]); } if (redRepCheck && blackRepCheck) { return(0); //双方都长将,平局分数 } if (redRepCheck) { return(-30000 + 100); } if (blackRepCheck) { return(30000 - 100); } return(0); // 双方都是允许着法时 } // 探查置换表 //UInt64 code1, code2; NodeOfSearch entry = transpositionTable.Probe(boardKey); // (depth=2) a // / \ // (depth=1) entry<b> c 这里的b是已经搜索过的节点,已经保存在置换表中,entry.Depth = 1 // / \ \ // (depth=0) d e [f] (当前搜索的节点是f) depth = 0 // .................. // 假设b是以前搜索过的节点,已经保存在置换表中,entry.depth=1 // 当前搜索的节点是f,depth=0,如果f与b的局面相同,由于b的评估值是经过了更深层的搜索得到的, // 所以f可以直接用b的评估值,这个结果只会好,不会差,所以应该判断entry.Depth >= depth if (entry.NodeType != NodeOfSearch.EMPTY_NODES && entry.Depth > depth) { //PrintDebugInfo("Hash表命中!" + entry); switch (entry.NodeType) { case NodeOfSearch.PV_NODES: return(entry.Score); case NodeOfSearch.CUT_NODES: // -------------\ /-----------------\ // 评估值的范围 | | 当前搜索窗口 | //--------------+---------+-----------------+-- // entry.Score <= alpha beta if (entry.Score <= alpha) // 剪枝! { return(alpha); } //-------------------------\ // 评估值的范围 | // /-----------------+---------------\ // | 当前搜| 索窗口 | //-------+-----------------+---------------+------- // alpha entry.Score < beta if (entry.Score < beta) //调整beta即可 { beta = entry.Score; } break; case NodeOfSearch.ALL_NODES: // /-----------------\ /------------- // | 当前搜索窗口 | |评估值的范围 //------+-----------------+------+-------------- // alpha beta <= entry.Score if (beta <= entry.Score) // 剪枝! { return(beta); } // /----------------------- // | 评估值的范围 // /-----------------+---------------\ // | 当前搜| 索窗口 | //-------+-----------------+---------------+------- // alpha < entry.Score beta if (alpha < entry.Score) // 此时只要调整alpha即可 { alpha = entry.Score; } break; } } // 到达叶子节点 if (depth == 0) { int valueLeaf = Evaluator.EvaluateAllWithSide(board); // 应该肯定是EXACT节点吧? /* if(v0 <= alpha) * node.Type = NODE_ALPHA; * else if(v0 >= beta) * node.Type = NODE_BETA; * else */ NodeOfSearch nodeLeaf = new NodeOfSearch(boardKey, depth, NodeOfSearch.PV_NODES, valueLeaf); transpositionTable.RecordHash(nodeLeaf); //DEBUG(DBG_DEBUG, SPACES[depth] << "到达最大深度:" << " return score: "<< v0); return(valueLeaf); } int nodeType = NodeOfSearch.ALL_NODES; Move[] moveList = new Move[200]; int countMove = MoveGenerator.GenAllMoveList(board, moveList); // 无着可走,说明是终止局面,即被将死 if (countMove == 0) { // TODO: 杀棋分数调整 http://www.xqbase.com/computer/stepbystep5.htm // (1) 对于RecordHash:置换表项记录的杀棋步数 = 实际杀棋步数 - 置换表项距离根节点的步数; // (2) 对于ProbeHash:实际杀棋步数 = 置换表项记录的杀棋步数 + 置换表项距离根节点的步数。 int scoreEndStatus = Evaluator.MIN_EVAL_VALUE + Ply; NodeOfSearch nodeEnd = new NodeOfSearch(boardKey, depth, NodeOfSearch.PV_NODES, scoreEndStatus); transpositionTable.RecordHash(nodeEnd); return(scoreEndStatus); } // 利用了置换表中的历史评估数据,进行着法排序 // 局面"9/4a4/3k5/3N5/3N5/r8/9/9/9/4K4 w" // 用迭代加深算法来测试效果:迭代加深计算到第8层 // DEBUG // 不排序时1.7秒,探查并对着法排序时:17秒,代价很大 // Release // 不排序时0.7秒,探查并对着法排序时:7秒 // if(depth == 0) // SortMovelist(moveList, countMove); //int bestScore = Evaluator.MIN_EVAL_VALUE + depth; Move bestMove = null; for (int i = 0; i < countMove; i++) { ++Ply; // 胜利局面中需要这个变量, -MAX + ply MoveHistory[Ply] = moveList[i]; HalfmoveClock[Ply] = moveList[i].Irreversible ? 0 : HalfmoveClock[Ply - 1] + 1; board.MakeMove(moveList[i]); int score = -NegaAlphaBetaTT(depth - 1, -beta, -alpha); board.UnmakeMove(moveList[i]); --Ply; //HalfmoveClock[Ply] = moveList[i].Irreversible ? 0 : HalfmoveClock[Ply - 1] + 1; // 这里负责记录最佳着法 //if (score > bestScore) //{ // bestScore = score; // if(depth == 0) bestMove = moveList[i]; //} if (score >= beta) { //PrintDebugInfo("发生剪枝!bestScore >= beta: " + bestScore + " >= " + beta); // 这里记录refutation move NodeOfSearch nodeBeta = new NodeOfSearch(boardKey, depth, NodeOfSearch.CUT_NODES, beta, moveList[i]); transpositionTable.RecordHash(nodeBeta); return(beta); } if (score > alpha) { // alpha = bestScore; // alpha = score???? // 这时只是记录alpha值的变化情况,并不写置换表 nodeType = NodeOfSearch.PV_NODES; alpha = score; bestMove = moveList[i]; //PrintDebugInfo("修改alpha: " + alpha); } } NodeOfSearch node = new NodeOfSearch(boardKey, depth, nodeType, alpha, bestMove); transpositionTable.RecordHash(node); return(alpha); }
/// <summary> /// 最简单的AlphaBeta剪枝搜索,没有用置换表。 /// 比MinMax算法要好多了,C++64位计算5层只需0.8秒,6层为6.8秒,7层为151秒。 /// C#版本5层要18秒 /// 把搜索到的最好的着法保存在BestMove中。 /// TODO: 这里没有检测胜利局面 /// </summary> /// <param name="depth">当前搜索深度</param> /// <param name="maxDepth">最大搜索深度</param> /// <param name="alpha">初始值可以设置为MIN_EVAL_VALUE</param> /// <param name="beta">初始值可以设置为MAX_EVAL_VALUE</param> /// <returns>评估值</returns> private int AlphaBeta(int depth, int alpha, int beta) { /* 要进行被将死情况的检测 * if (board.BlackTurn && board.IsRedKingCheckmated()) * { * return int.MinValue; * } * else if (board.RedTurn && board.IsBlackKingCheckmated()) * { * return int.MaxValue; * } */ if (depth == 0) { return(Evaluator.EvaluateAll(board)); } Move[] moveList = new Move[200]; int n_moves = MoveGenerator.GenAllMoveList(board, moveList); if (!board.IsRedTurn) { for (int i = 0; i < n_moves; i++) { board.MakeMove(moveList[i]); int score = AlphaBeta(depth - 1, alpha, beta); board.UnmakeMove(moveList[i]); if (score < beta) { beta = score; if (depth == 0) { bestMove = moveList[i]; } if (alpha >= beta) { return(alpha); } } } return(beta); } else { for (int i = 0; i < n_moves; i++) { board.MakeMove(moveList[i]); int score = AlphaBeta(depth - 1, alpha, beta); board.UnmakeMove(moveList[i]); if (score > alpha) { alpha = score; if (depth == 0) { bestMove = moveList[i]; } if (alpha >= beta) { return(beta); } } } return(alpha); } }