public static List<List<MoveDesc>> Run(AnalysisNode starting_node, INodeGenerator node_generator, uint fullmove_depth) { Debug.Assert(starting_node != null && starting_node.Position != null); Debug.Assert(node_generator != null); Debug.Assert(fullmove_depth > 0); List<List<MoveDesc>> result = null; // Run recursive function, which finds all forced mates // This call erases all nodes (except root), which do not // lead to a forced checkmate. So, if there is (are) forced mate(s) // then the remaining tree nodes give us an answer if (FindMateRecursive(starting_node, node_generator, fullmove_depth) && !starting_node.IsLeaf) { result = new List<List<MoveDesc>>(); foreach (var child in starting_node.Children) { CollectAllVariations(child, new List<MoveDesc>(), result); } Debug.Assert(result.Count > 0); } return result; }
public float Evaluate(AnalysisNode node, IPersonality personality) { m_white_data.Init(node.Position); m_black_data.Init(node.Position); ulong white_good_cells = 0; ulong black_good_cells = 0; float white_value = m_white_data.m_material; float black_value = m_black_data.m_material; // TODO - if not endgame.. white_good_cells = CentralSquares; black_good_cells = CentralSquares; // TODO - create arrays for pawns, we need them anyway var white_pieces = node.Position.GetPieces(Players.White); var black_pieces = node.Position.GetPieces(Players.Black); var white_good_cells_count = CountGoodCells(white_good_cells, Players.White, white_pieces); var black_good_cells_count = CountGoodCells(black_good_cells, Players.Black, black_pieces); // TODO - consider here optimistic and pessimistic players white_value += 0.05f * white_good_cells_count; black_value += 0.05f * black_good_cells_count; // TODO - do more return white_value - black_value; }
// TODO - consider removing //public Fingerprint Fingerprint { get; set; } public AnalysisNode(AnalysisNode parent, Position.Position p, Move m) { Debug.Assert(p != null); Parent = parent; // Must init ParentMove before Position m_parent_move = m; Position = p; Children = null; IsWhiteToMove = p.IsWhiteToMove; }
private static bool FindMateRecursive(AnalysisNode starting_node, INodeGenerator nodes_generator, uint fullmove_depth) { Debug.Assert(fullmove_depth > 0); if (fullmove_depth == 1) { nodes_generator.GenerateAllChildren(starting_node, 1); // Release all children nodes except checkmate nodes nodes_generator.ReleaseChildren(x => { if (x.Position.IsInCheck) { nodes_generator.GenerateAllChildren(x, 1); return !x.Position.IsCheckmate; } return true; }, starting_node); } else { nodes_generator.GenerateAllChildren(starting_node, 2); foreach (var my_move in starting_node.Children) { bool is_skip = false; foreach (var her_move in my_move.Children) { if (!FindMateRecursive(her_move, nodes_generator, fullmove_depth - 1)) { // My opponent can avoid being checkmated is_skip = true; break; } } if (is_skip) { nodes_generator.ReleaseChildren(my_move); } } // Erase all children except checkmate nodes or move sequences // that lead to a force mate nodes_generator.ReleaseChildren(x => { return !x.Position.IsCheckmate && x.Children == null; }, starting_node); } return starting_node.Children != null; }
public static void Evaluate(AnalysisNode root) { for (int i = 0; i < root.Children.Count; ++i) { var child = root.Children[i]; if (child.Children != null) Evaluate(child); } var best_child = GetBestChild(root); if (best_child != null) { root.MinmaxEvaluation = best_child.MinmaxEvaluation; } }
private static void CollectAllVariations(AnalysisNode current_node, List<MoveDesc> current_parents, List<List<MoveDesc>> variations) { var new_parents = new List<MoveDesc>(current_parents.Count + 1); new_parents.AddRange(current_parents); new_parents.Add(current_node.ParentMove.ToMoveDesc(current_node.Position)); if (current_node.Children == null || current_node.Children.Count == 0) { // Leaf node variations.Add(new List<MoveDesc>(new_parents)); } else { foreach (var child in current_node.Children) { CollectAllVariations(child, new_parents, variations); } } }
public static AnalysisNode GetBestChild(AnalysisNode root) { int best_index = -1; if (root.IsWhiteToMove) { float best_evaluation = float.MinValue; // Return the child with the greatest evaluation for (int i = 0; i < root.Children.Count; ++i) { var child = root.Children[i]; if (child.IsLeaf && child.HasStaticEvaluation) { child.MinmaxEvaluation = child.StaticEvaluation; } if (child.HasMinmaxEvaluation && child.MinmaxEvaluation > best_evaluation) { best_index = i; best_evaluation = child.MinmaxEvaluation; } } } else { // Return the child with the lowest evaluation float best_evaluation = float.MaxValue; for (int i = 0; i < root.Children.Count; ++i) { var child = root.Children[i]; if (child.IsLeaf && child.HasStaticEvaluation) { child.MinmaxEvaluation = child.StaticEvaluation; } if (child.HasMinmaxEvaluation && child.MinmaxEvaluation < best_evaluation) { best_index = i; best_evaluation = child.MinmaxEvaluation; } } } return best_index == -1 ? null : root.Children[best_index]; }
/// <summary> /// Erases all children nodes which satisfy the specified condition /// WARNING: this method is not thread-safe, run it in parallel only /// for nodes in non-overlapping subtrees /// </summary> public void ReleaseChildren(Predicate<AnalysisNode> condition, AnalysisNode subtree_root) { if (subtree_root.Children == null) { return; } List<AnalysisNode> new_children = null; foreach (var child in subtree_root.Children) { if (condition(child)) { ReleaseNode(child); } else { if (new_children == null) { new_children = new List<AnalysisNode>( subtree_root.Children.Count); } new_children.Add(child); } } if (new_children == null || (new_children.Count != subtree_root.Children.Count)) { subtree_root.IsSomeChildrenErased = true; subtree_root.SetChildrenList(new_children); } }
public void UpdateBestLine(AnalysisNode root_node, bool report) { if (root_node == null || root_node.Children == null || root_node.Children.Count == 0) { m_best_line = null; Evaluation = 0; } else { m_best_line = new List<MoveDesc>(64); Evaluation = root_node.HasMinmaxEvaluation ? root_node.MinmaxEvaluation : 0f; var cur_node = MinMax.GetBestChild(root_node); while (cur_node != null) { m_best_line.Add(cur_node.GetParentMoveDesc()); cur_node = cur_node.Children != null && cur_node.Children.Count > 0 ? MinMax.GetBestChild(cur_node) : null; } // Just in case there are no evaluated nodes if (m_best_line.Count == 0) { m_best_line.Add(root_node.Children[0].GetParentMoveDesc()); } } UpdateTime(); if (report) { Factory.Instance.AnalysisStatsReporter?.Report(this); } }
private void AnalyzeChildren(AnalysisNode root, int depth_to_go) { // If this is not true, then we must continue recursively after // we have statically evaluated a grandchild node Debug.Assert(depth_to_go <= 2); m_tree.GenerateAllChildren(root, 1); for (int i = 0; i < root.Children.Count; ++i) { var child = root.Children[i]; var move = child.ParentMove; // For some kind of moves we can't say if they are so // good or not without seeing possible replies if (move.IsCapture || move.IsPromotion || child.Position.IsInCheck) { m_tree.GenerateAllChildren(child, 1); for (int j = 0; j < child.Children.Count; ++j) { var grandchild = child.Children[j]; MakeStaticEvaluation(grandchild); // Continue recursively here if depth_to_go > 2 } } else { MakeStaticEvaluation(child); if (depth_to_go > 1) { AnalyzeChildren(child, depth_to_go - 1); } } } }
// Returns best children private AnalysisNode AnalyzeFourBestChildren(AnalysisNode root, int depth_to_go) { AnalysisNode best_1 = null; AnalysisNode best_2 = null; AnalysisNode best_3 = null; AnalysisNode best_4 = null; bool is_white_to_move = root.IsWhiteToMove; for (int i = 0; i < root.Children.Count; ++i) { var child = root.Children[i]; if (depth_to_go > 1) { var best_grandchild = AnalyzeFourBestChildren(child, depth_to_go - 1); if (best_grandchild != null) child.MinmaxEvaluation = best_grandchild.MinmaxEvaluation; } else { AnalyzeChildren(child, 2); MinMax.Evaluate(child); } bool is_release_grandchildren = true; if (child.HasMinmaxEvaluation) { if (best_4 == null || MinMax.IsBetter(child.MinmaxEvaluation, best_4.MinmaxEvaluation, is_white_to_move)) { is_release_grandchildren = false; if (best_4 != null) m_tree.ReleaseChildren(best_4); best_4 = child; if (best_3 == null || MinMax.IsBetter( child.MinmaxEvaluation, best_3.MinmaxEvaluation, is_white_to_move)) { best_4 = best_3; best_3 = child; if (best_2 == null || MinMax.IsBetter( child.MinmaxEvaluation, best_2.MinmaxEvaluation, is_white_to_move)) { best_3 = best_2; best_2 = child; if (best_1 == null || MinMax.IsBetter( child.MinmaxEvaluation, best_1.MinmaxEvaluation, is_white_to_move)) { best_2 = best_1; best_1 = child; } } } } } if (is_release_grandchildren) m_tree.ReleaseChildren(child); } // loop over root's children m_tree.ReleaseChildren(x => !(ReferenceEquals(x, best_1) || ReferenceEquals(x, best_2) || ReferenceEquals(x, best_3) || ReferenceEquals(x, best_4)), root); return best_1; }
/// <summary> /// Generates all immediate children of the specified node /// WARNING: this method is not thread-safe, run it in parallel only /// for nodes in non-overlapping subtrees /// </summary> public void GenerateAllChildren(AnalysisNode parent, uint depth, bool is_restore_missing_positions = true) { Debug.Assert(parent != null && parent.Position != null); Debug.Assert(depth > 0); // Check if all moves were generated for the parent node // If not, clear the list of children and re-generate them all if (parent.IsSomeChildrenErased) { parent.SetChildrenList(null); } if (parent.Children == null) { parent.InitChildrenList(); // TODO - conside memory pooling for the list of moves var moves = new List<Move>(64); MovesGenerator.GenerateMoves(parent.Position, moves); var player_to_move = parent.Position.PlayerToMove; bool is_parent_in_check = parent.Position.IsInCheck; foreach (var m in moves) { var child_position = parent.Position.PlayMove(m); // Last check if the move is a legal move // Didn't do it earlier, as we need resulting position // for this if (!PositionValidator.IsMyMovePutsMyKingInCheck( player_to_move, m, child_position, is_parent_in_check)) { parent.Children.Add(new AnalysisNode(parent, child_position, m)); Interlocked.Increment(ref m_node_count); } } // Let the position to know whether there are legal moves parent.Position.HasLegalMoves = !parent.IsLeaf; } else if (is_restore_missing_positions) { foreach (var node in parent.Children) { if (node.Position == null) { // Position object was destroyed to free the memory // Re-create it now node.RecreatePosition(); } } } // Indicate that all children are generated and present in the collection // Unless we have generated the moves having some restrictions imposed parent.IsSomeChildrenErased = false; // Generate next levels of child nodes recursively if (depth > 1) { foreach (var node in parent.Children) { GenerateAllChildren(node, depth - 1, is_restore_missing_positions); } } if (ReferenceEquals(parent, Root)) { m_listener.RootChildrenGenerated(Root.Children); } }
// Assumes the tree is locked private void ReleaseNode(AnalysisNode subtree_root) { if (subtree_root.Children != null) { foreach (var child in subtree_root.Children) { ReleaseNode(child); } subtree_root.SetChildrenList(null); } Interlocked.Decrement(ref m_node_count); }
// Assumes that the tree is locked private void Clean() { if (Root != null) { ReleaseNode(Root); Debug.Assert(NodeCount == 0); Root = null; m_listener.RootChildrenGenerated(null); } }
public void SetRoot(Position.Position p, Move parent_move) { Clean(); if (p != null) { Root = new AnalysisNode(null, p, parent_move); m_node_count = 1; // Probably this position was not analyzed yet, and IsInCheck // is not initialized yet. Evaluate and set IsInCheck flag p.IsInCheck = PositionHelper.IsCellAttacked( new ChessboardCell(p.GetPieces(p.PlayerToMove).KingsCell), Helper.GetOtherPlayer(p.PlayerToMove), p); // For root we want to create children and grandchildren // To detect stalemate or checkmate GenerateAllChildren(Root, 2); } m_listener.RootPositionChanged(p); }
/// <summary> /// Erases all children nodes /// WARNING: this method is not thread-safe, run it in parallel only /// for nodes in non-overlapping subtrees /// </summary> public void ReleaseChildren(AnalysisNode subtree_root) { if (subtree_root.Children != null) { foreach (var child in subtree_root.Children) { ReleaseNode(child); } } subtree_root.IsSomeChildrenErased = true; subtree_root.SetChildrenList(null); }
public bool MakeChildNewRoot(ChessboardCell from, ChessboardCell to, Exports.Pieces promote_to = Exports.Pieces.NoPiece) { if (Root == null) { return false; } // Discard all root children and their subtrees, except the child // specified by the method parameters ReleaseChildren(child => { return child.ParentMove.FromCell != from.Value || child.ParentMove.ToCell != to.Value || child.ParentMove.PromoteToPiece != promote_to; }, Root); if (Root.Children.Count != 1) { Clean(); m_listener.RootPositionChanged(null); return false; } // Discard current root Interlocked.Decrement(ref m_node_count); // Set a new root Root = Root.Children[0]; m_listener.RootPositionChanged(Root.Position); GenerateAllChildren(Root, 2); return true; }
private void Run(AnalysisNode root, int depth_to_go, ulong max_node_count) { if (depth_to_go == 1) { AnalyzeFourBestChildren(root, 2); // TODO - remove Console.WriteLine("Node count: " + m_tree.NodeCount); // Update stats for the whole tree MinMax.Evaluate(m_tree.Root); m_stats.UpdateBestLine(m_tree.Root, true); } else { root?.Children.ForEach(x => { if (m_stats.NodesEvaluated < max_node_count) Run(x, depth_to_go - 1, max_node_count); }); } }
private void MakeStaticEvaluation(AnalysisNode node) { if (node.Position == null) { node.RecreatePosition(); } node.StaticEvaluation = m_data_buffer.Evaluate(node, m_stats.Personality); //node.Fingerprint = data_buffer.MakeFingerprint(); m_stats.IncNodesEvaluatedCounter(); }