Wednesday, 19 August 2020

REST Random Move logic

Last time, we wrote all our logic around creating a game and checking if there is a winner, in this episode we are going to write our AI logic to take over the world make a random tic tac toe move. first lets take a look at our game.cs file.

using System;
using System.Linq;
using System.Collections.Generic;
using tictactoe.game.interfaces;

namespace tictactoe.game.models{
  public class Game : IGame
  {
    protected int[,] grid;
    public IList<IMoveMoves { getset; }
    public charWinner {getprivate set;}
    public Game(){
      this.grid = new int[3,3];
      this.Moves = new List<IMove>();
      this.Winner = null;
    }

    public Game(IMove[] moves) : this() { 
      for(var i = 0imoves.Lengthi++){
        this.ExecuteMove(moves[i]);
        if(i > 3 && (Winner = GameOver()) != null)
          break;
      }
    }

    public void ExecuteMove(IMove move) {
      if(Moves.Count() > 0){
        if(Moves.Contains(move))
          throw new Exception("Tic Tac Toe duplicate move violation");
        
        if(Moves.Last().Symbol == move.Symbol)
          throw new Exception($"Tic Tac Toe series violation; two {move.Symbol} moves cannot be made in a row");
      }
      Moves.Add(move);
      
      int x = move.Coordinates.X;
      int y = move.Coordinates.Y;
      var symbol = Char.ToLower(move.Symbol);

      grid[x,y] = symbol == 'x' ? 1 : -1
    }

    public charGameOver() {
      //test rows
      for(var x = 0x < 3x++){
        var total = 0;
        for(var y = 0y < 3y++)
          if(Math.Abs(total += grid[x,y]) == 3)
            return total == 3 ? 'x' : 'o';
      }

      //test cols
      for(var y = 0y < 3y++) {
        var total = 0;
        for(var x = 0x < 3x++)
          if(Math.Abs(total += grid[x,y]) ==3)
            return total == 3 ? 'x' : 'o';
      }

      //test / diagnal
      var forwardSlashTotal = grid[0,2] + grid[1,1] + grid[2,0];
      if(forwardSlashTotal == 3)
        return 'x';
      else if(forwardSlashTotal == -3)
        return 'o';

      //test \ diagnal
      var backSlashTotal = grid[0,0] + grid[1,1] + grid[2,2];
      if(backSlashTotal == 3)
        return 'x';
      else if(backSlashTotal == -3)
        return 'o';

      return null;
    }

    public IMove ExpertResponse() { throw new System.NotImplementedException(); }
    public IMove RandomResponse() { throw new System.NotImplementedException(); }
    public IMove SimpleResponse() { throw new System.NotImplementedException(); }
  }
}

so lets talk about how we are going to make a random move in our application; we have two sources of data:
  • Our 3 by 3 grid 
  • Our list of executed moves
so lets talk about our options
first we could just randomly select an x and a y, check if there is a value there, if not use that position if it is already selected just pick a different random combination, which for a small app like this would work, however if we say had a grid of 10000 by 100000 then that could be a nightmare as the grid fills up. Thus probably not the best option.

our second option would be to create a collection of the unused spaces and randomly select one from our collection, which is a pretty solid option, and the one we are going to implement

I'm sure there are cooler things that we could do, such as rather than creating a list of options we could instead maintain a list of available moves and remove those moves as they are made, however this post is not suppose to be a an in depth analysis of the most efficient low memory approach of picking an empty cell in a 3 by 3 grid, but to focus on building an api. 

So first lets create an algorithm to pick a random available cell

    public IMove RandomResponse() {
      var rnd = new Random((int)DateTime.Now.Ticks);
      var options = new List<(int X,int Y)>();
      
      // create a list of available moves
      for(var x = 0x < 3x++) 
        for(var y = 0y < 3y++)
          if(grid[x,y] == 0)
            options.Add((x,y));
      
      // return a random move
      return new Move { 
        Coordinates = options[rnd.Next(options.Count()-1)],
        Symbol = (Moves.Count & 1) == 0 ? 'x' : 'o' 
      };
    }

Now for the sake of brevity i have made the assumption that X will always be the first move.

next lets create a couple of tests for our random algorithm

        [Fact]
        public void Test_Game_Random_Response_1(){
          var game = new Game(new[]{ new Move("8X"), new Move("5o"), new Move("3x"), new Move("2o"), new Move("0x")});
          var expected = new []{(1,0),(1,1),(0,2),(1,2)};

          var move = game.RandomResponse();

          Assert.Equal('o'move.Symbol);
          Assert.Contains(move.Coordinatesexpected);
        }

        [Fact]
        public void Test_Game_Random_Response_First_Move(){
          var game = new Game();
          var expected = new []{
            (0,0),(1,0),(2,0),
            (0,1),(1,1),(2,1),
            (0,2),(1,2),(2,2)};

          var move = game.RandomResponse();

          Assert.Equal('x'move.Symbol);
          Assert.Contains(move.Coordinatesexpected);
        }

these by no means are exhaustive tests, that is they do not test every permutation possible, however they get the idea across.
 
Next lets look at our simple algorithm, but well put this into our next post, however feel free to commit this to our repo with

git add .
git commit -m 'created random response logic'
git push