public OddmentTable <int> Create(SearchContext <object, TicTacToeState, TicTacToeMove, object, TicTacToeMove> context, int samplesForGeneration) { var table = new Dictionary <int, double>(); for (var i = 0; i < samplesForGeneration; i++) { var action = new TicTacToeMove(TicTacToeMoveGenerator.AllEmptyPositions(context.Source).RandomElementOrDefault(), context.Source.ActivePlayerID); var newState = GameLogic.Apply(context, (TicTacToeState)context.Source.Copy(), action); var endState = PlayoutStrategy.Playout(context, newState); var value = EvaluationStrategy.Evaluate(context, new TreeSearchNode <TicTacToeState, TicTacToeMove>(action), endState); if (!table.ContainsKey(action.PositionToPlace)) { table.Add(action.PositionToPlace, 0); } table[action.PositionToPlace] += value; } var maxValue = table.Values.Max(); var minValue = table.Values.Min(); var oddmentTable = new OddmentTable <int>(); foreach (var kvPair in table) { var normalisedValue = Util.Normalise(kvPair.Value, minValue, maxValue); oddmentTable.Add(kvPair.Key, normalisedValue, recalculate: false); } oddmentTable.Recalculate(); return(oddmentTable); }
public TicTacToeMove Sample(TicTacToeState state, OddmentTable <int> sideInformation) { // Get all available positions in this state var emptyPositions = TicTacToeMoveGenerator.AllEmptyPositions(state); var positionSelected = false; var selectedPosition = -1; while (!positionSelected) { // Sample a position from the OddmentTable selectedPosition = sideInformation.Next(); // Check if this position can be played, otherwise generate a new one positionSelected = emptyPositions.Contains(selectedPosition); } return(new TicTacToeMove(selectedPosition, state.ActivePlayerID)); }
public List <int> CheckThreats(TicTacToeState state, int playerID) { var possibilities = TicTacToeMoveGenerator.AllEmptyPositions(state); var threats = new List <int>(); foreach (var possibility in possibilities) { var testMove = new TicTacToeMove(possibility, playerID); var clone = (TicTacToeState)state.Copy(); clone = Apply(null, clone, testMove); if (!CheckWinningPositions(clone, playerID).IsNullOrEmpty()) { threats.Add(possibility); } } return(threats); }
public List <int> CheckWinningPositions(TicTacToeState state, int playerID) { var possibilities = TicTacToeMoveGenerator.AllEmptyPositions(state); var wins = new List <int>(); foreach (var possibility in possibilities) { var testMove = new TicTacToeMove(possibility, playerID); var clone = (TicTacToeState)state.Copy(); clone = Apply(null, clone, testMove); if (clone.Done && clone.PlayerWon == playerID) { wins.Add(possibility); } } return(wins); }
/// <inheritdoc /> /// <summary> /// See <see cref="https://en.wikipedia.org/wiki/Tic-tac-toe#Strategy"/> /// </summary> public TicTacToeMove Act(SearchContext <object, TicTacToeState, TicTacToeMove, object, TicTacToeMove> context, TicTacToeState state) { var possibilities = TicTacToeMoveGenerator.AllEmptyPositions(state); var myID = state.ActivePlayerID; var oppID = TicTacToeState.SwitchPlayerID(myID); var oppMove = oppID == TicTacToeState.PLAYER_ONE_ID ? TicTacToeState.PLAYER_ONE_MOVE : TicTacToeState.PLAYER_TWO_MOVE; var openCorners = possibilities.Where(i => CornerPositions.Contains(i)).ToList(); var openEdges = possibilities.Where(i => EdgePositions.Contains(i)).ToList(); #region Opening and response // Opening move to corner or middle position if (possibilities.Count == 9) { //return new TicTacToeMove(CornerPositions.RandomElementOrDefault(), myID); return(new TicTacToeMove(4, myID)); } // Second turn if (possibilities.Count == 8) { // If the middle position was opened with, play corner if (!possibilities.Contains(4)) { return(new TicTacToeMove(CornerPositions.RandomElementOrDefault(), myID)); } // If a corner or an edge was opened with, play middle return(new TicTacToeMove(4, myID)); } #endregion #region 1. Win // Check for own winning moves var myWins = CheckWinningPositions(state, myID); // Take the win if (!myWins.IsNullOrEmpty()) { return(new TicTacToeMove(myWins.RandomElementOrDefault(), myID)); } #endregion #region 2. Block // Check for opponent's winning moves var oppWins = CheckWinningPositions(state, oppID); // Prevent the loss if (!oppWins.IsNullOrEmpty()) { return(new TicTacToeMove(oppWins.RandomElementOrDefault(), myID)); } #endregion #region 3. Fork // Check if we have any forks available var forks = CheckForks(state, myID); // Move to one of the available forks if (!forks.IsNullOrEmpty()) { return(new TicTacToeMove(forks.RandomElementOrDefault(), myID)); } #endregion #region 4. Blocking an opponent's fork // Check if the opponent has any forks available var oppForks = CheckForks(state, oppID); if (!oppForks.IsNullOrEmpty()) { // If there is only one possible fork for the opponent, the player should block it. if (oppForks.Count == 1) { return(new TicTacToeMove(oppForks[0], myID)); } // Otherwise, the player should block any forks in any way that simultaneously allows them to create two in a row, as long as it doesn't result in them creating a fork. var threats = CheckThreats(state, myID); var safeThreats = threats.Where(i => !DoesThisThreatCreateAForkForOpponent(state, i, myID)).ToList(); var safeBlockingThreats = safeThreats.Where(i => oppForks.Contains(i)).ToList(); if (!safeBlockingThreats.IsNullOrEmpty()) { return(new TicTacToeMove(safeBlockingThreats.RandomElementOrDefault(), myID)); } // Otherwise, the player should create a two in a row to force the opponent into defending, as long as it doesn't result in them creating a fork. if (!safeThreats.IsNullOrEmpty()) { return(new TicTacToeMove(safeThreats.RandomElementOrDefault(), myID)); } } #endregion #region 5. Center // If middle is open, play it if (possibilities.Contains(4)) { return(new TicTacToeMove(4, myID)); } #endregion #region 6. Opposite corner // If the opponent is in a corner and the opposite corner is available, move there foreach (var cornerPosition in CornerPositions) { var oppositeCorner = OppositeCorner(cornerPosition); if (state.State[cornerPosition] == oppMove && openCorners.Contains(oppositeCorner)) { return(new TicTacToeMove(oppositeCorner, myID)); } } #endregion #region 7. Empty corner // If a corner is open, play it if (!openCorners.IsNullOrEmpty()) { return(new TicTacToeMove(openCorners.RandomElementOrDefault(), myID)); } #endregion #region 8. Empty side // If an edge is open, play it if (!openEdges.IsNullOrEmpty()) { return(new TicTacToeMove(openEdges.RandomElementOrDefault(), myID)); } #endregion // Otherwise, act random var index = rng.Next(possibilities.Count); var randomPosition = possibilities.ToArray()[index]; // Return a random position to play for the active player return(new TicTacToeMove(randomPosition, myID)); }
public TicTacToeMove Sample(TicTacToeState state) { return(new TicTacToeMove(TicTacToeMoveGenerator.AllEmptyPositions(state).RandomElementOrDefault(), state.ActivePlayerID)); }