Saturday, November 21, 2015

Tic-Tac-Toe Algorithm


Until I setup my github or sourceforge again... here's the source code in C for my tic-tac-toe playing program.

/* This tic-tac-toe algorithm implements strategies to ensure never losing,
*  as well as strategies to create the most opportunities for the human player to make mistakes that lead to loss.
*
*  The strategies are pre-defined rules based on board position. There is no brute force searching of possible outcomes.
*  Decisions are *not* evaluated by future board states.
*  In this sense, the decision-making does not use artificial intelligence, it follows rules assigned by the programmer, based on human intelligence and understanding the game.
*
*  On the play, the computer will choose one of three attacks: center, corner or edge.
*  The order of selection made by the computer mostly follows Newell and Simon's 1972 algorithm.
*  Cutthroatcomputer will play to win, creating the most options for the human to make a mistake, even when a tying position is obvious, so as not to 'give it away' to the human the the game is tied.
*  Smartcomputer will play only to ensure it does not lose.
*  stupidcomputer plays the next available square. It is called when there is only one move available.
*
*  The human player can either play first or second.
*  X always plays first, O plays second
*/
Apu explains it better than I can:



#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
 //
#define  stratcenter  0
#define  stratcorner  1
#define  stratedge  2
// when the position is a tie, the computer will go into 'stupidcomputer' mode and play the first empty spot
static int board[9] = {0,0,0,0,0,0,0,0,0};
static int threes[8][3] = // ways to get tic tac toe
{
    // rows
    {0,1,2},
    {3,4,5},
    {6,7,8},
    // columns
    {0,3,6},
    {1,4,7},
    {2,5,8},
    // diagonals
    {0,4,8},
    {2,4,6}
};
static int corners[4] = {0,2,6,8};
static int cornerneighbors[4][2] = {{1,3},{1,5},{3,7},{7,5}};

static int human = 3;
static int computer = 5;
static const int emptysquare = 0;

static const int squareoccupied = 1;
static const int squareunoccupied = 0;
static const int gameunfinished = -1;

static int player1;
static int player2;
static int switchmove = 1;
static int squaresused = 0;

static int strategy = 3;
static int num_strats = 3;

char* getsymbol(int value)
{
    static char* oh = "O";
    static char* ex = "X";
    static char* blank = "_";
    static char* error = "ERROR";
    if (value == 0) return blank;
    if (value == player1) return ex;
    if (value == player2) return oh;
    return error;
}

void printboard(int* board)
{
    system("cls");
    int i, j;
    char* space;
    for (i = 0 ; i < 3; i++)
    {
        for ( j = 0; j < 3; j++)
        {
            printf("%s ", getsymbol(board[3*i+j]));
        }
        printf("\n");
    }
}

void pickstart(int* board)
{
    int i, first;
    for (i=0; i <9; i++) board[i] = emptysquare;
    squaresused = 0;
    printf("Play first or second?\n");
    scanf("%d", &first);
    if (first == 1)
    {
        player1 = human;
        player2 = computer;
        switchmove = 0;
    }
    else
    {
        player1 = computer;
        player2 = human;
        switchmove = 1;
    }
    strategy ++;
    strategy = modulo(strategy, num_strats); // modulo addition
}
int putmove(int* board, int player, int row, int col)
{
    row--;
    col--;
    if (board[3*row + col] == 0 ) // cell is unoccupied
    {
        board[3*row + col] = player;
        return squareunoccupied;
    }
    else return squareoccupied;
}

void askmove(int* board, int player)
{
    int row, col;
    printf("Type the row and column numbers of your move\n");
    scanf("%d %d", &row, &col);
    if (row >3 || row < 1 || col >3 || col <1)
    {
        printf("Square does not exist. Try again.\n");
        askmove(board, player);
    }
    else if (putmove(board, player, row,col) == squareoccupied)
    {
        printf("Square already occupied. Select another.\n");
        askmove(board, player);
    }
}
int checkwin(int* board)
{
    // row
    if (board[0] == board[1] && board[1] == board[2] && board[0] != emptysquare) return board[0];
    if (board[3] == board[4] && board[4] == board[5] && board[3] != emptysquare) return board[3];
    if (board[6] == board[7] && board[7] == board[8] && board[6] != emptysquare) return board[6];
    // col
    if (board[0] == board[3] && board[3] == board[6] && board[0] != emptysquare) return board[0];
    if (board[1] == board[4] && board[4] == board[7] && board[1] != emptysquare) return board[1];
    if (board[2] == board[5] && board[5] == board[8] && board[2] != emptysquare) return board[2];
    // diagonal
    if (board[0] == board[4] && board[4] == board[8] && board[0] != emptysquare) return board[4];
    if (board[2] == board[4] && board[4] == board[6] && board[2] != emptysquare) return board[4];

    int i;
    for (i =0; i < 9; i ++)
    {
        if (board[i] == emptysquare) return gameunfinished; // there's an empty square so game not over yet
    }
    return emptysquare; // all squares occupied game is a tie
}
void endgame(int wincondition)
{
    if (wincondition == gameunfinished) return;
    if (wincondition == emptysquare)
    {
        printf("Game was a tie!\n");
    }
    else
    {
        printf("%s wins!\n", getsymbol(wincondition));
    }
}
int playagain()
{
    int yesno;
    printf("Do you want to play again? Enter 1 for yes.\n");
    scanf("%d", &yesno);
    if (yesno == 1) return gameunfinished;
    return 0;
}
void stupidcomputer(int* board)
{
    // picks first empty spot
    int i;
    for (i=0; i < 9; i++)
    {
        if (board[i] == emptysquare)
        {
            board[i] = computer;
            break;
        }
    }
}
int modulo(int number, int modulus)
{
    return number - (number/modulus * modulus); // modulo addition
}
int cutthroatcomputer(int* board)
{

    int i,j;
    static int getcenterflag = 0;
    if (getcenterflag == 1)
    {
        board[4] = computer;
        getcenterflag = 0;
        return 0;
    }
    if (squaresused == 0)
        // on the play, the computer will choose one of three attacks
    {
        switch (strategy)
        {
        case stratcenter: // center
            board[4] = computer;
            return 0;
        case stratcorner: // corner
            board[0] = computer;
            return 0;
        case stratedge: // edge
            board[1] = computer;
            return 0;
        }
    }
    else if (squaresused == 1)
        // on the defense, the computer will ensure a tie and threaten potential wins
    {

        // if the opponents move is a corner, computer must play center to prevent loss
        if (board[4] == emptysquare)
        {
            for (i = 0; i < 4; i++)
            {
                if (board[corners[i]] == human)
                {
                    board[4] = computer;
                    return 0;
                }
            }
        }
        else if (board[4] == human)
            // if the human takes center, a corner must be played to force tie
        {
            goto cornermove;
        }
        // an adjacent corner to an edge, the center, or the opposite must be taken
        switch (strategy)
        {
        case stratcorner: // corner
            for (i = 0 ; i < 4; i++)
            {
                // pick a corner if human controls a neighbor
                int sum = 0;
                for (j =0; j < 2; j++)
                {
                    if ( board[cornerneighbors[i][j]] == human)
                    {
                        board[corners[i]] = computer;
                        return 0;
                    }
                }
            }
        case stratedge: // edge
            for (i =0; i < 9; i ++)
            {
                if (board[i] == human)
                {
                    int placehere;
                    switch(i)
                    {
                    case 1:
                        placehere = 7;
                        break;
                    case 3:
                        placehere = 5;
                        break;
                    case 5:
                        placehere = 3;
                        break;
                    case 7:
                        placehere = 1;
                        break;
                    }
                    board[placehere] = computer;
                    return 0;
                }
            }
        case stratcenter: // center
            board[4] = computer;
            return 0;
        }
    }
    else if (squaresused == 2 && strategy == stratcorner )
        // if human plays center after computer corner, take opposite corner looking for fork.
        // if human does not take center, computer selects a corner by default and wins so no need to do anything else
    {
        if (board[4] == human)
        {
            board[8] = computer;
            return 0;
        }
        else goto cornermove; // optional goto to skip some calculations. should do this by default anyways.
    }

    // check if can win
    // since human and player values are well selected prime, their sums are unique for three additions.
    // 5+5+5 , 3+3+3 we can just check if the sum of three values is equal to twice the value of one of them
    // since blank value is zero. then find that blank and fill it to win.
    for (i = 0; i < 8; i++)
    {
        // check if computer can win
        int sum=0;
        for (j = 0; j < 3; j++)
        {
            sum += board[threes[i][j]];
        }
        if (sum == 2*computer)
        {
            for (j = 0; j < 3; j++)
            {
                if (board[threes[i][j]] == emptysquare)
                {
                    board[threes[i][j]] = computer;
                    return 0;
                }
            }
        }
    }
    for (i = 0; i < 8; i++)
    {
        // check if opponent wins
        int sum=0;
        for (j = 0; j < 3; j++)
        {
            sum += board[threes[i][j]];
        }
        if (sum == 2*human) // check if human can win
        {
            for (j = 0; j < 3; j++)
            {
                if (board[threes[i][j]] == emptysquare)
                {
                    board[threes[i][j]] = computer;
                    return 0;
                }
            }
        }
    }
    if (squaresused == 2)
    {
        // the computer would not play into any of these situations if human plays edge, so
        // we can assume that the computer has played edge and the human played the other square
        int r;
        for (r = 0; r< 4; r++)
        {
            int trapcorner= 0;
            if (board[cornerneighbors[r][0]] != emptysquare && board[cornerneighbors[r][1]] != emptysquare)
            {
                // if human plays on adjacent edge to computer edge, win by playing the surrounded corner
                // followed by the center
                board[corners[r]] = computer;
                getcenterflag = 1;
                return 0;
            }
        }
        for (r = 0; r< 4; r++)
        {
            if (board[corners[r]] == human
                    &&  board[cornerneighbors[r][0]] == emptysquare
                    &&  board[cornerneighbors[r][1]] == emptysquare )
            {
                // if human plays non-adjacent corner to computer edge, win by playing the interior corner
                // followed by the center that simultaneously blocks and creates a fork
                int s;
                for (s = 0; s< 4; s++)
                {
                    if (board[s*2+1] == computer)
                    {
                        // find the corner adjacent to the computer edge that is NOT opposite the human's
                        int computeredge = s*2+1;
                        int oppositecorner = 8 - corners[r];
                        for (i = 0 ; i < 4; i++)
                        {
                            for (j =0; j < 2; j ++)
                            {
                                if (cornerneighbors[i][j] == computeredge && corners[i] != oppositecorner)
                                {
                                    board[corners[i]] = computer;
                                    return 0;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
// block human player's fork attempts
    if (squaresused == 3)
    {
        for (i = 6; i < 8; i ++)
        {
            // don't take a corner if human controls two opposite corners
            int sum = 0;
            for (j =0; j < 3; j++)
            {
                sum += board[threes[i][j]];
            }
            if (sum == 2 * human + computer )
            {
                board[5] = computer; // just pick an arbitrary edge, i.e. the first one
                return 0;
            }
        }

        for (i = 0 ; i < 4; i++)
        {
            // block a corner if human controls its two neighbors
            int sum = 0;
            for (j =0; j < 2; j++)
            {
                sum += board[cornerneighbors[i][j]];
            }
            if (sum == 2*human && board[corners[i]]== emptysquare)
            {
                if (board[4] == computer)
                {
                    // if you control the center, you have to block the surrounded corner
                    board[corners[i]] = computer;
                    return 0;
                }
                else
                {
                    // if you control a corner, you must threaten a non-center three in a row
                    goto forcetie;
                }
            }
            else if (sum == 2*human && board[corners[i]]==computer)
            {
                board[4] = computer;
                return 0;
            }
        }
    }
    // corner moves
cornermove:
    for  (i =0 ; i < 4; i++)
    {
        // prioritize the corner that is opposite a taken one
        if (board[corners[i]]==emptysquare)
        {

            if (board[corners[3-i]] == human)
            {
                board[corners[i]]=computer;
                return 0;
            }
        }
    }
    for  (i =0 ; i < 4; i++)
    {
        // take corner with no occupied neighbors
        int sum = 0;
        for (j =0; j < 2; j++)
        {
            sum += board[cornerneighbors[i][j]];
        }
        if (sum == 0 && board[corners[i]]==emptysquare)
        {
            board[corners[i]] = computer;
            return 0;
        }
    }
forcetie:
    for (i = 0; i < 6; i++)
    {
        // pick a corner to threaten a three in a row that cannot be blocked by center
        int sum=0;
        for (j = 0; j < 3; j++)
        {
            sum += board[threes[i][j]];
        }
        if (sum == computer)
        {
            for (j = 0; j < 3; j++)
            {
                if (board[threes[i][j]] == emptysquare && j != 1) // j=1 is a not a center
                {
                    board[threes[i][j]] = computer;
                    return 0;
                }
            }
        }
    }
    for (i = 7; i > 5; i--)
        // pick a corner first (to win for turn 3 XOX)
    {
        // check if an obvious threat to win exists (when human is about to tie, just to not look stupid)
        int sum=0;
        for (j = 0; j < 3; j++)
        {
            sum += board[threes[i][j]];
        }
        if (sum == computer) // check if computer has a chance to take all three
        {
            for (j = 0; j < 3; j++)
            {
                if (board[threes[i][j]] == emptysquare)
                {
                    board[threes[i][j]] = computer;
                    return 0;
                }
            }
        }
    }

    // TODO: the x_O attack with opposite corner to x, followed by selective fork
    // TODO: the OX_ attack with interior corner, followed by selective fork
    // no chance to win, pick any move to tie
dumbmove:
    stupidcomputer(board);
}
int smartcomputer(int* board)
{
    int i,j;
    if (squaresused < 2)
    {
        // in the first two moves center will always be taken by one of the players
        // so no more need to check it
        if (board[4] == emptysquare)
        {
            board[4] = computer;
            return 0;
        }
        else if (board[4] == human)
            // if the human takes center, a corner must be played to force tie
        {
            goto cornermove;
        }
    }
    // block human player's fork attempts
    if (squaresused == 3 && board[4] == computer)
    {
        for (i = 6; i < 8; i ++)
        {
            // don't take a corner if human controls two opposite corners
            int sum = 0;
            for (j =0; j < 3; j++)
            {
                sum += board[threes[i][j]];
            }
            if (sum == 2 * human + computer )
            {
                board[1] = computer; // just pick an arbitrary edge, i.e. the first one
                return 0;
            }
        }

        for (i = 0 ; i < 4; i++)
        {
            // block a corner if human controls its two neighbors
            int sum = 0;
            for (j =0; j < 2; j++)
            {
                sum += board[cornerneighbors[i][j]];
            }
            if (sum == 2*human && board[corners[i]]==0)
            {
                board[corners[i]] = computer;
                return 0;
            }
        }
    }
    // check if can win
    // since human and player values are well selected prime, their sums are unique for three additions.
    // 5+5+5 , 3+3+3 we can just check if the sum of three values is equal to twice the value of one of them
    // since blank value is zero. then find that blank and fill it to win.
    for (i = 0; i < 8; i++)
    {
        // check if computer can win
        int sum=0;
        for (j = 0; j < 3; j++)
        {
            sum += board[threes[i][j]];
        }
        if (sum == 2*computer)
        {
            for (j = 0; j < 3; j++)
            {
                if (board[threes[i][j]] == 0)
                {
                    board[threes[i][j]] = computer;
                    return 0;
                }
            }
        }
    }
    for (i = 0; i < 8; i++)
    {
        // check if opponent wins
        int sum=0;
        for (j = 0; j < 3; j++)
        {
            sum += board[threes[i][j]];
        }
        if (sum == 2*human) // check if human can win
        {
            for (j = 0; j < 3; j++)
            {
                if (board[threes[i][j]] == 0)
                {
                    board[threes[i][j]] = computer;
                    return 0;
                }
            }
        }
    }
    // corner moves
cornermove:
    for  (i =0 ; i < 4; i++)
    {
        // prioritize the corner that is opposite a taken one
        if (board[corners[i]]==0)
        {

            if (board[corners[3-i]] == human)
            {
                board[corners[i]]=computer;
                return 0;
            }
        }
    }
    for  (i =0 ; i < 4; i++)
    {
        // take corner with no occupied neighbors
        int sum = 0;
        for (j =0; j < 2; j++)
        {
            sum += board[cornerneighbors[i][j]];
        }
        if (sum == 0 && board[corners[i]]==0)
        {
            board[corners[i]] = computer;
            return 0;
        }
    }
    for (i = 7; i > -1; i--) // pick a corner first (to win for turn 3 XOX)
    {
        // check if an obvious threat to win exists (when human is about to tie, just to not look stupid)
        int sum=0;
        for (j = 0; j < 3; j++)
        {
            sum += board[threes[i][j]];
        }
        if (sum == computer) // check if computer has a chance to take all three
        {
            for (j = 0; j < 3; j++)
            {
                if (board[threes[i][j]] == 0)
                {
                    board[threes[i][j]] = computer;
                    return 0;
                }
            }
        }
    }
    // no chance to win, pick any move to tie
dumbmove:
    stupidcomputer(board);
}
void nextmove(int* board)
{
    switchmove = 1 - switchmove;
    if (switchmove == 1)
    {
        askmove(board, human);
    }
    else
    {
        cutthroatcomputer(board);
    }
    squaresused ++;
}
void test01()
{
    //int board[9] = {1,0,0,0,2,0,0,0,1};
    int gamestate = gameunfinished;
    int i;
    while (gamestate == gameunfinished)
    {
        pickstart(board);
        printboard(board);
        while(gamestate == gameunfinished)
        {
            for (i =0; i < 2 && gamestate == gameunfinished; i++)
            {
                printf("squares occupied %d\n",squaresused);
                nextmove(board);
                printboard(board);
                gamestate = checkwin(board);
            }
            endgame(gamestate);
        }
        gamestate = playagain();
    }
}
int main(int argc, char** argv)
{

    test01();

    return 0;
}

No comments:

Post a Comment

You can add Images, Colored Text and more to your comment.
See instructions at http://macrolayer.blogspot.com..