/// <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); }