Friday 21 August 2020

REST Service Simple Move algorithm

Now let's tackle our simple move logic, in the algorithm we are going to try and win the game if possible or block the opponent if they can win on the next move.

lets start with our simple move function

public IMove SimpleResponse() { 
      if(Moves.Count == 0)
        return new Move("0x");

      if(Moves.Count < 3)
        return RandomResponse();

      char symbol = (Moves.Count & 1) == 0 ? 'x' : 'o';

      //b = 0 try to win
      //b = 1 try to block
      for(var b = 0b < 2b++) {
        var multiplier = ((Moves.Count + b) & 1) == 0 ? 1 : -1;
        //cols
        for(var x = 0x < 3x++) {
          var total = 0;
          int yCandidate = -1;

          for(var y = 0y < 3y++){
            total += grid[x,y];
            if(grid[x,y] == 0
               yCandidate = y;
            if(y == 2 && total == (2 * multiplier))
              return new Move { Symbol = symbolCoordinates = (xyCandidate) };
          }
        }

        //rows
        for(var y = 0y < 3y++) {
          var total = 0;
          int xCandidate = -1;

          for(var x = 0x < 3x++){
            total += grid[x,y];
            if(grid[x,y] == 0
               xCandidate = x;
            if(x == 2 && total == (2 * multiplier))
              return new Move { Symbol = symbolCoordinates = (xCandidate,y) };
          }
        }
      
        //front slash /
        var fsTotal = 0;
        (int Xint YfsCanddiate = (-1,-1);

        for(int x = 0y = 2x < 3x++, y--){
          fsTotal += grid[xy];
          if(grid[xy] == 0)
            fsCanddiate = (x,y);
          if(x == 2 && fsTotal == (2 * multiplier))
           return new Move { Symbol = symbolCoordinates = fsCanddiate };
        }

        //backslash \ win
        var bsTotal = 0;
        (int Xint YbsCanddiate = (-1,-1);
        for(int x = 0x < 3x++){
          bsTotal += grid[xx];
          if(grid[xx] == 0)
            bsCanddiate = (x,x);
          if(x == 2 && bsTotal == (2 * multiplier))
           return new Move { Symbol = symbolCoordinates = bsCanddiate };
        }
      }

      return null;
    }

now it takes no parameters but uses the classes grid 2d array which represents our board, since we make the assumption that x always goes first and if no moves are made the algo will try to take the center for x otherwise since it wont be until the 3rd move that a possible winning or loosing move is available the simple algo will make a random move until there is the chance of connecting three in a row.

as for the move logic it is broken down into 4 main sections: 

  • Columns
  • Rows
  • / diagonal
  • \diagonal
each one of these should be refactored into its own function, but i am not in the mood for that.

now that we have our function written let's create some unit tests to validate our function

using tictactoe.game.models;
using Xunit;

namespace tictactoe.unitTests
{
  public class SimpleTests{

    [Fact]
    public void Test_Simple_Response_X_wins_First_Column()
    {
      //Given
      var game = new Game(new[]{ new Move("0x"), new Move("1o"), new Move("3x"), new Move("4o")});
      
      //When
      var move = game.SimpleResponse();
      
      //Then
      Assert.Equal('x'move.Symbol);
      Assert.Equal((0,2), move.Coordinates);
    }

    [Fact]
    public void Test_Simple_Response_X_wins_Second_Column()
    {
      //Given
      var game = new Game(new[]{ new Move("1x"), new Move("0o"), new Move("4x"), new Move("8o")});

      //When
      var move = game.SimpleResponse();

      //Then
      Assert.Equal('x'move.Symbol);
      Assert.Equal((1,2), move.Coordinates);
    }

    [Fact]
    public void Test_Simple_Response_X_wins_Third_Column()
    {
      //Given
      var game = new Game(new[]{ new Move("2x"), new Move("3o"), new Move("8x"), new Move("4o")});

      //When
      var move = game.SimpleResponse();

      //Then
      Assert.Equal('x'move.Symbol);
      Assert.Equal((2,1), move.Coordinates);
    }

    [Fact]
    public void Test_Simple_Response_O_wins_First_Column()
    {
      //Given
      var game = new Game(new[]{ new Move("2x"), new Move("0o"), new Move("1x"), new Move("3o"), new Move("8x")});

      //When
      var move = game.SimpleResponse();

      //Then
      Assert.Equal('o'move.Symbol);
      Assert.Equal((0,2), move.Coordinates);
    }

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

      //When
      var move = game.SimpleResponse();

      //Then
      Assert.Equal('x'move.Symbol);
      Assert.Equal((0,2), move.Coordinates);
    }

    [Fact]
    public void Test_Simple_Response_X_Wins_First_Row()
    {
      //Given
      var game = new Game(new[]{ new Move("2x"), new Move("4o"), new Move("1x"), new Move("3o") });

      //When
      var move = game.SimpleResponse();
      
      //Then
      Assert.Equal('x'move.Symbol);
      Assert.Equal((0,0), move.Coordinates);
    }



    [Fact]
    public void Test_Simple_Response_X_Wins_Front_Diagonal()
    {
      //Given
      var game = new Game(new[] { new Move("6x"), new Move("8o"), new Move("4x"), new Move("0o")});
      
      //When
      var move = game.SimpleResponse();

      //Then
      Assert.Equal('x'move.Symbol);
      Assert.Equal((2,0), move.Coordinates);
    }

    [Fact]
    public void Test_Simple_Response_O_Blocks_Front_Diagonal()
    {
      //Given
      var game = new Game(new[] { new Move("6x"), new Move("8o"), new Move("4x")});
      
      //When
      var move = game.SimpleResponse();

      //Then
      Assert.Equal('o'move.Symbol);
      Assert.Equal((2,0), move.Coordinates);
    }

    [Fact]
    public void Test_Simple_Response_X_Wins_Back_Diagonal()
    {
      //Given
      var game = new Game(new[] {new Move("0x"), new Move("1o"), new Move("4x"), new Move("2o")});
      //When
      var move = game.SimpleResponse();
      //Then
      Assert.Equal('x'move.Symbol);
      Assert.Equal((2,2), move.Coordinates);
    }
 

    [Fact]
    public void Test_Simple_Response_O_Wins_Back_Diagonal()
    {
      //Given
      var game = new Game(new [] {new Move("6x"), new Move("0o"),new Move("7x"), new Move("8o"), new Move("2x") });
      //When
      var move = game.SimpleResponse();
      //Then
      Assert.Equal('o'move.Symbol);
      Assert.Equal((1,1), move.Coordinates);
    } 

    [Fact]
    public void Test_Simple_Response_X_Blocks_Back_Diagonal()
    {
      //Given
      var game = new Game(new [] {new Move("6x"), new Move("0o"),new Move("7x"), new Move("8o") });
      //When
      var move = game.SimpleResponse();
      //Then
      Assert.Equal('x'move.Symbol);
      Assert.Equal((1,1), move.Coordinates);
    } 
  }
}

again these are not exhaustive tests, but they satisfy the purpose of this post, just notice that they are broken down into three sections, basically setup, execute, test, various testing frameworks use different terminology, however they all follow the basic three sections.

Next lets create our ultimate unbeatable algorithm.