1 /*
2  * $Source: /cvsroot/cgoban1/cgoban1/src/goGame.c,v $
3  * $Revision: 1.3 $
4  * $Date: 2002/05/10 05:30:36 $
5  *
6  * src/gogame.c, part of Complete Goban (game program)
7  * Copyright � 1995-2000 William Shubert.
8  * See "configure.h.in" for more copyright information.
9  */
10 
11 
12 #include <wms.h>
13 #include <wms/rnd.h>
14 #include <wms/str.h>
15 #include "goBoard.h"
16 #ifdef  _GOGAME_H_
17 #error LEVELIZATION ERROR
18 #endif
19 #include "goGame.h"
20 
21 
22 /**********************************************************************
23  * Forward Declarations
24  **********************************************************************/
25 static bool  goGame_forcedHandicapPlacement(GoGame *game);
26 static void  goGame_addHandicapStones(GoGame *game, GoBoard *board);
27 static GoBoard  *goGame_replay(GoGame *game, int moves, bool separateBoard);
28 static void  goGame_updateForMove(GoGame *game, GoStone moveColor, int move);
29 static bool  isDisputeAlive(GoGame *game);
30 static void  addMoveToMoves(GoGame *game, GoStone moveColor, int move,
31 			    int moveNum);
32 static bool  goGame_canMove(GoGame *game, GoStone s, int move);
33 static bool  goGame_canDispute(GoGame *game, int move);
34 
35 
36 /**********************************************************************
37  * Globals
38  **********************************************************************/
39 const bool  goRules_freeHandicaps[goRules_num] = {
40   FALSE, FALSE, TRUE, FALSE, TRUE, TRUE};
41 
42 const GoRulesKo  goRules_ko[goRules_num] = {
43   goRulesKo_super, goRulesKo_japanese, goRulesKo_super, goRulesKo_super,
44   goRulesKo_super, goRulesKo_tibetan};
45 
46 const bool  goRules_suicide[goRules_num] = {
47   TRUE, FALSE, TRUE, TRUE, TRUE, FALSE};
48 
49 const bool  goRules_dispute[goRules_num] = {
50   TRUE, FALSE, FALSE, FALSE, FALSE, TRUE};
51 
52 const bool  goRules_scoreKills[goRules_num] = {
53   FALSE, TRUE, FALSE, TRUE, FALSE, TRUE};
54 
55 
56 /**********************************************************************
57  * Functions
58  **********************************************************************/
goGame_create(int size,GoRules rules,int handicap,float komi,const GoTime * time,bool passive)59 GoGame  *goGame_create(int size, GoRules rules, int handicap, float komi,
60 		       const GoTime *time, bool passive)  {
61   GoGame *game;
62   int  i;
63 
64   assert((handicap >= 0) && (handicap <= 27));
65   game = wms_malloc(sizeof(GoGame));
66   MAGIC_SET(game);
67   game->size = size;
68   game->rules = rules;
69   game->passive = passive;
70   game->forcePlay = FALSE;
71   if (handicap == 1)
72     handicap = 0;
73   game->handicap = handicap;
74   game->komi = komi;
75   if (time)  {
76     game->time = *time;
77   } else  {
78     game->time.type = goTime_none;
79   }
80   game->moveNum = 0;
81   game->maxMoves = 0;
82   game->passCount = 0;
83   game->movesLen = 5 /* 400 */;
84   game->moves = wms_malloc(game->movesLen * sizeof(game->moves[0]));
85   game->noLastMove = FALSE;
86 
87   game->board = goBoard_create(size);
88   game->tmpBoard = goBoard_create(size);
89   game->flags = wms_malloc(game->board->area * sizeof(uint));
90   game->hotMoves = wms_malloc(game->board->area * sizeof(int));
91   for (i = 0;  i < goBoard_area(game->board);  ++i)  {
92     game->hotMoves[i] = -1;
93     game->flags[i] = 0;
94   }
95   game->flagsCleared = TRUE;
96   game->cooloff = 0;
97   game->state = goGameState_play;
98 
99   game->setWhoseMoveNum = -1;
100   game->disputeAlive = FALSE;
101   game->setWhoseMoveColor = 0;
102   game->disputedLoc = 0;
103   if (game->handicap && goGame_forcedHandicapPlacement(game))
104     goGame_addHandicapStones(game, game->board);
105 
106   game->whoseMove = goGame_whoseTurnOnMove(game, game->moveNum);
107 
108   return(game);
109 }
110 
111 
goGame_destroy(GoGame * game)112 void  goGame_destroy(GoGame *game)  {
113   assert(MAGIC(game));
114   goBoard_destroy(game->board);
115   goBoard_destroy(game->tmpBoard);
116   wms_free(game->flags);
117   wms_free(game->hotMoves);
118   wms_free(game->moves);
119   MAGIC_UNSET(game);
120   wms_free(game);
121 }
122 
123 
goGame_move(GoGame * game,GoStone moveColor,int move,GoTimer * timer)124 bool  goGame_move(GoGame *game, GoStone moveColor, int move, GoTimer *timer)  {
125   bool  stateChange = FALSE;
126   bool  disputeAnswer;
127   int  i;
128 
129   assert(MAGIC(game));
130   assert(game->passive || (moveColor == game->whoseMove));
131   assert((game->state == goGameState_play) ||
132 	 (game->state == goGameState_dispute));
133   assert(move >= 0);
134   assert((game->state == goGameState_play) ||
135 	 (game->setWhoseMoveNum >= 0));
136   assert(game->moveNum >= game->setWhoseMoveNum);
137   assert(goGame_isLegal(game, moveColor, move));
138   game->noLastMove = FALSE;
139   if ((game->state == goGameState_play) && !game->flagsCleared)  {
140     for (i = 0;  i < goBoard_area(game->board);  ++i)  {
141       game->flags[i] = 0;
142     }
143   }
144   addMoveToMoves(game, moveColor, move, game->moveNum);
145   /*
146    * Save this timer so if we rewind, we can reset the clock.
147    */
148   if (timer != NULL) {
149     game->moves[game->moveNum].time = *timer;
150     if (timer->timeLeft < 0)
151       game->moves[game->moveNum].time.timeLeft = 0;
152   } else {
153     game->moves[game->moveNum].time.timeLeft = 0;
154   }
155   /*
156    * Place the move on the board.
157    */
158   goGame_updateForMove(game, moveColor, move);
159   game->maxMoves = game->moveNum;
160   if (move == GOGAME_PASS)  {
161     /* A pass may change your state. */
162     if (game->state == goGameState_play)  {
163       /* Two passes puts you into selectDead state. */
164       if ((game->passCount == 2) && !game->passive && !game->forcePlay)  {
165 	stateChange = TRUE;
166 	game->setWhoseMoveNum = game->moveNum;
167 	game->state = goGameState_selectDead;
168       }
169     } else  {
170       assert(game->state == goGameState_dispute);
171       assert(game->setWhoseMoveNum >= 0);
172       /*
173        * Three passes puts you back into selectDead state.
174        */
175       if ((game->passCount == 3) && !game->passive && !game->forcePlay)  {
176 	disputeAnswer = game->disputeAlive;
177 	for (i = 0;  i < goBoard_area(game->board);  ++i)  {
178 	  if (game->flags[i] & GOGAMEFLAGS_DISPUTED)  {
179 	    if (disputeAnswer)  {
180 	      game->flags[i] = (game->flags[i] & ~(GOGAMEFLAGS_DISPUTED |
181 						   GOGAMEFLAGS_MARKDEAD |
182 						   GOGAMEFLAGS_WANTDEAD)) |
183 						     GOGAMEFLAGS_RESOLVEALIVE;
184 	    } else  {
185 	      game->flags[i] = (game->flags[i] & ~GOGAMEFLAGS_DISPUTED) |
186 		GOGAMEFLAGS_MARKDEAD | GOGAMEFLAGS_RESOLVEDEAD;
187 	    }
188 	  }
189 	}
190 	stateChange = TRUE;
191 	game->maxMoves = game->setWhoseMoveNum;
192 	goGame_moveTo(game, game->setWhoseMoveNum);
193 	game->state = goGameState_selectDead;
194 	/*
195 	 * disputeAlive is sometimes changed when we replay back to the
196 	 *   end of the game, so we restore it's value here.
197 	 */
198 	game->disputeAlive = disputeAnswer;
199       }
200     }
201   }
202   game->whoseMove = goGame_whoseTurnOnMove(game, game->moveNum);
203   return(stateChange);
204 }
205 
206 
goGame_updateForMove(GoGame * game,GoStone moveColor,int move)207 static void  goGame_updateForMove(GoGame *game, GoStone moveColor, int move)  {
208   int  caps;
209 
210   assert(MAGIC(game));
211   assert((game->state == goGameState_done) ||
212 	 goGame_isLegal(game, moveColor, move));
213 
214   caps = goBoard_addStone(game->board, moveColor, move, NULL);
215   ++game->moveNum;
216   assert(game->moveNum <= game->movesLen);
217 
218   /*
219    * Update the list of hashes seen.
220    * The hashes seen is used to quickly determine whether we have seen this
221    *   board position before.  It is used for the superko rule and also to
222    *   detect a game with no result under Japanese and Tibetan rules.
223    */
224   game->moves[game->moveNum].hash = goBoard_hashNoKo(game->board, moveColor);
225 
226   /*
227    * I use hot stones to decide the Japanese and Tibetan ko rules.
228    *   Basically, a hot stone is a stone that cannot be captured.
229    * Now decide whether or not to mark this stone as hot, and also whether or
230    *   not this was a "cooling" move; that is, one that made all hot stones
231    *   no longer hot.
232    */
233   /* Check for the tibetan anti-snapback rule activation! */
234   if ((caps == 1) && (goRules_ko[game->rules] == goRulesKo_tibetan) &&
235       (goBoard_liberties(game->board, move) == 1))  {
236     game->hotMoves[move] = game->moveNum;
237   }
238   /* Check for regular Japanese ko. */
239   if (goBoard_koLoc(game->board))  {
240     game->hotMoves[move] = game->moveNum;
241   }
242   if ((move == 0) || (game->state == goGameState_play))
243     game->cooloff = game->moveNum;
244 
245   if (move == 0)  {
246     if ((game->state == goGameState_dispute) &&
247 	(game->moveNum == game->setWhoseMoveNum))
248       game->passCount = 0;
249     else  {
250       ++game->passCount;
251       if ((game->state == goGameState_play) && (game->rules == goRules_aga))
252 	goBoard_addCaps(game->board, goStone_opponent(moveColor), 1);
253     }
254   } else  {
255     game->passCount = 0;
256     if (game->state == goGameState_dispute)  {
257       game->disputeAlive = isDisputeAlive(game);
258     }
259   }
260 }
261 
262 
goGame_isLegal(GoGame * game,GoStone moveColor,int move)263 bool  goGame_isLegal(GoGame *game, GoStone moveColor, int move)  {
264   assert(MAGIC(game));
265   assert((move >= 0) &&
266 	 (move < game->board->area));
267   switch(game->state)  {
268   case goGameState_play:
269   case goGameState_dispute:
270     if (!game->passive && !game->forcePlay && (game->whoseMove != moveColor))
271       return(FALSE);
272     return(goGame_canMove(game, moveColor, move));
273     break;
274   case goGameState_selectDead:
275   case goGameState_selectDisputed:
276     return(goGame_canDispute(game, move));
277     break;
278   case goGameState_done:
279     return(FALSE);
280     break;
281   default:
282     abort();
283     break;
284   }
285 }
286 
287 
goGame_canMove(GoGame * game,GoStone s,int move)288 static bool  goGame_canMove(GoGame *game, GoStone s, int move)  {
289   GoHash  newHash;
290   bool  suicide, superKoMatch;
291   GoBoard  *b;
292   int  i, d;
293 
294   if (move == GOGAME_PASS)
295     return(TRUE);
296   if (goBoard_stone(game->board, move) != goStone_empty)
297     return(FALSE);
298 
299   newHash = goBoard_quickHash(game->board, s, move, &suicide);
300 
301   if (suicide)  {
302     if (!goRules_suicide[game->rules])
303       return(FALSE);
304     /* Single stone suicide is _always_ illegal. */
305     for (d = 0;  d < 4;  ++d)  {
306       if (goBoard_stone(game->board, move+goBoard_dir(game->board, d)) == s)
307 	break;
308     }
309     if (d == 4)
310       return(FALSE);
311   }
312 
313   /* Check for a ko. */
314   switch(goRules_ko[game->rules])  {
315   case goRulesKo_super:
316     assert(game->moveNum <= game->movesLen);
317     for (i = 0;  i < game->moveNum;  ++i)  {
318       if (goHash_eq(game->moves[i].hash, newHash) &&
319 	  (goGame_whoseTurnOnMove(game, i) == goStone_opponent(s)))  {
320 	/*
321 	 * We _may_ have a match.  Rebuild the board at the move in question
322 	 *   and see if they are equal.
323 	 */
324 	goBoard_copy(game->board, game->tmpBoard);
325 	goBoard_addStone(game->tmpBoard, s, move, NULL);
326 	b = goGame_replay(game, i, TRUE);
327 	superKoMatch = goBoard_eq(game->tmpBoard, b);
328 	goBoard_destroy(b);
329 	if (superKoMatch)
330 	  return(FALSE);
331       }
332     }
333     return(TRUE);
334     break;
335   case goRulesKo_tibetan:
336   case goRulesKo_japanese:
337     /*
338      * Although these three ko rules are _not_ the same, they all restrict
339      *   moves with "hot" stones.  The only difference in them is how to
340      *   decide which stones are hot.
341      */
342     for (d = 0;  d < 4;  ++d)  {
343       if (goGame_isHot(game, move + goBoard_dir(game->board, d)) &&
344 	  (goBoard_stone(game->board,
345 			 move + goBoard_dir(game->board, d)) != s))
346 	return(FALSE);
347     }
348     return(TRUE);
349     break;
350   default:
351     abort();
352     break;
353   }
354 }
355 
356 
goGame_canDispute(GoGame * game,int move)357 static bool  goGame_canDispute(GoGame *game, int move)  {
358   assert(MAGIC(game));
359 
360   assert((move >= 0) && (move < goBoard_area(game->board)));
361   if (!goStone_isStone(goBoard_stone(game->board, move)))
362     return(FALSE);
363   if (game->flags[move] & GOGAMEFLAGS_RESOLVED)
364     return(FALSE);
365   return(TRUE);
366 }
367 
368 
goGame_whoseTurnOnMove(GoGame * game,int moveNum)369 GoStone  goGame_whoseTurnOnMove(GoGame *game, int moveNum)  {
370   assert(MAGIC(game));
371   if ((game->setWhoseMoveNum >= 0) &&
372       (game->moveNum >= game->setWhoseMoveNum))  {
373     if ((moveNum - game->setWhoseMoveNum) & 1)
374       return(goStone_opponent(game->setWhoseMoveColor));
375     else
376       return(game->setWhoseMoveColor);
377   } else if (goGame_forcedHandicapPlacement(game))  {
378     if (game->handicap)  {
379       if (moveNum & 1)
380 	return(goStone_black);
381       else
382 	return(goStone_white);
383     } else
384       if (moveNum & 1)
385 	return(goStone_white);
386       else
387 	return(goStone_black);
388   } else  {
389     if (game->handicap)  {
390       if (moveNum < game->handicap)
391 	return(goStone_black);
392       else  {
393 	if ((moveNum - game->handicap) & 1)
394 	  return(goStone_black);
395 	else
396 	  return(goStone_white);
397       }
398     } else  {
399       if (moveNum & 1)
400 	return(goStone_white);
401       else
402 	return(goStone_black);
403     }
404   }
405 }
406 
407 
goGame_forcedHandicapPlacement(GoGame * game)408 static bool  goGame_forcedHandicapPlacement(GoGame *game)  {
409   return(!goRules_freeHandicaps[game->rules] && !game->passive &&
410 	 (game->size >= 9) && (game->size & 1) && (game->handicap <= 9));
411 }
412 
413 
goGame_addHandicapStones(GoGame * game,GoBoard * board)414 static void  goGame_addHandicapStones(GoGame *game, GoBoard *board)  {
415   int  locs[9], numLocs = 0;
416   int  lo, mid, hi;
417 
418   assert(goGame_forcedHandicapPlacement(game));
419   if (game->size >= 13)
420     lo = 3;
421   else
422     lo = 2;
423   hi = game->size - lo - 1;
424   mid = game->size / 2;
425   locs[numLocs++] = goBoard_xy2Loc(board, hi, lo);
426   locs[numLocs++] = goBoard_xy2Loc(board, lo, hi);
427   if (game->handicap >= 3)
428     locs[numLocs++] = goBoard_xy2Loc(board, lo, lo);
429   if (game->handicap >= 4)  {
430     locs[numLocs++] = goBoard_xy2Loc(board, hi, hi);
431     if (game->handicap & 1)
432       locs[numLocs++] = goBoard_xy2Loc(board, mid, mid);
433   }
434   if (game->handicap >= 6)  {
435     locs[numLocs++] = goBoard_xy2Loc(board, lo, mid);
436     locs[numLocs++] = goBoard_xy2Loc(board, hi, mid);
437   }
438   if (game->handicap >= 8)  {
439     locs[numLocs++] = goBoard_xy2Loc(board, mid, lo);
440     locs[numLocs++] = goBoard_xy2Loc(board, mid, hi);
441   }
442   while (numLocs)  {
443     --numLocs;
444     goBoard_addStone(board, goStone_black, locs[numLocs], NULL);
445   }
446 }
447 
448 
goGame_moveTo(GoGame * game,int moveNum)449 bool  goGame_moveTo(GoGame *game, int moveNum)  {
450   bool  newState = FALSE;
451   int  oldSetColorMove;
452 
453   assert(MAGIC(game));
454   assert(moveNum >= 0);
455   assert(game->passive || game->forcePlay ||
456 	 ((game->state == goGameState_play) ||
457 	  (game->state == goGameState_done) ||
458 	  (game->state == goGameState_dispute)));
459   assert((game->state != goGameState_dispute) ||
460 	 (moveNum >= game->setWhoseMoveNum));
461   assert(moveNum <= game->maxMoves);
462   game->moveNum = moveNum;
463   oldSetColorMove = game->setWhoseMoveNum;
464   goGame_replay(game, game->moveNum, FALSE);
465   if ((game->state == goGameState_dispute) &&
466       (game->moveNum >= oldSetColorMove))
467     game->setWhoseMoveNum = oldSetColorMove;
468   if ((game->state == goGameState_play) && (game->passCount == 2) &&
469       !game->passive && !game->forcePlay)  {
470     newState = TRUE;
471     game->state = goGameState_done;
472   } else if ((game->state == goGameState_done) && (game->passCount < 2)
473 	     && !game->passive && !game->forcePlay)  {
474     newState = TRUE;
475     game->state = goGameState_play;
476   }
477   return(newState);
478 }
479 
480 
481 /*
482  * If separateBoard is set, then we assume that you only want the way the
483  *   board looked at that point so the game structure will be unchanged.
484  * Otherwise game will be exactly as if a game were played to that point,
485  *   except (of course) maxMoves will be different.
486  */
goGame_replay(GoGame * game,int moves,bool separateBoard)487 static GoBoard  *goGame_replay(GoGame *game, int moves, bool separateBoard)  {
488   GoBoard  *b;
489   int  i;
490 
491   assert(moves <= game->maxMoves);
492   b = goBoard_create(game->size);
493   if (!separateBoard)  {
494     goBoard_destroy(game->board);
495     game->setWhoseMoveNum = -1;
496     game->board = b;
497     game->moveNum = 0;
498     game->passCount = 0;
499     for (i = 0;  i < goBoard_area(b);  ++i)  {
500       game->hotMoves[i] = -1;
501     }
502   }
503   if (game->handicap && goGame_forcedHandicapPlacement(game))
504     goGame_addHandicapStones(game, b);
505   assert(moves <= game->movesLen);
506   for (i = 0;  i < moves;  ++i)  {
507     if (separateBoard) {
508       goBoard_addStone(b, game->moves[i].color, game->moves[i].move, NULL);
509     } else {
510       game->whoseMove = game->moves[i].color;
511       if ((game->time.type == goTime_none) ||
512 	  (game->moves[i].time.timeLeft >= 0))  {
513 	/*
514 	 * If there was a time loss, then the last move isn't valid - the
515 	 *   game ended before the move could be made.
516 	 */
517 	goGame_updateForMove(game, game->whoseMove, game->moves[i].move);
518       } else {
519 	++game->moveNum;
520 	assert(game->moveNum < game->movesLen);
521       }
522     }
523   }
524   if (!separateBoard)  {
525     game->whoseMove = goGame_whoseTurnOnMove(game, i);
526   }
527   return(b);
528 }
529 
530 
goGame_markDead(GoGame * game,int loc)531 void  goGame_markDead(GoGame *game, int loc)  {
532   GoBoardGroupIter  i;
533   int  iterLoc;
534 
535   assert(MAGIC(game));
536   assert(goStone_isStone(goBoard_stone(game->board, loc)));
537   goBoardGroupIter(i, game->board, loc)  {
538     iterLoc = goBoardGroupIter_loc(i, game->board);
539     game->flags[iterLoc] ^= GOGAMEFLAGS_MARKDEAD;
540   }
541 }
542 
543 
goGame_resume(GoGame * game)544 void  goGame_resume(GoGame *game)  {
545   assert(MAGIC(game));
546   assert(game->state == goGameState_selectDead);
547   game->maxMoves -= 2;
548   game->state = goGameState_play;
549   goGame_replay(game, game->maxMoves, FALSE);
550   game->whoseMove = goGame_whoseTurnOnMove(game, game->moveNum);
551 }
552 
553 
goGame_done(GoGame * game)554 void  goGame_done(GoGame *game)  {
555   assert(MAGIC(game));
556   assert(game->state == goGameState_selectDead);
557   game->state = goGameState_done;
558   game->maxMoves = game->moveNum;
559 }
560 
561 
goGame_dispute(GoGame * game,int loc)562 void  goGame_dispute(GoGame *game, int loc)  {
563   GoBoardGroupIter  i;
564   int  stoneLoc;
565 
566   assert(MAGIC(game));
567   assert(game->state == goGameState_selectDisputed);
568   game->state = goGameState_dispute;
569   game->disputeAlive = TRUE;
570   game->disputedLoc = loc;
571   game->setWhoseMoveColor = goStone_opponent(goBoard_stone(game->board, loc));
572   game->passCount = 0;
573   game->state = goGameState_dispute;
574   assert(goStone_isStone(game->setWhoseMoveColor));
575   for (stoneLoc = 0;  stoneLoc < goBoard_area(game->board);  ++stoneLoc);
576   goBoardGroupIter(i, game->board, loc)  {
577     stoneLoc = goBoardGroupIter_loc(i, game->board);
578     game->flags[stoneLoc] = GOGAMEFLAGS_DISPUTED;
579   }
580   game->whoseMove = goGame_whoseTurnOnMove(game, game->moveNum);
581 }
582 
583 
isDisputeAlive(GoGame * game)584 static bool  isDisputeAlive(GoGame *game)  {
585   int  i;
586 
587   for (i = 0;  i < goBoard_area(game->board);  ++i)  {
588     if ((game->flags[i] & GOGAMEFLAGS_DISPUTED) &&
589 	(goBoard_stone(game->board, i) ==
590 	 goStone_opponent(game->setWhoseMoveColor)))
591       return(TRUE);
592   }
593   return(FALSE);
594 }
595 
596 
addMoveToMoves(GoGame * game,GoStone moveColor,int move,int moveNum)597 static void  addMoveToMoves(GoGame *game, GoStone moveColor, int move,
598 			    int moveNum)  {
599   GoGameMove  *newMoves;
600   int  i;
601 
602   if (moveNum + 1 == game->movesLen)  {
603     newMoves = wms_malloc(game->movesLen * 2 * sizeof(game->moves[0]));
604     for (i = 0;  i < game->movesLen;  ++i)  {
605       newMoves[i] = game->moves[i];
606     }
607     wms_free(game->moves);
608     game->moves = newMoves;
609     game->movesLen *= 2;
610   }
611   assert(moveNum + 1 < game->movesLen);
612   game->moves[moveNum].move = move;
613   game->moves[moveNum].color = moveColor;
614 }
615 
616 
goGame_lastMove(GoGame * g)617 int  goGame_lastMove(GoGame *g)  {
618   assert(MAGIC(g));
619   if (g->noLastMove || (g->moveNum == 0))
620     return(0);
621   assert(g->moveNum > 0);
622   assert(g->moveNum <= g->movesLen);
623   return(g->moves[g->moveNum - 1].move);
624 }
625 
626 
goGame_setBoard(GoGame * game,GoStone color,int loc)627 void  goGame_setBoard(GoGame *game, GoStone color, int loc)  {
628   assert(MAGIC(game));
629   if (goStone_isStone(color) &&
630       goStone_isStone(goBoard_stone(game->board, loc)))  {
631     if (goBoard_stone(game->board, loc) == color)
632       return;
633     goBoard_addStone(game->board, goStone_empty, loc, NULL);
634   }
635   goBoard_addStone(game->board, color, loc, NULL);
636 }
637 
638 
goGame_getTimer(const GoGame * game,GoStone color)639 const GoTimer  *goGame_getTimer(const GoGame *game, GoStone color)  {
640   if (color == game->whoseMove)  {
641     if (game->moveNum <= 1) {
642       return(NULL);
643     } else {
644       return(&game->moves[game->moveNum - 2].time);
645     }
646   } else  {
647     if (game->moveNum <= 0) {
648       return(NULL);
649     } else {
650       return(&game->moves[game->moveNum - 1].time);
651     }
652   }
653 }
654 
655