/* Tic Tac Toe Demonstration Program, C compilable. */ /* */ /* Created March 18, 1999, for use with Computer Science 101 at USC. */ /* Version 1.07. Last edited March 20, 1999. */ /* Copyright 1999 by Rick Wagner, all rights reserved. */ #include #include /* For malloc(), free(), and exit(). */ #include /* For getch(). */ /* Node in the tic tac toe game tree: */ typedef struct node { int iMetric; int iComputerMove; int iBestMove; struct node* gnChild[9]; char cConfig[9]; } GameNode; /* Function prototypes: */ void about(); void play(GameNode* gnGameRoot); void drawBoard(char* board); void constructGameNodeChildren(GameNode* gnNode, int n); void destroyGameNodeChildren(GameNode* gnNode, int n); int getHumanMove(char* board); void computerMove(GameNode* gn); int getAvailableMoves(GameNode* gnNode, int* iAvailable); int evaluation(char* board); void main() { int i = 0; char cChoice = 0; GameNode* gnGameRoot; /* Tell about the program: */ about(); /* Construct the game root GameNode: */ gnGameRoot = malloc(sizeof(GameNode)); while (1) /* Loop forever. */ { /* Set the game root GameNode initial configuration: */ for (i = 0; i < 9; i++) { gnGameRoot->cConfig[i] = '-'; /* Hyphen represents empty square. */ } /* Initiate play: */ play(gnGameRoot); printf("\nPlay again? (y/n) "); scanf("%c", &cChoice); fflush(stdin); if(cChoice != 'y') exit(0); } } void about() { puts(" Tic Tac Toe Demonstration Program, C compilable."); puts(""); puts(" Created March 18, 1999, for use with Computer Science 101 at USC."); puts(" Version 1.07. Last edited March 20, 1999."); puts(" Copyright 1999 by Rick Wagner, all rights reserved."); puts(""); printf("Press any key to play. "); getch(); } /* Allocate memory for the n children of a game node: */ void constructGameNodeChildren(GameNode* gnNode, int n) { int i = 0; for (i = 0; i < n; i++) { gnNode->gnChild[i] = malloc(sizeof(GameNode)); } } /* Free up the memory of no-longer-needed game node children. */ void destroyGameNodeChildren(GameNode* gnNode, int n) { int i = 0; for (i = 0; i < n; i++) { free(gnNode->gnChild[i]); } } /* Play one game of tic tac toe. Human is X and goes first. */ void play(GameNode* gnGameRoot) { int i = 0; int iCount = 0; /* Number of moves can't exceed 9 */ int iPlay = 1; /* Play as long as this flag is true */ int iStatus = 0; char cBoardConfig[9]; /* The Xs and Os played. */ int iAvailable[9]; /* Array of available squares. */ int iNumAvailableMoves = 0; /* Number of available squares. */ /* Make a copy of the initial game configuration (all hyphens). */ for (i = 0; i < 9; i++) { cBoardConfig[i] = gnGameRoot->cConfig[i]; } while (iPlay) { drawBoard(cBoardConfig); if (iCount % 2 == 0) { cBoardConfig[getHumanMove(cBoardConfig)] = 'X'; } else { /* Copy the board configuration to the root GameNode */ for (i = 0; i < 9; i++) { gnGameRoot->cConfig[i] = cBoardConfig[i]; } iNumAvailableMoves = getAvailableMoves(gnGameRoot, iAvailable); gnGameRoot->iBestMove = iAvailable[0]; /* Initialize the best move. */ gnGameRoot->iMetric = 0; gnGameRoot->iComputerMove = 1; /* It's the computer's turn to move. */ /* Start the recursive search: */ computerMove(gnGameRoot); cBoardConfig[gnGameRoot->iBestMove] = 'O'; /* Mark an O for the computer's move. */ } iCount++; if (iCount > 8) iPlay = 0; iStatus = evaluation(cBoardConfig); /* Check for three in a row. */ if (iStatus != 0) iPlay = 0;; } drawBoard(cBoardConfig); if (iStatus == -1) { printf("\n\nGame over, the human player won.\n"); } else { if (iStatus == 1) { printf("\n\nGame over, the computer player won.\n"); } else { printf("\n\nGame over, draw.\n"); } } } /* Draw the game board as characters on the screen. */ void drawBoard(char* board) { int i = 0; printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); /* Get some vertical white space. */ for (i = 0; i < 9; i++) { if (i % 3 == 0) printf(" "); /* Left edge padding. */ printf(" %c ", board[i]); /* Print the board character. */ if ((i + 1) % 3 == 0) printf("\n\n"); /* Skip a line. */ } printf("\n\n\n"); } /* Get the human's move. */ int getHumanMove(char* board) { int r = 0; int i = 0; int iNumAvailableMoves = 0; int iAvailable[9]; int iNotCorrect = 1; for (i = 0; i < 9; i++) { if (board[i] == '-') { iAvailable[iNumAvailableMoves] = i; iNumAvailableMoves++; } } printf("Which square? ("); for (i = 0; i < iNumAvailableMoves - 1; i++) { printf("%d, ", iAvailable[i] + 1); } printf("%d) ", iAvailable[iNumAvailableMoves - 1] + 1); while (iNotCorrect) { scanf("%d", &r); fflush(stdin); for (i = 0; i < iNumAvailableMoves; i++) { if ((r - 1) == iAvailable[i]) { iNotCorrect = 0; break; } } if (iNotCorrect) /* Don't let the human input an unavailable move. */ { printf("\nThat move is not available. Try again. "); } } return r - 1; /* Translate from human's one-based index to zero-based index. */ } /* The classic min-max algorithm. */ void computerMove(GameNode* gn) { int i = 0; int j = 0; int iStatus = 0; /* Game metric semaphore: 1 is computer won, -1 is human won. */ int iNumAvailableMoves = 0; /* Number of available moves (= branching factor). */ int iAvailable[9]; /* Available move indices. */ int iMin = 0; int iMax = 0; iNumAvailableMoves = getAvailableMoves(gn, iAvailable); /* See if the game is over */ iStatus = evaluation(gn->cConfig); if (iStatus != 0 || iNumAvailableMoves == 0) { /* Game is over, bottom of tree (at a leaf). Carry the outcome back up. */ gn->iMetric = iStatus; //gn->iDiagnostic = gn->iBestMove; } else { /* Game is not over, keep recursion going... */ /* Construct my children: */ constructGameNodeChildren(gn, iNumAvailableMoves); /* One child to try each available move. */ /* Spawn by recursion: */ for (i = 0; i < iNumAvailableMoves; i++) { /* Toggle the computer move flag: */ gn->gnChild[i]->iComputerMove = !(gn->iComputerMove); gn->gnChild[i]->iMetric = 0; /* Copy the child initial board configuration from the parent: */ for (j = 0; j < 9; j++) { gn->gnChild[i]->cConfig[j] = gn->cConfig[j]; } /* Each child makes a different available move: */ if (gn->iComputerMove) { gn->gnChild[i]->cConfig[iAvailable[i]] = 'O'; } else { gn->gnChild[i]->cConfig[iAvailable[i]] = 'X'; } /* Recursion */ computerMove(gn->gnChild[i]); } /* All recursion is complete now. Initialize the default best move */ gn->iBestMove = iAvailable[0]; iMax = gn->gnChild[0]->iMetric; iMin = gn->gnChild[0]->iMetric; /* Maximize on the computer's move, minimize on the human's move. */ if (gn->iComputerMove) { /* It's the computer's move, search the children for the best outcome (1). */ for (i = 1; i < iNumAvailableMoves; i++) { if (gn->gnChild[i]->iMetric > iMax) { iMax = gn->gnChild[i]->iMetric; gn->iBestMove = iAvailable[i]; if (iMax == 1) break; } } gn->iMetric = iMax; } else { /* It's the human's move, search the children for the worst outcome (-1). */ for (i = 1; i < iNumAvailableMoves; i++) { if (gn->gnChild[i]->iMetric < iMin) { iMin = gn->gnChild[i]->iMetric; gn->iBestMove = iAvailable[i]; if (iMin == -1) break; } } gn->iMetric = iMin; } /* Recursion is complete and we've gathered the information: destroy the children. */ destroyGameNodeChildren(gn, iNumAvailableMoves); } } int getAvailableMoves(GameNode* gnNode, int* iAvailable) { /* The hyphen indicates an empty position. */ int iNumAvailable = 0; int i = 0; for (i = 0; i < 9; i++) { if (gnNode->cConfig[i] == '-') { iAvailable[iNumAvailable] = i; iNumAvailable++; } } return iNumAvailable; } /* Return 1 for computer victory, -1 for human victory, 0 for neither. */ int evaluation(char* board) { /* The game is over if there are three in a row: */ int i = 0; for (i = 0; i < 3; i++) { /* Check for rows: */ if (board[i * 3 + 0] == 'X' && board[i * 3 + 1] == 'X' && board[i * 3 + 2] == 'X') { /* We have a row of Xs and the human wins. */ return -1; } if (board[i * 3 + 0] == 'O' && board[i * 3 + 1] == 'O' && board[i * 3 + 2] == 'O') { /* We have a row of Os and the computer wins. */ return 1; } /* Check for columns: */ if (board[0 + i] == 'X' && board[3 + i] == 'X' && board[6 + i] == 'X') { /* We have a column of Xs and the human wins. */ return -1; } if (board[0 + i] == 'O' && board[3 + i] == 'O' && board[6 + i] == 'O') { /* We have a column of Os and the computer wins. */ return 1; } } /* Check for diagonals: */ if (board[0] == 'X' && board[4] == 'X' && board[8] == 'X') { /* We have a diagonal of Xs and the human wins. */ return -1; } if (board[2] == 'X' && board[4] == 'X' && board[6] == 'X') { /* We have a diagonal of Xs and the human wins. */ return -1; } if (board[0] == 'O' && board[4] == 'O' && board[8] == 'O') { /* We have a diagonal of Os and the computer wins. */ return 1; } if (board[2] == 'O' && board[4] == 'O' && board[6] == 'O') { /* We have a diagonal of Os and the computer wins. */ return 1; } return 0; }