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