Implementing a tic-tac-toe rival

In order to make use of the previous recipes, we will devise a way to implement a rival for a popular game: tic-tac-toe. Not only does it help us extend the base classes, but it also gives us a way to create rivals for our own board games.

Getting ready…

We will need to create a specific move class for the tic-tac-toe board derived from the parent class we created at the beginning of the chapter:

using UnityEngine;
using System.Collections;

public class MoveTicTac : Move
{
    public int x;
    public int y;
    public int player;

    public MoveTicTac(int x, int y, int player)
    {
        this.x = x;
        this.y = y;
        this.player = player;
    }
}

How to do it…

We will create a new class, deriving it from Board, override its parent's methods, and create new ones.

  1. Create the BoardTicTac class, deriving it from Board, and add the corresponding member variables for storing the board's values:
    using UnityEngine;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    
    public class BoardTicTac : Board
    {
        protected int[,] board;
        protected const int ROWS = 3;
        protected const int COLS = 3;
    }
  2. Implement the default constructor:
    public BoardTicTac(int player = 1)
    {
        this.player = player;
        board = new int[ROWS, COLS];
        board[1,1] = 1;
    }
  3. Define the function for retrieving the next player in turn:
    private int GetNextPlayer(int p)
    {
        if (p == 1)
            return 2;
        return 1;
    }
  4. Create a function for evaluating a given position regarding a given player:
    private float EvaluatePosition(int x, int y, int p)
    {
        if (board[y, x] == 0)
            return 1f;
        else if (board[y, x] == p)
            return 2f;
        return -1f;
    }
  5. Define a function for evaluating the neighbors of a given position regarding a given player:
    private float EvaluateNeighbours(int x, int y, int p)
    {
        float eval = 0f;
        int i, j;
        for (i = y - 1; i < y + 2; y++)
        {
            if (i < 0 || i >= ROWS)
                continue;
            for (j = x - 1; j < x + 2; j++)
            {
                if (j < 0 || j >= COLS)
                    continue;
                if (i == j)
                    continue;
                eval += EvaluatePosition(j, i, p);
            }
        }
        return eval;
    }
  6. Implement a constructor for building new states with values:
    public BoardTicTac(int[,] board, int player)
    {
        this.board = board;
        this.player = player;
    }
  7. Override the member function for getting the available moves from the current state:
    public override Move[] GetMoves()
    {
        List<Move> moves = new List<Move>();
        int i;
        int j;
        for (i = 0; i < ROWS; i++)
        {
            for (j = 0; j < COLS; j++)
            {
                if (board[i, j] != 0)
                    continue;
                MoveTicTac m = new MoveTicTac(j, i, player);
                moves.Add(m);
            }
        }
        return moves.ToArray();
    }
  8. Override the function for retrieving a new state from a given move:
    public override Board MakeMove(Move m)
    {
        MoveTicTac move = (MoveTicTac)m;
        int nextPlayer = GetNextPlayer(move.player);
        int[,] copy = new int[ROWS, COLS];
        Array.Copy(board, 0, copy, 0, board.Length);
        copy[move.y, move.x] = move.player;
        BoardTicTac b = new BoardTicTac(copy, nextPlayer);
        return b;
    }
  9. Define the function for evaluating the current state, given a player:
    public override float Evaluate(int player)
    {
        float eval = 0f;
        int i, j;
        for (i = 0; i < ROWS; i++)
        {
            for (j = 0; j < COLS; j++)
            {
                eval += EvaluatePosition(j, i, player);
                eval += EvaluateNeighbours(j, i, player);
            }
        }
        return eval;
    }
  10. Implement the function for evaluating the current state of the current player:
    public override float Evaluate()
    {
        float eval = 0f;
        int i, j;
        for (i = 0; i < ROWS; i++)
        {
            for (j = 0; j < COLS; j++)
            {
                eval += EvaluatePosition(j, i, player);
                eval += EvaluateNeighbours(j, i, player);
            }
        }
        return eval;
    }

How it works…

We define a new type of move for the board that works well with the base algorithms because they make use of it only at a high level as a data structure. The recipe's bread and butter come from overriding the virtual functions from the Board class in order to model the problem. We use a two-dimensional integer array for storing the players' moves on the board (0 represents an empty place), and we work out a heuristic for defining the value of a given state regarding its neighbors.

There is more…

The functions for evaluating a board's (state) score have an admissible heuristic, but it's probably not optimal. It is up to us to revisit this problem and refactor the body of the aforementioned functions in order to have a better tuned rival.

See also

  • The Working with the game-tree class recipe
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.144.229