9 tic tac toe program to illustrate simple minimax searching
\r
13 Scott Gasch (SGasch) 18 Mar 2004
\r
18 ver1 : simple search
\r
19 ver2 : alpha beta search
\r
29 SQUARE g_sComputerPlays = O_MARK; // what side comp plays
\r
30 unsigned int g_uPly = 0;
\r
31 MOVE g_mvBest = { 0 };
\r
32 unsigned int g_uNodes = 0;
\r
34 //+----------------------------------------------------------------------------
\r
36 // Function: SquareContentsToChar
\r
38 // Synopsis: Helper function for DrawBoard
\r
40 // Arguments: IN SQUARE s - a square to return a char to represent
\r
42 // Returns: char - character representing square
\r
44 //+----------------------------------------------------------------------------
\r
45 char SquareContentsToChar(IN SQUARE s)
\r
67 //+----------------------------------------------------------------------------
\r
69 // Function: DrawBoard
\r
71 // Synopsis: Draw the board
\r
73 // Arguments: IN POSITION *p - pointer to a position whose board to draw
\r
77 //+----------------------------------------------------------------------------
\r
78 void DrawBoard(IN POSITION *p)
\r
82 for (y = 0; y < BOARD_SIZE; y++)
\r
84 for (x = 0; x < BOARD_SIZE; x++)
\r
86 printf("%c ", SquareContentsToChar(p->sBoard[y][x]));
\r
90 ASSERT(X_OR_O(p->sWhoseTurn));
\r
91 printf("\n%c to move.\n", SquareContentsToChar(p->sWhoseTurn));
\r
94 //+----------------------------------------------------------------------------
\r
96 // Function: ClearBoard
\r
98 // Synopsis: Clear the board
\r
100 // Arguments: IN OUT POSITION *p - pointer to position whose board to clear
\r
104 //+----------------------------------------------------------------------------
\r
105 void ClearBoard(IN OUT POSITION *p)
\r
107 memset(p->sBoard, 0, sizeof(p->sBoard));
\r
108 p->sWhoseTurn = X_MARK; // x's go first
\r
109 p->uNumEmpty = (BOARD_SIZE * BOARD_SIZE);
\r
112 //+----------------------------------------------------------------------------
\r
114 // Function: IsLegalMove
\r
116 // Synopsis: Determine if a given move is legal on a given board
\r
118 // Arguments: IN POSITION *p - the board to play the move on
\r
119 // IN MOVE *m - the move in question
\r
121 // Returns: BOOL - TRUE if it's legal, FALSE otherwise
\r
123 //+----------------------------------------------------------------------------
\r
124 BOOL IsLegalMove(IN POSITION *p, IN MOVE *m)
\r
126 if ((m->cVpos < BOARD_SIZE) && (m->cHpos < BOARD_SIZE))
\r
128 if (IS_SQUARE_EMPTY(p->sBoard[m->cVpos][m->cHpos]))
\r
136 //+----------------------------------------------------------------------------
\r
138 // Function: GetHumanMove
\r
140 // Synopsis: Ask the human for a move
\r
142 // Arguments: IN POSITION *p - the current board
\r
143 // OUT MOVE *m - the move the human made; this struct is populated
\r
144 // as a side-effect of this function.
\r
146 // Returns: void* (populates the move struct)
\r
148 //+----------------------------------------------------------------------------
\r
149 void GetHumanMove(IN POSITION *p, OUT MOVE *m)
\r
155 printf("Enter your move number: ");
\r
158 m->cHpos = NUM_TO_HPOS(x);
\r
159 m->cVpos = NUM_TO_VPOS(x);
\r
160 m->sMark = OPPOSITE_MARK(g_sComputerPlays);
\r
162 while(FALSE == IsLegalMove(p, m));
\r
165 //+----------------------------------------------------------------------------
\r
167 // Function: GameOver
\r
169 // Synopsis: Is the game over?
\r
171 // Arguments: IN POSITION *p - the board
\r
172 // OUT SQUARE *psWhoWon - who won the game (if it's over)
\r
174 // Returns: TRUE if the game is over. Also sets psWhoWon telling
\r
175 // which side one if the game is over. This also serves
\r
176 // as a very simple evaluation routine for the search.
\r
178 // FALSE if the game is not over.
\r
180 //+----------------------------------------------------------------------------
\r
181 BOOL GameOver(IN POSITION *p, OUT SQUARE *psWhoWon)
\r
186 for (x = 0; x < BOARD_SIZE; x++)
\r
190 for (y = 0; y < BOARD_SIZE; y++)
\r
192 iSum += p->sBoard[x][y];
\r
194 if (abs(iSum) == BOARD_SIZE) goto winner;
\r
197 for (y = 0; y < BOARD_SIZE; y++)
\r
201 for (x = 0; x < BOARD_SIZE; x++)
\r
203 iSum += p->sBoard[x][y];
\r
205 if (abs(iSum) == BOARD_SIZE) goto winner;
\r
209 for (x = 0; x < BOARD_SIZE; x++)
\r
211 iSum += p->sBoard[x][x];
\r
213 if (abs(iSum) == BOARD_SIZE) goto winner;
\r
216 for (x = 0; x < BOARD_SIZE; x++)
\r
218 iSum += p->sBoard[x][(BOARD_SIZE - 1 - x)];
\r
220 if (abs(iSum) == BOARD_SIZE) goto winner;
\r
223 if (p->uNumEmpty == 0)
\r
233 *psWhoWon = (iSum / BOARD_SIZE);
\r
234 ASSERT(X_OR_O(*psWhoWon));
\r
239 //+----------------------------------------------------------------------------
\r
241 // Function: MakeMove
\r
243 // Synopsis: Make a move on a board
\r
245 // Arguments: IN OUT POSITION *p - the board
\r
246 // IN MOVE *m - the move
\r
250 //+----------------------------------------------------------------------------
\r
251 void MakeMove(IN OUT POSITION *p, IN MOVE *m)
\r
253 if (TRUE == IsLegalMove(p, m))
\r
255 ASSERT(p->sBoard[m->cVpos][m->cHpos] == EMPTY);
\r
256 p->sBoard[m->cVpos][m->cHpos] = m->sMark;
\r
258 ASSERT(p->uNumEmpty < (BOARD_SIZE * BOARD_SIZE));
\r
259 p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn);
\r
261 ASSERT(g_uPly > 0);
\r
265 //+----------------------------------------------------------------------------
\r
267 // Function: UnmakeMove
\r
269 // Synopsis: The opposite of MakeMove
\r
271 // Arguments: IN OUT POSITION *p - the board
\r
272 // IN MOVE *m - the move to undo
\r
276 //+----------------------------------------------------------------------------
\r
277 void UnmakeMove(IN OUT POSITION *p, IN MOVE *m)
\r
279 if (p->sBoard[m->cVpos][m->cHpos] == m->sMark)
\r
281 p->sBoard[m->cVpos][m->cHpos] = EMPTY;
\r
283 ASSERT(p->uNumEmpty > 0);
\r
284 ASSERT(p->uNumEmpty <= (BOARD_SIZE * BOARD_SIZE));
\r
285 p->sWhoseTurn = OPPOSITE_MARK(p->sWhoseTurn);
\r
286 ASSERT(g_uPly > 0);
\r
293 AlphaBeta(IN POSITION *p, IN int iAlpha, IN int iBeta)
\r
299 int iBestScore = -INFINITY;
\r
304 // Evaluate this position
\r
306 if (TRUE == GameOver(p, &sWhoWon))
\r
308 if (sWhoWon == p->sWhoseTurn)
\r
310 return(+INFINITY - g_uPly);
\r
312 else if (sWhoWon == (p->sWhoseTurn * -1))
\r
314 return(-INFINITY + g_uPly);
\r
320 // No one won, game is still going. Evaluate every
\r
321 // possible move from here.
\r
323 ASSERT(p->uNumEmpty > 0);
\r
324 for (s = 0; s < (BOARD_SIZE * BOARD_SIZE); s++)
\r
326 mv.cHpos = NUM_TO_HPOS(s);
\r
327 mv.cVpos = NUM_TO_VPOS(s);
\r
328 mv.sMark = p->sWhoseTurn;
\r
330 if (IsLegalMove(p, &mv))
\r
334 iScore = -1 * AlphaBeta(p, -iBeta, -iAlpha);
\r
336 UnmakeMove(p, &mv);
\r
338 if (iScore >= iBeta)
\r
343 if (iScore > iBestScore)
\r
345 iBestScore = iScore;
\r
347 if (iScore > iAlpha)
\r
358 return(iBestScore);
\r
363 SimpleSearch(IN POSITION *p)
\r
369 int iBestScore = -INFINITY;
\r
374 // Evaluate this position
\r
376 if (TRUE == GameOver(p, &sWhoWon))
\r
378 if (sWhoWon == p->sWhoseTurn)
\r
380 return(+INFINITY - g_uPly);
\r
382 else if (sWhoWon == (p->sWhoseTurn * -1))
\r
384 return(-INFINITY + g_uPly);
\r
390 // No one won, game is still going. Evaluate every
\r
391 // possible move from here.
\r
393 ASSERT(p->uNumEmpty > 0);
\r
394 for (s = 0; s < (BOARD_SIZE * BOARD_SIZE); s++)
\r
396 mv.cHpos = NUM_TO_HPOS(s);
\r
397 mv.cVpos = NUM_TO_VPOS(s);
\r
398 mv.sMark = p->sWhoseTurn;
\r
400 if (IsLegalMove(p, &mv))
\r
404 iScore = -1 * SimpleSearch(p);
\r
405 if (iScore > iBestScore)
\r
407 iBestScore = iScore;
\r
414 UnmakeMove(p, &mv);
\r
417 return(iBestScore);
\r
420 //+----------------------------------------------------------------------------
\r
422 // Function: SearchForComputerMove
\r
424 // Synopsis: Use our sophisticated search algorithm to find a computer
\r
427 // Arguments: IN POSITION *p - the current board
\r
428 // OUT MOVE *m - the move the computer chooses; this move struct
\r
429 // is populated as a side-effect of this function.
\r
431 // Returns: void* (populates move struct)
\r
433 //+----------------------------------------------------------------------------
\r
434 void SearchForComputerMove(IN POSITION *p, OUT MOVE *m)
\r
438 #if defined(PLAY_RANDOMLY)
\r
441 x = rand() % (BOARD_SIZE * BOARD_SIZE);
\r
442 m->cHpos = NUM_TO_HPOS(x);
\r
443 m->cVpos = NUM_TO_VPOS(x);
\r
444 m->sMark = g_sComputerPlays;
\r
446 while(FALSE == IsLegalMove(p, m));
\r
447 #elif defined(SIMPLE_SEARCH)
\r
448 g_uPly = g_uNodes = 0;
\r
451 printf("Searched %u node(s).\n", g_uNodes);
\r
452 #elif defined(ALPHA_BETA_SEARCH)
\r
453 g_uPly = g_uNodes = 0;
\r
454 AlphaBeta(p, -INFINITY-1, +INFINITY+1);
\r
456 printf("Searched %u node(s).\n", g_uNodes);
\r
458 #error "No Search Strategy Defined"
\r
462 //+----------------------------------------------------------------------------
\r
466 // Synopsis: The program entry point and main game loop.
\r
472 //+----------------------------------------------------------------------------
\r
480 // Randomize: the random numbers returned by rand() will be based on
\r
481 // the system clock when the program starts up.
\r
486 // Setup the board and draw it once.
\r
497 // See whose turn it is -- the human's or the computers -- and
\r
498 // get a move from whoever's turn it is.
\r
500 if (p.sWhoseTurn == g_sComputerPlays)
\r
502 SearchForComputerMove(&p, &mv);
\r
506 GetHumanMove(&p, &mv);
\r
510 // Make the move on the board and draw the board again.
\r
515 while(FALSE == GameOver(&p, &sResult));
\r
518 // If we get here the game is over... see what happened.
\r
523 printf("\nX's win.\n");
\r
526 printf("\nO's win.\n");
\r
529 printf("Tie (what a surprise)\n");
\r