/// <summary> /// Expands a selected node, if possible. /// </summary> /// <param name="id">The selected node.</param> /// <param name="tree">The game tree object.</param> /// <returns></returns> private static int Expand(int id, GameTree tree) { // Get the node object that corresponds with the input ID. GameTreeNode node = tree.GetNode(id); // If the node has at least one unplayed child, is not a dead end, and is not a goal state. if (node.Unplayed.Count > 0 && !node.DeadEnd && !node.IsGoal) { // Choose an unplayed child at random. GameTreeEdge outgoing = node.Unplayed.PickRandom <GameTreeEdge>(); // Remove the child from the list of unplayed edges. node.Unplayed.Remove(outgoing); // Create the node object and store the child's ID. int child = tree.CreateNode(node.Domain, node.Problem, outgoing).ID; // Save the parent node to disk. tree.SetNode(node); // Return the child ID. return(child); } // Return the leaf node. return(node.ID); }
/// <summary> /// The base constructor. /// </summary> /// <param name="domain">The base domain object.</param> /// <param name="problem">The base problem object.</param> /// <param name="path">The path used to save files to disk.</param> public GameTree(Domain domain, Problem problem, string path) { // Store the domain, problem, and path. this.domain = domain; this.problem = problem; this.path = path; // Store the player's name. player = problem.Player.ToLower(); // Get and store the NPC names. npcs = GetNPCs(); // Establish the turn order. turnOrder = GetTurnOrder(); // Create a new hashtable to store tree edges. tree = new Hashtable(); // Initialize the session counters. nodeCounter = 0; totalPlays = 0; lowestDepth = 0; goalStateCount = 0; deadEndCount = 0; totalWins = 0; totalLosses = 0; // Create the tree's root node. GameTreeNode root = CreateNode(domain, problem, null); }
/// <summary> /// Calculate and return the interval of the current node. /// </summary> /// <param name="node">The current node object.</param> /// <returns></returns> public double GetInterval(GameTreeNode node) { // If the node has been played before and is not a dead end. if (node.TimesPlayed > 0 && !node.DeadEnd) { // Calculate and return the node's interval. return(((double)node.Wins / (double)node.TimesPlayed) + Math.Sqrt((2 * Math.Log(TotalPlays)) / (double)node.TimesPlayed)); } // Otherwise, return the error value. return(-1); }
/// <summary> /// Creates a new node object. /// </summary> /// <param name="domain">The node's domain.</param> /// <param name="problem">The node's problem.</param> /// <param name="incoming">The node's incoming edge.</param> /// <returns></returns> public GameTreeNode CreateNode(Domain domain, Problem problem, GameTreeEdge incoming) { // Create a placeholder for the new node object. GameTreeNode node = null; // If the node is a root, initialize a root node. if (incoming == null) { node = new GameTreeNode(domain, problem, 0); } // Otherwise, it is a child node... else { // Store the current node's ID in the incoming edge. incoming.Child = ++nodeCounter; // Create the new node object. node = new GameTreeNode(domain, problem, incoming, GetSuccessorState(incoming), incoming.Child, GetDepth(incoming.Parent) + 1); // Add the edge to the tree hashtable. tree.Add(incoming.Child, incoming.Parent); } // If the node is a goal state, iterate the goal state counter. if (node.IsGoal) { goalStateCount++; } // If the node is at a lower depth than the previous record holder, update the depth counter. if (node.Depth > lowestDepth) { lowestDepth = node.Depth; } // Find and store the node's outgoing edges. node.Outgoing = GetOutgoingEdges(node, GetCurrentTurn(node)); // Iterate through the node's outgoing edges. foreach (GameTreeEdge edge in node.Outgoing) { // And add each of them to the unplayed collection. node.Unplayed.Add(edge); } // Save the current node to disk. SetNode(node); // Return the current node object. return(node); }
/// <summary> /// Returns a list of outgoing edges for a given node. /// </summary> /// <param name="node">The node object.</param> /// <param name="actor">The name of the current actor.</param> /// <returns>A list of outgoing edges.</returns> public List <GameTreeEdge> GetOutgoingEdges(GameTreeNode node, string actor) { // Initialize the list of outgoing edges. List <GameTreeEdge> outgoing = new List <GameTreeEdge>(); // Iterate through the actions enabled for the input actor in the current node's state. foreach (Operator action in StateSpaceTools.GetActions(actor, node.Domain, node.Problem, node.State)) { // Add an outgoing edge for each of these actions to the list. outgoing.Add(new GameTreeEdge(action, node.ID)); } // Return the list of outgoing edges. return(outgoing); }
/// <summary> /// Simulates a playout from the current node. /// </summary> /// <param name="id">The current node's ID.</param> /// <returns>Whether the playout was won or lost.</returns> public bool Simulate(int id) { // Read the node's object from disk. GameTreeNode node = GetNode(id); // Iterate the total plays. totalPlays++; // Initialize a dead end check. bool check = false; // If the node is not a dead end before it is played, we need to check. if (!node.DeadEnd) { check = true; } // Play a game and store the result. bool result = node.Play(); // Store a win. if (result) { totalWins++; } // Otherwise, store a loss. else { totalLosses++; } // If we found a new dead end, record it. if (check && node.DeadEnd) { deadEndCount++; } // Save the node to disk. SetNode(node); // Return the result. return(result); }
/// <summary> /// Selects a leaf node to expand. /// </summary> /// <param name="id">The ID of the current node.</param> /// <param name="tree">The game tree object.</param> /// <returns></returns> private static int Select(int id, GameTree tree) { // Get the node object that corresponds to the current ID. GameTreeNode node = tree.GetNode(id); // If the node has not been played before, has at least one outgoing edge, is not a dead end, and is not a goal state... if (node.Unplayed.Count == 0 && node.Outgoing.Count > 0 && !node.DeadEnd && !node.IsGoal) { // Create a variable to store the child with the highest interval. int highestChild = -1; // If it's not the player's turn... if (!tree.TurnOrder[node.Depth % tree.TurnOrder.Count].Equals(tree.Player)) { // Set the highest child to be the first child. highestChild = node.Outgoing[0].Child; // Loop through every remaining child. for (int i = 1; i < node.Outgoing.Count; i++) { // If the current child has a higher interval than the stored child. if (tree.GetInterval(node.Outgoing[i].Child) > tree.GetInterval(highestChild)) { // Store the current child. highestChild = node.Outgoing[i].Child; } } } // If it's the player's turn, chose an outgoing edge at random. else { highestChild = node.Outgoing.PickRandom <GameTreeEdge>().Child; } // Recursively call this method with the selected child. return(Select(highestChild, tree)); } // Return the leaf node. return(node.ID); }
/// <summary> /// Propagates a roll out's result back through the tree. /// </summary> /// <param name="result">Whether the roll out resulted in a win or loss.</param> /// <param name="node">The current node.</param> /// <param name="tree">The game tree object.</param> private static void Propagate(bool result, int node, GameTree tree) { // Get the current node's parent ID. int parent = tree.GetParent(node); // Loop until we hit the root node's null parent link. while (parent != -1) { // Get the parent's node object from the game tree. GameTreeNode parentNode = tree.GetNode(parent); // Add the win/loss to the parent's node object. parentNode.AddResult(result); // Save the parent's node object to disk. tree.SetNode(parentNode); // Set the parent ID to the grandparent ID. parent = tree.GetParent(parent); } }
/// <summary> /// Write a node object to disk. /// </summary> /// <param name="node">The node's ID.</param> public void SetNode(GameTreeNode node) { // Given the path and ID, write the node object to disk. BinarySerializer.SerializeObject <GameTreeNode>(path + node.ID, node); }
/// <summary> /// Get the current turn taker. /// </summary> /// <param name="node">The current node.</param> /// <returns>The name of the character who gets to act.</returns> private string GetCurrentTurn(GameTreeNode node) { // Returns the character who gets to act at this level. return(turnOrder[node.Depth % turnOrder.Count]); }