TicTacToe

I thought that now that I am back in a couple of programming courses, I would be posting here more often. However, most of the course work I end up doing is pretty tame, and not really worth writing about. But I finally ran into a fun little puzzle with the tic-tac-toe challenge. Not with the game of course, but with the AI. It is easy enough make an AI that cannot be beat, but I wanted to make one that would intelligently try to win as well. I went with a move-weight model, scanning every possible move adding up weights, and then taking the highest weighted move.

The following is the code for the AI;

/*
    CSCI 1733 Introduction to Object Oriented Programming with C++

    Document:   Assignment 3 Excercise 4;

    File Name:   AI.hpp

    Author:     Abner Holsinger

    Description:
    A TicTacToe class that is used to play the game of tic tac toe; additionaly
    a rudimentary AI class has been added to automate one or both players.

    The AI class
 */


#ifndef AI_HPP
#define AI_HPP

class AI
{
public:
    // intialize pointer array to board positions
    void linkBoard(int board[3][3]);
    void setMarker(char xo);
    void smartPlay();

private:
    int m; // the opponent marker
    int n; // the AI's marker
    int *b_ptr[3][3];
};

/**
    set a given marker; converts the char 'X' or 'O' into game equivalent 1 or 2
    @param char xo marker to place; valid markers are 'X' and 'O'
  */
void AI::setMarker(char xo)
{
    if (xo == 'X')
    {
        m = 1;
        n = 2;
    }
    else
    {
        n = 1;
        m = 2;
    }
}

/**
    create a pointer array linked to the board array elements for the AI
 */
void AI::linkBoard(int board[3][3])
{
    for(int i=0; i<3; i++)
    {
        for(int j=0; j<3; j++)
        {
            // connect each pointer to each array position
            b_ptr[i][j] = &board[i][j];
        }
    }
}

/**
  create a move weight array to calculate the best move
 */
void AI::smartPlay()
{
    // how valuable is each open position?
    int move_weight[3][3] = {{0}};
    for (int i=0; i<3; i++)
    {
        for (int j=0; j<3; j++)
        {
            // weigh verticals; priority is : winning, blocking, other moves
                        // winning scan; open column contains two of players markers
            if (*b_ptr[(0+j)%3][i] == m && *b_ptr[(1+j)%3][i] == m && *b_ptr[(2+j)%3][i] == 0)
                        {
                move_weight [(2+j)%3][i] += 10 ; // has to be larger than the next two combined
                        }

                        // blocking scan; open column contains two of opponents markers
            else if (*b_ptr[(0+j)%3][i] == n && *b_ptr[(1+j)%3][i] == n && *b_ptr[(2+j)%3][i] == 0)
                        {
                move_weight [(2+j)%3][i] +=5 ;
                        }

                        // open column with one of players marker
            else if (*b_ptr[(0+j)%3][i] == m && *b_ptr[(1+j)%3][i] == 0 && *b_ptr[(2+j)%3][i] == 0)
            {
                move_weight [(1+j)%3][i] += 4 ;
                move_weight [(2+j)%3][i] += 4 ;
            }

                        // open column with no markers
            else if (*b_ptr[(0+j)%3][i] == 0 && *b_ptr[(1+j)%3][i] == 0 && *b_ptr[(2+j)%3][i] == 0)
            {
                move_weight [(0+j)%3][i]++ ;
                move_weight [(1+j)%3][i]++ ;
                move_weight [(2+j)%3][i]++ ;
            }

            // weigh horizontals
                        // winning scan; open row contains two of players markers
            if (*b_ptr[i][(0+j)%3] == m && *b_ptr[i][(1+j)%3] == m && *b_ptr[i][(2+j)%3] == 0)
            {
                move_weight [i][(2+j)%3] += 10;// has to be larger than the next two combined
            }

                        // blocking scan; open row contains two of opponents markers
            else if (*b_ptr[i][(0+j)%3] == n && *b_ptr[i][(1+j)%3] == n && *b_ptr[i][(2+j)%3] == 0)
            {
                move_weight [i][(2+j)%3] += 5;
            }

                        // open row with one of players markers
            else if (*b_ptr[i][(0+j)%3] == m && *b_ptr[i][(1+j)%3] == 0 && *b_ptr[i][(2+j)%3] == 0)
            {
                move_weight [i][(1+j)%3] += 4;
                move_weight [i][(2+j)%3] += 4;
            }

                        // open row with no markers
            else if (*b_ptr[i][(0+j)%3] == 0 && *b_ptr[i][(1+j)%3] == 0 && *b_ptr[i][(2+j)%3] == 0)
            {
                move_weight [i][(0+j)%3]++;
                move_weight [i][(1+j)%3]++;
                move_weight [i][(2+j)%3]++;
            }
        }



        if (i != 1) // skip middle iteration for diagonals; there are only two of them.
        {
            // new pointer array for iterating over diagonals
            int *line[3] = {b_ptr[0+i][0], b_ptr[1][1], b_ptr[(2+i)%4][2]};

            int *mw_pntr[3];
            mw_pntr[0] = &move_weight[0+i][0];
            mw_pntr[1] = &move_weight[1][1];
            mw_pntr[2] = &move_weight[(2+i)%4][2];

            for (int j=0; j<3; j++)
            {
                                // winning scan; open diagonal contains two of players markers
                if ( *line[(0+j)%3] == m && *line[(1+j)%3] == m && *line[(2+j)%3] == 0)
                    *mw_pntr [(2+j)%3] = *mw_pntr [(2+j)%3] + 10;

                                // blocking scan; open diagonal contains two of opponents markers
                else if ( *line[(0+j)%3] == n && *line[(1+j)%3] == n && *line[(2+j)%3] == 0)
                    *mw_pntr [(2+j)%3] = *mw_pntr [(2+j)%3] + 5;

                                // open diagonal with one of players markers
                else if ( *line[(0+j)%3] == m && *line[(1+j)%3] == 0 && *line[(2+j)%3] == 0)
                {
                    *mw_pntr [(1+j)%3] = *mw_pntr [(1+j)%3] + 3;
                    *mw_pntr [(2+j)%3] = *mw_pntr [(2+j)%3] + 3;
                }
                                // open diagonal with no markers
                else if ( *line[(0+j)%3] == 0 && *line[(1+j)%3] == 0 && *line[(2+j)%3] == 0)
                {
                    *mw_pntr [(0+j)%3] = *mw_pntr [(0+j)%3] + 1;
                    *mw_pntr [(1+j)%3] = *mw_pntr [(1+j)%3] + 1;
                    *mw_pntr [(2+j)%3] = *mw_pntr [(2+j)%3] + 1;
                }
            }
        }
    }
    // find the highest weighted move
    int row = 0;
    int col = 0;

    for (int i=0; i<3; i++)
    {
        for (int j=0; j<3; j++)
        {
            // weight disallowed positions -1; fixes all zero-weight scenario, e.g. the last move
            if (*b_ptr[i][j] != 0)
                move_weight[i][j] = -1;

            if (move_weight[i][j] > move_weight[row][col])
            {
                row = i;
                col = j;
            }
            // randomly pick equally weighted positions
            else if ((move_weight[i][j] == move_weight[row][col]) && *b_ptr[i][j] == 0 && rand()%2)
            {
                row = i;
                col = j;
            }
        }
    }
    *b_ptr[row][col] = m;
}
#endif
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s