/// <summary> /// 创建一棵博弈树 /// </summary> /// <param name="RootNode">待生成树的根节点</param> /// <param name="ChessBoard_Init">初始棋盘状态</param> /// <param name="DepthMax_Set">博弈树深度</param> /// <param name="IfShowDebugLog">是否显示调试日志,默认不显示</param> public static void CreateGameTree(EnumNowPlayer PolicyPlayer, GameTreeNode RootNode, ChessBoard ChessBoard_Init, int DepthMax_Set, bool IfShowDebugLog = false) { try { Exception E = new Exception("最大深度设定错误!请设置为偶数!"); if (DepthMax_Set % 2 != 0)//必须是偶数 { throw E; } } catch (Exception e) { throw; } DepthMax = DepthMax_Set - 1; RootNode.PolicyPlayer = PolicyPlayer; if (SearchFrameWork == Enum_GameTreeSearchFrameWork.MinMax) { RootNode.ExpandNode_MinMax(ChessBoard_Init, RootNode);//3W数量级节点数 double MaxScore = -1000; foreach (GameTreeNode GTN in RootNode.SonNode) { if (MaxScore < GTN.score) { MaxScore = GTN.score; RootNode.NodePlayer = GTN.NodePlayer; RootNode.NodeAction = GTN.NodeAction; RootNode.score = MaxScore; } } } else { RootNode.NodeHashCode = GameTreeNode.InitChessBoardHashCode; RootNode.ExpandNode_ABPruning(ChessBoard_Init, RootNode, GameTreeNode.IfUseTanslationTable); double MaxScore = -1000; foreach (GameTreeNode GTN in RootNode.SonNode) { if (MaxScore < GTN.beta) { MaxScore = GTN.beta; RootNode.NodePlayer = GTN.NodePlayer; RootNode.NodeAction = GTN.NodeAction; RootNode.score = MaxScore; RootNode.NodeHashCode = GTN.NodeHashCode; } } InitChessBoardHashCode = RootNode.NodeHashCode; } if (IfShowDebugLog) { PrintGameTree(RootNode); } }
/// <summary> /// 计算博弈树节点总数量,用于测试剪枝性能 /// </summary> /// <param name="NowNode">博弈树根节点</param> public static void CalGameTreeNodeNum(GameTreeNode NowNode) { if (NowNode.SonNode.Count <= 0) { return; } foreach (GameTreeNode Son in NowNode.SonNode) { NodeNum++; CalGameTreeNodeNum(Son); } }
public static int DepthMax = 1000;///博弈树最大深度 /// <summary> /// 以极大极小搜索框架生成博弈树 /// </summary> /// <param name="ThisChessBoard">当前棋盘状态</param> /// <param name="ThisNode">当前博弈树节点</param> public void ExpandNode_MinMax(ChessBoard ThisChessBoard, GameTreeNode ThisNode) { ///暂存一些量以便恢复 EnumNowPlayer PlayerSave = NowQuoridor.ReversePlayer(ThisNode.NodePlayer); NowQuoridor.Player_Now = PlayerSave; List <QuoridorAction> QABuff = NowQuoridor.ActionList; QABuff = NowQuoridor.CreateActionList(ThisChessBoard, GameTreePlayer , ThisNode.NodeAction.ActionCheckResult.P1Distance , ThisNode.NodeAction.ActionCheckResult.P2Distance); foreach (QuoridorAction QA in QABuff) { #region 保存棋盘状态 ChessBoard ChessBoardBuff = new ChessBoard(); ChessBoard.SaveChessBoard(ref ChessBoardBuff, ThisChessBoard); #endregion #region 模拟落子 string Hint = NowQuoridor.QuoridorRule.Action(ref ThisChessBoard, QA.ActionPoint.X, QA.ActionPoint.Y, QA.PlayerAction); try { if (Hint != "OK") { Exception e = new Exception(); } } catch (Exception) { throw; } #endregion if (ThisNode.depth <= DepthMax) { CreateNewSon(ThisNode, new GameTreeNode(QA , PlayerSave, ThisNode.depth + 1, ThisNode.alpha, ThisNode.beta, ThisNode.beta)); ExpandNode_MinMax(ThisChessBoard, ThisNode.SonNode.Last()); } else { CreateNewSon(ThisNode, new GameTreeNode(QA , PlayerSave, ThisNode.depth + 1, QA.WholeScore, QA.WholeScore, QA.WholeScore)); } #region 恢复棋盘状态 ChessBoard.ResumeChessBoard(ref ThisChessBoard, ChessBoardBuff); #endregion } if (ThisNode.NodePlayer == NowQuoridor.PlayerBuff)//MIN层 { double minvalue = 99999; foreach (GameTreeNode Son in ThisNode.SonNode) { if (Son.score < minvalue) { minvalue = Son.score; ThisNode.score = minvalue; } } } else //MAX层 { double maxvalue = -10000; foreach (GameTreeNode Son in ThisNode.SonNode) { if (Son.score > maxvalue) { maxvalue = Son.score; ThisNode.score = maxvalue; if (ThisNode.depth == 0)//根节点层 { ThisNode.NodeAction = Son.NodeAction; ThisNode.NodePlayer = Son.NodePlayer; } } } } }
/// <summary> /// 给该节点添加新的子节点 /// </summary> /// <param name="NewNode">待添加的子节点</param> public void CreateNewSon(GameTreeNode FatherNode, GameTreeNode NewNode) { FatherNode.SonNode.Add(NewNode); }
/// <summary> /// 获得博弈树节点在TreeView控件上应有的Text属性字符串 /// </summary> /// <param name="NowNode">当前待生成的节点</param> /// <param name="IfShowAction">是否显示动作信息</param> /// <param name="IfShowActionScore">是否显示动作的评分信息</param> /// <returns></returns> public static string GetGameTreeNodeViewText(GameTreeNode NowNode, bool IfShowAction = true, bool IfShowActionScore = false) { string SonTextbuff = "D:"; SonTextbuff += (NowNode.depth + RootDepth).ToString() + " P"; switch (NowNode.NodePlayer) { case EnumNowPlayer.Player1: SonTextbuff += "1"; break; case EnumNowPlayer.Player2: SonTextbuff += "2"; break; default: SonTextbuff += "Error"; break; } if (IfShowAction) { switch (NowNode.NodeAction.PlayerAction) { case NowAction.Action_PlaceVerticalBoard: SonTextbuff += ((NowNode.NodeAction.ActionPoint.X) * 8 + NowNode.NodeAction.ActionPoint.Y + 1).ToString() + "点和" + ((NowNode.NodeAction.ActionPoint.X + 1) * 8 + NowNode.NodeAction.ActionPoint.Y + 1).ToString() + "点;"; break; case NowAction.Action_PlaceHorizontalBoard: SonTextbuff += ((NowNode.NodeAction.ActionPoint.X) * 8 + NowNode.NodeAction.ActionPoint.Y + 1).ToString() + "点和" + ((NowNode.NodeAction.ActionPoint.X) * 8 + NowNode.NodeAction.ActionPoint.Y + 1 + 1).ToString() + "点"; break; case NowAction.Action_Move_Player1: SonTextbuff += ((NowNode.NodeAction.ActionPoint.X) * 8 + NowNode.NodeAction.ActionPoint.Y + 1).ToString() + "点"; break; case NowAction.Action_Move_Player2: SonTextbuff += ((NowNode.NodeAction.ActionPoint.X) * 8 + NowNode.NodeAction.ActionPoint.Y + 1).ToString() + "点"; break; case NowAction.Action_Wait: SonTextbuff += "Error"; break; default: SonTextbuff += "Error"; break; } } SonTextbuff += " A:"; Int64 Score = Convert.ToInt64(NowNode.alpha * 100.0); string ScoreStr = (Convert.ToDouble(Score) / 100.0).ToString(); SonTextbuff += ScoreStr; SonTextbuff += ",B:"; Score = Convert.ToInt64(NowNode.beta * 100.0); ScoreStr = (Convert.ToDouble(Score) / 100.0).ToString(); SonTextbuff += ScoreStr.ToString(); SonTextbuff += ",S:"; Score = Convert.ToInt64(NowNode.score * 100.0); ScoreStr = (Convert.ToDouble(Score) / 100.0).ToString(); SonTextbuff += ScoreStr; if (IfShowActionScore) { SonTextbuff += " SS:"; SonTextbuff += NowNode.NodeAction.SelfScore.ToString(); SonTextbuff += " OS:"; SonTextbuff += NowNode.NodeAction.OpponentScore.ToString(); SonTextbuff += " WS:"; Score = Convert.ToInt64(NowNode.NodeAction.WholeScore * 100.0); ScoreStr = (Convert.ToDouble(Score) / 100.0).ToString(); SonTextbuff += ScoreStr.ToString(); } # region 显示该动作评分 # endregion if (GameTreeNode.IfUseTanslationTable)
/// <summary> /// 以Alpha-Beta剪枝框架并使用TranslationTable生成博弈树 /// </summary> /// <param name="ThisChessBoard">当前棋盘状态</param> /// <param name="ThisNode">当前博弈树节点</param> public void ExpandNode_ABPruning(ChessBoard ThisChessBoard, GameTreeNode ThisNode, bool IfUseTT = true) { if (IfUseTT) { bool IfInTT = false; TranslationTable.GameTreeNodeForHash HashNode1 = new TranslationTable.GameTreeNodeForHash(); HashNode1 = NodeTranslationTable.Search(ThisNode.NodeHashCode, ref IfInTT); if (ThisNode.depth != 0 && IfInTT && ThisNode.depth + RootDepth <= HashNode1.depth) { ThisNode.alpha = HashNode1.alpha; ThisNode.beta = HashNode1.beta; return; } } ///暂存一些量以便恢复 EnumNowPlayer PlayerSave = NowQuoridor.ReversePlayer(ThisNode.NodePlayer); NowQuoridor.Player_Now = PlayerSave; List <QuoridorAction> QABuff = NowQuoridor.ActionList; //QABuff = NowQuoridor.CreateActionList_ALL(ThisChessBoard, ThisNode.P1Distance, ThisNode.P2Distance); QABuff = NowQuoridor.CreateActionList(ThisChessBoard, GameTreePlayer , ThisNode.NodeAction.ActionCheckResult.P1Distance , ThisNode.NodeAction.ActionCheckResult.P2Distance); foreach (QuoridorAction QA in QABuff) { #region 保存棋盘状态 ChessBoard ChessBoardBuff = new ChessBoard(); ChessBoard.SaveChessBoard(ref ChessBoardBuff, ThisChessBoard); #endregion #region 模拟落子 string Hint = NowQuoridor.QuoridorRule.Action(ref ThisChessBoard, QA.ActionPoint.X, QA.ActionPoint.Y, QA.PlayerAction); try { if (Hint != "OK") { Exception e = new Exception(); } } catch (Exception) { throw; } if (QA.PlayerAction == NowAction.Action_PlaceHorizontalBoard || QA.PlayerAction == NowAction.Action_PlaceVerticalBoard) { if (PlayerSave == EnumNowPlayer.Player1) { ThisChessBoard.NumPlayer1Board -= 2; } else { ThisChessBoard.NumPlayer2Board -= 2; } } #endregion if (ThisNode.depth <= DepthMax) { CreateNewSon(ThisNode, new GameTreeNode(QA , PlayerSave, ThisNode.depth + 1, ThisNode.alpha, ThisNode.beta, ThisNode.score)); if (IfUseTT) { long HashCodeBuff = NodeTranslationTable.NodeGetHashCode(ThisNode.NodeHashCode, QA, ChessBoardBuff);//ThisChessBoard已变,不能作为原棋盘传入,只能上一步的棋盘ChessBoardBuff ThisNode.SonNode.Last().NodeHashCode = HashCodeBuff; } ExpandNode_ABPruning(ThisChessBoard, ThisNode.SonNode.Last(), IfUseTT); } else { CreateNewSon(ThisNode, new GameTreeNode(QA , PlayerSave, ThisNode.depth + 1, ThisNode.alpha, QA.WholeScore, QA.WholeScore)); } ChessBoard.ResumeChessBoard(ref ThisChessBoard, ChessBoardBuff); #region Min层 if (ThisNode.NodePlayer == PolicyPlayer) { if (ThisNode.SonNode.Last().alpha < ThisNode.beta) { ThisNode.beta = ThisNode.SonNode.Last().alpha; ThisNode.score = ThisNode.SonNode.Last().alpha; } } #endregion #region Max层 else { if (ThisNode.SonNode.Last().beta > ThisNode.alpha) { ThisNode.alpha = ThisNode.SonNode.Last().beta; ThisNode.score = ThisNode.SonNode.Last().beta; } } #endregion if (ThisNode.depth <= DepthMax && ThisNode.beta <= ThisNode.alpha)//剪枝 { #region 存入置换表 if (IfUseTT) { /*剪枝break前这个时刻该节点已经遍历完毕,可以加入置换表*/ TranslationTable.GameTreeNodeForHash HashNodeBuff = new TranslationTable.GameTreeNodeForHash(); HashNodeBuff.alpha = ThisNode.alpha; HashNodeBuff.beta = ThisNode.beta; HashNodeBuff.depth = ThisNode.depth + RootDepth; NodeTranslationTable.Add(ThisNode.NodeHashCode, HashNodeBuff); } #endregion break; } } #region 存入置换表 if (IfUseTT) { /*遍历完整个动作列表后可以加入置换表*/ TranslationTable.GameTreeNodeForHash HashNodeBuff2 = new TranslationTable.GameTreeNodeForHash(); HashNodeBuff2.alpha = ThisNode.alpha; HashNodeBuff2.beta = ThisNode.beta; HashNodeBuff2.depth = ThisNode.depth + RootDepth; NodeTranslationTable.Add(ThisNode.NodeHashCode, HashNodeBuff2); } #endregion }