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;
}
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment
You can add Images, Colored Text and more to your comment.
See instructions at http://macrolayer.blogspot.com..