From: Scott Gasch Date: Thu, 2 Jun 2016 03:13:13 +0000 (-0700) Subject: Remove more CRs. X-Git-Url: https://wannabe.guru.org/gitweb/?a=commitdiff_plain;h=b16e63d1e9e508fd9ed40a02c4675831dbfb27a4;p=ttt.git Remove more CRs. --- diff --git a/ver1/ttt.c b/ver1/ttt.c index 80efd08..541ba80 100644 --- a/ver1/ttt.c +++ b/ver1/ttt.c @@ -1,434 +1,434 @@ -#include -#include -#include -#include -#include "ttt.h" - -SQUARE g_sComputerPlays = O_MARK; // what side comp plays -unsigned int g_uPly = 0; -MOVE g_mvBest = { 0 }; - -//+---------------------------------------------------------------------------- -// -// Function: SquareContentsToChar -// -// Synopsis: Helper function for DrawBoard -// -// Arguments: IN SQUARE s - a square to return a char to represent -// -// Returns: char - character representing square -// -//+---------------------------------------------------------------------------- -char SquareContentsToChar(IN SQUARE s) -{ - static char c; - switch(s) - { - case X_MARK: - c = 'X'; - break; - case O_MARK: - c = 'O'; - break; - case EMPTY: - c = '_'; - break; - default: - ASSERT(FALSE); - c = '?'; - break; - } - return(c); -} - -//+---------------------------------------------------------------------------- -// -// Function: DrawBoard -// -// Synopsis: Draw the board -// -// Arguments: IN POSITION *p - pointer to a position whose board to draw -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void DrawBoard(IN POSITION *p) -{ - COORD x, y; - - for (y = 0; y < BOARD_SIZE; y++) - { - for (x = 0; x < BOARD_SIZE; x++) - { - printf("%c ", SquareContentsToChar(p->sBoard[y][x])); - } - printf("\n"); - } - ASSERT(X_OR_O(p->sWhoseTurn)); - printf("\n%c to move.\n", SquareContentsToChar(p->sWhoseTurn)); -} - -//+---------------------------------------------------------------------------- -// -// Function: ClearBoard -// -// Synopsis: Clear the board -// -// Arguments: IN OUT POSITION *p - pointer to position whose board to clear -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void ClearBoard(IN OUT POSITION *p) -{ - memset(p->sBoard, 0, sizeof(p->sBoard)); - p->sWhoseTurn = X_MARK; // x's go first - p->uNumEmpty = (BOARD_SIZE * BOARD_SIZE); -} - -//+---------------------------------------------------------------------------- -// -// Function: IsLegalMove -// -// Synopsis: Determine if a given move is legal on a given board -// -// Arguments: IN POSITION *p - the board to play the move on -// IN MOVE *m - the move in question -// -// Returns: BOOL - TRUE if it's legal, FALSE otherwise -// -//+---------------------------------------------------------------------------- -BOOL IsLegalMove(IN POSITION *p, IN MOVE *m) -{ - if ((m->cVpos < BOARD_SIZE) && (m->cHpos < BOARD_SIZE)) - { - if (IS_SQUARE_EMPTY(p->sBoard[m->cVpos][m->cHpos])) - { - return(TRUE); - } - } - return(FALSE); -} - -//+---------------------------------------------------------------------------- -// -// Function: GetHumanMove -// -// Synopsis: Ask the human for a move -// -// Arguments: IN POSITION *p - the current board -// OUT MOVE *m - the move the human made; this struct is populated -// as a side-effect of this function. -// -// Returns: void* (populates the move struct) -// -//+---------------------------------------------------------------------------- -void GetHumanMove(IN POSITION *p, OUT MOVE *m) -{ - unsigned int x; - - do - { - printf("Enter your move number: "); - scanf("%u", &x); - - m->cHpos = NUM_TO_HPOS(x); - m->cVpos = NUM_TO_VPOS(x); - m->sMark = OPPOSITE_MARK(g_sComputerPlays); - } - while(FALSE == IsLegalMove(p, m)); -} - -//+---------------------------------------------------------------------------- -// -// Function: GameOver -// -// Synopsis: Is the game over? -// -// Arguments: IN POSITION *p - the board -// OUT SQUARE *psWhoWon - who won the game (if it's over) -// -// Returns: TRUE if the game is over. Also sets psWhoWon telling -// which side one if the game is over. This also serves -// as a very simple evaluation routine for the search. -// -// FALSE if the game is not over. -// -//+---------------------------------------------------------------------------- -BOOL GameOver(IN POSITION *p, OUT SQUARE *psWhoWon) -{ - int iSum; - COORD x, y; - - for (x = 0; x < BOARD_SIZE; x++) - { - iSum = 0; - - for (y = 0; y < BOARD_SIZE; y++) - { - iSum += p->sBoard[x][y]; - } - if (abs(iSum) == BOARD_SIZE) goto winner; - } - - for (y = 0; y < BOARD_SIZE; y++) - { - iSum = 0; - - for (x = 0; x < BOARD_SIZE; x++) - { - iSum += p->sBoard[x][y]; - } - if (abs(iSum) == BOARD_SIZE) goto winner; - } - - iSum = 0; - for (x = 0; x < BOARD_SIZE; x++) - { - iSum += p->sBoard[x][x]; - } - if (abs(iSum) == BOARD_SIZE) goto winner; - - iSum = 0; - for (x = 0; x < BOARD_SIZE; x++) - { - iSum += p->sBoard[x][(BOARD_SIZE - 1 - x)]; - } - if (abs(iSum) == BOARD_SIZE) goto winner; - - *psWhoWon = EMPTY; - if (p->uNumEmpty == 0) - { - return(TRUE); - } - else - { - return(FALSE); - } - - winner: - *psWhoWon = (iSum / BOARD_SIZE); - ASSERT(X_OR_O(*psWhoWon)); - return(TRUE); -} - - -//+---------------------------------------------------------------------------- -// -// Function: MakeMove -// -// Synopsis: Make a move on a board -// -// Arguments: IN OUT POSITION *p - the board -// IN MOVE *m - the move -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void MakeMove(IN OUT POSITION *p, IN MOVE *m) -{ - if (TRUE == IsLegalMove(p, m)) - { - ASSERT(p->sBoard[m->cVpos][m->cHpos] == EMPTY); - p->sBoard[m->cVpos][m->cHpos] = m->sMark; - p->uNumEmpty--; - ASSERT(p->uNumEmpty < (BOARD_SIZE * BOARD_SIZE)); - p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); - g_uPly++; - ASSERT(g_uPly > 0); - } -} - -//+---------------------------------------------------------------------------- -// -// Function: UnmakeMove -// -// Synopsis: The opposite of MakeMove -// -// Arguments: IN OUT POSITION *p - the board -// IN MOVE *m - the move to undo -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void UnmakeMove(IN OUT POSITION *p, IN MOVE *m) -{ - if (p->sBoard[m->cVpos][m->cHpos] == m->sMark) - { - p->sBoard[m->cVpos][m->cHpos] = EMPTY; - p->uNumEmpty++; - ASSERT(p->uNumEmpty > 0); - ASSERT(p->uNumEmpty <= (BOARD_SIZE * BOARD_SIZE)); - p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); - ASSERT(g_uPly > 0); - g_uPly--; - } -} - - -int -SimpleSearch(IN POSITION *p) -{ - SQUARE sWhoWon; - SQUARE s; - MOVE mv; - int iScore; - int iBestScore = -INFINITY; - - // - // Evaluate this position - // - if (TRUE == GameOver(p, &sWhoWon)) - { - if (sWhoWon == p->sWhoseTurn) - { - return(+INFINITY); - } - else if (sWhoWon == (p->sWhoseTurn * -1)) - { - return(-INFINITY); - } - return(DRAWSCORE); - } - - // - // No one won, game is still going. Evaluate every - // possible move from here. - // - ASSERT(p->uNumEmpty > 0); - for (s = 0; s < (BOARD_SIZE * BOARD_SIZE); s++) - { - mv.cHpos = NUM_TO_HPOS(s); - mv.cVpos = NUM_TO_VPOS(s); - mv.sMark = p->sWhoseTurn; - - if (IsLegalMove(p, &mv)) - { - MakeMove(p, &mv); - - iScore = -1 * SimpleSearch(p); - if (iScore > iBestScore) - { - iBestScore = iScore; - if (g_uPly == 1) - { - g_mvBest = mv; - } - } - - UnmakeMove(p, &mv); - } - } - return(iBestScore); -} - -//+---------------------------------------------------------------------------- -// -// Function: SearchForComputerMove -// -// Synopsis: Use our sophisticated search algorithm to find a computer -// move -// -// Arguments: IN POSITION *p - the current board -// OUT MOVE *m - the move the computer chooses; this move struct -// is populated as a side-effect of this function. -// -// Returns: void* (populates move struct) -// -//+---------------------------------------------------------------------------- -void SearchForComputerMove(IN POSITION *p, OUT MOVE *m) -{ - unsigned int x; - -#if defined(PLAY_RANDOMLY) - do - { - x = rand() % (BOARD_SIZE * BOARD_SIZE); - m->cHpos = NUM_TO_HPOS(x); - m->cVpos = NUM_TO_VPOS(x); - m->sMark = g_sComputerPlays; - } - while(FALSE == IsLegalMove(p, m)); -#elif defined(SIMPLE_SEARCH) - g_uPly = 0; - SimpleSearch(p); - *m = g_mvBest; -#else - #error "No Search Strategy Defined" -#endif -} - -//+---------------------------------------------------------------------------- -// -// Function: main -// -// Synopsis: The program entry point and main game loop. -// -// Arguments: void -// -// Returns: int -// -//+---------------------------------------------------------------------------- -int main(void) -{ - POSITION p; - MOVE mv; - SQUARE sResult; - - // - // Randomize: the random numbers returned by rand() will be based on - // the system clock when the program starts up. - // - srand(time(0)); - - // - // Setup the board and draw it once. - // - ClearBoard(&p); - DrawBoard(&p); - - // - // Main game loop - // - do - { - // - // See whose turn it is -- the human's or the computers -- and - // get a move from whoever's turn it is. - // - if (p.sWhoseTurn == g_sComputerPlays) - { - SearchForComputerMove(&p, &mv); - } - else - { - GetHumanMove(&p, &mv); - } - - // - // Make the move on the board and draw the board again. - // - MakeMove(&p, &mv); - DrawBoard(&p); - } - while(FALSE == GameOver(&p, &sResult)); - - // - // If we get here the game is over... see what happened. - // - switch(sResult) - { - case X_MARK: - printf("\nX's win.\n"); - break; - case O_MARK: - printf("\nO's win.\n"); - break; - default: - printf("Tie (what a surprise)\n"); - break; - } - - exit(0); -} - +#include +#include +#include +#include +#include "ttt.h" + +SQUARE g_sComputerPlays = O_MARK; // what side comp plays +unsigned int g_uPly = 0; +MOVE g_mvBest = { 0 }; + +//+---------------------------------------------------------------------------- +// +// Function: SquareContentsToChar +// +// Synopsis: Helper function for DrawBoard +// +// Arguments: IN SQUARE s - a square to return a char to represent +// +// Returns: char - character representing square +// +//+---------------------------------------------------------------------------- +char SquareContentsToChar(IN SQUARE s) +{ + static char c; + switch(s) + { + case X_MARK: + c = 'X'; + break; + case O_MARK: + c = 'O'; + break; + case EMPTY: + c = '_'; + break; + default: + ASSERT(FALSE); + c = '?'; + break; + } + return(c); +} + +//+---------------------------------------------------------------------------- +// +// Function: DrawBoard +// +// Synopsis: Draw the board +// +// Arguments: IN POSITION *p - pointer to a position whose board to draw +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void DrawBoard(IN POSITION *p) +{ + COORD x, y; + + for (y = 0; y < BOARD_SIZE; y++) + { + for (x = 0; x < BOARD_SIZE; x++) + { + printf("%c ", SquareContentsToChar(p->sBoard[y][x])); + } + printf("\n"); + } + ASSERT(X_OR_O(p->sWhoseTurn)); + printf("\n%c to move.\n", SquareContentsToChar(p->sWhoseTurn)); +} + +//+---------------------------------------------------------------------------- +// +// Function: ClearBoard +// +// Synopsis: Clear the board +// +// Arguments: IN OUT POSITION *p - pointer to position whose board to clear +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void ClearBoard(IN OUT POSITION *p) +{ + memset(p->sBoard, 0, sizeof(p->sBoard)); + p->sWhoseTurn = X_MARK; // x's go first + p->uNumEmpty = (BOARD_SIZE * BOARD_SIZE); +} + +//+---------------------------------------------------------------------------- +// +// Function: IsLegalMove +// +// Synopsis: Determine if a given move is legal on a given board +// +// Arguments: IN POSITION *p - the board to play the move on +// IN MOVE *m - the move in question +// +// Returns: BOOL - TRUE if it's legal, FALSE otherwise +// +//+---------------------------------------------------------------------------- +BOOL IsLegalMove(IN POSITION *p, IN MOVE *m) +{ + if ((m->cVpos < BOARD_SIZE) && (m->cHpos < BOARD_SIZE)) + { + if (IS_SQUARE_EMPTY(p->sBoard[m->cVpos][m->cHpos])) + { + return(TRUE); + } + } + return(FALSE); +} + +//+---------------------------------------------------------------------------- +// +// Function: GetHumanMove +// +// Synopsis: Ask the human for a move +// +// Arguments: IN POSITION *p - the current board +// OUT MOVE *m - the move the human made; this struct is populated +// as a side-effect of this function. +// +// Returns: void* (populates the move struct) +// +//+---------------------------------------------------------------------------- +void GetHumanMove(IN POSITION *p, OUT MOVE *m) +{ + unsigned int x; + + do + { + printf("Enter your move number: "); + scanf("%u", &x); + + m->cHpos = NUM_TO_HPOS(x); + m->cVpos = NUM_TO_VPOS(x); + m->sMark = OPPOSITE_MARK(g_sComputerPlays); + } + while(FALSE == IsLegalMove(p, m)); +} + +//+---------------------------------------------------------------------------- +// +// Function: GameOver +// +// Synopsis: Is the game over? +// +// Arguments: IN POSITION *p - the board +// OUT SQUARE *psWhoWon - who won the game (if it's over) +// +// Returns: TRUE if the game is over. Also sets psWhoWon telling +// which side one if the game is over. This also serves +// as a very simple evaluation routine for the search. +// +// FALSE if the game is not over. +// +//+---------------------------------------------------------------------------- +BOOL GameOver(IN POSITION *p, OUT SQUARE *psWhoWon) +{ + int iSum; + COORD x, y; + + for (x = 0; x < BOARD_SIZE; x++) + { + iSum = 0; + + for (y = 0; y < BOARD_SIZE; y++) + { + iSum += p->sBoard[x][y]; + } + if (abs(iSum) == BOARD_SIZE) goto winner; + } + + for (y = 0; y < BOARD_SIZE; y++) + { + iSum = 0; + + for (x = 0; x < BOARD_SIZE; x++) + { + iSum += p->sBoard[x][y]; + } + if (abs(iSum) == BOARD_SIZE) goto winner; + } + + iSum = 0; + for (x = 0; x < BOARD_SIZE; x++) + { + iSum += p->sBoard[x][x]; + } + if (abs(iSum) == BOARD_SIZE) goto winner; + + iSum = 0; + for (x = 0; x < BOARD_SIZE; x++) + { + iSum += p->sBoard[x][(BOARD_SIZE - 1 - x)]; + } + if (abs(iSum) == BOARD_SIZE) goto winner; + + *psWhoWon = EMPTY; + if (p->uNumEmpty == 0) + { + return(TRUE); + } + else + { + return(FALSE); + } + + winner: + *psWhoWon = (iSum / BOARD_SIZE); + ASSERT(X_OR_O(*psWhoWon)); + return(TRUE); +} + + +//+---------------------------------------------------------------------------- +// +// Function: MakeMove +// +// Synopsis: Make a move on a board +// +// Arguments: IN OUT POSITION *p - the board +// IN MOVE *m - the move +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void MakeMove(IN OUT POSITION *p, IN MOVE *m) +{ + if (TRUE == IsLegalMove(p, m)) + { + ASSERT(p->sBoard[m->cVpos][m->cHpos] == EMPTY); + p->sBoard[m->cVpos][m->cHpos] = m->sMark; + p->uNumEmpty--; + ASSERT(p->uNumEmpty < (BOARD_SIZE * BOARD_SIZE)); + p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); + g_uPly++; + ASSERT(g_uPly > 0); + } +} + +//+---------------------------------------------------------------------------- +// +// Function: UnmakeMove +// +// Synopsis: The opposite of MakeMove +// +// Arguments: IN OUT POSITION *p - the board +// IN MOVE *m - the move to undo +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void UnmakeMove(IN OUT POSITION *p, IN MOVE *m) +{ + if (p->sBoard[m->cVpos][m->cHpos] == m->sMark) + { + p->sBoard[m->cVpos][m->cHpos] = EMPTY; + p->uNumEmpty++; + ASSERT(p->uNumEmpty > 0); + ASSERT(p->uNumEmpty <= (BOARD_SIZE * BOARD_SIZE)); + p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); + ASSERT(g_uPly > 0); + g_uPly--; + } +} + + +int +SimpleSearch(IN POSITION *p) +{ + SQUARE sWhoWon; + SQUARE s; + MOVE mv; + int iScore; + int iBestScore = -INFINITY; + + // + // Evaluate this position + // + if (TRUE == GameOver(p, &sWhoWon)) + { + if (sWhoWon == p->sWhoseTurn) + { + return(+INFINITY); + } + else if (sWhoWon == (p->sWhoseTurn * -1)) + { + return(-INFINITY); + } + return(DRAWSCORE); + } + + // + // No one won, game is still going. Evaluate every + // possible move from here. + // + ASSERT(p->uNumEmpty > 0); + for (s = 0; s < (BOARD_SIZE * BOARD_SIZE); s++) + { + mv.cHpos = NUM_TO_HPOS(s); + mv.cVpos = NUM_TO_VPOS(s); + mv.sMark = p->sWhoseTurn; + + if (IsLegalMove(p, &mv)) + { + MakeMove(p, &mv); + + iScore = -1 * SimpleSearch(p); + if (iScore > iBestScore) + { + iBestScore = iScore; + if (g_uPly == 1) + { + g_mvBest = mv; + } + } + + UnmakeMove(p, &mv); + } + } + return(iBestScore); +} + +//+---------------------------------------------------------------------------- +// +// Function: SearchForComputerMove +// +// Synopsis: Use our sophisticated search algorithm to find a computer +// move +// +// Arguments: IN POSITION *p - the current board +// OUT MOVE *m - the move the computer chooses; this move struct +// is populated as a side-effect of this function. +// +// Returns: void* (populates move struct) +// +//+---------------------------------------------------------------------------- +void SearchForComputerMove(IN POSITION *p, OUT MOVE *m) +{ + unsigned int x; + +#if defined(PLAY_RANDOMLY) + do + { + x = rand() % (BOARD_SIZE * BOARD_SIZE); + m->cHpos = NUM_TO_HPOS(x); + m->cVpos = NUM_TO_VPOS(x); + m->sMark = g_sComputerPlays; + } + while(FALSE == IsLegalMove(p, m)); +#elif defined(SIMPLE_SEARCH) + g_uPly = 0; + SimpleSearch(p); + *m = g_mvBest; +#else + #error "No Search Strategy Defined" +#endif +} + +//+---------------------------------------------------------------------------- +// +// Function: main +// +// Synopsis: The program entry point and main game loop. +// +// Arguments: void +// +// Returns: int +// +//+---------------------------------------------------------------------------- +int main(void) +{ + POSITION p; + MOVE mv; + SQUARE sResult; + + // + // Randomize: the random numbers returned by rand() will be based on + // the system clock when the program starts up. + // + srand(time(0)); + + // + // Setup the board and draw it once. + // + ClearBoard(&p); + DrawBoard(&p); + + // + // Main game loop + // + do + { + // + // See whose turn it is -- the human's or the computers -- and + // get a move from whoever's turn it is. + // + if (p.sWhoseTurn == g_sComputerPlays) + { + SearchForComputerMove(&p, &mv); + } + else + { + GetHumanMove(&p, &mv); + } + + // + // Make the move on the board and draw the board again. + // + MakeMove(&p, &mv); + DrawBoard(&p); + } + while(FALSE == GameOver(&p, &sResult)); + + // + // If we get here the game is over... see what happened. + // + switch(sResult) + { + case X_MARK: + printf("\nX's win.\n"); + break; + case O_MARK: + printf("\nO's win.\n"); + break; + default: + printf("Tie (what a surprise)\n"); + break; + } + + exit(0); +} + diff --git a/ver2/ttt.c b/ver2/ttt.c index d39bc7e..91b75ed 100644 --- a/ver2/ttt.c +++ b/ver2/ttt.c @@ -1,535 +1,535 @@ -/*++ - -Module Name: - - ttt.c - -Abstract: - - tic tac toe program to illustrate simple minimax searching - -Author: - - Scott Gasch (SGasch) 18 Mar 2004 - -Revision History: - - ver0 : random play - ver1 : simple search - ver2 : alpha beta search - ---*/ - -#include -#include -#include -#include -#include "ttt.h" - -SQUARE g_sComputerPlays = O_MARK; // what side comp plays -unsigned int g_uPly = 0; -MOVE g_mvBest = { 0 }; -unsigned int g_uNodes = 0; - -//+---------------------------------------------------------------------------- -// -// Function: SquareContentsToChar -// -// Synopsis: Helper function for DrawBoard -// -// Arguments: IN SQUARE s - a square to return a char to represent -// -// Returns: char - character representing square -// -//+---------------------------------------------------------------------------- -char SquareContentsToChar(IN SQUARE s) -{ - static char c; - switch(s) - { - case X_MARK: - c = 'X'; - break; - case O_MARK: - c = 'O'; - break; - case EMPTY: - c = '_'; - break; - default: - ASSERT(FALSE); - c = '?'; - break; - } - return(c); -} - -//+---------------------------------------------------------------------------- -// -// Function: DrawBoard -// -// Synopsis: Draw the board -// -// Arguments: IN POSITION *p - pointer to a position whose board to draw -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void DrawBoard(IN POSITION *p) -{ - COORD x, y; - - for (y = 0; y < BOARD_SIZE; y++) - { - for (x = 0; x < BOARD_SIZE; x++) - { - printf("%c ", SquareContentsToChar(p->sBoard[y][x])); - } - printf("\n"); - } - ASSERT(X_OR_O(p->sWhoseTurn)); - printf("\n%c to move.\n", SquareContentsToChar(p->sWhoseTurn)); -} - -//+---------------------------------------------------------------------------- -// -// Function: ClearBoard -// -// Synopsis: Clear the board -// -// Arguments: IN OUT POSITION *p - pointer to position whose board to clear -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void ClearBoard(IN OUT POSITION *p) -{ - memset(p->sBoard, 0, sizeof(p->sBoard)); - p->sWhoseTurn = X_MARK; // x's go first - p->uNumEmpty = (BOARD_SIZE * BOARD_SIZE); -} - -//+---------------------------------------------------------------------------- -// -// Function: IsLegalMove -// -// Synopsis: Determine if a given move is legal on a given board -// -// Arguments: IN POSITION *p - the board to play the move on -// IN MOVE *m - the move in question -// -// Returns: BOOL - TRUE if it's legal, FALSE otherwise -// -//+---------------------------------------------------------------------------- -BOOL IsLegalMove(IN POSITION *p, IN MOVE *m) -{ - if ((m->cVpos < BOARD_SIZE) && (m->cHpos < BOARD_SIZE)) - { - if (IS_SQUARE_EMPTY(p->sBoard[m->cVpos][m->cHpos])) - { - return(TRUE); - } - } - return(FALSE); -} - -//+---------------------------------------------------------------------------- -// -// Function: GetHumanMove -// -// Synopsis: Ask the human for a move -// -// Arguments: IN POSITION *p - the current board -// OUT MOVE *m - the move the human made; this struct is populated -// as a side-effect of this function. -// -// Returns: void* (populates the move struct) -// -//+---------------------------------------------------------------------------- -void GetHumanMove(IN POSITION *p, OUT MOVE *m) -{ - unsigned int x; - - do - { - printf("Enter your move number: "); - scanf("%u", &x); - - m->cHpos = NUM_TO_HPOS(x); - m->cVpos = NUM_TO_VPOS(x); - m->sMark = OPPOSITE_MARK(g_sComputerPlays); - } - while(FALSE == IsLegalMove(p, m)); -} - -//+---------------------------------------------------------------------------- -// -// Function: GameOver -// -// Synopsis: Is the game over? -// -// Arguments: IN POSITION *p - the board -// OUT SQUARE *psWhoWon - who won the game (if it's over) -// -// Returns: TRUE if the game is over. Also sets psWhoWon telling -// which side one if the game is over. This also serves -// as a very simple evaluation routine for the search. -// -// FALSE if the game is not over. -// -//+---------------------------------------------------------------------------- -BOOL GameOver(IN POSITION *p, OUT SQUARE *psWhoWon) -{ - int iSum; - COORD x, y; - - for (x = 0; x < BOARD_SIZE; x++) - { - iSum = 0; - - for (y = 0; y < BOARD_SIZE; y++) - { - iSum += p->sBoard[x][y]; - } - if (abs(iSum) == BOARD_SIZE) goto winner; - } - - for (y = 0; y < BOARD_SIZE; y++) - { - iSum = 0; - - for (x = 0; x < BOARD_SIZE; x++) - { - iSum += p->sBoard[x][y]; - } - if (abs(iSum) == BOARD_SIZE) goto winner; - } - - iSum = 0; - for (x = 0; x < BOARD_SIZE; x++) - { - iSum += p->sBoard[x][x]; - } - if (abs(iSum) == BOARD_SIZE) goto winner; - - iSum = 0; - for (x = 0; x < BOARD_SIZE; x++) - { - iSum += p->sBoard[x][(BOARD_SIZE - 1 - x)]; - } - if (abs(iSum) == BOARD_SIZE) goto winner; - - *psWhoWon = EMPTY; - if (p->uNumEmpty == 0) - { - return(TRUE); - } - else - { - return(FALSE); - } - - winner: - *psWhoWon = (iSum / BOARD_SIZE); - ASSERT(X_OR_O(*psWhoWon)); - return(TRUE); -} - - -//+---------------------------------------------------------------------------- -// -// Function: MakeMove -// -// Synopsis: Make a move on a board -// -// Arguments: IN OUT POSITION *p - the board -// IN MOVE *m - the move -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void MakeMove(IN OUT POSITION *p, IN MOVE *m) -{ - if (TRUE == IsLegalMove(p, m)) - { - ASSERT(p->sBoard[m->cVpos][m->cHpos] == EMPTY); - p->sBoard[m->cVpos][m->cHpos] = m->sMark; - p->uNumEmpty--; - ASSERT(p->uNumEmpty < (BOARD_SIZE * BOARD_SIZE)); - p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); - g_uPly++; - ASSERT(g_uPly > 0); - } -} - -//+---------------------------------------------------------------------------- -// -// Function: UnmakeMove -// -// Synopsis: The opposite of MakeMove -// -// Arguments: IN OUT POSITION *p - the board -// IN MOVE *m - the move to undo -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void UnmakeMove(IN OUT POSITION *p, IN MOVE *m) -{ - if (p->sBoard[m->cVpos][m->cHpos] == m->sMark) - { - p->sBoard[m->cVpos][m->cHpos] = EMPTY; - p->uNumEmpty++; - ASSERT(p->uNumEmpty > 0); - ASSERT(p->uNumEmpty <= (BOARD_SIZE * BOARD_SIZE)); - p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); - ASSERT(g_uPly > 0); - g_uPly--; - } -} - - -int -AlphaBeta(IN POSITION *p, IN int iAlpha, IN int iBeta) -{ - SQUARE sWhoWon; - SQUARE s; - MOVE mv; - int iScore; - int iBestScore = -INFINITY; - - g_uNodes++; - - // - // Evaluate this position - // - if (TRUE == GameOver(p, &sWhoWon)) - { - if (sWhoWon == p->sWhoseTurn) - { - return(+INFINITY - g_uPly); - } - else if (sWhoWon == (p->sWhoseTurn * -1)) - { - return(-INFINITY + g_uPly); - } - return(DRAWSCORE); - } - - // - // No one won, game is still going. Evaluate every - // possible move from here. - // - ASSERT(p->uNumEmpty > 0); - for (s = 0; s < (BOARD_SIZE * BOARD_SIZE); s++) - { - mv.cHpos = NUM_TO_HPOS(s); - mv.cVpos = NUM_TO_VPOS(s); - mv.sMark = p->sWhoseTurn; - - if (IsLegalMove(p, &mv)) - { - MakeMove(p, &mv); - - iScore = -1 * AlphaBeta(p, -iBeta, -iAlpha); - - UnmakeMove(p, &mv); - - if (iScore >= iBeta) - { - return(iScore); - } - - if (iScore > iBestScore) - { - iBestScore = iScore; - - if (iScore > iAlpha) - { - iAlpha = iScore; - if (g_uPly == 0) - { - g_mvBest = mv; - } - } - } - } - } - return(iBestScore); -} - - -int -SimpleSearch(IN POSITION *p) -{ - SQUARE sWhoWon; - SQUARE s; - MOVE mv; - int iScore; - int iBestScore = -INFINITY; - - g_uNodes++; - - // - // Evaluate this position - // - if (TRUE == GameOver(p, &sWhoWon)) - { - if (sWhoWon == p->sWhoseTurn) - { - return(+INFINITY - g_uPly); - } - else if (sWhoWon == (p->sWhoseTurn * -1)) - { - return(-INFINITY + g_uPly); - } - return(DRAWSCORE); - } - - // - // No one won, game is still going. Evaluate every - // possible move from here. - // - ASSERT(p->uNumEmpty > 0); - for (s = 0; s < (BOARD_SIZE * BOARD_SIZE); s++) - { - mv.cHpos = NUM_TO_HPOS(s); - mv.cVpos = NUM_TO_VPOS(s); - mv.sMark = p->sWhoseTurn; - - if (IsLegalMove(p, &mv)) - { - MakeMove(p, &mv); - - iScore = -1 * SimpleSearch(p); - if (iScore > iBestScore) - { - iBestScore = iScore; - if (g_uPly == 1) - { - g_mvBest = mv; - } - } - - UnmakeMove(p, &mv); - } - } - return(iBestScore); -} - -//+---------------------------------------------------------------------------- -// -// Function: SearchForComputerMove -// -// Synopsis: Use our sophisticated search algorithm to find a computer -// move -// -// Arguments: IN POSITION *p - the current board -// OUT MOVE *m - the move the computer chooses; this move struct -// is populated as a side-effect of this function. -// -// Returns: void* (populates move struct) -// -//+---------------------------------------------------------------------------- -void SearchForComputerMove(IN POSITION *p, OUT MOVE *m) -{ - unsigned int x; - -#if defined(PLAY_RANDOMLY) - do - { - x = rand() % (BOARD_SIZE * BOARD_SIZE); - m->cHpos = NUM_TO_HPOS(x); - m->cVpos = NUM_TO_VPOS(x); - m->sMark = g_sComputerPlays; - } - while(FALSE == IsLegalMove(p, m)); -#elif defined(SIMPLE_SEARCH) - g_uPly = g_uNodes = 0; - SimpleSearch(p); - *m = g_mvBest; - printf("Searched %u node(s).\n", g_uNodes); -#elif defined(ALPHA_BETA_SEARCH) - g_uPly = g_uNodes = 0; - AlphaBeta(p, -INFINITY-1, +INFINITY+1); - *m = g_mvBest; - printf("Searched %u node(s).\n", g_uNodes); -#else - #error "No Search Strategy Defined" -#endif -} - -//+---------------------------------------------------------------------------- -// -// Function: main -// -// Synopsis: The program entry point and main game loop. -// -// Arguments: void -// -// Returns: int -// -//+---------------------------------------------------------------------------- -int main(void) -{ - POSITION p; - MOVE mv; - SQUARE sResult; - - // - // Randomize: the random numbers returned by rand() will be based on - // the system clock when the program starts up. - // - srand(time(0)); - - // - // Setup the board and draw it once. - // - ClearBoard(&p); - DrawBoard(&p); - - // - // Main game loop - // - do - { - // - // See whose turn it is -- the human's or the computers -- and - // get a move from whoever's turn it is. - // - if (p.sWhoseTurn == g_sComputerPlays) - { - SearchForComputerMove(&p, &mv); - } - else - { - GetHumanMove(&p, &mv); - } - - // - // Make the move on the board and draw the board again. - // - MakeMove(&p, &mv); - DrawBoard(&p); - } - while(FALSE == GameOver(&p, &sResult)); - - // - // If we get here the game is over... see what happened. - // - switch(sResult) - { - case X_MARK: - printf("\nX's win.\n"); - break; - case O_MARK: - printf("\nO's win.\n"); - break; - default: - printf("Tie (what a surprise)\n"); - break; - } - - exit(0); -} - +/*++ + +Module Name: + + ttt.c + +Abstract: + + tic tac toe program to illustrate simple minimax searching + +Author: + + Scott Gasch (SGasch) 18 Mar 2004 + +Revision History: + + ver0 : random play + ver1 : simple search + ver2 : alpha beta search + +--*/ + +#include +#include +#include +#include +#include "ttt.h" + +SQUARE g_sComputerPlays = O_MARK; // what side comp plays +unsigned int g_uPly = 0; +MOVE g_mvBest = { 0 }; +unsigned int g_uNodes = 0; + +//+---------------------------------------------------------------------------- +// +// Function: SquareContentsToChar +// +// Synopsis: Helper function for DrawBoard +// +// Arguments: IN SQUARE s - a square to return a char to represent +// +// Returns: char - character representing square +// +//+---------------------------------------------------------------------------- +char SquareContentsToChar(IN SQUARE s) +{ + static char c; + switch(s) + { + case X_MARK: + c = 'X'; + break; + case O_MARK: + c = 'O'; + break; + case EMPTY: + c = '_'; + break; + default: + ASSERT(FALSE); + c = '?'; + break; + } + return(c); +} + +//+---------------------------------------------------------------------------- +// +// Function: DrawBoard +// +// Synopsis: Draw the board +// +// Arguments: IN POSITION *p - pointer to a position whose board to draw +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void DrawBoard(IN POSITION *p) +{ + COORD x, y; + + for (y = 0; y < BOARD_SIZE; y++) + { + for (x = 0; x < BOARD_SIZE; x++) + { + printf("%c ", SquareContentsToChar(p->sBoard[y][x])); + } + printf("\n"); + } + ASSERT(X_OR_O(p->sWhoseTurn)); + printf("\n%c to move.\n", SquareContentsToChar(p->sWhoseTurn)); +} + +//+---------------------------------------------------------------------------- +// +// Function: ClearBoard +// +// Synopsis: Clear the board +// +// Arguments: IN OUT POSITION *p - pointer to position whose board to clear +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void ClearBoard(IN OUT POSITION *p) +{ + memset(p->sBoard, 0, sizeof(p->sBoard)); + p->sWhoseTurn = X_MARK; // x's go first + p->uNumEmpty = (BOARD_SIZE * BOARD_SIZE); +} + +//+---------------------------------------------------------------------------- +// +// Function: IsLegalMove +// +// Synopsis: Determine if a given move is legal on a given board +// +// Arguments: IN POSITION *p - the board to play the move on +// IN MOVE *m - the move in question +// +// Returns: BOOL - TRUE if it's legal, FALSE otherwise +// +//+---------------------------------------------------------------------------- +BOOL IsLegalMove(IN POSITION *p, IN MOVE *m) +{ + if ((m->cVpos < BOARD_SIZE) && (m->cHpos < BOARD_SIZE)) + { + if (IS_SQUARE_EMPTY(p->sBoard[m->cVpos][m->cHpos])) + { + return(TRUE); + } + } + return(FALSE); +} + +//+---------------------------------------------------------------------------- +// +// Function: GetHumanMove +// +// Synopsis: Ask the human for a move +// +// Arguments: IN POSITION *p - the current board +// OUT MOVE *m - the move the human made; this struct is populated +// as a side-effect of this function. +// +// Returns: void* (populates the move struct) +// +//+---------------------------------------------------------------------------- +void GetHumanMove(IN POSITION *p, OUT MOVE *m) +{ + unsigned int x; + + do + { + printf("Enter your move number: "); + scanf("%u", &x); + + m->cHpos = NUM_TO_HPOS(x); + m->cVpos = NUM_TO_VPOS(x); + m->sMark = OPPOSITE_MARK(g_sComputerPlays); + } + while(FALSE == IsLegalMove(p, m)); +} + +//+---------------------------------------------------------------------------- +// +// Function: GameOver +// +// Synopsis: Is the game over? +// +// Arguments: IN POSITION *p - the board +// OUT SQUARE *psWhoWon - who won the game (if it's over) +// +// Returns: TRUE if the game is over. Also sets psWhoWon telling +// which side one if the game is over. This also serves +// as a very simple evaluation routine for the search. +// +// FALSE if the game is not over. +// +//+---------------------------------------------------------------------------- +BOOL GameOver(IN POSITION *p, OUT SQUARE *psWhoWon) +{ + int iSum; + COORD x, y; + + for (x = 0; x < BOARD_SIZE; x++) + { + iSum = 0; + + for (y = 0; y < BOARD_SIZE; y++) + { + iSum += p->sBoard[x][y]; + } + if (abs(iSum) == BOARD_SIZE) goto winner; + } + + for (y = 0; y < BOARD_SIZE; y++) + { + iSum = 0; + + for (x = 0; x < BOARD_SIZE; x++) + { + iSum += p->sBoard[x][y]; + } + if (abs(iSum) == BOARD_SIZE) goto winner; + } + + iSum = 0; + for (x = 0; x < BOARD_SIZE; x++) + { + iSum += p->sBoard[x][x]; + } + if (abs(iSum) == BOARD_SIZE) goto winner; + + iSum = 0; + for (x = 0; x < BOARD_SIZE; x++) + { + iSum += p->sBoard[x][(BOARD_SIZE - 1 - x)]; + } + if (abs(iSum) == BOARD_SIZE) goto winner; + + *psWhoWon = EMPTY; + if (p->uNumEmpty == 0) + { + return(TRUE); + } + else + { + return(FALSE); + } + + winner: + *psWhoWon = (iSum / BOARD_SIZE); + ASSERT(X_OR_O(*psWhoWon)); + return(TRUE); +} + + +//+---------------------------------------------------------------------------- +// +// Function: MakeMove +// +// Synopsis: Make a move on a board +// +// Arguments: IN OUT POSITION *p - the board +// IN MOVE *m - the move +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void MakeMove(IN OUT POSITION *p, IN MOVE *m) +{ + if (TRUE == IsLegalMove(p, m)) + { + ASSERT(p->sBoard[m->cVpos][m->cHpos] == EMPTY); + p->sBoard[m->cVpos][m->cHpos] = m->sMark; + p->uNumEmpty--; + ASSERT(p->uNumEmpty < (BOARD_SIZE * BOARD_SIZE)); + p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); + g_uPly++; + ASSERT(g_uPly > 0); + } +} + +//+---------------------------------------------------------------------------- +// +// Function: UnmakeMove +// +// Synopsis: The opposite of MakeMove +// +// Arguments: IN OUT POSITION *p - the board +// IN MOVE *m - the move to undo +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void UnmakeMove(IN OUT POSITION *p, IN MOVE *m) +{ + if (p->sBoard[m->cVpos][m->cHpos] == m->sMark) + { + p->sBoard[m->cVpos][m->cHpos] = EMPTY; + p->uNumEmpty++; + ASSERT(p->uNumEmpty > 0); + ASSERT(p->uNumEmpty <= (BOARD_SIZE * BOARD_SIZE)); + p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); + ASSERT(g_uPly > 0); + g_uPly--; + } +} + + +int +AlphaBeta(IN POSITION *p, IN int iAlpha, IN int iBeta) +{ + SQUARE sWhoWon; + SQUARE s; + MOVE mv; + int iScore; + int iBestScore = -INFINITY; + + g_uNodes++; + + // + // Evaluate this position + // + if (TRUE == GameOver(p, &sWhoWon)) + { + if (sWhoWon == p->sWhoseTurn) + { + return(+INFINITY - g_uPly); + } + else if (sWhoWon == (p->sWhoseTurn * -1)) + { + return(-INFINITY + g_uPly); + } + return(DRAWSCORE); + } + + // + // No one won, game is still going. Evaluate every + // possible move from here. + // + ASSERT(p->uNumEmpty > 0); + for (s = 0; s < (BOARD_SIZE * BOARD_SIZE); s++) + { + mv.cHpos = NUM_TO_HPOS(s); + mv.cVpos = NUM_TO_VPOS(s); + mv.sMark = p->sWhoseTurn; + + if (IsLegalMove(p, &mv)) + { + MakeMove(p, &mv); + + iScore = -1 * AlphaBeta(p, -iBeta, -iAlpha); + + UnmakeMove(p, &mv); + + if (iScore >= iBeta) + { + return(iScore); + } + + if (iScore > iBestScore) + { + iBestScore = iScore; + + if (iScore > iAlpha) + { + iAlpha = iScore; + if (g_uPly == 0) + { + g_mvBest = mv; + } + } + } + } + } + return(iBestScore); +} + + +int +SimpleSearch(IN POSITION *p) +{ + SQUARE sWhoWon; + SQUARE s; + MOVE mv; + int iScore; + int iBestScore = -INFINITY; + + g_uNodes++; + + // + // Evaluate this position + // + if (TRUE == GameOver(p, &sWhoWon)) + { + if (sWhoWon == p->sWhoseTurn) + { + return(+INFINITY - g_uPly); + } + else if (sWhoWon == (p->sWhoseTurn * -1)) + { + return(-INFINITY + g_uPly); + } + return(DRAWSCORE); + } + + // + // No one won, game is still going. Evaluate every + // possible move from here. + // + ASSERT(p->uNumEmpty > 0); + for (s = 0; s < (BOARD_SIZE * BOARD_SIZE); s++) + { + mv.cHpos = NUM_TO_HPOS(s); + mv.cVpos = NUM_TO_VPOS(s); + mv.sMark = p->sWhoseTurn; + + if (IsLegalMove(p, &mv)) + { + MakeMove(p, &mv); + + iScore = -1 * SimpleSearch(p); + if (iScore > iBestScore) + { + iBestScore = iScore; + if (g_uPly == 1) + { + g_mvBest = mv; + } + } + + UnmakeMove(p, &mv); + } + } + return(iBestScore); +} + +//+---------------------------------------------------------------------------- +// +// Function: SearchForComputerMove +// +// Synopsis: Use our sophisticated search algorithm to find a computer +// move +// +// Arguments: IN POSITION *p - the current board +// OUT MOVE *m - the move the computer chooses; this move struct +// is populated as a side-effect of this function. +// +// Returns: void* (populates move struct) +// +//+---------------------------------------------------------------------------- +void SearchForComputerMove(IN POSITION *p, OUT MOVE *m) +{ + unsigned int x; + +#if defined(PLAY_RANDOMLY) + do + { + x = rand() % (BOARD_SIZE * BOARD_SIZE); + m->cHpos = NUM_TO_HPOS(x); + m->cVpos = NUM_TO_VPOS(x); + m->sMark = g_sComputerPlays; + } + while(FALSE == IsLegalMove(p, m)); +#elif defined(SIMPLE_SEARCH) + g_uPly = g_uNodes = 0; + SimpleSearch(p); + *m = g_mvBest; + printf("Searched %u node(s).\n", g_uNodes); +#elif defined(ALPHA_BETA_SEARCH) + g_uPly = g_uNodes = 0; + AlphaBeta(p, -INFINITY-1, +INFINITY+1); + *m = g_mvBest; + printf("Searched %u node(s).\n", g_uNodes); +#else + #error "No Search Strategy Defined" +#endif +} + +//+---------------------------------------------------------------------------- +// +// Function: main +// +// Synopsis: The program entry point and main game loop. +// +// Arguments: void +// +// Returns: int +// +//+---------------------------------------------------------------------------- +int main(void) +{ + POSITION p; + MOVE mv; + SQUARE sResult; + + // + // Randomize: the random numbers returned by rand() will be based on + // the system clock when the program starts up. + // + srand(time(0)); + + // + // Setup the board and draw it once. + // + ClearBoard(&p); + DrawBoard(&p); + + // + // Main game loop + // + do + { + // + // See whose turn it is -- the human's or the computers -- and + // get a move from whoever's turn it is. + // + if (p.sWhoseTurn == g_sComputerPlays) + { + SearchForComputerMove(&p, &mv); + } + else + { + GetHumanMove(&p, &mv); + } + + // + // Make the move on the board and draw the board again. + // + MakeMove(&p, &mv); + DrawBoard(&p); + } + while(FALSE == GameOver(&p, &sResult)); + + // + // If we get here the game is over... see what happened. + // + switch(sResult) + { + case X_MARK: + printf("\nX's win.\n"); + break; + case O_MARK: + printf("\nO's win.\n"); + break; + default: + printf("Tie (what a surprise)\n"); + break; + } + + exit(0); +} + diff --git a/ver3/ttt.c b/ver3/ttt.c index 426fc73..2b54b97 100644 --- a/ver3/ttt.c +++ b/ver3/ttt.c @@ -1,775 +1,775 @@ -/*++ - -Module Name: - - ttt.c - -Abstract: - - tic tac toe program to illustrate simple minimax searching - -Author: - - Scott Gasch (SGasch) 18 Mar 2004 - -Revision History: - - ver0 : random play - ver1 : simple search - ver2 : alpha beta search - ver3 : added eval and depth on ab search, more efficient gaveover - ---*/ - -#include -#include -#include -#include -#include "ttt.h" - -SQUARE g_sComputerPlays = O_MARK; // what side comp plays -unsigned int g_uPly = 0; -MOVE g_mvBest = { 0 }; -unsigned int g_uNodes = 0; - -//+---------------------------------------------------------------------------- -// -// Function: SquareContentsToChar -// -// Synopsis: Helper function for DrawBoard -// -// Arguments: IN SQUARE s - a square to return a char to represent -// -// Returns: char - character representing square -// -//+---------------------------------------------------------------------------- -char SquareContentsToChar(IN SQUARE s) -{ - static char c; - switch(s) - { - case X_MARK: - c = 'X'; - break; - case O_MARK: - c = 'O'; - break; - case EMPTY: - c = '_'; - break; - default: - ASSERT(FALSE); - c = '?'; - break; - } - return(c); -} - -//+---------------------------------------------------------------------------- -// -// Function: DrawBoard -// -// Synopsis: Draw the board -// -// Arguments: IN POSITION *p - pointer to a position whose board to draw -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void DrawBoard(IN POSITION *p) -{ - COORD x, y; - - for (y = 0; y < BOARD_SIZE; y++) - { - for (x = 0; x < BOARD_SIZE; x++) - { - printf("%c ", SquareContentsToChar(p->sBoard[y][x])); - } -#ifdef DEBUG - printf(" = %d\n", p->iHSums[y]); -#else - printf("\n"); -#endif - } - -#ifdef DEBUG - for (x = 0; x < BOARD_SIZE; x++) - { - printf("| "); - } - printf("\n"); - for (x = 0; x < BOARD_SIZE; x++) - { - printf("%d ", p->iVSums[x]); - } - printf("\n"); -#endif - - ASSERT(X_OR_O(p->sWhoseTurn)); - printf("\n%c to move.\n", SquareContentsToChar(p->sWhoseTurn)); -} - -//+---------------------------------------------------------------------------- -// -// Function: ClearBoard -// -// Synopsis: Clear the board -// -// Arguments: IN OUT POSITION *p - pointer to position whose board to clear -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void ClearBoard(IN OUT POSITION *p) -{ - memset(p->sBoard, 0, sizeof(p->sBoard)); - memset(p->iHSums, 0, sizeof(p->iHSums)); - memset(p->iVSums, 0, sizeof(p->iVSums)); - p->iDSums[0] = p->iDSums[1] = 0; - p->sWhoseTurn = X_MARK; // x's go first - p->uNumEmpty = (BOARD_SIZE * BOARD_SIZE); -} - -//+---------------------------------------------------------------------------- -// -// Function: IsLegalMove -// -// Synopsis: Determine if a given move is legal on a given board -// -// Arguments: IN POSITION *p - the board to play the move on -// IN MOVE *m - the move in question -// -// Returns: BOOL - TRUE if it's legal, FALSE otherwise -// -//+---------------------------------------------------------------------------- -BOOL IsLegalMove(IN POSITION *p, IN MOVE *m) -{ - if ((m->cVpos < BOARD_SIZE) && (m->cHpos < BOARD_SIZE)) - { - if (IS_SQUARE_EMPTY(p->sBoard[m->cVpos][m->cHpos])) - { - return(TRUE); - } - } - return(FALSE); -} - -//+---------------------------------------------------------------------------- -// -// Function: GetHumanMove -// -// Synopsis: Ask the human for a move -// -// Arguments: IN POSITION *p - the current board -// OUT MOVE *m - the move the human made; this struct is populated -// as a side-effect of this function. -// -// Returns: void* (populates the move struct) -// -//+---------------------------------------------------------------------------- -void GetHumanMove(IN POSITION *p, OUT MOVE *m) -{ - unsigned int x; - - do - { - printf("Enter your move number: "); - scanf("%u", &x); - - m->cHpos = NUM_TO_HPOS(x); - m->cVpos = NUM_TO_VPOS(x); - m->sMark = OPPOSITE_MARK(g_sComputerPlays); - } - while(FALSE == IsLegalMove(p, m)); -} - - -//+---------------------------------------------------------------------------- -// -// Function: GameOver -// -// Synopsis: Is the game over? -// -// Arguments: IN POSITION *p - the board -// OUT SQUARE *psWhoWon - who won the game (if it's over) -// -// Returns: TRUE if the game is over. Also sets psWhoWon telling -// which side one if the game is over. This also serves -// as a very simple evaluation routine for the search. -// -// FALSE if the game is not over. -// -//+---------------------------------------------------------------------------- -BOOL GameOver(IN POSITION *p, OUT SQUARE *psWhoWon) -{ - int iSum; - COORD x; - - for (x = 0; x < BOARD_SIZE; x++) - { - iSum = p->iHSums[x]; - if (abs(iSum) == BOARD_SIZE) goto winner; - - iSum = p->iVSums[x]; - if (abs(iSum) == BOARD_SIZE) goto winner; - } - - iSum = p->iDSums[0]; - if (abs(iSum) == BOARD_SIZE) goto winner; - - iSum = p->iDSums[1]; - if (abs(iSum) == BOARD_SIZE) goto winner; - - // - // No one won yet, either game ongoing or draw. - // - *psWhoWon = EMPTY; - if (p->uNumEmpty == 0) - { - return(TRUE); - } - else - { - return(FALSE); - } - - winner: - // - // Some side won - // - *psWhoWon = (iSum / BOARD_SIZE); - ASSERT(X_OR_O(*psWhoWon)); - return(TRUE); -} - - -//+---------------------------------------------------------------------------- -// -// Function: CountAdjacents -// -// Synopsis: Return the number of marks in adjacent squares to square x -// that are of the same type (x or o) as the mark in square x. -// -// FIXME: does not consider diagonals -// -// Arguments: IN POSITION *p - the board -// IN SQUARE x - the square to test -// -// Returns: A count -// -//+---------------------------------------------------------------------------- -unsigned int CountAdjacents(IN POSITION *p, IN SQUARE x) -{ - COORD v = NUM_TO_VPOS(x); - COORD h = NUM_TO_HPOS(x); - SQUARE sSide = p->sBoard[h][v]; - unsigned int uCount = 0; - - // - // If nothing at square x, nothing to count - // - if (sSide == EMPTY) goto end; - - // - // Look above, below, left and right - // - if ((v > 0) && (p->sBoard[h][v-1] == sSide)) - { - uCount++; - } - - if ((v < (BOARD_SIZE - 1)) && (p->sBoard[h][v+1] == sSide)) - { - uCount++; - } - - if ((h > 0) && (p->sBoard[h-1][v] == sSide)) - { - uCount++; - } - - if ((h < (BOARD_SIZE - 1)) && (p->sBoard[h+1][v] == sSide)) - { - uCount++; - } - - end: - ASSERT(0 <= uCount); - ASSERT(uCount <= 4); - return(uCount); -} - - -//+---------------------------------------------------------------------------- -// -// Function: Eval -// -// Synopsis: Evaluate a position -// -// Arguments: IN POSITION *p - the board -// -// Returns: A score -// -//+---------------------------------------------------------------------------- -int Eval(IN POSITION *p) -{ - SQUARE sWinner; - COORD x; - SQUARE sMark; - int iScore = 0; - - // - // See if the game is already over. - // - if (TRUE == GameOver(p, &sWinner)) - { - if (sWinner == p->sWhoseTurn) - { - iScore = +INFINITY - g_uPly; - goto end; - } - else if (sWinner == OPPOSITE_MARK(p->sWhoseTurn)) - { - iScore = -INFINITY + g_uPly; - goto end; - } - iScore = DRAWSCORE; - goto end; - } - - // - // No one won but instead of returning score=0 see if we can - // find some "good characteristics" or "bad characteristics" - // of the position and give bonuses / penalties. - // - for (x = BOARD_SIZE + 1; - x < ((BOARD_SIZE * BOARD_SIZE) - BOARD_SIZE - 1); - x++) - { - sMark = p->sBoard[NUM_TO_HPOS(x)][NUM_TO_VPOS(x)]; - if (sMark == p->sWhoseTurn) - { - iScore += CountAdjacents(p, x); - } - else if (sMark == OPPOSITE_MARK(p->sWhoseTurn)) - { - iScore -= CountAdjacents(p, x); - } - } - - end: - ASSERT(-INFINITY <= iScore); - ASSERT(iScore <= +INFINITY); - return(iScore); -} - - - -//+---------------------------------------------------------------------------- -// -// Function: MakeMove -// -// Synopsis: Make a move on a board -// -// Arguments: IN OUT POSITION *p - the board -// IN MOVE *m - the move -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void MakeMove(IN OUT POSITION *p, IN MOVE *m) -{ - if (TRUE == IsLegalMove(p, m)) - { - // - // Make the new make on the board - // - ASSERT(p->sBoard[m->cVpos][m->cHpos] == EMPTY); - p->sBoard[m->cVpos][m->cHpos] = m->sMark; - - // - // One less empty square - // - p->uNumEmpty--; - ASSERT(p->uNumEmpty < (BOARD_SIZE * BOARD_SIZE)); - - // - // Update sums as appropriate - // - p->iHSums[m->cHpos] += m->sMark; - ASSERT(VALID_SUM(p->iHSums[m->cHpos])); - p->iVSums[m->cVpos] += m->sMark; - ASSERT(VALID_SUM(p->iVSums[m->cVpos])); - if (m->cHpos == m->cVpos) - { - p->iDSums[0] += m->sMark; - ASSERT(VALID_SUM(p->iDSums[0])); - } - else if (m->cHpos == (BOARD_SIZE - m->cVpos)) - { - p->iDSums[1] += m->sMark; - ASSERT(VALID_SUM(p->iDSums[1])); - } - - // - // Other guy's turn - // - p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); - ASSERT(X_OR_O(p->sWhoseTurn)); - - // - // One ply deeper - // - g_uPly++; - ASSERT(g_uPly > 0); - } -} - -//+---------------------------------------------------------------------------- -// -// Function: UnmakeMove -// -// Synopsis: The opposite of MakeMove -// -// Arguments: IN OUT POSITION *p - the board -// IN MOVE *m - the move to undo -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void UnmakeMove(IN OUT POSITION *p, IN MOVE *m) -{ - if (p->sBoard[m->cVpos][m->cHpos] == m->sMark) - { - p->sBoard[m->cVpos][m->cHpos] = EMPTY; - - // - // One more empty square - // - p->uNumEmpty++; - ASSERT(p->uNumEmpty > 0); - ASSERT(p->uNumEmpty <= (BOARD_SIZE * BOARD_SIZE)); - - // - // Update sums as appropriate - // - p->iHSums[m->cHpos] -= m->sMark; - ASSERT(VALID_SUM(p->iHSums[m->cHpos])); - p->iVSums[m->cVpos] -= m->sMark; - ASSERT(VALID_SUM(p->iVSums[m->cVpos])); - if (m->cHpos == m->cVpos) - { - p->iDSums[0] -= m->sMark; - ASSERT(VALID_SUM(p->iDSums[0])); - } - else if (m->cHpos == (BOARD_SIZE - m->cVpos)) - { - p->iDSums[1] -= m->sMark; - ASSERT(VALID_SUM(p->iDSums[1])); - } - - // - // Other guy's turn - // - p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); - ASSERT(X_OR_O(p->sWhoseTurn)); - - // - // One ply deeper - // - ASSERT(g_uPly > 0); - g_uPly--; - } -} - - -//+---------------------------------------------------------------------------- -// -// Function: AlphaBeta -// -// Synopsis: An AlphaBeta Search -// -// Arguments: IN OUT POSITION *p - the board -// IN int iAlpha - the lower bound of the score window -// IN int iBeta - the upper bound of the score window -// IN int uDepth - search depth horizon -// -// Returns: int -// -//+---------------------------------------------------------------------------- -int -AlphaBeta(IN POSITION *p, IN int iAlpha, IN int iBeta, IN unsigned int uDepth) -{ - SQUARE sWhoWon; - SQUARE s; - MOVE mv; - int iScore; - int iBestScore = -INFINITY; - - g_uNodes++; - - // - // Evaluate this position - // - if (TRUE == GameOver(p, &sWhoWon)) - { - if (sWhoWon == p->sWhoseTurn) - { - return(+INFINITY - g_uPly); - } - else if (sWhoWon == (p->sWhoseTurn * -1)) - { - return(-INFINITY + g_uPly); - } - return(DRAWSCORE); - } - else - { - if (uDepth == 0) - { - return(Eval(p)); - } - } - - // - // No one won, game is still going. Evaluate every - // possible move from here. - // - ASSERT(p->uNumEmpty > 0); - for (s = 0; s < (BOARD_SIZE * BOARD_SIZE); s++) - { - mv.cHpos = NUM_TO_HPOS(s); - mv.cVpos = NUM_TO_VPOS(s); - mv.sMark = p->sWhoseTurn; - - if (IsLegalMove(p, &mv)) - { - MakeMove(p, &mv); - - iScore = -1 * AlphaBeta(p, -iBeta, -iAlpha, uDepth - 1); - ASSERT(-INFINITY <= iScore); - ASSERT(iScore <= +INFINITY); - - UnmakeMove(p, &mv); - - if (iScore >= iBeta) - { - return(iScore); - } - - if (iScore > iBestScore) - { - iBestScore = iScore; - - if (iScore > iAlpha) - { - iAlpha = iScore; - - // - // If this is the ply 0 move, remember it. - // - if (g_uPly == 0) - { - g_mvBest = mv; - } - } - } - } - } - return(iBestScore); -} - - -//+---------------------------------------------------------------------------- -// -// Function: SimpleSearch -// -// Synopsis: A Simple Search -// -// Arguments: IN OUT POSITION *p - the board -// -// Returns: int -// -//+---------------------------------------------------------------------------- -int -SimpleSearch(IN POSITION *p) -{ - SQUARE sWhoWon; - SQUARE s; - MOVE mv; - int iScore; - int iBestScore = -INFINITY; - - g_uNodes++; - - // - // Evaluate this position - // - if (TRUE == GameOver(p, &sWhoWon)) - { - if (sWhoWon == p->sWhoseTurn) - { - return(+INFINITY - g_uPly); - } - else if (sWhoWon == (p->sWhoseTurn * -1)) - { - return(-INFINITY + g_uPly); - } - return(DRAWSCORE); - } - - // - // No one won, game is still going. Evaluate every - // possible move from here. - // - ASSERT(p->uNumEmpty > 0); - for (s = 0; s < (BOARD_SIZE * BOARD_SIZE); s++) - { - mv.cHpos = NUM_TO_HPOS(s); - mv.cVpos = NUM_TO_VPOS(s); - mv.sMark = p->sWhoseTurn; - - if (IsLegalMove(p, &mv)) - { - MakeMove(p, &mv); - - iScore = -1 * SimpleSearch(p); - if (iScore > iBestScore) - { - iBestScore = iScore; - if (g_uPly == 1) - { - g_mvBest = mv; - } - } - UnmakeMove(p, &mv); - } - } - return(iBestScore); -} - -//+---------------------------------------------------------------------------- -// -// Function: SearchForComputerMove -// -// Synopsis: Use our sophisticated search algorithm to find a computer -// move -// -// Arguments: IN POSITION *p - the current board -// OUT MOVE *m - the move the computer chooses; this move struct -// is populated as a side-effect of this function. -// -// Returns: void* (populates move struct) -// -//+---------------------------------------------------------------------------- -void SearchForComputerMove(IN POSITION *p, OUT MOVE *m) -{ - unsigned int x; - -#if defined(PLAY_RANDOMLY) - - do - { - x = rand() % (BOARD_SIZE * BOARD_SIZE); - m->cHpos = NUM_TO_HPOS(x); - m->cVpos = NUM_TO_VPOS(x); - m->sMark = g_sComputerPlays; - } - while(FALSE == IsLegalMove(p, m)); - -#elif defined(SIMPLE_SEARCH) - - g_uPly = g_uNodes = 0; - SimpleSearch(p); - *m = g_mvBest; - printf("Searched %u node(s).\n", g_uNodes); - -#elif defined(ALPHA_BETA_SEARCH) - - g_uPly = g_uNodes = 0; - AlphaBeta(p, -INFINITY-1, +INFINITY+1, BOARD_SIZE); - *m = g_mvBest; - printf("Searched %u node(s).\n", g_uNodes); - -#else - - #error "No Search Strategy Defined" - -#endif -} - -//+---------------------------------------------------------------------------- -// -// Function: main -// -// Synopsis: The program entry point and main game loop. -// -// Arguments: void -// -// Returns: int -// -//+---------------------------------------------------------------------------- -int main(void) -{ - POSITION p; - MOVE mv; - SQUARE sResult; - - // - // Randomize: the random numbers returned by rand() will be based on - // the system clock when the program starts up. - // - srand(time(0)); - - // - // Setup the board and draw it once. - // - ClearBoard(&p); - DrawBoard(&p); - - // - // Main game loop - // - do - { - // - // See whose turn it is -- the human's or the computers -- and - // get a move from whoever's turn it is. - // - if (p.sWhoseTurn == g_sComputerPlays) - { - SearchForComputerMove(&p, &mv); - } - else - { - GetHumanMove(&p, &mv); - } - - // - // Make the move on the board and draw the board again. - // - MakeMove(&p, &mv); - DrawBoard(&p); - } - while(FALSE == GameOver(&p, &sResult)); - - // - // If we get here the game is over... see what happened. - // - switch(sResult) - { - case X_MARK: - printf("\nX's win.\n"); - break; - case O_MARK: - printf("\nO's win.\n"); - break; - default: - printf("Tie (what a surprise)\n"); - break; - } - - exit(0); -} - +/*++ + +Module Name: + + ttt.c + +Abstract: + + tic tac toe program to illustrate simple minimax searching + +Author: + + Scott Gasch (SGasch) 18 Mar 2004 + +Revision History: + + ver0 : random play + ver1 : simple search + ver2 : alpha beta search + ver3 : added eval and depth on ab search, more efficient gaveover + +--*/ + +#include +#include +#include +#include +#include "ttt.h" + +SQUARE g_sComputerPlays = O_MARK; // what side comp plays +unsigned int g_uPly = 0; +MOVE g_mvBest = { 0 }; +unsigned int g_uNodes = 0; + +//+---------------------------------------------------------------------------- +// +// Function: SquareContentsToChar +// +// Synopsis: Helper function for DrawBoard +// +// Arguments: IN SQUARE s - a square to return a char to represent +// +// Returns: char - character representing square +// +//+---------------------------------------------------------------------------- +char SquareContentsToChar(IN SQUARE s) +{ + static char c; + switch(s) + { + case X_MARK: + c = 'X'; + break; + case O_MARK: + c = 'O'; + break; + case EMPTY: + c = '_'; + break; + default: + ASSERT(FALSE); + c = '?'; + break; + } + return(c); +} + +//+---------------------------------------------------------------------------- +// +// Function: DrawBoard +// +// Synopsis: Draw the board +// +// Arguments: IN POSITION *p - pointer to a position whose board to draw +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void DrawBoard(IN POSITION *p) +{ + COORD x, y; + + for (y = 0; y < BOARD_SIZE; y++) + { + for (x = 0; x < BOARD_SIZE; x++) + { + printf("%c ", SquareContentsToChar(p->sBoard[y][x])); + } +#ifdef DEBUG + printf(" = %d\n", p->iHSums[y]); +#else + printf("\n"); +#endif + } + +#ifdef DEBUG + for (x = 0; x < BOARD_SIZE; x++) + { + printf("| "); + } + printf("\n"); + for (x = 0; x < BOARD_SIZE; x++) + { + printf("%d ", p->iVSums[x]); + } + printf("\n"); +#endif + + ASSERT(X_OR_O(p->sWhoseTurn)); + printf("\n%c to move.\n", SquareContentsToChar(p->sWhoseTurn)); +} + +//+---------------------------------------------------------------------------- +// +// Function: ClearBoard +// +// Synopsis: Clear the board +// +// Arguments: IN OUT POSITION *p - pointer to position whose board to clear +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void ClearBoard(IN OUT POSITION *p) +{ + memset(p->sBoard, 0, sizeof(p->sBoard)); + memset(p->iHSums, 0, sizeof(p->iHSums)); + memset(p->iVSums, 0, sizeof(p->iVSums)); + p->iDSums[0] = p->iDSums[1] = 0; + p->sWhoseTurn = X_MARK; // x's go first + p->uNumEmpty = (BOARD_SIZE * BOARD_SIZE); +} + +//+---------------------------------------------------------------------------- +// +// Function: IsLegalMove +// +// Synopsis: Determine if a given move is legal on a given board +// +// Arguments: IN POSITION *p - the board to play the move on +// IN MOVE *m - the move in question +// +// Returns: BOOL - TRUE if it's legal, FALSE otherwise +// +//+---------------------------------------------------------------------------- +BOOL IsLegalMove(IN POSITION *p, IN MOVE *m) +{ + if ((m->cVpos < BOARD_SIZE) && (m->cHpos < BOARD_SIZE)) + { + if (IS_SQUARE_EMPTY(p->sBoard[m->cVpos][m->cHpos])) + { + return(TRUE); + } + } + return(FALSE); +} + +//+---------------------------------------------------------------------------- +// +// Function: GetHumanMove +// +// Synopsis: Ask the human for a move +// +// Arguments: IN POSITION *p - the current board +// OUT MOVE *m - the move the human made; this struct is populated +// as a side-effect of this function. +// +// Returns: void* (populates the move struct) +// +//+---------------------------------------------------------------------------- +void GetHumanMove(IN POSITION *p, OUT MOVE *m) +{ + unsigned int x; + + do + { + printf("Enter your move number: "); + scanf("%u", &x); + + m->cHpos = NUM_TO_HPOS(x); + m->cVpos = NUM_TO_VPOS(x); + m->sMark = OPPOSITE_MARK(g_sComputerPlays); + } + while(FALSE == IsLegalMove(p, m)); +} + + +//+---------------------------------------------------------------------------- +// +// Function: GameOver +// +// Synopsis: Is the game over? +// +// Arguments: IN POSITION *p - the board +// OUT SQUARE *psWhoWon - who won the game (if it's over) +// +// Returns: TRUE if the game is over. Also sets psWhoWon telling +// which side one if the game is over. This also serves +// as a very simple evaluation routine for the search. +// +// FALSE if the game is not over. +// +//+---------------------------------------------------------------------------- +BOOL GameOver(IN POSITION *p, OUT SQUARE *psWhoWon) +{ + int iSum; + COORD x; + + for (x = 0; x < BOARD_SIZE; x++) + { + iSum = p->iHSums[x]; + if (abs(iSum) == BOARD_SIZE) goto winner; + + iSum = p->iVSums[x]; + if (abs(iSum) == BOARD_SIZE) goto winner; + } + + iSum = p->iDSums[0]; + if (abs(iSum) == BOARD_SIZE) goto winner; + + iSum = p->iDSums[1]; + if (abs(iSum) == BOARD_SIZE) goto winner; + + // + // No one won yet, either game ongoing or draw. + // + *psWhoWon = EMPTY; + if (p->uNumEmpty == 0) + { + return(TRUE); + } + else + { + return(FALSE); + } + + winner: + // + // Some side won + // + *psWhoWon = (iSum / BOARD_SIZE); + ASSERT(X_OR_O(*psWhoWon)); + return(TRUE); +} + + +//+---------------------------------------------------------------------------- +// +// Function: CountAdjacents +// +// Synopsis: Return the number of marks in adjacent squares to square x +// that are of the same type (x or o) as the mark in square x. +// +// FIXME: does not consider diagonals +// +// Arguments: IN POSITION *p - the board +// IN SQUARE x - the square to test +// +// Returns: A count +// +//+---------------------------------------------------------------------------- +unsigned int CountAdjacents(IN POSITION *p, IN SQUARE x) +{ + COORD v = NUM_TO_VPOS(x); + COORD h = NUM_TO_HPOS(x); + SQUARE sSide = p->sBoard[h][v]; + unsigned int uCount = 0; + + // + // If nothing at square x, nothing to count + // + if (sSide == EMPTY) goto end; + + // + // Look above, below, left and right + // + if ((v > 0) && (p->sBoard[h][v-1] == sSide)) + { + uCount++; + } + + if ((v < (BOARD_SIZE - 1)) && (p->sBoard[h][v+1] == sSide)) + { + uCount++; + } + + if ((h > 0) && (p->sBoard[h-1][v] == sSide)) + { + uCount++; + } + + if ((h < (BOARD_SIZE - 1)) && (p->sBoard[h+1][v] == sSide)) + { + uCount++; + } + + end: + ASSERT(0 <= uCount); + ASSERT(uCount <= 4); + return(uCount); +} + + +//+---------------------------------------------------------------------------- +// +// Function: Eval +// +// Synopsis: Evaluate a position +// +// Arguments: IN POSITION *p - the board +// +// Returns: A score +// +//+---------------------------------------------------------------------------- +int Eval(IN POSITION *p) +{ + SQUARE sWinner; + COORD x; + SQUARE sMark; + int iScore = 0; + + // + // See if the game is already over. + // + if (TRUE == GameOver(p, &sWinner)) + { + if (sWinner == p->sWhoseTurn) + { + iScore = +INFINITY - g_uPly; + goto end; + } + else if (sWinner == OPPOSITE_MARK(p->sWhoseTurn)) + { + iScore = -INFINITY + g_uPly; + goto end; + } + iScore = DRAWSCORE; + goto end; + } + + // + // No one won but instead of returning score=0 see if we can + // find some "good characteristics" or "bad characteristics" + // of the position and give bonuses / penalties. + // + for (x = BOARD_SIZE + 1; + x < ((BOARD_SIZE * BOARD_SIZE) - BOARD_SIZE - 1); + x++) + { + sMark = p->sBoard[NUM_TO_HPOS(x)][NUM_TO_VPOS(x)]; + if (sMark == p->sWhoseTurn) + { + iScore += CountAdjacents(p, x); + } + else if (sMark == OPPOSITE_MARK(p->sWhoseTurn)) + { + iScore -= CountAdjacents(p, x); + } + } + + end: + ASSERT(-INFINITY <= iScore); + ASSERT(iScore <= +INFINITY); + return(iScore); +} + + + +//+---------------------------------------------------------------------------- +// +// Function: MakeMove +// +// Synopsis: Make a move on a board +// +// Arguments: IN OUT POSITION *p - the board +// IN MOVE *m - the move +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void MakeMove(IN OUT POSITION *p, IN MOVE *m) +{ + if (TRUE == IsLegalMove(p, m)) + { + // + // Make the new make on the board + // + ASSERT(p->sBoard[m->cVpos][m->cHpos] == EMPTY); + p->sBoard[m->cVpos][m->cHpos] = m->sMark; + + // + // One less empty square + // + p->uNumEmpty--; + ASSERT(p->uNumEmpty < (BOARD_SIZE * BOARD_SIZE)); + + // + // Update sums as appropriate + // + p->iHSums[m->cHpos] += m->sMark; + ASSERT(VALID_SUM(p->iHSums[m->cHpos])); + p->iVSums[m->cVpos] += m->sMark; + ASSERT(VALID_SUM(p->iVSums[m->cVpos])); + if (m->cHpos == m->cVpos) + { + p->iDSums[0] += m->sMark; + ASSERT(VALID_SUM(p->iDSums[0])); + } + else if (m->cHpos == (BOARD_SIZE - m->cVpos)) + { + p->iDSums[1] += m->sMark; + ASSERT(VALID_SUM(p->iDSums[1])); + } + + // + // Other guy's turn + // + p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); + ASSERT(X_OR_O(p->sWhoseTurn)); + + // + // One ply deeper + // + g_uPly++; + ASSERT(g_uPly > 0); + } +} + +//+---------------------------------------------------------------------------- +// +// Function: UnmakeMove +// +// Synopsis: The opposite of MakeMove +// +// Arguments: IN OUT POSITION *p - the board +// IN MOVE *m - the move to undo +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void UnmakeMove(IN OUT POSITION *p, IN MOVE *m) +{ + if (p->sBoard[m->cVpos][m->cHpos] == m->sMark) + { + p->sBoard[m->cVpos][m->cHpos] = EMPTY; + + // + // One more empty square + // + p->uNumEmpty++; + ASSERT(p->uNumEmpty > 0); + ASSERT(p->uNumEmpty <= (BOARD_SIZE * BOARD_SIZE)); + + // + // Update sums as appropriate + // + p->iHSums[m->cHpos] -= m->sMark; + ASSERT(VALID_SUM(p->iHSums[m->cHpos])); + p->iVSums[m->cVpos] -= m->sMark; + ASSERT(VALID_SUM(p->iVSums[m->cVpos])); + if (m->cHpos == m->cVpos) + { + p->iDSums[0] -= m->sMark; + ASSERT(VALID_SUM(p->iDSums[0])); + } + else if (m->cHpos == (BOARD_SIZE - m->cVpos)) + { + p->iDSums[1] -= m->sMark; + ASSERT(VALID_SUM(p->iDSums[1])); + } + + // + // Other guy's turn + // + p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); + ASSERT(X_OR_O(p->sWhoseTurn)); + + // + // One ply deeper + // + ASSERT(g_uPly > 0); + g_uPly--; + } +} + + +//+---------------------------------------------------------------------------- +// +// Function: AlphaBeta +// +// Synopsis: An AlphaBeta Search +// +// Arguments: IN OUT POSITION *p - the board +// IN int iAlpha - the lower bound of the score window +// IN int iBeta - the upper bound of the score window +// IN int uDepth - search depth horizon +// +// Returns: int +// +//+---------------------------------------------------------------------------- +int +AlphaBeta(IN POSITION *p, IN int iAlpha, IN int iBeta, IN unsigned int uDepth) +{ + SQUARE sWhoWon; + SQUARE s; + MOVE mv; + int iScore; + int iBestScore = -INFINITY; + + g_uNodes++; + + // + // Evaluate this position + // + if (TRUE == GameOver(p, &sWhoWon)) + { + if (sWhoWon == p->sWhoseTurn) + { + return(+INFINITY - g_uPly); + } + else if (sWhoWon == (p->sWhoseTurn * -1)) + { + return(-INFINITY + g_uPly); + } + return(DRAWSCORE); + } + else + { + if (uDepth == 0) + { + return(Eval(p)); + } + } + + // + // No one won, game is still going. Evaluate every + // possible move from here. + // + ASSERT(p->uNumEmpty > 0); + for (s = 0; s < (BOARD_SIZE * BOARD_SIZE); s++) + { + mv.cHpos = NUM_TO_HPOS(s); + mv.cVpos = NUM_TO_VPOS(s); + mv.sMark = p->sWhoseTurn; + + if (IsLegalMove(p, &mv)) + { + MakeMove(p, &mv); + + iScore = -1 * AlphaBeta(p, -iBeta, -iAlpha, uDepth - 1); + ASSERT(-INFINITY <= iScore); + ASSERT(iScore <= +INFINITY); + + UnmakeMove(p, &mv); + + if (iScore >= iBeta) + { + return(iScore); + } + + if (iScore > iBestScore) + { + iBestScore = iScore; + + if (iScore > iAlpha) + { + iAlpha = iScore; + + // + // If this is the ply 0 move, remember it. + // + if (g_uPly == 0) + { + g_mvBest = mv; + } + } + } + } + } + return(iBestScore); +} + + +//+---------------------------------------------------------------------------- +// +// Function: SimpleSearch +// +// Synopsis: A Simple Search +// +// Arguments: IN OUT POSITION *p - the board +// +// Returns: int +// +//+---------------------------------------------------------------------------- +int +SimpleSearch(IN POSITION *p) +{ + SQUARE sWhoWon; + SQUARE s; + MOVE mv; + int iScore; + int iBestScore = -INFINITY; + + g_uNodes++; + + // + // Evaluate this position + // + if (TRUE == GameOver(p, &sWhoWon)) + { + if (sWhoWon == p->sWhoseTurn) + { + return(+INFINITY - g_uPly); + } + else if (sWhoWon == (p->sWhoseTurn * -1)) + { + return(-INFINITY + g_uPly); + } + return(DRAWSCORE); + } + + // + // No one won, game is still going. Evaluate every + // possible move from here. + // + ASSERT(p->uNumEmpty > 0); + for (s = 0; s < (BOARD_SIZE * BOARD_SIZE); s++) + { + mv.cHpos = NUM_TO_HPOS(s); + mv.cVpos = NUM_TO_VPOS(s); + mv.sMark = p->sWhoseTurn; + + if (IsLegalMove(p, &mv)) + { + MakeMove(p, &mv); + + iScore = -1 * SimpleSearch(p); + if (iScore > iBestScore) + { + iBestScore = iScore; + if (g_uPly == 1) + { + g_mvBest = mv; + } + } + UnmakeMove(p, &mv); + } + } + return(iBestScore); +} + +//+---------------------------------------------------------------------------- +// +// Function: SearchForComputerMove +// +// Synopsis: Use our sophisticated search algorithm to find a computer +// move +// +// Arguments: IN POSITION *p - the current board +// OUT MOVE *m - the move the computer chooses; this move struct +// is populated as a side-effect of this function. +// +// Returns: void* (populates move struct) +// +//+---------------------------------------------------------------------------- +void SearchForComputerMove(IN POSITION *p, OUT MOVE *m) +{ + unsigned int x; + +#if defined(PLAY_RANDOMLY) + + do + { + x = rand() % (BOARD_SIZE * BOARD_SIZE); + m->cHpos = NUM_TO_HPOS(x); + m->cVpos = NUM_TO_VPOS(x); + m->sMark = g_sComputerPlays; + } + while(FALSE == IsLegalMove(p, m)); + +#elif defined(SIMPLE_SEARCH) + + g_uPly = g_uNodes = 0; + SimpleSearch(p); + *m = g_mvBest; + printf("Searched %u node(s).\n", g_uNodes); + +#elif defined(ALPHA_BETA_SEARCH) + + g_uPly = g_uNodes = 0; + AlphaBeta(p, -INFINITY-1, +INFINITY+1, BOARD_SIZE); + *m = g_mvBest; + printf("Searched %u node(s).\n", g_uNodes); + +#else + + #error "No Search Strategy Defined" + +#endif +} + +//+---------------------------------------------------------------------------- +// +// Function: main +// +// Synopsis: The program entry point and main game loop. +// +// Arguments: void +// +// Returns: int +// +//+---------------------------------------------------------------------------- +int main(void) +{ + POSITION p; + MOVE mv; + SQUARE sResult; + + // + // Randomize: the random numbers returned by rand() will be based on + // the system clock when the program starts up. + // + srand(time(0)); + + // + // Setup the board and draw it once. + // + ClearBoard(&p); + DrawBoard(&p); + + // + // Main game loop + // + do + { + // + // See whose turn it is -- the human's or the computers -- and + // get a move from whoever's turn it is. + // + if (p.sWhoseTurn == g_sComputerPlays) + { + SearchForComputerMove(&p, &mv); + } + else + { + GetHumanMove(&p, &mv); + } + + // + // Make the move on the board and draw the board again. + // + MakeMove(&p, &mv); + DrawBoard(&p); + } + while(FALSE == GameOver(&p, &sResult)); + + // + // If we get here the game is over... see what happened. + // + switch(sResult) + { + case X_MARK: + printf("\nX's win.\n"); + break; + case O_MARK: + printf("\nO's win.\n"); + break; + default: + printf("Tie (what a surprise)\n"); + break; + } + + exit(0); +} + diff --git a/ver5/ttt.c b/ver5/ttt.c index 58f69e9..00b7c6d 100644 --- a/ver5/ttt.c +++ b/ver5/ttt.c @@ -1,766 +1,766 @@ -/*++ - -Module Name: - - ttt.c - -Abstract: - - tic tac toe program to illustrate simple minimax searching - -Author: - - Scott Gasch (SGasch) 18 Mar 2004 - -Revision History: - - ver0 : random play - ver1 : simple search - ver2 : alpha beta search - ver3 : added eval and depth on ab search, more efficient gaveover - ver4 : variable sized board - ver5 : bugfixes, added singular extension to search - ---*/ - -#include -#include -#include -#include -#include "ttt.h" - -SQUARE g_sComputerPlays = O_MARK; // what side comp plays -unsigned int g_uPly = 0; -MOVE g_mvBest = { 0 }; -unsigned int g_uNodes = 0; -unsigned int g_uExtensions = 0; -COORD g_uBoardSize = 3; - -//+---------------------------------------------------------------------------- -// -// Function: SquareContentsToChar -// -// Synopsis: Helper function for DrawBoard -// -// Arguments: IN SQUARE s - a square to return a char to represent -// -// Returns: char - character representing square -// -//+---------------------------------------------------------------------------- -char SquareContentsToChar(IN SQUARE s) -{ - static char c; - switch(s) - { - case X_MARK: - c = 'X'; - break; - case O_MARK: - c = 'O'; - break; - case EMPTY: - c = '_'; - break; - default: - ASSERT(FALSE); - c = '?'; - break; - } - return(c); -} - -//+---------------------------------------------------------------------------- -// -// Function: DrawBoard -// -// Synopsis: Draw the board -// -// Arguments: IN POSITION *p - pointer to a position whose board to draw -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void DrawBoard(IN POSITION *p) -{ - COORD x, y; - - for (y = 0; y < g_uBoardSize; y++) - { - for (x = 0; x < g_uBoardSize; x++) - { - printf("%c ", SquareContentsToChar(p->sBoard[y][x])); - } -#ifdef DEBUG - printf(" = %d\n", p->iVSums[y]); -#else - printf("\n"); -#endif - } - -#ifdef DEBUG - for (x = 0; x < g_uBoardSize; x++) - { - printf("| "); - } - printf("\n"); - for (x = 0; x < g_uBoardSize; x++) - { - printf("%d ", p->iHSums[x]); - } - printf("\t%d %d\n", p->iDSums[0], p->iDSums[1]); - printf("\n"); -#endif - - ASSERT(X_OR_O(p->sWhoseTurn)); - printf("\n%c to move.\n", SquareContentsToChar(p->sWhoseTurn)); -} - -//+---------------------------------------------------------------------------- -// -// Function: ClearBoard -// -// Synopsis: Clear the board -// -// Arguments: IN OUT POSITION *p - pointer to position whose board to clear -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void ClearBoard(IN OUT POSITION *p) -{ - COORD h; - - for (h = 0; h < g_uBoardSize; h++) - { - memset(p->sBoard[h], 0, sizeof(int) * g_uBoardSize); - } - memset(p->iHSums, 0, sizeof(int) * g_uBoardSize); - memset(p->iVSums, 0, sizeof(int) * g_uBoardSize); - p->iDSums[0] = p->iDSums[1] = 0; - p->sWhoseTurn = X_MARK; // x's go first - p->uNumEmpty = (g_uBoardSize * g_uBoardSize); -} - -//+---------------------------------------------------------------------------- -// -// Function: IsLegalMove -// -// Synopsis: Determine if a given move is legal on a given board -// -// Arguments: IN POSITION *p - the board to play the move on -// IN MOVE *m - the move in question -// -// Returns: BOOL - TRUE if it's legal, FALSE otherwise -// -//+---------------------------------------------------------------------------- -BOOL IsLegalMove(IN POSITION *p, IN MOVE *m) -{ - if ((m->cVpos < g_uBoardSize) && (m->cHpos < g_uBoardSize)) - { - if (IS_SQUARE_EMPTY(p->sBoard[m->cVpos][m->cHpos])) - { - return(TRUE); - } - } - return(FALSE); -} - -//+---------------------------------------------------------------------------- -// -// Function: GetHumanMove -// -// Synopsis: Ask the human for a move -// -// Arguments: IN POSITION *p - the current board -// OUT MOVE *m - the move the human made; this struct is populated -// as a side-effect of this function. -// -// Returns: void* (populates the move struct) -// -//+---------------------------------------------------------------------------- -void GetHumanMove(IN POSITION *p, OUT MOVE *m) -{ - unsigned int x; - - do - { - printf("Enter your move number: "); - scanf("%u", &x); - - m->cHpos = NUM_TO_HPOS(x); - m->cVpos = NUM_TO_VPOS(x); - m->sMark = OPPOSITE_MARK(g_sComputerPlays); - } - while(FALSE == IsLegalMove(p, m)); -} - - -//+---------------------------------------------------------------------------- -// -// Function: GameOver -// -// Synopsis: Is the game over? -// -// Arguments: IN POSITION *p - the board -// OUT SQUARE *psWhoWon - who won the game (if it's over) -// -// Returns: TRUE if the game is over. Also sets psWhoWon telling -// which side one if the game is over. This also serves -// as a very simple evaluation routine for the search. -// -// FALSE if the game is not over. -// -//+---------------------------------------------------------------------------- -BOOL GameOver(IN POSITION *p, OUT SQUARE *psWhoWon) -{ - int iSum; - COORD x; - unsigned int uFull = (g_uBoardSize * g_uBoardSize) - p->uNumEmpty; - - // - // The game can't be over if less than g_uBoardSize * 2 - 1 marks on it - // - if (uFull < (g_uBoardSize * 2 - 1)) - { - *psWhoWon = EMPTY; - return(FALSE); - } - - for (x = 0; x < g_uBoardSize; x++) - { - iSum = p->iHSums[x]; - if (abs(iSum) == g_uBoardSize) goto winner; - - iSum = p->iVSums[x]; - if (abs(iSum) == g_uBoardSize) goto winner; - } - - iSum = p->iDSums[0]; - if (abs(iSum) == g_uBoardSize) goto winner; - - iSum = p->iDSums[1]; - if (abs(iSum) == g_uBoardSize) goto winner; - - // - // No one won yet, either game ongoing or draw. - // - *psWhoWon = EMPTY; - if (p->uNumEmpty == 0) - { - return(TRUE); - } - else - { - return(FALSE); - } - - winner: - // - // Some side won - // - *psWhoWon = (iSum / (int)g_uBoardSize); - ASSERT(X_OR_O(*psWhoWon)); - return(TRUE); -} - - -//+---------------------------------------------------------------------------- -// -// Function: MakeMove -// -// Synopsis: Make a move on a board -// -// Arguments: IN OUT POSITION *p - the board -// IN MOVE *m - the move -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void MakeMove(IN OUT POSITION *p, IN MOVE *m) -{ - if (TRUE == IsLegalMove(p, m)) - { - // - // Make the new make on the board - // - ASSERT(p->sBoard[m->cVpos][m->cHpos] == EMPTY); - p->sBoard[m->cVpos][m->cHpos] = m->sMark; - - // - // One less empty square - // - p->uNumEmpty--; - ASSERT(p->uNumEmpty < (g_uBoardSize * g_uBoardSize)); - - // - // Update sums as appropriate - // - p->iHSums[m->cHpos] += m->sMark; - ASSERT(VALID_SUM(p->iHSums[m->cHpos])); - p->iVSums[m->cVpos] += m->sMark; - ASSERT(VALID_SUM(p->iVSums[m->cVpos])); - if (ON_DIAGONAL_1(m->cHpos, m->cVpos)) - { - p->iDSums[0] += m->sMark; - ASSERT(VALID_SUM(p->iDSums[0])); - } - if (ON_DIAGONAL_2(m->cHpos, m->cVpos)) - { - p->iDSums[1] += m->sMark; - ASSERT(VALID_SUM(p->iDSums[1])); - } - - // - // Other guy's turn - // - p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); - ASSERT(X_OR_O(p->sWhoseTurn)); - - // - // One ply deeper - // - g_uPly++; - ASSERT(g_uPly > 0); - } -} - -//+---------------------------------------------------------------------------- -// -// Function: UnmakeMove -// -// Synopsis: The opposite of MakeMove -// -// Arguments: IN OUT POSITION *p - the board -// IN MOVE *m - the move to undo -// -// Returns: void -// -//+---------------------------------------------------------------------------- -void UnmakeMove(IN OUT POSITION *p, IN MOVE *m) -{ - if (p->sBoard[m->cVpos][m->cHpos] == m->sMark) - { - p->sBoard[m->cVpos][m->cHpos] = EMPTY; - - // - // One more empty square - // - p->uNumEmpty++; - ASSERT(p->uNumEmpty > 0); - ASSERT(p->uNumEmpty <= (g_uBoardSize * g_uBoardSize)); - - // - // Update sums as appropriate - // - p->iHSums[m->cHpos] -= m->sMark; - ASSERT(VALID_SUM(p->iHSums[m->cHpos])); - p->iVSums[m->cVpos] -= m->sMark; - ASSERT(VALID_SUM(p->iVSums[m->cVpos])); - if (ON_DIAGONAL_1(m->cHpos, m->cVpos)) - { - p->iDSums[0] -= m->sMark; - ASSERT(VALID_SUM(p->iDSums[0])); - } - if (ON_DIAGONAL_2(m->cHpos, m->cVpos)) - { - p->iDSums[1] -= m->sMark; - ASSERT(VALID_SUM(p->iDSums[1])); - } - - // - // Other guy's turn - // - p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); - ASSERT(X_OR_O(p->sWhoseTurn)); - - // - // One ply deeper - // - ASSERT(g_uPly > 0); - g_uPly--; - } -} - - -//+---------------------------------------------------------------------------- -// -// Function: IsMoveSingular -// -// Synopsis: Determine if a move is singular (i.e. only good move) or not -// -// Arguments: IN POSITION *p - the board -// IN MOVE *m - the move to undo -// -// Returns: BOOL : TRUE if *m is singular -// -//+---------------------------------------------------------------------------- -BOOL -IsMoveSingular(IN POSITION *p, IN MOVE *m) -{ - if ((abs(p->iVSums[m->cVpos]) >= (g_uBoardSize - 2)) || - (abs(p->iHSums[m->cHpos]) >= (g_uBoardSize - 2))) - { - return(TRUE); - } - if ((m->cHpos == m->cVpos) && - (abs(p->iDSums[0]) == (g_uBoardSize - 1))) - { - return(TRUE); - } - if ((m->cVpos == ((g_uBoardSize - m->cHpos) - 1)) && - (abs(p->iDSums[1] == (g_uBoardSize - 1)))) - { - return(TRUE); - } - return(FALSE); -} - - -BOOL -IsMoveWorthSearching(POSITION *p, MOVE *m) -{ - signed int h; - signed int v; - unsigned int uSum = 0; - - for (h = m->cHpos - 1; - h < (signed int)m->cHpos + 2; - h++) - { - for (v = m->cVpos - 1; - v < (signed int)m->cVpos + 2; - v++) - { - if (GOOD_COORD((COORD)v) && GOOD_COORD((COORD)h)) - { - uSum += abs(p->sBoard[v][h]); - } - } - } - - if (uSum == 0) - { - return(FALSE); - } - return(TRUE); -} - - -int -Eval(POSITION *p) -{ - int iSum = p->iDSums[0]; - COORD x; - - for (x = 0; - x < g_uBoardSize; - x++) - { - iSum += p->iHSums[x]; - iSum += p->iVSums[x]; - } - iSum += p->iDSums[1]; - - return(iSum * p->sWhoseTurn); -} - - -//+---------------------------------------------------------------------------- -// -// Function: AlphaBeta -// -// Synopsis: An AlphaBeta Search -// -// Arguments: IN OUT POSITION *p - the board -// IN int iAlpha - the lower bound of the score window -// IN int iBeta - the upper bound of the score window -// IN int uDepth - search depth horizon -// -// Returns: int -// -//+---------------------------------------------------------------------------- -int -AlphaBeta(IN POSITION *p, IN int iAlpha, IN int iBeta, IN unsigned int uDepth) -{ - SQUARE sWhoWon; - COORD s; - MOVE mv; - int iScore; - int iBestScore = -INFINITY - 2; - BOOL fMoveIsSingular; - unsigned int uNextDepth; - unsigned int uMoveNum = 1; - - g_uNodes++; - - // - // Evaluate this position - // - if (TRUE == GameOver(p, &sWhoWon)) - { - if (sWhoWon == p->sWhoseTurn) - { - return(+INFINITY - g_uPly); - } - else if (sWhoWon == (p->sWhoseTurn * -1)) - { - return(-INFINITY + g_uPly); - } - return(DRAWSCORE); - } - else if (uDepth == 0) - { - return(Eval(p)); - } - - // - // No one won, game is still going. Evaluate some moves from here. - // - ASSERT(p->uNumEmpty > 0); - for (s = 0; s < (g_uBoardSize * g_uBoardSize); s++) - { - mv.cHpos = NUM_TO_HPOS(s); - mv.cVpos = NUM_TO_VPOS(s); - mv.sMark = p->sWhoseTurn; - - if (IsLegalMove(p, &mv)) - { - // - // Determine if move is singular - // - fMoveIsSingular = IsMoveSingular(p, &mv); - - if ((FALSE == fMoveIsSingular) && - (uMoveNum > 1)) - { - // - // Determine if we should bother with this subtree... - // - if (FALSE == IsMoveWorthSearching(p, &mv)) - { - continue; - } - } - - // - // Do it - // - MakeMove(p, &mv); - uMoveNum++; - - uNextDepth = uDepth - 1; - if (TRUE == fMoveIsSingular) - { - uNextDepth = uDepth; - g_uExtensions++; - } - iScore = -1 * AlphaBeta(p, -iBeta, -iAlpha, uNextDepth); - ASSERT(-INFINITY <= iScore); - ASSERT(iScore <= +INFINITY); - - UnmakeMove(p, &mv); - - // - // Fail high - // - if (iScore >= iBeta) - { - return(iScore); - } - - if (iScore > iBestScore) - { - iBestScore = iScore; - - if (iScore > iAlpha) - { - // - // PV node - // - iAlpha = iScore; - - // - // If this is the ply 0 move, remember it. - // - if (g_uPly == 0) - { - g_mvBest = mv; - } - } - } - } - } - return(iBestScore); -} - -//+---------------------------------------------------------------------------- -// -// Function: SearchForComputerMove -// -// Synopsis: Use our sophisticated search algorithm to find a computer -// move -// -// Arguments: IN POSITION *p - the current board -// OUT MOVE *m - the move the computer chooses; this move struct -// is populated as a side-effect of this function. -// -// Returns: void* (populates move struct) -// -//+---------------------------------------------------------------------------- -void SearchForComputerMove(IN POSITION *p, OUT MOVE *m) -{ -#if defined(PLAY_RANDOMLY) - unsigned int x; - - do - { - x = rand() % (g_uBoardSize * g_uBoardSize); - m->cHpos = NUM_TO_HPOS(x); - m->cVpos = NUM_TO_VPOS(x); - m->sMark = g_sComputerPlays; - } - while(FALSE == IsLegalMove(p, m)); - -#elif defined(ALPHA_BETA_SEARCH) - double dTime; - - g_uPly = g_uNodes = g_uExtensions = 0; - - AlphaBeta(p, -INFINITY-1, +INFINITY+1, 3); - *m = g_mvBest; - printf("Searched %u node(s), %u extension(s)\n", - g_uNodes, - g_uExtensions); -#else - - #error "No Search Strategy Defined" - -#endif -} - -//+---------------------------------------------------------------------------- -// -// Function: main -// -// Synopsis: The program entry point and main game loop. -// -// Arguments: void -// -// Returns: int -// -//+---------------------------------------------------------------------------- -int -main(void) -{ - POSITION p; - MOVE mv; - SQUARE sResult; - unsigned int u; - - // - // Randomize: the random numbers returned by rand() will be based on - // the system clock when the program starts up. - // - srand(time(0)); - - // - // Make the board - // - do - { - printf("How big do you want the board (2..20)? "); - scanf("%u", &g_uBoardSize); - } - while((g_uBoardSize < 2) || (g_uBoardSize > 20)); - - // - // Allocate space for 2d int array ptr - // - p.sBoard = (SQUARE **)malloc(g_uBoardSize * sizeof(SQUARE *)); - if (NULL == p.sBoard) - { - fprintf(stderr, "Out of memory\n"); - exit(1); - } - - // - // Allocate each row of the array - // - for (u = 0; u < g_uBoardSize; u++) - { - p.sBoard[u] = (SQUARE *) - malloc(g_uBoardSize * sizeof(SQUARE)); - if (NULL == p.sBoard[u]) - { - fprintf(stderr, "Out of memory!\n"); - exit(1); - } - } - - // - // Allocate space for sums - // - p.iHSums = (int *)malloc(g_uBoardSize * sizeof(int)); - p.iVSums = (int *)malloc(g_uBoardSize * sizeof(int)); - if ((NULL == p.iHSums) || - (NULL == p.iVSums)) - { - fprintf(stderr, "Out of memory!\n"); - exit(1); - } - - // - // Setup the board and draw it once. - // - ClearBoard(&p); - DrawBoard(&p); - - // - // Main game loop - // - do - { - // - // See whose turn it is -- the human's or the computers -- and - // get a move from whoever's turn it is. - // - if (p.sWhoseTurn == g_sComputerPlays) - { - SearchForComputerMove(&p, &mv); - } - else - { - //SearchForComputerMove(&p, &mv); - GetHumanMove(&p, &mv); - } - - // - // Make the move on the board and draw the board again. - // - MakeMove(&p, &mv); - DrawBoard(&p); - } - while(FALSE == GameOver(&p, &sResult)); - - // - // If we get here the game is over... see what happened. - // - switch(sResult) - { - case X_MARK: - printf("\nX's win.\n"); - break; - case O_MARK: - printf("\nO's win.\n"); - break; - default: - printf("Tie (what a surprise)\n"); - break; - } - - // - // TODO: cleanup heap - // - - exit(0); -} +/*++ + +Module Name: + + ttt.c + +Abstract: + + tic tac toe program to illustrate simple minimax searching + +Author: + + Scott Gasch (SGasch) 18 Mar 2004 + +Revision History: + + ver0 : random play + ver1 : simple search + ver2 : alpha beta search + ver3 : added eval and depth on ab search, more efficient gaveover + ver4 : variable sized board + ver5 : bugfixes, added singular extension to search + +--*/ + +#include +#include +#include +#include +#include "ttt.h" + +SQUARE g_sComputerPlays = O_MARK; // what side comp plays +unsigned int g_uPly = 0; +MOVE g_mvBest = { 0 }; +unsigned int g_uNodes = 0; +unsigned int g_uExtensions = 0; +COORD g_uBoardSize = 3; + +//+---------------------------------------------------------------------------- +// +// Function: SquareContentsToChar +// +// Synopsis: Helper function for DrawBoard +// +// Arguments: IN SQUARE s - a square to return a char to represent +// +// Returns: char - character representing square +// +//+---------------------------------------------------------------------------- +char SquareContentsToChar(IN SQUARE s) +{ + static char c; + switch(s) + { + case X_MARK: + c = 'X'; + break; + case O_MARK: + c = 'O'; + break; + case EMPTY: + c = '_'; + break; + default: + ASSERT(FALSE); + c = '?'; + break; + } + return(c); +} + +//+---------------------------------------------------------------------------- +// +// Function: DrawBoard +// +// Synopsis: Draw the board +// +// Arguments: IN POSITION *p - pointer to a position whose board to draw +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void DrawBoard(IN POSITION *p) +{ + COORD x, y; + + for (y = 0; y < g_uBoardSize; y++) + { + for (x = 0; x < g_uBoardSize; x++) + { + printf("%c ", SquareContentsToChar(p->sBoard[y][x])); + } +#ifdef DEBUG + printf(" = %d\n", p->iVSums[y]); +#else + printf("\n"); +#endif + } + +#ifdef DEBUG + for (x = 0; x < g_uBoardSize; x++) + { + printf("| "); + } + printf("\n"); + for (x = 0; x < g_uBoardSize; x++) + { + printf("%d ", p->iHSums[x]); + } + printf("\t%d %d\n", p->iDSums[0], p->iDSums[1]); + printf("\n"); +#endif + + ASSERT(X_OR_O(p->sWhoseTurn)); + printf("\n%c to move.\n", SquareContentsToChar(p->sWhoseTurn)); +} + +//+---------------------------------------------------------------------------- +// +// Function: ClearBoard +// +// Synopsis: Clear the board +// +// Arguments: IN OUT POSITION *p - pointer to position whose board to clear +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void ClearBoard(IN OUT POSITION *p) +{ + COORD h; + + for (h = 0; h < g_uBoardSize; h++) + { + memset(p->sBoard[h], 0, sizeof(int) * g_uBoardSize); + } + memset(p->iHSums, 0, sizeof(int) * g_uBoardSize); + memset(p->iVSums, 0, sizeof(int) * g_uBoardSize); + p->iDSums[0] = p->iDSums[1] = 0; + p->sWhoseTurn = X_MARK; // x's go first + p->uNumEmpty = (g_uBoardSize * g_uBoardSize); +} + +//+---------------------------------------------------------------------------- +// +// Function: IsLegalMove +// +// Synopsis: Determine if a given move is legal on a given board +// +// Arguments: IN POSITION *p - the board to play the move on +// IN MOVE *m - the move in question +// +// Returns: BOOL - TRUE if it's legal, FALSE otherwise +// +//+---------------------------------------------------------------------------- +BOOL IsLegalMove(IN POSITION *p, IN MOVE *m) +{ + if ((m->cVpos < g_uBoardSize) && (m->cHpos < g_uBoardSize)) + { + if (IS_SQUARE_EMPTY(p->sBoard[m->cVpos][m->cHpos])) + { + return(TRUE); + } + } + return(FALSE); +} + +//+---------------------------------------------------------------------------- +// +// Function: GetHumanMove +// +// Synopsis: Ask the human for a move +// +// Arguments: IN POSITION *p - the current board +// OUT MOVE *m - the move the human made; this struct is populated +// as a side-effect of this function. +// +// Returns: void* (populates the move struct) +// +//+---------------------------------------------------------------------------- +void GetHumanMove(IN POSITION *p, OUT MOVE *m) +{ + unsigned int x; + + do + { + printf("Enter your move number: "); + scanf("%u", &x); + + m->cHpos = NUM_TO_HPOS(x); + m->cVpos = NUM_TO_VPOS(x); + m->sMark = OPPOSITE_MARK(g_sComputerPlays); + } + while(FALSE == IsLegalMove(p, m)); +} + + +//+---------------------------------------------------------------------------- +// +// Function: GameOver +// +// Synopsis: Is the game over? +// +// Arguments: IN POSITION *p - the board +// OUT SQUARE *psWhoWon - who won the game (if it's over) +// +// Returns: TRUE if the game is over. Also sets psWhoWon telling +// which side one if the game is over. This also serves +// as a very simple evaluation routine for the search. +// +// FALSE if the game is not over. +// +//+---------------------------------------------------------------------------- +BOOL GameOver(IN POSITION *p, OUT SQUARE *psWhoWon) +{ + int iSum; + COORD x; + unsigned int uFull = (g_uBoardSize * g_uBoardSize) - p->uNumEmpty; + + // + // The game can't be over if less than g_uBoardSize * 2 - 1 marks on it + // + if (uFull < (g_uBoardSize * 2 - 1)) + { + *psWhoWon = EMPTY; + return(FALSE); + } + + for (x = 0; x < g_uBoardSize; x++) + { + iSum = p->iHSums[x]; + if (abs(iSum) == g_uBoardSize) goto winner; + + iSum = p->iVSums[x]; + if (abs(iSum) == g_uBoardSize) goto winner; + } + + iSum = p->iDSums[0]; + if (abs(iSum) == g_uBoardSize) goto winner; + + iSum = p->iDSums[1]; + if (abs(iSum) == g_uBoardSize) goto winner; + + // + // No one won yet, either game ongoing or draw. + // + *psWhoWon = EMPTY; + if (p->uNumEmpty == 0) + { + return(TRUE); + } + else + { + return(FALSE); + } + + winner: + // + // Some side won + // + *psWhoWon = (iSum / (int)g_uBoardSize); + ASSERT(X_OR_O(*psWhoWon)); + return(TRUE); +} + + +//+---------------------------------------------------------------------------- +// +// Function: MakeMove +// +// Synopsis: Make a move on a board +// +// Arguments: IN OUT POSITION *p - the board +// IN MOVE *m - the move +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void MakeMove(IN OUT POSITION *p, IN MOVE *m) +{ + if (TRUE == IsLegalMove(p, m)) + { + // + // Make the new make on the board + // + ASSERT(p->sBoard[m->cVpos][m->cHpos] == EMPTY); + p->sBoard[m->cVpos][m->cHpos] = m->sMark; + + // + // One less empty square + // + p->uNumEmpty--; + ASSERT(p->uNumEmpty < (g_uBoardSize * g_uBoardSize)); + + // + // Update sums as appropriate + // + p->iHSums[m->cHpos] += m->sMark; + ASSERT(VALID_SUM(p->iHSums[m->cHpos])); + p->iVSums[m->cVpos] += m->sMark; + ASSERT(VALID_SUM(p->iVSums[m->cVpos])); + if (ON_DIAGONAL_1(m->cHpos, m->cVpos)) + { + p->iDSums[0] += m->sMark; + ASSERT(VALID_SUM(p->iDSums[0])); + } + if (ON_DIAGONAL_2(m->cHpos, m->cVpos)) + { + p->iDSums[1] += m->sMark; + ASSERT(VALID_SUM(p->iDSums[1])); + } + + // + // Other guy's turn + // + p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); + ASSERT(X_OR_O(p->sWhoseTurn)); + + // + // One ply deeper + // + g_uPly++; + ASSERT(g_uPly > 0); + } +} + +//+---------------------------------------------------------------------------- +// +// Function: UnmakeMove +// +// Synopsis: The opposite of MakeMove +// +// Arguments: IN OUT POSITION *p - the board +// IN MOVE *m - the move to undo +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void UnmakeMove(IN OUT POSITION *p, IN MOVE *m) +{ + if (p->sBoard[m->cVpos][m->cHpos] == m->sMark) + { + p->sBoard[m->cVpos][m->cHpos] = EMPTY; + + // + // One more empty square + // + p->uNumEmpty++; + ASSERT(p->uNumEmpty > 0); + ASSERT(p->uNumEmpty <= (g_uBoardSize * g_uBoardSize)); + + // + // Update sums as appropriate + // + p->iHSums[m->cHpos] -= m->sMark; + ASSERT(VALID_SUM(p->iHSums[m->cHpos])); + p->iVSums[m->cVpos] -= m->sMark; + ASSERT(VALID_SUM(p->iVSums[m->cVpos])); + if (ON_DIAGONAL_1(m->cHpos, m->cVpos)) + { + p->iDSums[0] -= m->sMark; + ASSERT(VALID_SUM(p->iDSums[0])); + } + if (ON_DIAGONAL_2(m->cHpos, m->cVpos)) + { + p->iDSums[1] -= m->sMark; + ASSERT(VALID_SUM(p->iDSums[1])); + } + + // + // Other guy's turn + // + p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn); + ASSERT(X_OR_O(p->sWhoseTurn)); + + // + // One ply deeper + // + ASSERT(g_uPly > 0); + g_uPly--; + } +} + + +//+---------------------------------------------------------------------------- +// +// Function: IsMoveSingular +// +// Synopsis: Determine if a move is singular (i.e. only good move) or not +// +// Arguments: IN POSITION *p - the board +// IN MOVE *m - the move to undo +// +// Returns: BOOL : TRUE if *m is singular +// +//+---------------------------------------------------------------------------- +BOOL +IsMoveSingular(IN POSITION *p, IN MOVE *m) +{ + if ((abs(p->iVSums[m->cVpos]) >= (g_uBoardSize - 2)) || + (abs(p->iHSums[m->cHpos]) >= (g_uBoardSize - 2))) + { + return(TRUE); + } + if ((m->cHpos == m->cVpos) && + (abs(p->iDSums[0]) == (g_uBoardSize - 1))) + { + return(TRUE); + } + if ((m->cVpos == ((g_uBoardSize - m->cHpos) - 1)) && + (abs(p->iDSums[1] == (g_uBoardSize - 1)))) + { + return(TRUE); + } + return(FALSE); +} + + +BOOL +IsMoveWorthSearching(POSITION *p, MOVE *m) +{ + signed int h; + signed int v; + unsigned int uSum = 0; + + for (h = m->cHpos - 1; + h < (signed int)m->cHpos + 2; + h++) + { + for (v = m->cVpos - 1; + v < (signed int)m->cVpos + 2; + v++) + { + if (GOOD_COORD((COORD)v) && GOOD_COORD((COORD)h)) + { + uSum += abs(p->sBoard[v][h]); + } + } + } + + if (uSum == 0) + { + return(FALSE); + } + return(TRUE); +} + + +int +Eval(POSITION *p) +{ + int iSum = p->iDSums[0]; + COORD x; + + for (x = 0; + x < g_uBoardSize; + x++) + { + iSum += p->iHSums[x]; + iSum += p->iVSums[x]; + } + iSum += p->iDSums[1]; + + return(iSum * p->sWhoseTurn); +} + + +//+---------------------------------------------------------------------------- +// +// Function: AlphaBeta +// +// Synopsis: An AlphaBeta Search +// +// Arguments: IN OUT POSITION *p - the board +// IN int iAlpha - the lower bound of the score window +// IN int iBeta - the upper bound of the score window +// IN int uDepth - search depth horizon +// +// Returns: int +// +//+---------------------------------------------------------------------------- +int +AlphaBeta(IN POSITION *p, IN int iAlpha, IN int iBeta, IN unsigned int uDepth) +{ + SQUARE sWhoWon; + COORD s; + MOVE mv; + int iScore; + int iBestScore = -INFINITY - 2; + BOOL fMoveIsSingular; + unsigned int uNextDepth; + unsigned int uMoveNum = 1; + + g_uNodes++; + + // + // Evaluate this position + // + if (TRUE == GameOver(p, &sWhoWon)) + { + if (sWhoWon == p->sWhoseTurn) + { + return(+INFINITY - g_uPly); + } + else if (sWhoWon == (p->sWhoseTurn * -1)) + { + return(-INFINITY + g_uPly); + } + return(DRAWSCORE); + } + else if (uDepth == 0) + { + return(Eval(p)); + } + + // + // No one won, game is still going. Evaluate some moves from here. + // + ASSERT(p->uNumEmpty > 0); + for (s = 0; s < (g_uBoardSize * g_uBoardSize); s++) + { + mv.cHpos = NUM_TO_HPOS(s); + mv.cVpos = NUM_TO_VPOS(s); + mv.sMark = p->sWhoseTurn; + + if (IsLegalMove(p, &mv)) + { + // + // Determine if move is singular + // + fMoveIsSingular = IsMoveSingular(p, &mv); + + if ((FALSE == fMoveIsSingular) && + (uMoveNum > 1)) + { + // + // Determine if we should bother with this subtree... + // + if (FALSE == IsMoveWorthSearching(p, &mv)) + { + continue; + } + } + + // + // Do it + // + MakeMove(p, &mv); + uMoveNum++; + + uNextDepth = uDepth - 1; + if (TRUE == fMoveIsSingular) + { + uNextDepth = uDepth; + g_uExtensions++; + } + iScore = -1 * AlphaBeta(p, -iBeta, -iAlpha, uNextDepth); + ASSERT(-INFINITY <= iScore); + ASSERT(iScore <= +INFINITY); + + UnmakeMove(p, &mv); + + // + // Fail high + // + if (iScore >= iBeta) + { + return(iScore); + } + + if (iScore > iBestScore) + { + iBestScore = iScore; + + if (iScore > iAlpha) + { + // + // PV node + // + iAlpha = iScore; + + // + // If this is the ply 0 move, remember it. + // + if (g_uPly == 0) + { + g_mvBest = mv; + } + } + } + } + } + return(iBestScore); +} + +//+---------------------------------------------------------------------------- +// +// Function: SearchForComputerMove +// +// Synopsis: Use our sophisticated search algorithm to find a computer +// move +// +// Arguments: IN POSITION *p - the current board +// OUT MOVE *m - the move the computer chooses; this move struct +// is populated as a side-effect of this function. +// +// Returns: void* (populates move struct) +// +//+---------------------------------------------------------------------------- +void SearchForComputerMove(IN POSITION *p, OUT MOVE *m) +{ +#if defined(PLAY_RANDOMLY) + unsigned int x; + + do + { + x = rand() % (g_uBoardSize * g_uBoardSize); + m->cHpos = NUM_TO_HPOS(x); + m->cVpos = NUM_TO_VPOS(x); + m->sMark = g_sComputerPlays; + } + while(FALSE == IsLegalMove(p, m)); + +#elif defined(ALPHA_BETA_SEARCH) + double dTime; + + g_uPly = g_uNodes = g_uExtensions = 0; + + AlphaBeta(p, -INFINITY-1, +INFINITY+1, 3); + *m = g_mvBest; + printf("Searched %u node(s), %u extension(s)\n", + g_uNodes, + g_uExtensions); +#else + + #error "No Search Strategy Defined" + +#endif +} + +//+---------------------------------------------------------------------------- +// +// Function: main +// +// Synopsis: The program entry point and main game loop. +// +// Arguments: void +// +// Returns: int +// +//+---------------------------------------------------------------------------- +int +main(void) +{ + POSITION p; + MOVE mv; + SQUARE sResult; + unsigned int u; + + // + // Randomize: the random numbers returned by rand() will be based on + // the system clock when the program starts up. + // + srand(time(0)); + + // + // Make the board + // + do + { + printf("How big do you want the board (2..20)? "); + scanf("%u", &g_uBoardSize); + } + while((g_uBoardSize < 2) || (g_uBoardSize > 20)); + + // + // Allocate space for 2d int array ptr + // + p.sBoard = (SQUARE **)malloc(g_uBoardSize * sizeof(SQUARE *)); + if (NULL == p.sBoard) + { + fprintf(stderr, "Out of memory\n"); + exit(1); + } + + // + // Allocate each row of the array + // + for (u = 0; u < g_uBoardSize; u++) + { + p.sBoard[u] = (SQUARE *) + malloc(g_uBoardSize * sizeof(SQUARE)); + if (NULL == p.sBoard[u]) + { + fprintf(stderr, "Out of memory!\n"); + exit(1); + } + } + + // + // Allocate space for sums + // + p.iHSums = (int *)malloc(g_uBoardSize * sizeof(int)); + p.iVSums = (int *)malloc(g_uBoardSize * sizeof(int)); + if ((NULL == p.iHSums) || + (NULL == p.iVSums)) + { + fprintf(stderr, "Out of memory!\n"); + exit(1); + } + + // + // Setup the board and draw it once. + // + ClearBoard(&p); + DrawBoard(&p); + + // + // Main game loop + // + do + { + // + // See whose turn it is -- the human's or the computers -- and + // get a move from whoever's turn it is. + // + if (p.sWhoseTurn == g_sComputerPlays) + { + SearchForComputerMove(&p, &mv); + } + else + { + //SearchForComputerMove(&p, &mv); + GetHumanMove(&p, &mv); + } + + // + // Make the move on the board and draw the board again. + // + MakeMove(&p, &mv); + DrawBoard(&p); + } + while(FALSE == GameOver(&p, &sResult)); + + // + // If we get here the game is over... see what happened. + // + switch(sResult) + { + case X_MARK: + printf("\nX's win.\n"); + break; + case O_MARK: + printf("\nO's win.\n"); + break; + default: + printf("Tie (what a surprise)\n"); + break; + } + + // + // TODO: cleanup heap + // + + exit(0); +}