public void EndGame() { ActionApplier actionApplier = GetActionApplier(out ActionApplierContext actionApplierContext, false, false, ActionApplierContext.EndGameSymbol); GameFlow flow = actionApplier.ApplyAction(new Board(), ActionApplierContext.EndGameSymbol); Assert.Equal(GameFlow.END_GAME, flow); actionApplierContext.MovementDisplayNamesResolver .Verify(mdnr => mdnr.TryResolve(Match.Create <string>(action => action == ActionApplierContext.EndGameSymbol), out movement), Times.Once); actionApplierContext.TileMover.Verify(tm => tm.TryMove(It.IsAny <Board>(), It.IsAny <Movement>(), out err), Times.Never); }
public void ActionResolved_MovementDone() { ActionApplier actionApplier = GetActionApplier(out ActionApplierContext actionApplierContext, true, true, ACTION_NAME); GameFlow flow = actionApplier.ApplyAction(new Board(), ACTION_NAME); Assert.Equal(GameFlow.KEEP_PLAYING, flow); actionApplierContext.IO.Verify(io => io.WriteLine(It.IsAny <string>(), It.IsAny <int>()), Times.Never); actionApplierContext.MovementDisplayNamesResolver .Verify(mdnr => mdnr.TryResolve(Match.Create <string>(action => action == ACTION_NAME), out movement), Times.Once); actionApplierContext.TileMover.Verify(tm => tm.TryMove(It.IsAny <Board>(), It.IsAny <Movement>(), out err), Times.Once); }
public void UnknownAction() { string unknwonAction = "SOME_UNKNOWN_ACTION"; ActionApplier actionApplier = GetActionApplier(out ActionApplierContext actionApplierContext, false, false, unknwonAction); GameFlow flow = actionApplier.ApplyAction(new Board(), unknwonAction); Assert.Equal(GameFlow.KEEP_PLAYING, flow); actionApplierContext.IO.Verify(io => io.WriteLine( Match.Create <string>(err => err.Contains(unknwonAction)), It.IsAny <int>()), Times.Once); actionApplierContext.MovementDisplayNamesResolver .Verify(mdnr => mdnr.TryResolve(Match.Create <string>(action => action == unknwonAction), out movement), Times.Once); actionApplierContext.TileMover.Verify(tm => tm.TryMove(It.IsAny <Board>(), It.IsAny <Movement>(), out err), Times.Never); }
/// <summary> /// Takes a state and returns the expected value (pre-division) of its outcome. /// </summary> /// <param name="state">The state to start in.</param> /// <param name="samples">The number of samples to take for each actino.</param> /// <param name="cloner">The means by which duplicate states may be generated.</param> /// <param name="action_enumerator">Enumerates the actions available in any given state.</param> /// <param name="updater">Takes a state and an action and applies one to the other.</param> /// <param name="state_hueristic">Determines how good a terminal state is.</param> /// <returns>Returns the expected value (pre-division) of the state's outcome.</returns> private static int Explore(S state, int samples, StateCloner <S> cloner, ActionEnumerator <A, S> action_enumerator, ActionApplier <A, S> updater, StateEvaluator <S> state_hueristic) { int ret = 0; for (int i = 0; i < samples; i++) { ret += PlayToCompletion(cloner(state), action_enumerator, updater, state_hueristic); } return(ret); }
/// <summary> /// Given a start state, performs a random sampling of it to terminal states and determines which available action is best to take. /// </summary> /// <param name="state">The state to start in.</param> /// <param name="samples">The number of samples to take for each actino.</param> /// <param name="cloner">The means by which duplicate states may be generated.</param> /// <param name="action_enumerator">Enumerates the actions available in any given state.</param> /// <param name="updater">Takes a state and an action and applies one to the other.</param> /// <param name="state_hueristic">Determines how good a terminal state is.</param> /// <returns>Returns the best action to take.</returns> public static A Search(S state, int samples, StateCloner <S> cloner, ActionEnumerator <A, S> action_enumerator, ActionApplier <A, S> updater, StateEvaluator <S> state_hueristic) { List <A> actions = new List <A>(); List <int> expected_values = new List <int>(); // Get all the actions IEnumerator <A> acts = action_enumerator(state); while (acts.MoveNext()) { actions.Add(acts.Current); } // If we have no actions, we can't do anything if (actions.Count == 0) { return(default(A)); } // If we have one action, don't waste time if (actions.Count == 1) { return(actions[0]); } // Obtain the expected value of each action foreach (A a in actions) { expected_values.Add(Explore(updater(cloner(state), a), samples, cloner, action_enumerator, updater, state_hueristic)); } // Find the best action int max = expected_values[0]; List <A> best = new List <A>(); best.Add(actions[0]); for (int i = 1; i < expected_values.Count; i++) { if (expected_values[i] > max) { max = expected_values[i]; best.Clear(); best.Add(actions[i]); } else if (expected_values[i] == max) { best.Add(actions[i]); } } return(best[rand.Next(0, best.Count)]); }
/// <summary> /// Takes a state and executes it to completion randomly and returns a value representing how good the outcome is. /// </summary> /// <param name="state">The state to start in.</param> /// <param name="action_enumerator">Enumerates the actions available in any given state.</param> /// <param name="updater">Takes a state and an action and applies one to the other.</param> /// <param name="state_hueristic">Determines how good a terminal state is.</param> /// <returns>A value representing how good a random terminal node of the given state is.</returns> private static int PlayToCompletion(S state, ActionEnumerator <A, S> action_enumerator, ActionApplier <A, S> updater, StateEvaluator <S> state_hueristic) { List <A> actions = new List <A>(); // Get all the actions IEnumerator <A> acts = action_enumerator(state); while (acts.MoveNext()) { actions.Add(acts.Current); } // If we have no actions left, we're in a terminal state if (actions.Count == 0) { return(state_hueristic(state)); } // Pick a random action and proceed further return(PlayToCompletion(updater(state, actions[rand.Next(0, actions.Count)]), action_enumerator, updater, state_hueristic)); }
/// <summary> /// Given a state and a maximum search depth, this performs a recursive search using the alpha-beta algorithm to find the optimal action to take. /// </summary> /// <param name="state">The current state.</param> /// <param name="max_depth">The maximum number of sequential actions that may be taken.</param> /// <param name="state_hueristic">The means of determining how good a state is.</param> /// <param name="action_enumerator">The means of proceeding to a new state.</param> /// <param name="updater">The means by which a state is updated with new actions.</param> /// <param name="maximising">True if the algorithm should maximise at this step and false if it should minimise.</param> /// <param name="alpha">The best maximisation value so far.</param> /// <param name="beta">The best minimisation value so far.</param> /// <returns>Returns the value of the optimal action to take in the given state.</returns> private static int RSearch(S state, int depth, StateEvaluator <S> state_hueristic, ActionEnumerator <A, S> action_enumerator, ActionApplier <A, S> updater, Maximising <S> maximising, int alpha, int beta) { // If we've reached our terminal depth, evaluate and return if (depth == 0) { return(state_hueristic(state)); } // Enumerate the available moves in the given state IEnumerator <A> actions = action_enumerator(state); // If there are no moves, we've reached a terminal state and need to evaluate it and return if (!actions.MoveNext()) { return(state_hueristic(state)); } if (maximising(state)) { int max = int.MinValue; do { max = Math.Max(max, RSearch(updater(state, actions.Current), depth - 1, state_hueristic, action_enumerator, updater, maximising, alpha, beta)); alpha = alpha > max ? alpha : max; if (alpha >= beta) { break; } }while(actions.MoveNext()); return(max); } int min = int.MaxValue; do { min = Math.Min(min, RSearch(updater(state, actions.Current), depth - 1, state_hueristic, action_enumerator, updater, maximising, alpha, beta)); beta = beta < min ? beta : min; if (alpha >= beta) { break; } }while(actions.MoveNext()); return(min); }
/// <summary> /// Given a state and a maximum search depth, this performs a search using the alpha-beta algorithm to find the optimal action to take. /// </summary> /// <param name="state">The current state.</param> /// <param name="max_depth">The maximum number of sequential actions that may be taken.</param> /// <param name="state_hueristic">The means of determining how good a state is.</param> /// <param name="action_enumerator">The means of proceeding to a new state.</param> /// <param name="updater">The means by which a state is updated with new actions.</param> /// <param name="maximizing">Determines if we are in a maximizing or minimizing state.</param> /// <returns>Returns the optimal action to take in the given state or default(A) if something went wrong.</returns> public static A Search(S state, int max_depth, StateEvaluator <S> state_hueristic, ActionEnumerator <A, S> action_enumerator, ActionApplier <A, S> updater, Maximising <S> maximising) { // Sanity check if (max_depth < 1 || state == null || state_hueristic == null || action_enumerator == null || maximising == null) { return(default(A)); } // Bookkeeping List <A> best_actions = new List <A>(); int best_val = int.MinValue; // Enumerate the available moves in the given state IEnumerator <A> actions = action_enumerator(state); // If there are no moves, we've reached a terminal state and can't do anything if (!actions.MoveNext()) { return(default(A)); } // If there's only one move (a forced move) then just take it // Note that this is only a useful case in the inital call as later one we're trying to compute the values of each move we could take if (!actions.MoveNext()) { actions.Reset(); actions.MoveNext(); return(actions.Current); } // There's more than one move, so check them all out actions.Reset(); actions.MoveNext(); // We assume we start with the maximising player do { // Check out how good the current action is int val = RSearch(updater(state, actions.Current), max_depth - 1, state_hueristic, action_enumerator, updater, maximising, best_val, int.MaxValue); // If the current action is trash, ignore it if (val < best_val) { continue; } // If the current action is okay, add it to the list if (val == best_val) { best_actions.Add(actions.Current); continue; } // The current action is crazy, so rock on best_actions.Clear(); best_actions.Add(actions.Current); best_val = val; }while(actions.MoveNext()); // We're garunteed to have at least one action to take, so this list will never be empty return(best_actions[rand.Next() % best_actions.Count]); }