9 tic tac toe program to illustrate simple minimax searching
13 Scott Gasch (SGasch) 18 Mar 2004
19 ver2 : alpha beta search
20 ver3 : added eval and depth on ab search, more efficient gaveover
30 SQUARE g_sComputerPlays = O_MARK; // what side comp plays
31 unsigned int g_uPly = 0;
32 MOVE g_mvBest = { 0 };
33 unsigned int g_uNodes = 0;
35 //+----------------------------------------------------------------------------
37 // Function: SquareContentsToChar
39 // Synopsis: Helper function for DrawBoard
41 // Arguments: IN SQUARE s - a square to return a char to represent
43 // Returns: char - character representing square
45 //+----------------------------------------------------------------------------
46 char SquareContentsToChar(IN SQUARE s)
68 //+----------------------------------------------------------------------------
70 // Function: DrawBoard
72 // Synopsis: Draw the board
74 // Arguments: IN POSITION *p - pointer to a position whose board to draw
78 //+----------------------------------------------------------------------------
79 void DrawBoard(IN POSITION *p)
83 for (y = 0; y < BOARD_SIZE; y++)
85 for (x = 0; x < BOARD_SIZE; x++)
87 printf("%c ", SquareContentsToChar(p->sBoard[y][x]));
90 printf(" = %d\n", p->iHSums[y]);
97 for (x = 0; x < BOARD_SIZE; x++)
102 for (x = 0; x < BOARD_SIZE; x++)
104 printf("%d ", p->iVSums[x]);
109 ASSERT(X_OR_O(p->sWhoseTurn));
110 printf("\n%c to move.\n", SquareContentsToChar(p->sWhoseTurn));
113 //+----------------------------------------------------------------------------
115 // Function: ClearBoard
117 // Synopsis: Clear the board
119 // Arguments: IN OUT POSITION *p - pointer to position whose board to clear
123 //+----------------------------------------------------------------------------
124 void ClearBoard(IN OUT POSITION *p)
126 memset(p->sBoard, 0, sizeof(p->sBoard));
127 memset(p->iHSums, 0, sizeof(p->iHSums));
128 memset(p->iVSums, 0, sizeof(p->iVSums));
129 p->iDSums[0] = p->iDSums[1] = 0;
130 p->sWhoseTurn = X_MARK; // x's go first
131 p->uNumEmpty = (BOARD_SIZE * BOARD_SIZE);
134 //+----------------------------------------------------------------------------
136 // Function: IsLegalMove
138 // Synopsis: Determine if a given move is legal on a given board
140 // Arguments: IN POSITION *p - the board to play the move on
141 // IN MOVE *m - the move in question
143 // Returns: BOOL - TRUE if it's legal, FALSE otherwise
145 //+----------------------------------------------------------------------------
146 BOOL IsLegalMove(IN POSITION *p, IN MOVE *m)
148 if ((m->cVpos < BOARD_SIZE) && (m->cHpos < BOARD_SIZE))
150 if (IS_SQUARE_EMPTY(p->sBoard[m->cVpos][m->cHpos]))
158 //+----------------------------------------------------------------------------
160 // Function: GetHumanMove
162 // Synopsis: Ask the human for a move
164 // Arguments: IN POSITION *p - the current board
165 // OUT MOVE *m - the move the human made; this struct is populated
166 // as a side-effect of this function.
168 // Returns: void* (populates the move struct)
170 //+----------------------------------------------------------------------------
171 void GetHumanMove(IN POSITION *p, OUT MOVE *m)
177 printf("Enter your move number: ");
180 m->cHpos = NUM_TO_HPOS(x);
181 m->cVpos = NUM_TO_VPOS(x);
182 m->sMark = OPPOSITE_MARK(g_sComputerPlays);
184 while(FALSE == IsLegalMove(p, m));
188 //+----------------------------------------------------------------------------
190 // Function: GameOver
192 // Synopsis: Is the game over?
194 // Arguments: IN POSITION *p - the board
195 // OUT SQUARE *psWhoWon - who won the game (if it's over)
197 // Returns: TRUE if the game is over. Also sets psWhoWon telling
198 // which side one if the game is over. This also serves
199 // as a very simple evaluation routine for the search.
201 // FALSE if the game is not over.
203 //+----------------------------------------------------------------------------
204 BOOL GameOver(IN POSITION *p, OUT SQUARE *psWhoWon)
209 for (x = 0; x < BOARD_SIZE; x++)
212 if (abs(iSum) == BOARD_SIZE) goto winner;
215 if (abs(iSum) == BOARD_SIZE) goto winner;
219 if (abs(iSum) == BOARD_SIZE) goto winner;
222 if (abs(iSum) == BOARD_SIZE) goto winner;
225 // No one won yet, either game ongoing or draw.
228 if (p->uNumEmpty == 0)
241 *psWhoWon = (iSum / BOARD_SIZE);
242 ASSERT(X_OR_O(*psWhoWon));
247 //+----------------------------------------------------------------------------
249 // Function: CountAdjacents
251 // Synopsis: Return the number of marks in adjacent squares to square x
252 // that are of the same type (x or o) as the mark in square x.
254 // FIXME: does not consider diagonals
256 // Arguments: IN POSITION *p - the board
257 // IN SQUARE x - the square to test
261 //+----------------------------------------------------------------------------
262 unsigned int CountAdjacents(IN POSITION *p, IN SQUARE x)
264 COORD v = NUM_TO_VPOS(x);
265 COORD h = NUM_TO_HPOS(x);
266 SQUARE sSide = p->sBoard[h][v];
267 unsigned int uCount = 0;
270 // If nothing at square x, nothing to count
272 if (sSide == EMPTY) goto end;
275 // Look above, below, left and right
277 if ((v > 0) && (p->sBoard[h][v-1] == sSide))
282 if ((v < (BOARD_SIZE - 1)) && (p->sBoard[h][v+1] == sSide))
287 if ((h > 0) && (p->sBoard[h-1][v] == sSide))
292 if ((h < (BOARD_SIZE - 1)) && (p->sBoard[h+1][v] == sSide))
304 //+----------------------------------------------------------------------------
308 // Synopsis: Evaluate a position
310 // Arguments: IN POSITION *p - the board
314 //+----------------------------------------------------------------------------
315 int Eval(IN POSITION *p)
323 // See if the game is already over.
325 if (TRUE == GameOver(p, &sWinner))
327 if (sWinner == p->sWhoseTurn)
329 iScore = +INFINITY - g_uPly;
332 else if (sWinner == OPPOSITE_MARK(p->sWhoseTurn))
334 iScore = -INFINITY + g_uPly;
342 // No one won but instead of returning score=0 see if we can
343 // find some "good characteristics" or "bad characteristics"
344 // of the position and give bonuses / penalties.
346 for (x = BOARD_SIZE + 1;
347 x < ((BOARD_SIZE * BOARD_SIZE) - BOARD_SIZE - 1);
350 sMark = p->sBoard[NUM_TO_HPOS(x)][NUM_TO_VPOS(x)];
351 if (sMark == p->sWhoseTurn)
353 iScore += CountAdjacents(p, x);
355 else if (sMark == OPPOSITE_MARK(p->sWhoseTurn))
357 iScore -= CountAdjacents(p, x);
362 ASSERT(-INFINITY <= iScore);
363 ASSERT(iScore <= +INFINITY);
369 //+----------------------------------------------------------------------------
371 // Function: MakeMove
373 // Synopsis: Make a move on a board
375 // Arguments: IN OUT POSITION *p - the board
376 // IN MOVE *m - the move
380 //+----------------------------------------------------------------------------
381 void MakeMove(IN OUT POSITION *p, IN MOVE *m)
383 if (TRUE == IsLegalMove(p, m))
386 // Make the new make on the board
388 ASSERT(p->sBoard[m->cVpos][m->cHpos] == EMPTY);
389 p->sBoard[m->cVpos][m->cHpos] = m->sMark;
392 // One less empty square
395 ASSERT(p->uNumEmpty < (BOARD_SIZE * BOARD_SIZE));
398 // Update sums as appropriate
400 p->iHSums[m->cHpos] += m->sMark;
401 ASSERT(VALID_SUM(p->iHSums[m->cHpos]));
402 p->iVSums[m->cVpos] += m->sMark;
403 ASSERT(VALID_SUM(p->iVSums[m->cVpos]));
404 if (m->cHpos == m->cVpos)
406 p->iDSums[0] += m->sMark;
407 ASSERT(VALID_SUM(p->iDSums[0]));
409 else if (m->cHpos == (BOARD_SIZE - m->cVpos))
411 p->iDSums[1] += m->sMark;
412 ASSERT(VALID_SUM(p->iDSums[1]));
418 p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn);
419 ASSERT(X_OR_O(p->sWhoseTurn));
429 //+----------------------------------------------------------------------------
431 // Function: UnmakeMove
433 // Synopsis: The opposite of MakeMove
435 // Arguments: IN OUT POSITION *p - the board
436 // IN MOVE *m - the move to undo
440 //+----------------------------------------------------------------------------
441 void UnmakeMove(IN OUT POSITION *p, IN MOVE *m)
443 if (p->sBoard[m->cVpos][m->cHpos] == m->sMark)
445 p->sBoard[m->cVpos][m->cHpos] = EMPTY;
448 // One more empty square
451 ASSERT(p->uNumEmpty > 0);
452 ASSERT(p->uNumEmpty <= (BOARD_SIZE * BOARD_SIZE));
455 // Update sums as appropriate
457 p->iHSums[m->cHpos] -= m->sMark;
458 ASSERT(VALID_SUM(p->iHSums[m->cHpos]));
459 p->iVSums[m->cVpos] -= m->sMark;
460 ASSERT(VALID_SUM(p->iVSums[m->cVpos]));
461 if (m->cHpos == m->cVpos)
463 p->iDSums[0] -= m->sMark;
464 ASSERT(VALID_SUM(p->iDSums[0]));
466 else if (m->cHpos == (BOARD_SIZE - m->cVpos))
468 p->iDSums[1] -= m->sMark;
469 ASSERT(VALID_SUM(p->iDSums[1]));
475 p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn);
476 ASSERT(X_OR_O(p->sWhoseTurn));
487 //+----------------------------------------------------------------------------
489 // Function: AlphaBeta
491 // Synopsis: An AlphaBeta Search
493 // Arguments: IN OUT POSITION *p - the board
494 // IN int iAlpha - the lower bound of the score window
495 // IN int iBeta - the upper bound of the score window
496 // IN int uDepth - search depth horizon
500 //+----------------------------------------------------------------------------
502 AlphaBeta(IN POSITION *p, IN int iAlpha, IN int iBeta, IN unsigned int uDepth)
508 int iBestScore = -INFINITY;
513 // Evaluate this position
515 if (TRUE == GameOver(p, &sWhoWon))
517 if (sWhoWon == p->sWhoseTurn)
519 return(+INFINITY - g_uPly);
521 else if (sWhoWon == (p->sWhoseTurn * -1))
523 return(-INFINITY + g_uPly);
536 // No one won, game is still going. Evaluate every
537 // possible move from here.
539 ASSERT(p->uNumEmpty > 0);
540 for (s = 0; s < (BOARD_SIZE * BOARD_SIZE); s++)
542 mv.cHpos = NUM_TO_HPOS(s);
543 mv.cVpos = NUM_TO_VPOS(s);
544 mv.sMark = p->sWhoseTurn;
546 if (IsLegalMove(p, &mv))
550 iScore = -1 * AlphaBeta(p, -iBeta, -iAlpha, uDepth - 1);
551 ASSERT(-INFINITY <= iScore);
552 ASSERT(iScore <= +INFINITY);
561 if (iScore > iBestScore)
570 // If this is the ply 0 move, remember it.
584 //+----------------------------------------------------------------------------
586 // Function: SimpleSearch
588 // Synopsis: A Simple Search
590 // Arguments: IN OUT POSITION *p - the board
594 //+----------------------------------------------------------------------------
596 SimpleSearch(IN POSITION *p)
602 int iBestScore = -INFINITY;
607 // Evaluate this position
609 if (TRUE == GameOver(p, &sWhoWon))
611 if (sWhoWon == p->sWhoseTurn)
613 return(+INFINITY - g_uPly);
615 else if (sWhoWon == (p->sWhoseTurn * -1))
617 return(-INFINITY + g_uPly);
623 // No one won, game is still going. Evaluate every
624 // possible move from here.
626 ASSERT(p->uNumEmpty > 0);
627 for (s = 0; s < (BOARD_SIZE * BOARD_SIZE); s++)
629 mv.cHpos = NUM_TO_HPOS(s);
630 mv.cVpos = NUM_TO_VPOS(s);
631 mv.sMark = p->sWhoseTurn;
633 if (IsLegalMove(p, &mv))
637 iScore = -1 * SimpleSearch(p);
638 if (iScore > iBestScore)
652 //+----------------------------------------------------------------------------
654 // Function: SearchForComputerMove
656 // Synopsis: Use our sophisticated search algorithm to find a computer
659 // Arguments: IN POSITION *p - the current board
660 // OUT MOVE *m - the move the computer chooses; this move struct
661 // is populated as a side-effect of this function.
663 // Returns: void* (populates move struct)
665 //+----------------------------------------------------------------------------
666 void SearchForComputerMove(IN POSITION *p, OUT MOVE *m)
670 #if defined(PLAY_RANDOMLY)
674 x = rand() % (BOARD_SIZE * BOARD_SIZE);
675 m->cHpos = NUM_TO_HPOS(x);
676 m->cVpos = NUM_TO_VPOS(x);
677 m->sMark = g_sComputerPlays;
679 while(FALSE == IsLegalMove(p, m));
681 #elif defined(SIMPLE_SEARCH)
683 g_uPly = g_uNodes = 0;
686 printf("Searched %u node(s).\n", g_uNodes);
688 #elif defined(ALPHA_BETA_SEARCH)
690 g_uPly = g_uNodes = 0;
691 AlphaBeta(p, -INFINITY-1, +INFINITY+1, BOARD_SIZE);
693 printf("Searched %u node(s).\n", g_uNodes);
697 #error "No Search Strategy Defined"
702 //+----------------------------------------------------------------------------
706 // Synopsis: The program entry point and main game loop.
712 //+----------------------------------------------------------------------------
720 // Randomize: the random numbers returned by rand() will be based on
721 // the system clock when the program starts up.
726 // Setup the board and draw it once.
737 // See whose turn it is -- the human's or the computers -- and
738 // get a move from whoever's turn it is.
740 if (p.sWhoseTurn == g_sComputerPlays)
742 SearchForComputerMove(&p, &mv);
746 GetHumanMove(&p, &mv);
750 // Make the move on the board and draw the board again.
755 while(FALSE == GameOver(&p, &sResult));
758 // If we get here the game is over... see what happened.
763 printf("\nX's win.\n");
766 printf("\nO's win.\n");
769 printf("Tie (what a surprise)\n");