1 /*
2  * $Source: /cvsroot/cgoban1/cgoban1/src/local.c,v $
3  * $Revision: 1.2 $
4  * $Date: 2000/02/09 06:50:02 $
5  *
6  * Part of Complete Goban (game program)
7  * Copyright � 1995-1996,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 <but/but.h>
15 #include <but/plain.h>
16 #include <but/ctext.h>
17 #include <but/text.h>
18 #include <abut/term.h>
19 #include <abut/msg.h>
20 #include <abut/fsel.h>
21 #include <wms/clp.h>
22 #include <wms/str.h>
23 #include <but/text.h>
24 #include <but/textin.h>
25 #include "cgoban.h"
26 #include "goban.h"
27 #include "msg.h"
28 #include "goGame.h"
29 #include "goPic.h"
30 #include "cgbuts.h"
31 #include "sgf.h"
32 #include "sgfIn.h"
33 #include "sgfOut.h"
34 #include "sgfPlay.h"
35 #include "editBoard.h"
36 #ifdef  _LOCAL_H_
37    Levelization Error.
38 #endif
39 #include "local.h"
40 
41 
42 /**********************************************************************
43  * Forward references
44  **********************************************************************/
45 static GobanOut  gridPressed(void *packet, int loc);
46 static GobanOut  quitPressed(void *packet);
47 static GobanOut  passPressed(void *packet);
48 static GobanOut  rewPressed(void *packet);
49 static GobanOut  backPressed(void *packet);
50 static GobanOut  fwdPressed(void *packet);
51 static GobanOut  ffPressed(void *packet);
52 static GobanOut  donePressed(void *packet);
53 static GobanOut  disputePressed(void *packet);
54 static GobanOut  savePressed(void *packet);
55 static GobanOut  editPressed(void *packet);
56 static void  saveFile(AbutFsel *fsel, void *packet, const char *fname);
57 static void  writeGobanComments(Local *l), readGobanComments(Local *l);
58 static void  addDisputedTriangles(GoGame *game, Sgf *sgf);
59 static void  addTimeInfoToSgf(Local *l, GoStone color);
60 static void  recordTimeLoss(Local *l);
61 static bool  rewOk(void *packet), backOk(void *packet), fwdOk(void *packet);
62 static void  gobanDestroyed(void *packet);
63 static ButOut  reallyQuit(But *but);
64 static ButOut  dontQuit(But *but);
65 static ButOut  quitWinDead(void *packet);
66 static void  setTimers(Local *l);
67 
68 
69 /**********************************************************************
70  * Global variables
71  **********************************************************************/
72 static const GobanActions  local_actions = {
73   gridPressed, quitPressed, passPressed, rewPressed, backPressed,
74   fwdPressed, ffPressed, donePressed, disputePressed, savePressed,
75   editPressed, NULL /* gameInfo */,
76   &help_localBoard,
77   gobanDestroyed,
78   rewOk, backOk, fwdOk, fwdOk};
79 
80 
81 /**********************************************************************
82  * Functions
83  **********************************************************************/
local_create(Cgoban * cg,GoRules rules,int size,int hcap,float komi,const char * white,const char * black,GoTimeType timeType,int mainTime,int byTime,int auxTime)84 Local  *local_create(Cgoban *cg, GoRules rules, int size, int hcap,
85 		     float komi, const char *white, const char *black,
86 		     GoTimeType timeType, int mainTime, int byTime,
87 		     int auxTime)  {
88   Sgf  *mc;
89   Str  tmp;
90   GoTime  time;
91 
92   mc = sgf_create();
93   str_init(&tmp);
94   sgf_setRules(mc, rules);
95   sgf_setSize(mc, size);
96   sgf_setHandicap(mc, hcap);
97   sgf_setKomi(mc, komi);
98   sgf_setPlayerName(mc, goStone_white, white);
99   sgf_setPlayerName(mc, goStone_black, black);
100   str_print(&tmp, msg_localTitle, white, black);
101   sgf_setTitle(mc, str_chars(&tmp));
102   sgf_setDate(mc);
103   sgf_style(mc, "Cgoban " VERSION);
104   time.type = timeType;
105   time.main = mainTime;
106   time.by = byTime;
107   time.aux = auxTime;
108   goTime_describeStr(&time, &tmp);
109   sgf_setTimeFormat(mc, str_chars(&tmp));
110   str_deinit(&tmp);
111   return(local_createSgf(cg, mc));
112 }
113 
114 
local_createFile(Cgoban * cg,const char * fname)115 Local  *local_createFile(Cgoban *cg, const char *fname)  {
116   Str  badFile;
117   Sgf  *mc;
118   SgfElem  *style;
119   const char  *err;
120 
121   mc = sgf_createFile(cg, fname, &err, NULL);
122   if (mc == NULL)  {
123     abutMsg_winCreate(cg->abut, "Cgoban Error", err);
124     return(NULL);
125   }
126   style = sgf_findType(mc, sgfType_style);
127   if (!style || strncmp(str_chars(style->sVal), "Cgoban", 6))  {
128     str_init(&badFile);
129     str_print(&badFile, msg_notCgobanFile, fname);
130     abutMsg_winCreate(cg->abut, "Cgoban Error", str_chars(&badFile));
131     str_deinit(&badFile);
132     sgf_destroy(mc);
133     return(NULL);
134   }
135   return(local_createSgf(cg, mc));
136 }
137 
138 
local_createSgf(Cgoban * cg,Sgf * mc)139 Local  *local_createSgf(Cgoban *cg, Sgf *mc)  {
140   Local  *l;
141   SgfElem  *me;
142   GoRules  rules;
143   int  size, hcap;
144   float  komi;
145   GoTime  time;
146   const char  *title;
147 
148   assert(MAGIC(cg));
149   l = wms_malloc(sizeof(Local));
150   MAGIC_SET(l);
151   l->cg = cg;
152   l->moves = mc;
153   l->endGame = NULL;
154   me = sgf_findType(mc, sgfType_rules);
155   if (me)  {
156     rules = (GoRules)me->iVal;
157   } else
158     rules = goRules_japanese;
159   me = sgf_findType(mc, sgfType_size);
160   if (me)  {
161     size = me->iVal;
162   } else  {
163     size = 19;
164   }
165   me = sgf_findType(mc, sgfType_handicap);
166   if (me)  {
167     hcap = me->iVal;
168   } else  {
169     hcap = 0;
170   }
171   me = sgf_findType(mc, sgfType_komi);
172   if (me)  {
173     komi = (float)me->iVal / 2.0;
174   } else  {
175     komi = 0.0;
176   }
177   me = sgf_findFirstType(mc, sgfType_playerName);
178   while (me)  {
179     me = sgfElem_findFirstType(me, sgfType_playerName);
180   }
181   me = sgf_findFirstType(mc, sgfType_time);
182   if (me)  {
183     goTime_parseDescribeChars(&time, str_chars(me->sVal));
184   } else  {
185     time.type = goTime_none;
186   }
187   l->game = goGame_create(size, rules, hcap, komi, &time, FALSE);
188   sgf_play(mc, l->game, NULL, -1, NULL);
189   l->modified = FALSE;
190 
191   me = sgf_findFirstType(mc, sgfType_title);
192   if (me)  {
193     title = str_chars(me->sVal);
194   } else  {
195     title = msg_noTitle;
196   }
197 
198   l->goban = goban_create(cg, &local_actions, l, l->game, "local",
199 			  title);
200   l->goban->iDec1 = grid_create(&cg->cgbuts, NULL, NULL, l->goban->iWin, 2,
201 				BUT_DRAWABLE, 0);
202   grid_setStone(l->goban->iDec1, goStone_white, FALSE);
203   l->goban->iDec2 = grid_create(&cg->cgbuts, NULL, NULL, l->goban->iWin, 2,
204 				BUT_DRAWABLE, 0);
205   grid_setStone(l->goban->iDec2, goStone_black, FALSE);
206   setTimers(l);
207   l->lastComment = NULL;
208   assert((hcap == 0) || ((hcap >= 2) && (hcap <= 27)));
209 
210   l->fsel = NULL;
211   l->reallyQuit = NULL;
212   return(l);
213 }
214 
215 
local_destroy(Local * l)216 void  local_destroy(Local *l)  {
217   assert(MAGIC(l));
218   if (l->goban)
219     goban_destroy(l->goban, FALSE);
220   if (l->game)
221     goGame_destroy(l->game);
222   if (l->fsel)
223     abutFsel_destroy(l->fsel, FALSE);
224   if (l->reallyQuit)
225     abutMsg_destroy(l->reallyQuit, FALSE);
226   MAGIC_UNSET(l);
227   wms_free(l);
228 }
229 
230 
gridPressed(void * packet,int loc)231 static GobanOut  gridPressed(void *packet, int loc)  {
232   Local  *l = packet;
233   GoStone  moveColor;
234   bool  timeLeft;
235 
236   assert(MAGIC(l));
237   l->modified = TRUE;
238   moveColor = goGame_whoseMove(l->game);
239   if (l->game->state <= goGameState_dispute)  {
240     readGobanComments(l);
241     sgf_addNode(l->moves);
242     if (l->game->state == goGameState_dispute)  {
243       addDisputedTriangles(l->game, l->moves);
244     }
245     timeLeft = goban_stopTimer(l->goban);
246     if (!timeLeft)  {
247       l->game->state = goGameState_done;
248       recordTimeLoss(l);
249     } else  {
250       sgf_move(l->moves, moveColor, goBoard_loc2Sgf(l->game->board, loc));
251       goGame_move(l->game, moveColor, loc, &l->goban->timers[moveColor]);
252       addTimeInfoToSgf(l, moveColor);
253       goban_startTimer(l->goban, goStone_opponent(moveColor));
254     }
255   } else if (l->game->state == goGameState_selectDead)  {
256     goGame_markDead(l->game, loc);
257   } else if (l->game->state == goGameState_selectDisputed)  {
258     l->endGame = l->moves->active;
259     goGame_dispute(l->game, loc);
260     /*
261      * We briefly change to "variant" insert mode so that multiple disputes
262      *   will be seen in the order they were made.  Then we switch back to
263      *   "main" insert mode so that undos and ends of disputes will be
264      *   the main variation.
265      */
266     l->moves->mode = sgfInsert_variant;
267     sgf_addNode(l->moves);
268     addDisputedTriangles(l->game, l->moves);
269     sgf_setWhoseMove(l->moves, l->game->setWhoseMoveColor);
270     l->moves->mode = sgfInsert_main;
271   }
272   writeGobanComments(l);
273   return(gobanOut_draw);
274 }
275 
276 
277 /*
278  * This transfers comments from the SGF move chain to the goban.
279  */
writeGobanComments(Local * l)280 static void  writeGobanComments(Local *l)  {
281   SgfElem  *comElem;
282 
283   assert(MAGIC(l));
284   comElem = sgfElem_findTypeInNode(l->moves->active, sgfType_comment);
285   if (comElem)  {
286     if (strcmp(str_chars(comElem->sVal), goban_getComments(l->goban)))
287       goban_setComments(l->goban, str_chars(comElem->sVal));
288   } else  {
289     if (l->lastComment)
290       goban_setComments(l->goban, "");
291   }
292   l->lastComment = comElem;
293 }
294 
295 
296 /*
297  * This transfers comments from the goban to the SGF move chain.
298  */
readGobanComments(Local * l)299 static void  readGobanComments(Local *l)  {
300   const char  *comm;
301 
302   comm = goban_getComments(l->goban);
303   if (comm[0])  {
304     if (l->lastComment)  {
305       if (!strcmp(comm, str_chars(l->lastComment->sVal)))
306 	return;
307     }
308     l->modified = TRUE;
309     l->moves->mode = sgfInsert_inline;
310     sgf_comment(l->moves, comm);
311     l->moves->mode = sgfInsert_main;
312     l->lastComment = l->moves->active;
313   } else if (l->lastComment) {
314     l->modified = TRUE;
315     sgfElem_snip(l->lastComment, l->moves);
316     l->lastComment = NULL;
317   }
318 }
319 
320 
quitPressed(void * packet)321 static GobanOut  quitPressed(void *packet)  {
322   Local  *l = packet;
323   AbutMsgOpt  buttons[2];
324 
325   assert(MAGIC(l));
326   if (l->modified)  {
327     buttons[0].name = msg_noCancel;
328     buttons[0].callback = dontQuit;
329     buttons[0].packet = l;
330     buttons[0].keyEq = NULL;
331     buttons[1].name = msg_yesQuit;
332     buttons[1].callback = reallyQuit;
333     buttons[1].packet = l;
334     buttons[1].keyEq = NULL;
335     if (l->reallyQuit)
336       abutMsg_destroy(l->reallyQuit, FALSE);
337     l->reallyQuit = abutMsg_winOptCreate(l->cg->abut, "Cgoban Warning",
338 					 msg_reallyQuitGame,
339 					 quitWinDead, l, 2, buttons);
340   } else
341     local_destroy(l);
342   return(gobanOut_noDraw);
343 }
344 
345 
passPressed(void * packet)346 static GobanOut  passPressed(void *packet)  {
347   Local  *l = packet;
348   GoGameState  oldState = l->game->state;
349   GoStone  moveColor;
350   bool  timeLeft;
351 
352   assert(MAGIC(l));
353   readGobanComments(l);
354   moveColor = goGame_whoseMove(l->game);
355   sgf_addNode(l->moves);
356   if (oldState == goGameState_dispute)
357     addDisputedTriangles(l->game, l->moves);
358   sgf_pass(l->moves, moveColor);
359   addTimeInfoToSgf(l, moveColor);
360   timeLeft = goban_stopTimer(l->goban);
361   if (!timeLeft)  {
362     l->game->state = goGameState_done;
363     recordTimeLoss(l);
364   } else if (goGame_pass(l->game, moveColor, &l->goban->timers[moveColor]))  {
365     /*
366      * The game state has changed.
367      */
368     if (oldState == goGameState_dispute)  {
369       assert(MAGIC(l->endGame));
370       l->moves->active = l->endGame;
371       l->endGame = NULL;
372       if (l->game->disputeAlive)
373 	goban_message(l->goban, msg_disputeOverAlive);
374       else
375 	goban_message(l->goban, msg_disputeOverDead);
376     } else  {
377       assert(oldState == goGameState_play);
378       if ((l->game->rules == goRules_japanese) ||
379 	  (l->game->rules == goRules_tibetan))
380 	goban_message(l->goban, msg_localJapRemDead);
381       else
382 	goban_message(l->goban, msg_localChiRemDead);
383     }
384   } else  {
385     goban_startTimer(l->goban, goStone_opponent(moveColor));
386   }
387   writeGobanComments(l);
388   return(gobanOut_draw);
389 }
390 
391 
rewPressed(void * packet)392 static GobanOut  rewPressed(void *packet)  {
393   Local  *l = packet;
394 
395   assert(MAGIC(l));
396   readGobanComments(l);
397   goban_noMessage(l->goban);
398   goban_stopTimer(l->goban);
399   if (l->game->state == goGameState_dispute)
400     goGame_moveTo(l->game, l->game->setWhoseMoveNum);
401   else
402     goGame_moveTo(l->game, 0);
403   sgf_setActiveNodeNumber(l->moves, l->game->moveNum);
404   setTimers(l);
405   writeGobanComments(l);
406   return(gobanOut_draw);
407 }
408 
409 
backPressed(void * packet)410 static GobanOut  backPressed(void *packet)  {
411   Local  *l = packet;
412 
413   assert(MAGIC(l));
414   readGobanComments(l);
415   goban_noMessage(l->goban);
416   goban_stopTimer(l->goban);
417   if (l->game->state != goGameState_selectDead)  {
418     goGame_moveTo(l->game, l->game->moveNum - 1);
419   } else  {
420     /* We must be in the endgame.  Let's continue the game now instead! */
421     goGame_resume(l->game);
422   }
423   sgf_setActiveNodeNumber(l->moves, l->game->moveNum);
424   setTimers(l);
425   writeGobanComments(l);
426   return(gobanOut_draw);
427 }
428 
429 
fwdPressed(void * packet)430 static GobanOut  fwdPressed(void *packet)  {
431   Local  *l = packet;
432 
433   assert(MAGIC(l));
434   readGobanComments(l);
435   goban_noMessage(l->goban);
436   goban_stopTimer(l->goban);
437   goGame_moveTo(l->game, l->game->moveNum + 1);
438   sgf_setActiveNodeNumber(l->moves, l->game->moveNum);
439   setTimers(l);
440   writeGobanComments(l);
441   return(gobanOut_draw);
442 }
443 
444 
ffPressed(void * packet)445 static GobanOut  ffPressed(void *packet)  {
446   Local  *l = packet;
447 
448   assert(MAGIC(l));
449   readGobanComments(l);
450   goban_noMessage(l->goban);
451   goban_stopTimer(l->goban);
452   goGame_moveTo(l->game, l->game->maxMoves);
453   sgf_setActiveNodeNumber(l->moves, l->game->moveNum);
454   setTimers(l);
455   writeGobanComments(l);
456   return(gobanOut_draw);
457 }
458 
459 
donePressed(void * packet)460 static GobanOut  donePressed(void *packet)  {
461   Local *l = packet;
462   int  i;
463   Str  *scoreComment, winner, doneMessage, result;
464   float  wScore, bScore;
465   SgfElem  *resultElem;
466 
467   assert(MAGIC(l));
468   assert(l->game->state == goGameState_selectDead);
469   readGobanComments(l);
470   goban_stopTimer(l->goban);
471   str_init(&winner);
472   str_init(&doneMessage);
473   str_init(&result);
474   goban_noMessage(l->goban);
475   goGame_done(l->game);
476   sgf_addNode(l->moves);
477   /*
478    * Add a territory list.
479    */
480   for (i = 0;  i < goBoard_area(l->game->board);  ++i)  {
481     if (((l->game->flags[i] & GOGAMEFLAGS_SEEN) == GOGAMEFLAGS_SEEWHITE) &&
482 	((goBoard_stone(l->game->board, i) == goStone_empty) ||
483 	 (l->game->flags[i] & GOGAMEFLAGS_MARKDEAD)))
484       sgf_addTerritory(l->moves, goStone_white,
485 			     goBoard_loc2Sgf(l->game->board, i));
486   }
487   for (i = 0;  i < goBoard_area(l->game->board);  ++i)  {
488     if (((l->game->flags[i] & GOGAMEFLAGS_SEEN) == GOGAMEFLAGS_SEEBLACK) &&
489 	((goBoard_stone(l->game->board, i) == goStone_empty) ||
490 	 (l->game->flags[i] & GOGAMEFLAGS_MARKDEAD)))
491       sgf_addTerritory(l->moves, goStone_black,
492 			     goBoard_loc2Sgf(l->game->board, i));
493   }
494   wScore = goban_score(l->goban, goStone_white);
495   bScore = goban_score(l->goban, goStone_black);
496   if (wScore > bScore)  {
497     str_print(&winner, msg_winnerComment,
498 	      msg_stoneNames[goStone_white],
499 	      wScore - bScore);
500     str_print(&result, "W+%g", wScore - bScore);
501   } else if ((bScore > wScore) || (l->game->rules == goRules_ing))  {
502     str_print(&winner, msg_winnerComment,
503 	      msg_stoneNames[goStone_black],
504 	      bScore - wScore);
505     str_print(&result, "B+%g", bScore - wScore);
506   } else  {
507     str_print(&winner, msg_jigoComment);
508     str_copyChar(&result, '0');
509   }
510   str_print(&doneMessage, msg_gameIsOver, winner);
511   goban_message(l->goban, str_chars(&doneMessage));
512 
513   scoreComment = goScore_str(&l->goban->score, l->game,
514 			     &l->game->time, l->goban->timers);
515   sgf_catComment(l->moves, str_chars(scoreComment));
516   str_destroy(scoreComment);
517 
518   resultElem = sgf_findType(l->moves, sgfType_result);
519   if (resultElem)  {
520     sgfElem_newString(resultElem, str_chars(&result));
521   } else  {
522     sgf_setActiveNodeNumber(l->moves, 0);
523     l->moves->mode = sgfInsert_inline;
524     sgf_result(l->moves, str_chars(&result));
525     l->moves->mode = sgfInsert_main;
526     sgf_setActiveToEnd(l->moves);
527   }
528   str_deinit(&winner);
529   str_deinit(&doneMessage);
530   str_deinit(&result);
531   writeGobanComments(l);
532   return(gobanOut_draw);
533 }
534 
535 
disputePressed(void * packet)536 static GobanOut  disputePressed(void *packet)  {
537   Local *l = packet;
538 
539   assert(MAGIC(l));
540   goGame_selectDisputed(l->game);
541   goban_message(l->goban, msg_selectDisputedMsg);
542   return(gobanOut_draw);
543 }
544 
545 
savePressed(void * packet)546 static GobanOut  savePressed(void *packet)  {
547   Local  *l = packet;
548 
549   assert(MAGIC(l));
550   if (l->fsel)
551     abutFsel_destroy(l->fsel, FALSE);
552   l->fsel = abutFsel_create(l->cg->abut, saveFile, l, "CGoban",
553 			    msg_saveGameName,
554 			    clp_getStr(l->cg->clp, "local.sgfName"));
555   return(gobanOut_noDraw);
556 }
557 
558 
editPressed(void * packet)559 static GobanOut  editPressed(void *packet)  {
560   Local  *l = packet;
561 
562   assert(MAGIC(l));
563   editBoard_createSgf(l->cg, l->moves);
564   return(gobanOut_noDraw);
565 }
566 
567 
saveFile(AbutFsel * fsel,void * packet,const char * fname)568 static void  saveFile(AbutFsel *fsel, void *packet, const char *fname)  {
569   Local  *l = packet;
570   int error;
571   Str str;
572 
573   assert(MAGIC(l));
574   str_copy(&l->cg->lastDirAccessed, &fsel->pathVal);
575   if (fname != NULL)  {
576     readGobanComments(l);
577     l->modified = FALSE;
578     clp_setStr(l->cg->clp, "local.sgfName", butTextin_get(fsel->in));
579     if (!sgf_writeFile(l->moves, fname, &error)) {
580       str_init(&str);
581       str_print(&str, "Error saving file \"%s\": %s",
582 		fname, strerror(errno));
583       cgoban_createMsgWindow(l->cg, "Cgoban Error", str_chars(&str));
584       str_deinit(&str);
585     }
586   }
587   l->fsel = NULL;
588 }
589 
590 
addDisputedTriangles(GoGame * game,Sgf * sgf)591 static void  addDisputedTriangles(GoGame *game, Sgf *sgf)  {
592   int  i;
593 
594   for (i = 0;  i < goBoard_area(game->board);  ++i)  {
595     if (game->flags[i] & GOGAMEFLAGS_DISPUTED)
596       sgf_addTriangle(sgf, goBoard_loc2Sgf(game->board, i));
597   }
598 }
599 
600 
addTimeInfoToSgf(Local * l,GoStone who)601 static void  addTimeInfoToSgf(Local *l, GoStone who)  {
602   if (l->game->time.type != goTime_none)  {
603     sgf_timeLeft(l->moves, who,
604 		 l->goban->timers[who].timeLeft +
605 		 (l->goban->timers[who].usLeft > 0));
606     if ((l->game->time.type == goTime_ing) ||
607 	((l->game->time.type == goTime_canadian) &&
608 	 l->goban->timers[who].aux))  {
609       sgf_stonesLeft(l->moves, who, l->goban->timers[who].aux);
610     }
611   }
612 }
613 
614 
recordTimeLoss(Local * l)615 static void  recordTimeLoss(Local *l)  {
616   Str  timeMsg;
617   GoStone  loser = l->game->whoseMove;
618 
619   /* I don't think that this code is needed any more.
620    * sgf_timeLeft(l->moves, l->game->timeLoss, -1);
621    * if ((l->game->time.type == goTime_ing) ||
622    *     ((l->game->time.type == goTime_canadian) &&
623    *      l->goban->timers[loser].aux))  {
624    *   sgf_stonesLeft(l->moves, loser,
625    *                  l->goban->timers[loser].aux +
626    *                  (l->game->time.type == goTime_canadian));
627    * }
628    */
629   str_init(&timeMsg);
630   str_print(&timeMsg, msg_timeLoss,
631 	    msg_stoneNames[loser],
632 	    msg_stoneNames[goStone_opponent(loser)]);
633   goban_message(l->goban, str_chars(&timeMsg));
634   str_catChar(&timeMsg, '\n');
635   sgf_catComment(l->moves, str_chars(&timeMsg));
636   str_print(&timeMsg, "%c+T",
637 	    (int)goStone_char(goStone_opponent(loser)));
638   sgf_setActiveNodeNumber(l->moves, 0);
639   l->moves->mode = sgfInsert_inline;
640   sgf_result(l->moves, str_chars(&timeMsg));
641   l->moves->mode = sgfInsert_main;
642   sgf_setActiveToEnd(l->moves);
643   str_deinit(&timeMsg);
644 }
645 
646 
rewOk(void * packet)647 static bool  rewOk(void *packet)  {
648   Local  *l = packet;
649   GoGame  *game;
650 
651   assert(MAGIC(l));
652   game = l->game;
653   switch(game->state)  {
654   case(goGameState_selectDead):
655   case(goGameState_selectDisputed):
656     return(FALSE);
657     break;
658   case(goGameState_dispute):
659     return(game->moveNum > game->setWhoseMoveNum);
660     break;
661   default:
662     return(game->moveNum > 0);
663     break;
664   }
665 }
666 
667 
backOk(void * packet)668 static bool  backOk(void *packet)  {
669   Local  *l = packet;
670   GoGame  *game;
671 
672   assert(MAGIC(l));
673   game = l->game;
674   switch(game->state)  {
675   case(goGameState_selectDead):
676     return(TRUE);
677     break;
678   case(goGameState_selectDisputed):
679     return(FALSE);
680     break;
681   case(goGameState_dispute):
682     return(game->moveNum > game->setWhoseMoveNum);
683     break;
684   default:
685     return(game->moveNum > 0);
686     break;
687   }
688 }
689 
690 
fwdOk(void * packet)691 static bool  fwdOk(void *packet)  {
692   Local  *l = packet;
693   GoGame  *game;
694 
695   assert(MAGIC(l));
696   game = l->game;
697   switch(game->state)  {
698   case(goGameState_selectDead):
699   case(goGameState_selectDisputed):
700     return(FALSE);
701     break;
702   default:
703     return(game->moveNum < game->maxMoves);
704     break;
705   }
706 }
707 
708 
gobanDestroyed(void * packet)709 static void  gobanDestroyed(void *packet)  {
710   Local  *l = packet;
711 
712   assert(MAGIC(l));
713   l->goban = NULL;
714   local_destroy(l);
715 }
716 
717 
reallyQuit(But * but)718 static ButOut  reallyQuit(But *but)  {
719   local_destroy(but_packet(but));
720   return(0);
721 }
722 
723 
dontQuit(But * but)724 static ButOut  dontQuit(But *but)  {
725   Local  *l = but_packet(but);
726 
727   assert(MAGIC(l));
728   abutMsg_destroy(l->reallyQuit, FALSE);
729   l->reallyQuit = NULL;
730   return(0);
731 }
732 
733 
quitWinDead(void * packet)734 static ButOut  quitWinDead(void *packet)  {
735   Local  *l = packet;
736 
737   assert(MAGIC(l));
738   l->reallyQuit = NULL;
739   return(0);
740 }
741 
742 
setTimers(Local * l)743 static void  setTimers(Local *l)  {
744   GoStone  stone;
745   const GoTimer  *timer;
746 
747   assert(MAGIC(l));
748   goStoneIter(stone)  {
749     timer = goGame_getTimer(l->game, stone);
750     if (timer == NULL)
751       goTimer_init(&l->goban->timers[stone], &l->game->time);
752     else
753       l->goban->timers[stone] = *timer;
754   }
755   goban_updateTimeReadouts(l->goban);
756   goban_startTimer(l->goban, goGame_whoseMove(l->game));
757 }
758