1 /*
2  * play.c
3  *
4  * by Gary Wong <gtw@gnu.org>, 1999, 2000, 2001, 2002.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of version 3 or later of the GNU General Public License as
8  * published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  * $Id: play.c,v 1.450 2018/06/15 19:37:01 plm Exp $
20  */
21 
22 #include "config.h"
23 
24 #include <glib.h>
25 #include <ctype.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #include "analysis.h"
30 #include "backgammon.h"
31 #include "dice.h"
32 #include "drawboard.h"
33 #include "external.h"
34 #include "eval.h"
35 #include "positionid.h"
36 #include "matchid.h"
37 #include "matchequity.h"
38 #include "sound.h"
39 #include "renderprefs.h"
40 #include "md5.h"
41 #include "lib/simd.h"
42 
43 #if defined (USE_GTK)
44 #include "gtkboard.h"
45 #include "gtkwindows.h"
46 #endif
47 #if defined(USE_BOARD3D)
48 #include "fun3d.h"
49 #endif
50 
51 static moverecord *pmr_hint = NULL;
52 
53 const char *aszGameResult[] = {
54     N_("single game"),
55     N_("gammon"),
56     N_("backgammon")
57 };
58 
59 const char *aszSkillType[] = {
60     N_("very bad"),
61     N_("bad"),
62     N_("doubtful"),
63     NULL,
64 };
65 
66 const char *aszSkillTypeCommand[] = {
67     "verybad",
68     "bad",
69     "doubtful",
70     "none",
71 };
72 const char *aszSkillTypeAbbr[] = { "??", "? ", "?!", "  ", "  " };
73 
74 const char *aszLuckTypeCommand[] = {
75     "veryunlucky",
76     "unlucky",
77     "none",
78     "lucky",
79     "verylucky"
80 };
81 
82 const char *aszLuckType[] = {
83     N_("very unlucky"),
84     N_("unlucky"),
85     NULL,
86     N_("lucky"),
87     N_("very lucky")
88 };
89 const char *aszLuckTypeAbbr[] = { "--", "-", "", "+", "++" };
90 
91 listOLD lMatch, *plGame, *plLastMove;
92 statcontext scMatch;
93 static int fComputerDecision = FALSE;
94 static int fEndGame = FALSE;
95 static int fCrawfordState = -1;
96 int automaticTask = FALSE;
97 
98 typedef enum _annotatetype {
99     ANNOTATE_ACCEPT, ANNOTATE_CUBE, ANNOTATE_DOUBLE, ANNOTATE_DROP,
100     ANNOTATE_MOVE, ANNOTATE_ROLL, ANNOTATE_RESIGN, ANNOTATE_REJECT
101 } annotatetype;
102 
103 static annotatetype at;
104 
105 extern moverecord *
NewMoveRecord(void)106 NewMoveRecord(void)
107 {
108 
109     moverecord *pmr = malloc(sizeof *pmr);
110 
111     if (!pmr) {
112         outputerr("Memory allocation failure in NewMoveRecord()");
113         g_assert_not_reached();
114         exit(EXIT_FAILURE);
115     }
116 
117     memset(pmr, 0, sizeof *pmr);
118 
119     pmr->mt = (movetype) - 1;
120     pmr->sz = NULL;
121     pmr->fPlayer = 0;
122     pmr->anDice[0] = pmr->anDice[1] = 0;
123     pmr->lt = LUCK_NONE;
124     pmr->rLuck = (float) ERR_VAL;
125     pmr->esChequer.et = EVAL_NONE;
126     pmr->nAnimals = 0;
127     pmr->CubeDecPtr = &pmr->CubeDec;
128     pmr->CubeDecPtr->esDouble.et = EVAL_NONE;
129     pmr->CubeDecPtr->cmark = CMARK_NONE;
130     pmr->stCube = SKILL_NONE;
131     pmr->ml.cMoves = 0;
132     pmr->ml.amMoves = NULL;
133 
134     /* movenormal */
135     pmr->n.stMove = SKILL_NONE;
136     pmr->n.anMove[0] = pmr->n.anMove[1] = -1;
137     pmr->n.iMove = UINT_MAX;
138 
139     /* moveresign */
140     pmr->r.esResign.et = EVAL_NONE;
141 
142 
143     return pmr;
144 
145 }
146 
147 static int
148  CheatDice(unsigned int anDice[2], matchstate * pms, const int fBest);
149 
150 
151 #if defined (USE_GTK)
152 #include "gtkgame.h"
153 
154 static int anLastMove[8], fLastMove, fLastPlayer;
155 #endif
156 
157 static void
PlayMove(matchstate * pms,int const anMove[8],int const fPlayer)158 PlayMove(matchstate * pms, int const anMove[8], int const fPlayer)
159 {
160     int i, nSrc, nDest;
161 
162 #if defined (USE_GTK)
163     if (pms == &ms) {
164         memcpy(anLastMove, anMove, sizeof anLastMove);
165         CanonicalMoveOrder(anLastMove);
166         fLastPlayer = fPlayer;
167         fLastMove = anMove[0] >= 0;
168     }
169 #endif
170 
171     if (pms->fMove != -1 && fPlayer != pms->fMove)
172         SwapSides(pms->anBoard);
173 
174     for (i = 0; i < 8; i += 2) {
175         nSrc = anMove[i];
176         nDest = anMove[i | 1];
177 
178         if (nSrc < 0)
179             /* move is finished */
180             break;
181 
182         if (!pms->anBoard[1][nSrc])
183             /* source point is empty; ignore */
184             continue;
185 
186         pms->anBoard[1][nSrc]--;
187         if (nDest >= 0)
188             pms->anBoard[1][nDest]++;
189 
190         if (nDest >= 0 && nDest <= 23) {
191             pms->anBoard[0][24] += pms->anBoard[0][23 - nDest];
192             pms->anBoard[0][23 - nDest] = 0;
193         }
194     }
195 
196     pms->fMove = pms->fTurn = !fPlayer;
197     SwapSides(pms->anBoard);
198 }
199 
200 static void
ApplyGameOver(matchstate * pms,const listOLD * plGame)201 ApplyGameOver(matchstate * pms, const listOLD * plGame)
202 {
203     moverecord *pmr = (moverecord *) plGame->plNext->p;
204     xmovegameinfo *pmgi = &pmr->g;
205 
206     g_assert(pmr->mt == MOVE_GAMEINFO);
207 
208     if (pmgi->fWinner < 0)
209         return;
210 
211     pms->anScore[pmgi->fWinner] += pmgi->nPoints;
212     pms->cGames++;
213     if (fEndGame)
214         outputf(ngettext("End Game done.\n%s wins %d point\n", "End Game done.\n%s wins %d points\n", pmgi->nPoints),
215                 ap[pmgi->fWinner].szName, pmgi->nPoints);
216 }
217 
218 extern void
ApplyMoveRecord(matchstate * pms,const listOLD * plGame,const moverecord * pmr)219 ApplyMoveRecord(matchstate * pms, const listOLD * plGame, const moverecord * pmr)
220 {
221     int n;
222     moverecord *pmrx = (moverecord *) plGame->plNext->p;
223     xmovegameinfo *pmgi;
224 
225     if (!pmr) {
226         g_assert_not_reached();
227         outputerr("Applying null move record!");
228         return;
229     }
230 
231     /* FIXME this is wrong -- plGame is not necessarily the right game */
232 
233     g_assert(pmr->mt == MOVE_GAMEINFO || (pmrx && pmrx->mt == MOVE_GAMEINFO));
234 
235     pms->gs = GAME_PLAYING;
236     pms->fResigned = pms->fResignationDeclined = 0;
237 
238 #if defined (USE_GTK)
239     if (pms == &ms)
240         fLastMove = FALSE;
241 #endif
242 
243     switch (pmr->mt) {
244     case MOVE_GAMEINFO:
245         InitBoard(pms->anBoard, pmr->g.bgv);
246 
247         pms->nMatchTo = pmr->g.nMatch;
248         pms->anScore[0] = pmr->g.anScore[0];
249         pms->anScore[1] = pmr->g.anScore[1];
250         pms->cGames = pmr->g.i;
251 
252         pms->gs = GAME_NONE;
253         pms->fMove = pms->fTurn = pms->fCubeOwner = -1;
254         pms->anDice[0] = pms->anDice[1] = pms->cBeavers = 0;
255         pms->fDoubled = FALSE;
256         pms->nCube = 1 << pmr->g.nAutoDoubles;
257         pms->fCrawford = pmr->g.fCrawfordGame;
258         pms->fPostCrawford = !pms->fCrawford &&
259             (pms->anScore[0] == pms->nMatchTo - 1 || pms->anScore[1] == pms->nMatchTo - 1);
260         pms->bgv = pmr->g.bgv;
261         pms->fCubeUse = pmr->g.fCubeUse;
262         pms->fJacoby = pmr->g.fJacoby;
263         break;
264 
265     case MOVE_DOUBLE:
266 
267         if (pms->fMove < 0)
268             pms->fMove = pmr->fPlayer;
269 
270         if (pms->nCube >= MAX_CUBE)
271             break;
272 
273         if (pms->fDoubled) {
274             pms->cBeavers++;
275             pms->nCube <<= 1;
276             pms->fCubeOwner = !pms->fMove;
277         } else
278             pms->fDoubled = TRUE;
279 
280         pms->fTurn = !pmr->fPlayer;
281 
282         break;
283 
284     case MOVE_TAKE:
285         if (!pms->fDoubled)
286             break;
287 
288         pms->nCube <<= 1;
289         pms->cBeavers = 0;
290         pms->fDoubled = FALSE;
291         pms->fCubeOwner = !pms->fMove;
292         pms->fTurn = pms->fMove;
293         break;
294 
295     case MOVE_DROP:
296         if (!pms->fDoubled)
297             break;
298 
299         pms->fDoubled = FALSE;
300         pms->cBeavers = 0;
301         pms->gs = GAME_DROP;
302         pmgi = &pmrx->g;
303         pmgi->nPoints = pms->nCube;
304         pmgi->fWinner = !pmr->fPlayer;
305         pmgi->fResigned = FALSE;
306 
307         ApplyGameOver(pms, plGame);
308         break;
309 
310     case MOVE_NORMAL:
311         pms->fDoubled = FALSE;
312 
313         PlayMove(pms, pmr->n.anMove, pmr->fPlayer);
314         pms->anDice[0] = pms->anDice[1] = 0;
315 
316         if ((n = GameStatus((ConstTanBoard) pms->anBoard, pms->bgv))) {
317 
318             if (pms->fJacoby && pms->fCubeOwner == -1 && !pms->nMatchTo)
319                 /* gammons do not count on a centred cube during money
320                  * sessions under the Jacoby rule */
321                 n = 1;
322 
323             pms->gs = GAME_OVER;
324             pmgi = &pmrx->g;
325             pmgi->nPoints = pms->nCube * n;
326             pmgi->fWinner = pmr->fPlayer;
327             pmgi->fResigned = FALSE;
328             ApplyGameOver(pms, plGame);
329         }
330 
331         break;
332 
333     case MOVE_RESIGN:
334         pms->gs = GAME_RESIGNED;
335         pmgi = &pmrx->g;
336         pmgi->nPoints = pms->nCube * (pms->fResigned = pmr->r.nResigned);
337         pmgi->fWinner = !pmr->fPlayer;
338         pmgi->fResigned = TRUE;
339 
340         ApplyGameOver(pms, plGame);
341         break;
342 
343     case MOVE_SETBOARD:
344         PositionFromKey(pms->anBoard, &pmr->sb.key);
345 
346         if (pms->fMove < 0)
347             pms->fTurn = pms->fMove = 0;
348 
349         if (pms->fMove)
350             SwapSides(pms->anBoard);
351 
352         break;
353 
354     case MOVE_SETDICE:
355         pms->anDice[0] = pmr->anDice[0];
356         pms->anDice[1] = pmr->anDice[1];
357         if (pms->fMove != pmr->fPlayer)
358             SwapSides(pms->anBoard);
359         pms->fTurn = pms->fMove = pmr->fPlayer;
360         pms->fDoubled = FALSE;
361         break;
362 
363     case MOVE_SETCUBEVAL:
364         if (pms->fMove < 0)
365             pms->fMove = 0;
366 
367         pms->nCube = pmr->scv.nCube;
368         pms->fDoubled = FALSE;
369         pms->fTurn = pms->fMove;
370         break;
371 
372     case MOVE_SETCUBEPOS:
373         if (pms->fMove < 0)
374             pms->fMove = 0;
375 
376         pms->fCubeOwner = pmr->scp.fCubeOwner;
377         pms->fDoubled = FALSE;
378         pms->fTurn = pms->fMove;
379         break;
380     }
381 }
382 
383 extern void
CalculateBoard(void)384 CalculateBoard(void)
385 {
386     listOLD *pl;
387 
388     pl = plGame;
389     do {
390         pl = pl->plNext;
391 
392         if (!pl) {
393             g_assert_not_reached();
394             break;
395         }
396 
397         ApplyMoveRecord(&ms, plGame, pl->p);
398 
399         if (pl->plNext && pl->plNext->p)
400             FixMatchState(&ms, pl->plNext->p);
401 
402 
403     } while (pl != plLastMove);
404 
405 #if defined(USE_BOARD3D)
406     RestrictiveRedraw();
407 #endif
408 }
409 
410 static void
FreeMoveRecord(moverecord * pmr)411 FreeMoveRecord(moverecord * pmr)
412 {
413     if (!pmr)
414         return;
415 
416     switch (pmr->mt) {
417     case MOVE_NORMAL:
418         if (pmr->ml.cMoves && pmr->ml.amMoves) {
419             free(pmr->ml.amMoves);
420             pmr->ml.amMoves = NULL;
421         }
422         break;
423 
424     default:
425         break;
426     }
427 
428     if (pmr->sz)
429         free(pmr->sz);
430 
431     free(pmr);
432 }
433 
434 static void
FreeGame(listOLD * pl)435 FreeGame(listOLD * pl)
436 {
437 
438     while (pl->plNext != pl) {
439         FreeMoveRecord(pl->plNext->p);
440         ListDelete(pl->plNext);
441     }
442 
443     free(pl);
444 }
445 
446 static int
PopGame(listOLD * plDelete,int fInclusive)447 PopGame(listOLD * plDelete, int fInclusive)
448 {
449 
450     listOLD *pl;
451     int i;
452 
453     for (i = 0, pl = lMatch.plNext; pl != &lMatch && pl->p != plDelete; pl = pl->plNext, i++);
454 
455     if (pl->p && !fInclusive) {
456         pl = pl->plNext;
457         i++;
458     }
459 
460     if (!pl->p)
461         /* couldn't find node to delete to */
462         return -1;
463 
464 #if defined (USE_GTK)
465     if (fX)
466         GTKPopGame(i);
467 #endif
468 
469     do {
470         pl = pl->plNext;
471         FreeGame(pl->plPrev->p);
472         ListDelete(pl->plPrev);
473     } while (pl->p);
474 
475     pmr_hint_destroy();
476     return 0;
477 }
478 
479 static int
PopMoveRecord(listOLD * plDelete)480 PopMoveRecord(listOLD * plDelete)
481 {
482 
483     listOLD *pl;
484 
485     for (pl = plGame->plNext; pl != plGame && pl != plDelete; pl = pl->plNext);
486 
487     if (pl == plGame)
488         /* couldn't find node to delete to */
489         return -1;
490 
491 #if defined (USE_GTK)
492     if (fX) {
493         GTKPopMoveRecord(pl->p);
494         GTKSetMoveRecord(pl->plPrev->p);
495     }
496 #endif
497 
498     pl = pl->plPrev;
499 
500     while (pl->plNext->p) {
501         if (pl->plNext == plLastMove)
502             plLastMove = pl;
503         FreeMoveRecord(pl->plNext->p);
504         ListDelete(pl->plNext);
505     }
506     pmr_hint_destroy();
507 
508     return 0;
509 }
510 
511 static void
copy_from_pmr_cur(moverecord * pmr,gboolean get_move,gboolean get_cube)512 copy_from_pmr_cur(moverecord * pmr, gboolean get_move, gboolean get_cube)
513 {
514     moverecord *pmr_cur;
515     pmr_cur = get_current_moverecord(NULL);
516     if (!pmr_cur)
517         return;
518     if (get_move && pmr_cur->ml.cMoves > 0) {
519         if (pmr->ml.cMoves > 0)
520             free(pmr->ml.amMoves);
521         CopyMoveList(&pmr->ml, &pmr_cur->ml);
522         pmr->n.iMove = locateMove(msBoard(), pmr->n.anMove, &pmr->ml);
523     }
524 
525     if (get_cube && pmr_cur->CubeDecPtr->esDouble.et != EVAL_NONE) {
526         memcpy(pmr->CubeDecPtr->aarOutput, pmr_cur->CubeDecPtr->aarOutput, sizeof pmr->CubeDecPtr->aarOutput);
527         memcpy(pmr->CubeDecPtr->aarStdDev, pmr_cur->CubeDecPtr->aarStdDev, sizeof pmr->CubeDecPtr->aarStdDev);
528         memcpy(&pmr->CubeDecPtr->esDouble, &pmr_cur->CubeDecPtr->esDouble, sizeof pmr->CubeDecPtr->esDouble);
529         pmr->CubeDecPtr->cmark = pmr_cur->CubeDecPtr->cmark;
530     }
531 
532 }
533 
534 static void
add_moverecord_get_cur(moverecord * pmr)535 add_moverecord_get_cur(moverecord * pmr)
536 {
537     switch (pmr->mt) {
538     case MOVE_NORMAL:
539     case MOVE_RESIGN:
540         copy_from_pmr_cur(pmr, TRUE, TRUE);
541         pmr_hint_destroy();
542         find_skills(pmr, &ms, FALSE, -1);
543         break;
544     case MOVE_DOUBLE:
545         copy_from_pmr_cur(pmr, FALSE, TRUE);
546         find_skills(pmr, &ms, TRUE, -1);
547         break;
548     case MOVE_TAKE:
549         copy_from_pmr_cur(pmr, FALSE, TRUE);
550         pmr_hint_destroy();
551         find_skills(pmr, &ms, -1, TRUE);
552         break;
553     case MOVE_DROP:
554         copy_from_pmr_cur(pmr, FALSE, TRUE);
555         pmr_hint_destroy();
556         find_skills(pmr, &ms, -1, FALSE);
557         break;
558     case MOVE_SETDICE:
559         copy_from_pmr_cur(pmr, FALSE, TRUE);
560         find_skills(pmr, &ms, FALSE, -1);
561         break;
562     default:
563         pmr_hint_destroy();
564         break;
565     }
566 }
567 
568 static void
add_moverecord_sanity_check(moverecord * pmr)569 add_moverecord_sanity_check(moverecord * pmr)
570 {
571     g_assert(pmr->fPlayer >= 0 && pmr->fPlayer <= 1);
572     g_assert(pmr->ml.cMoves < MAX_MOVES);
573     switch (pmr->mt) {
574     case MOVE_GAMEINFO:
575         g_assert(pmr->g.nMatch >= 0);
576         g_assert(pmr->g.i >= 0);
577         if (pmr->g.nMatch) {
578             g_assert(pmr->g.i <= pmr->g.nMatch * 2 + 1);
579             g_assert(pmr->g.anScore[0] < pmr->g.nMatch);
580             g_assert(pmr->g.anScore[1] < pmr->g.nMatch);
581         }
582         if (!pmr->g.fCrawford)
583             g_assert(!pmr->g.fCrawfordGame);
584 
585         break;
586 
587     case MOVE_NORMAL:
588         if (pmr->ml.cMoves)
589             g_assert(pmr->n.iMove <= pmr->ml.cMoves);
590 
591         break;
592 
593     case MOVE_RESIGN:
594         g_assert(pmr->r.nResigned >= 1 && pmr->r.nResigned <= 3);
595         break;
596 
597     case MOVE_DOUBLE:
598     case MOVE_TAKE:
599     case MOVE_DROP:
600     case MOVE_SETDICE:
601     case MOVE_SETBOARD:
602     case MOVE_SETCUBEVAL:
603     case MOVE_SETCUBEPOS:
604         break;
605 
606     default:
607         g_assert_not_reached();
608     }
609 }
610 
611 extern void
AddMoveRecord(moverecord * pmr)612 AddMoveRecord(moverecord *pmr)
613 {
614     moverecord *pmrOld;
615 
616     add_moverecord_get_cur(pmr);
617 
618     add_moverecord_sanity_check(pmr);
619 
620 
621     /* Delete all games after plGame, and all records after plLastMove. */
622     PopGame(plGame, FALSE);
623     /* FIXME when we can handle variations, we should save the old moves
624      * as an alternate variation. */
625     PopMoveRecord(plLastMove->plNext);
626 
627     if (pmr->mt == MOVE_NORMAL && (pmrOld = plLastMove->p)->mt == MOVE_SETDICE && pmrOld->fPlayer == pmr->fPlayer) {
628         PopMoveRecord(plLastMove);
629     }
630 
631     /* FIXME perform other elision (e.g. consecutive "set" records) */
632 
633     /* Partial fix. For edited positions it would be nice to look
634      * further back, but this is still an improvement */
635 
636     if (pmr->mt >= MOVE_SETBOARD && (pmrOld = plLastMove->p)->mt == pmr->mt && pmrOld->fPlayer == pmr->fPlayer) {
637         PopMoveRecord(plLastMove);
638     }
639 #if defined (USE_GTK)
640     if (fX)
641         GTKAddMoveRecord(pmr);
642 #endif
643 
644     FixMatchState(&ms, pmr);
645 
646     ApplyMoveRecord(&ms, plGame, pmr);
647 
648     plLastMove = ListInsert(plGame, pmr);
649 
650     SetMoveRecord(pmr);
651 }
652 
653 static gboolean
move_is_last_in_match(void)654 move_is_last_in_match(void)
655 {
656     return (!plLastMove || !plLastMove->plNext || !plLastMove->plNext->p);
657 }
658 
659 static gboolean
move_not_last_in_match_ok(void)660 move_not_last_in_match_ok(void)
661 {
662     if (automaticTask)
663         return TRUE;
664     if (move_is_last_in_match())
665         return TRUE;
666 
667     return GetInputYN(_("The current move is not the last in the match.\n"
668                         "Continuing will destroy the remainder of the match. Continue?"));
669 }
670 
671 extern void
SetMoveRecord(moverecord * pmr)672 SetMoveRecord(moverecord *pmr)
673 {
674 
675 #if defined (USE_GTK)
676     if (fX)
677         GTKSetMoveRecord(pmr);
678 #else
679     (void) pmr;                 /* suppress unused parameter compiler warning */
680 #endif
681 }
682 
683 extern void
ClearMoveRecord(void)684 ClearMoveRecord(void)
685 {
686     plLastMove = plGame = malloc(sizeof(*plGame));
687     ListCreate(plGame);
688 }
689 
690 #if defined (USE_GTK)
691 static guint nTimeout, fDelaying;
692 
693 static gint
DelayTimeout(gpointer UNUSED (p))694 DelayTimeout(gpointer UNUSED(p))
695 {
696 
697     if (fDelaying)
698         fEndDelay = TRUE;
699 
700     nTimeout = 0;
701 
702     return FALSE;
703 }
704 
705 static void
ResetDelayTimer(void)706 ResetDelayTimer(void)
707 {
708 
709     if (fX && nDelay && fDisplay) {
710         if (nTimeout)
711             g_source_remove(nTimeout);
712 
713         nTimeout = g_timeout_add(nDelay, DelayTimeout, NULL);
714     }
715 }
716 #else
717 #define ResetDelayTimer()
718 #endif
719 
720 extern void
AddGame(moverecord * pmr)721 AddGame(moverecord * pmr)
722 {
723     g_assert(pmr->mt == MOVE_GAMEINFO);
724 
725 #if defined (USE_GTK)
726     if (fX)
727         GTKAddGame(pmr);
728 #else
729     (void) pmr;                 /* silence compiler warning */
730 #endif
731 }
732 
733 static void
DiceRolled(void)734 DiceRolled(void)
735 {
736     playSound(SOUND_ROLL);
737 
738 #if defined (USE_GTK)
739     if (fX) {
740         BoardData *bd = BOARD(pwBoard)->board_data;
741         /* Make sure dice are updated */
742         bd->diceRoll[0] = 0;
743         bd->diceShown = DICE_ROLLING;
744         ShowBoard();
745     } else
746 #endif
747     if (fDisplay)
748         ShowBoard();
749 }
750 
751 static int
NewGame(void)752 NewGame(void)
753 {
754     moverecord *pmr;
755     listOLD *plLastMove_store = plLastMove;
756     listOLD *plGame_store = lMatch.plNext->p;
757     int fError;
758 
759     if (!fRecord && !ms.nMatchTo && lMatch.plNext->p) {
760         /* only recording the active game of a session; discard any others */
761 
762         if (!get_input_discard())
763             return -1;
764 
765         PopGame(lMatch.plNext->p, TRUE);
766     }
767 
768 #if defined (USE_GTK)
769     if (fX)
770         GTKClearMoveRecord();
771 #endif
772 
773     InitBoard(ms.anBoard, ms.bgv);
774 
775     ClearMoveRecord();
776 
777     ListInsert(&lMatch, plGame);
778 
779     pmr = NewMoveRecord();
780     pmr->mt = MOVE_GAMEINFO;
781     pmr->sz = NULL;
782 
783     if (fCrawfordState >= 0) {
784         ms.fCrawford = fCrawfordState & 0x1;
785         ms.fPostCrawford = (fCrawfordState & 0x2) >> 1;
786     } else {
787         if (ms.nMatchTo && fAutoCrawford) {
788             ms.fPostCrawford |= ms.fCrawford && MAX(ms.anScore[0], ms.anScore[1]) < ms.nMatchTo;
789             ms.fCrawford = !ms.fPostCrawford && !ms.fCrawford &&
790                 MAX(ms.anScore[0], ms.anScore[1]) == ms.nMatchTo - 1
791                 && MIN(ms.anScore[0], ms.anScore[1]) < ms.nMatchTo - 1;
792         }
793     }
794 
795     pmr->g.i = ms.cGames;
796     pmr->g.nMatch = ms.nMatchTo;
797     pmr->g.anScore[0] = ms.anScore[0];
798     pmr->g.anScore[1] = ms.anScore[1];
799     pmr->g.fCrawford = fAutoCrawford && ms.nMatchTo > 1;
800     pmr->g.fCrawfordGame = ms.fCrawford;
801     pmr->g.fJacoby = ms.fJacoby && !ms.nMatchTo;
802     pmr->g.fWinner = -1;
803     pmr->g.nPoints = 0;
804     pmr->g.fResigned = FALSE;
805     pmr->g.nAutoDoubles = 0;
806     pmr->g.bgv = ms.bgv;
807     pmr->g.fCubeUse = ms.fCubeUse;
808     IniStatcontext(&pmr->g.sc);
809     AddMoveRecord(pmr);
810 
811 
812     UpdateSetting(&ms.nCube);
813     UpdateSetting(&ms.fCubeOwner);
814     UpdateSetting(&ms.fTurn);
815 
816 #if defined(USE_BOARD3D)
817     if (fX) {
818         BoardData *bd = BOARD(pwBoard)->board_data;
819         InitBoardData(bd);
820     }
821 #endif
822 
823     AddGame(pmr);
824 
825   reroll:
826     fError = RollDice(ms.anDice, &rngCurrent, rngctxCurrent);
827 
828     if (fInterrupt || fError) {
829         PopGame(plGame, TRUE);
830         plGame = plGame_store;
831         plLastMove = plLastMove_store;
832         if (!plGame)
833             return -1;
834         ChangeGame(plGame);
835         if (!plLastMove)
836             return -1;
837         CalculateBoard();
838         SetMoveRecord(plLastMove->p);
839         ShowBoard();
840         return -1;
841     }
842 
843     if (fDisplay) {
844         outputnew();
845         outputf(_("%s rolls %u, %s rolls %u.\n"), ap[0].szName, ms.anDice[0], ap[1].szName, ms.anDice[1]);
846     }
847 
848     if (ms.anDice[0] == ms.anDice[1]) {
849         if (!ms.nMatchTo && ms.nCube < (1 << cAutoDoubles) && ms.fCubeUse && ms.nCube < MAX_CUBE) {
850             pmr->g.nAutoDoubles++;
851             if (fDisplay)
852                 outputf(_("The cube is now at %d.\n"), ms.nCube <<= 1);
853             UpdateSetting(&ms.nCube);
854         }
855 
856         goto reroll;
857     }
858 
859     g_assert(ms.nCube <= MAX_CUBE);
860     g_assert(ms.anDice[1] != ms.anDice[0]);
861 
862     outputx();
863 
864     pmr = NewMoveRecord();
865     pmr->mt = MOVE_SETDICE;
866 
867     pmr->anDice[0] = MAX(ms.anDice[0], ms.anDice[1]);
868     pmr->anDice[1] = MIN(ms.anDice[0], ms.anDice[1]);
869     pmr->fPlayer = ms.anDice[1] > ms.anDice[0];
870 
871 
872     AddMoveRecord(pmr);
873 
874 #if defined(USE_BOARD3D)
875     RestrictiveRedraw();
876 #endif
877 
878     UpdateSetting(&ms.fTurn);
879     UpdateSetting(&ms.gs);
880     /* Play sound after initial dice decided */
881     DiceRolled();
882 #if defined (USE_GTK)
883     ResetDelayTimer();
884 #endif
885 
886     return 0;
887 }
888 
889 static void
ShowAutoMove(const TanBoard anBoard,int anMove[8])890 ShowAutoMove(const TanBoard anBoard, int anMove[8])
891 {
892 
893     char sz[40];
894 
895     if (!fDisplay)
896         return;
897 
898     if (anMove[0] == -1)
899         outputf(_("%s cannot move.\n"), ap[ms.fTurn].szName);
900     else
901         outputf(_("%s moves %s.\n"), ap[ms.fTurn].szName, FormatMove(sz, anBoard, anMove));
902 }
903 
904 static void
get_eq_before_resign(cubeinfo * pci,decisionData * pdd)905 get_eq_before_resign(cubeinfo * pci, decisionData * pdd)
906 {
907     const evalcontext ecResign = { FALSE, 0, FALSE, TRUE, 0.0 };
908 
909     pdd->pboard = msBoard();
910     pdd->pci = pci;
911     pdd->pec = &ecResign;
912 
913     if (ms.anDice[0] > 0) {
914         float t;
915         /* Opponent has rolled the dice and then resigned. We want to find out if the resignation is OK after
916          * the roll */
917         RunAsyncProcess((AsyncFun) asyncEvalRoll, pdd, _("Considering resignation..."));
918         /* Swap the equities as evaluation is for other player */
919         pdd->aarOutput[0][OUTPUT_WIN] = 1 - pdd->aarOutput[0][OUTPUT_WIN];
920         t = pdd->aarOutput[0][OUTPUT_WINGAMMON];
921         pdd->aarOutput[0][OUTPUT_WINGAMMON] = pdd->aarOutput[0][OUTPUT_LOSEGAMMON];
922         pdd->aarOutput[0][OUTPUT_LOSEGAMMON] = t;
923         t = pdd->aarOutput[0][OUTPUT_WINBACKGAMMON];
924         pdd->aarOutput[0][OUTPUT_WINBACKGAMMON] = pdd->aarOutput[0][OUTPUT_LOSEBACKGAMMON];
925         pdd->aarOutput[0][OUTPUT_LOSEBACKGAMMON] = t;
926     } else {
927         RunAsyncProcess((AsyncFun) asyncMoveDecisionE, pdd, _("Considering resignation..."));
928     }
929 }
930 
931 extern int
check_resigns(cubeinfo * pci)932 check_resigns(cubeinfo * pci)
933 {
934     float rEqBefore, rEqAfter;
935     const float max_cost = 0.05f;
936     const float max_gain = 1e-6f;
937     decisionData dd;
938     cubeinfo ci;
939     int resigned = 1;
940 
941     if (pci == NULL) {
942         GetMatchStateCubeInfo(&ci, &ms);
943         pci = &ci;
944     }
945 
946     get_eq_before_resign(pci, &dd);
947     do {
948         getResignEquities(dd.aarOutput[0], pci, resigned, &rEqBefore, &rEqAfter);
949         if (rEqBefore - rEqAfter > max_cost) {
950             resigned = 4;
951             break;
952         } else if (rEqAfter - rEqBefore < max_gain)
953             break;
954     }
955     while (resigned++ <= 3);
956     return resigned == 4 ? -1 : resigned;
957 }
958 
959 static void
parsemove_to_anmove(int c,int anMove[])960 parsemove_to_anmove(int c, int anMove[])
961 {
962     int i;
963     for (i = 0; i < 4; i++) {
964         if (i < c) {
965             anMove[i << 1]--;
966             anMove[(i << 1) + 1]--;
967         } else {
968             anMove[i << 1] = -1;
969             anMove[(i << 1) + 1] = -1;
970         }
971     }
972     CanonicalMoveOrder(anMove);
973 }
974 
975 /*! \brief parses the move and finds the new board in the list of moves/
976  * \param pml the list of moves
977  * \param old_board the board before move
978  * \param board the new board
979  * \return 1 if found, 0 otherwise
980  */
981 static gboolean
parse_move_is_legal(char * sz,const matchstate * pms,int * an)982 parse_move_is_legal(char *sz, const matchstate * pms, int *an)
983 {
984     int c;
985     TanBoard anBoardNew;
986     movelist ml;
987     c = ParseMove(sz, an);
988     if (c < 0)
989         return FALSE;
990     parsemove_to_anmove(c, an);
991     memcpy(anBoardNew, ms.anBoard, sizeof(TanBoard));
992     ApplyMove(anBoardNew, an, FALSE);
993     GenerateMoves(&ml, pms->anBoard, pms->anDice[0], pms->anDice[1], FALSE);
994     return board_in_list(&ml, pms->anBoard, (ConstTanBoard) anBoardNew, an);
995 }
996 
997 
998 static void
current_pmr_cubedata_update(evalsetup * pes,float output[][NUM_ROLLOUT_OUTPUTS],float stddev[][NUM_ROLLOUT_OUTPUTS])999 current_pmr_cubedata_update(evalsetup * pes, float output[][NUM_ROLLOUT_OUTPUTS], float stddev[][NUM_ROLLOUT_OUTPUTS])
1000 {
1001     moverecord *pmr = get_current_moverecord(NULL);
1002     if (!pmr)
1003         return;
1004     if (pmr->CubeDecPtr->esDouble.et == EVAL_NONE)
1005         pmr_cubedata_set(pmr, pes, output, stddev);
1006 }
1007 
1008 
1009 static int
ComputerTurn(void)1010 ComputerTurn(void)
1011 {
1012 
1013     moverecord *pmr;
1014     cubeinfo ci;
1015     float arDouble[4], rDoublePoint;
1016 #if defined(HAVE_SOCKETS)
1017     char szBoard[256], szResponse[256];
1018     int fTurnOrig;
1019     TanBoard anBoardTemp;
1020 #endif
1021 
1022     if (fInterrupt || ms.gs != GAME_PLAYING)
1023         return -1;
1024 
1025     GetMatchStateCubeInfo(&ci, &ms);
1026 
1027     switch (ap[ms.fTurn].pt) {
1028     case PLAYER_GNU:
1029         if (ms.fResigned) {
1030             int resign;
1031             if (ms.fResigned == -1)
1032                 resign = check_resigns(&ci);
1033             else {
1034                 float rEqBefore, rEqAfter;
1035                 const float max_gain = 1e-6f;
1036                 decisionData dd;
1037                 get_eq_before_resign(&ci, &dd);
1038                 getResignEquities(dd.aarOutput[0], &ci, ms.fResigned, &rEqBefore, &rEqAfter);
1039                 if (rEqAfter - rEqBefore < max_gain)
1040                     resign = ms.fResigned;
1041                 else
1042                     resign = -1;
1043             }
1044 
1045             fComputerDecision = TRUE;
1046             if (resign > 0) {
1047                 ms.fResigned = resign;
1048                 CommandAgree(NULL);
1049             } else {
1050                 CommandDecline(NULL);
1051             }
1052 
1053             fComputerDecision = FALSE;
1054             return 0;
1055         } else if (ms.fDoubled) {
1056             decisionData dd;
1057             cubedecision cd;
1058 
1059             /* Consider cube action */
1060 
1061             /*
1062              * We may get here in three different scenarios:
1063              * (1) normal double by opponent: fMove != fTurn and fCubeOwner is
1064              *     either -1 (centered cube) or = fMove.
1065              * (2) beaver by opponent: fMove = fTurn and fCubeOwner = !
1066              *     fMove
1067              * (3) raccoon by opponent: fMove != fTurn and fCubeOwner =
1068              *     fTurn.
1069              *
1070              */
1071 
1072             if (ms.fMove != ms.fTurn && ms.fCubeOwner == ms.fTurn) {
1073 
1074                 /* raccoon: consider this a normal double, i.e.
1075                  * fCubeOwner = fMove */
1076 
1077                 SetCubeInfo(&ci, ci.nCube,
1078                             ci.fMove, ci.fMove, ci.nMatchTo, ci.anScore, ci.fCrawford, ci.fJacoby, ci.fBeavers, ci.bgv);
1079 
1080             }
1081 
1082             if (ms.fMove == ms.fTurn && ms.fCubeOwner != ms.fMove) {
1083 
1084                 /* opponent beavered: consider this a normal double by me */
1085 
1086                 SetCubeInfo(&ci, ci.nCube,
1087                             ci.fMove, ci.fMove, ci.nMatchTo, ci.anScore, ci.fCrawford, ci.fJacoby, ci.fBeavers, ci.bgv);
1088 
1089             }
1090 
1091             /* Evaluate cube decision */
1092             dd.pboard = msBoard();
1093             dd.pci = &ci;
1094             dd.pes = &ap[ms.fTurn].esCube;
1095             if (RunAsyncProcess((AsyncFun) asyncCubeDecision, &dd, _("Considering cube action...")) != 0)
1096                 return -1;
1097 
1098             current_pmr_cubedata_update(dd.pes, dd.aarOutput, dd.aarStdDev);
1099 
1100             cd = FindCubeDecision(arDouble, dd.aarOutput, &ci);
1101 
1102             fComputerDecision = TRUE;
1103 
1104             if (ms.fTurn == ms.fMove) {
1105 
1106                 /* opponent has beavered */
1107 
1108                 switch (cd) {
1109 
1110                 case DOUBLE_TAKE:
1111                 case REDOUBLE_TAKE:
1112                 case TOOGOOD_TAKE:
1113                 case TOOGOODRE_TAKE:
1114                 case DOUBLE_PASS:
1115                 case TOOGOOD_PASS:
1116                 case REDOUBLE_PASS:
1117                 case TOOGOODRE_PASS:
1118                 case OPTIONAL_DOUBLE_TAKE:
1119                 case OPTIONAL_REDOUBLE_TAKE:
1120                 case OPTIONAL_DOUBLE_PASS:
1121                 case OPTIONAL_REDOUBLE_PASS:
1122 
1123                     /* Opponent out of his right mind: Raccoon if possible */
1124 
1125                     if (ms.cBeavers < nBeavers && !ms.nMatchTo && ms.nCube < (MAX_CUBE >> 1))
1126                         /* he he: raccoon */
1127                         CommandRedouble(NULL);
1128                     else
1129                         /* Damn, no raccoons allowed */
1130                         CommandTake(NULL);
1131 
1132                     break;
1133 
1134 
1135                 case NODOUBLE_TAKE:
1136                 case NO_REDOUBLE_TAKE:
1137 
1138                     /* hmm, oops: opponent beavered us:
1139                      * consider dropping the beaver */
1140 
1141                     /* Note, this should not happen as the computer plays
1142                      * "perfectly"!! */
1143 
1144                     if (arDouble[OUTPUT_TAKE] <= -1.0f)
1145                         /* drop beaver */
1146                         CommandDrop(NULL);
1147                     else
1148                         /* take */
1149                         CommandTake(NULL);
1150 
1151                     break;
1152 
1153 
1154                 case DOUBLE_BEAVER:
1155                 case NODOUBLE_BEAVER:
1156                 case NO_REDOUBLE_BEAVER:
1157                 case OPTIONAL_DOUBLE_BEAVER:
1158 
1159                     /* opponent beaver was correct */
1160 
1161                     CommandTake(NULL);
1162                     break;
1163 
1164                 default:
1165 
1166                     g_assert_not_reached();
1167 
1168                 }               /* switch cubedecision */
1169 
1170             } /* consider beaver */
1171             else {
1172 
1173                 /* normal double by opponent */
1174 
1175                 switch (cd) {
1176 
1177                 case DOUBLE_TAKE:
1178                 case NODOUBLE_TAKE:
1179                 case TOOGOOD_TAKE:
1180                 case REDOUBLE_TAKE:
1181                 case NO_REDOUBLE_TAKE:
1182                 case TOOGOODRE_TAKE:
1183                 case NODOUBLE_DEADCUBE:
1184                 case NO_REDOUBLE_DEADCUBE:
1185                 case OPTIONAL_DOUBLE_TAKE:
1186                 case OPTIONAL_REDOUBLE_TAKE:
1187 
1188                     CommandTake(NULL);
1189                     break;
1190 
1191                 case DOUBLE_PASS:
1192                 case TOOGOOD_PASS:
1193                 case REDOUBLE_PASS:
1194                 case TOOGOODRE_PASS:
1195                 case OPTIONAL_DOUBLE_PASS:
1196                 case OPTIONAL_REDOUBLE_PASS:
1197 
1198                     CommandDrop(NULL);
1199                     break;
1200 
1201                 case DOUBLE_BEAVER:
1202                 case NODOUBLE_BEAVER:
1203                 case NO_REDOUBLE_BEAVER:
1204                 case OPTIONAL_DOUBLE_BEAVER:
1205 
1206                     if (ms.cBeavers < nBeavers && !ms.nMatchTo && ms.nCube < (MAX_CUBE >> 1))
1207                         /* Beaver all night! */
1208                         CommandRedouble(NULL);
1209                     else
1210                         /* Damn, no beavers allowed */
1211                         CommandTake(NULL);
1212 
1213                     break;
1214 
1215                 default:
1216 
1217                     g_assert_not_reached();
1218 
1219                 }               /* switch cubedecision */
1220 
1221             }                   /* normal cube */
1222 
1223             fComputerDecision = FALSE;
1224 
1225             return 0;
1226 
1227         } else {
1228             findData fd;
1229             TanBoard anBoardMove;
1230             float arResign[NUM_ROLLOUT_OUTPUTS];
1231             static char achResign[3] = { 'n', 'g', 'b' };
1232             char ach[2];
1233 
1234             /* Don't use the global board for this call, to avoid
1235              * race conditions with updating the board and aborting the
1236              * move with an interrupt. */
1237             memcpy(anBoardMove, msBoard(), sizeof(TanBoard));
1238 
1239             /* Consider resigning -- no point wasting time over the decision,
1240              * so only evaluate at 0 plies. */
1241 
1242             if (ClassifyPosition(msBoard(), ms.bgv) <= CLASS_RACE) {
1243                 int nResign;
1244 
1245                 evalcontext ecResign = { FALSE, 0, FALSE, TRUE, 0.0 };
1246                 evalsetup esResign;
1247 
1248                 esResign.et = EVAL_EVAL;
1249                 esResign.ec = ecResign;
1250 
1251                 nResign = getResignation(arResign, anBoardMove, &ci, &esResign);
1252 
1253                 if (nResign > 0 && nResign > ms.fResignationDeclined) {
1254 
1255                     fComputerDecision = TRUE;
1256                     ach[0] = achResign[nResign - 1];
1257                     ach[1] = 0;
1258                     CommandResign(ach);
1259                     fComputerDecision = FALSE;
1260 
1261                     return 0;
1262                 }
1263             }
1264 
1265             /* Consider doubling */
1266 
1267             if (ms.fCubeUse && !ms.anDice[0] && ms.nCube < MAX_CUBE && GetDPEq(NULL, NULL, &ci)) {
1268                 evalcontext ecDH;
1269                 SSE_ALIGN(float arOutput[NUM_ROLLOUT_OUTPUTS]);
1270                 memcpy(&ecDH, &ap[ms.fTurn].esCube.ec, sizeof ecDH);
1271                 ecDH.fCubeful = FALSE;
1272                 if (ecDH.nPlies)
1273                     ecDH.nPlies--;
1274 
1275                 /* We have access to the cube */
1276 
1277                 /* Determine market window */
1278 
1279                 if (EvaluatePosition(NULL, (ConstTanBoard) anBoardMove, arOutput, &ci, &ecDH))
1280                     return -1;
1281 
1282                 rDoublePoint = GetDoublePointDeadCube(arOutput, &ci);
1283 
1284                 if (arOutput[0] >= rDoublePoint) {
1285 
1286                     /* We're in market window */
1287                     decisionData dd;
1288                     cubedecision cd;
1289 
1290                     /* Consider cube action */
1291                     dd.pboard = msBoard();
1292                     dd.pci = &ci;
1293                     dd.pes = &ap[ms.fTurn].esCube;
1294                     if (RunAsyncProcess((AsyncFun) asyncCubeDecision, &dd, _("Considering cube action...")) != 0)
1295                         return -1;
1296 
1297 
1298                     cd = FindCubeDecision(arDouble, dd.aarOutput, &ci);
1299 
1300                     switch (cd) {
1301 
1302                     case DOUBLE_TAKE:
1303                     case REDOUBLE_TAKE:
1304                     case DOUBLE_PASS:
1305                     case REDOUBLE_PASS:
1306                     case DOUBLE_BEAVER:
1307                         if (fTutor && fTutorCube)
1308                             /* only store cube if tutoring or we get comments
1309                              * where people don't want them */
1310                             current_pmr_cubedata_update(dd.pes, dd.aarOutput, dd.aarStdDev);
1311                         fComputerDecision = TRUE;
1312                         CommandDouble(NULL);
1313                         fComputerDecision = FALSE;
1314                         return 0;
1315 
1316                     case NODOUBLE_TAKE:
1317                     case TOOGOOD_TAKE:
1318                     case NO_REDOUBLE_TAKE:
1319                     case TOOGOODRE_TAKE:
1320                     case TOOGOOD_PASS:
1321                     case TOOGOODRE_PASS:
1322                     case NODOUBLE_BEAVER:
1323                     case NO_REDOUBLE_BEAVER:
1324 
1325                         current_pmr_cubedata_update(dd.pes, dd.aarOutput, dd.aarStdDev);
1326                         /* better leave cube where it is: no op */
1327                         break;
1328 
1329                     case OPTIONAL_DOUBLE_BEAVER:
1330                     case OPTIONAL_DOUBLE_TAKE:
1331                     case OPTIONAL_REDOUBLE_TAKE:
1332                     case OPTIONAL_DOUBLE_PASS:
1333                     case OPTIONAL_REDOUBLE_PASS:
1334 
1335                         if (ap[ms.fTurn].esCube.et == EVAL_EVAL && ap[ms.fTurn].esCube.ec.nPlies == 0
1336                             && arOutput[0] > 0.001f) {
1337                             /* double if 0-ply except when about to lose game */
1338                             if (fTutor && fTutorCube)
1339                                 current_pmr_cubedata_update(dd.pes, dd.aarOutput, dd.aarStdDev);
1340                             fComputerDecision = TRUE;
1341                             CommandDouble(NULL);
1342                             fComputerDecision = FALSE;
1343                             return 0;
1344                         } else
1345                             current_pmr_cubedata_update(dd.pes, dd.aarOutput, dd.aarStdDev);
1346                         break;
1347 
1348                     default:
1349 
1350                         g_assert_not_reached();
1351 
1352                     }
1353 
1354                 }               /* market window */
1355             }
1356 
1357             /* access to cube */
1358             /* Roll dice and move */
1359             if (!ms.anDice[0]) {
1360                 if (!fCheat || CheatDice(ms.anDice, &ms, afCheatRoll[ms.fMove]))
1361                     if (RollDice(ms.anDice, &rngCurrent, rngctxCurrent) < 0)
1362                         return -1;
1363 
1364                 DiceRolled();
1365                 /* write line to status bar if using GTK */
1366 #if defined (USE_GTK)
1367                 if (fX) {
1368 
1369                     outputnew();
1370                     outputf(_("%s rolls %1u and %1u.\n"), ap[ms.fTurn].szName, ms.anDice[0], ms.anDice[1]);
1371                     outputx();
1372 
1373                 }
1374 #endif
1375                 ResetDelayTimer();      /* Start the timer again -- otherwise the time
1376                                          * we spent contemplating the cube could replace
1377                                          * the delay. */
1378             }
1379 
1380             pmr = NewMoveRecord();
1381 
1382             pmr->mt = MOVE_NORMAL;
1383             pmr->anDice[0] = MAX(ms.anDice[0], ms.anDice[1]);
1384             pmr->anDice[1] = MIN(ms.anDice[0], ms.anDice[1]);
1385             pmr->fPlayer = ms.fTurn;
1386             pmr->esChequer = ap[ms.fTurn].esChequer;
1387 
1388 
1389             fd.pml = &pmr->ml;
1390             fd.pboard = (ConstTanBoard) anBoardMove;
1391             fd.keyMove = NULL;
1392             fd.rThr = 0.0f;
1393             fd.pci = &ci;
1394             fd.pec = &ap[ms.fTurn].esChequer.ec;
1395             fd.aamf = ap[ms.fTurn].aamf;
1396             if ((RunAsyncProcess((AsyncFun) asyncFindMove, &fd, _("Considering move...")) != 0) || fInterrupt) {
1397                 free(pmr);
1398                 return -1;
1399             }
1400             /* resorts the moves according to cubeful (if applicable),
1401              * cubeless and chequer on highest point to avoid some silly
1402              * looking moves */
1403 
1404             RefreshMoveList(&pmr->ml, NULL);
1405 
1406 
1407             /* make the move found above */
1408             if (pmr->ml.cMoves) {
1409                 memcpy(pmr->n.anMove, pmr->ml.amMoves[0].anMove, sizeof(pmr->n.anMove));
1410                 pmr->n.iMove = 0;
1411             }
1412 
1413             /* write move to status bar or stdout */
1414             outputnew();
1415             ShowAutoMove(msBoard(), pmr->n.anMove);
1416             outputx();
1417 
1418             if (pmr->n.anMove[0] < 0)
1419                 playSound(SOUND_BOT_DANCE);
1420 
1421             AddMoveRecord(pmr);
1422 
1423             return 0;
1424         }
1425 
1426     case PLAYER_EXTERNAL:
1427 #if defined(HAVE_SOCKETS)
1428         fTurnOrig = ms.fTurn;
1429 
1430 #if 1
1431         /* FIXME handle resignations -- there must be some way of indicating
1432          * that a resignation has been offered to the external player. */
1433         if (ms.fResigned == 3) {
1434             fComputerDecision = TRUE;
1435             CommandAgree(NULL);
1436             fComputerDecision = FALSE;
1437             return 0;
1438         } else if (ms.fResigned) {
1439             fComputerDecision = TRUE;
1440             CommandDecline(NULL);
1441             fComputerDecision = FALSE;
1442             return 0;
1443         }
1444 #endif
1445 
1446         if (!ms.anDice[0] && !ms.fDoubled && !ms.fResigned &&
1447             (!ms.fCubeUse || ms.nCube >= MAX_CUBE || !GetDPEq(NULL, NULL, &ci))) {
1448             if (RollDice(ms.anDice, &rngCurrent, rngctxCurrent) < 0)
1449                 return -1;
1450 
1451             DiceRolled();
1452         }
1453 
1454         memcpy(anBoardTemp, msBoard(), sizeof(TanBoard));
1455         if (!ms.fMove)
1456             SwapSides(anBoardTemp);
1457 
1458         FIBSBoard(szBoard, anBoardTemp, ms.fMove,
1459                   ap[ms.fMove].szName, ap[!ms.fMove].szName,
1460                   ms.nMatchTo, ms.anScore[ms.fMove], ms.anScore[!ms.fMove],
1461                   ms.anDice[0], ms.anDice[1], ms.nCube, ms.fCubeOwner, ms.fDoubled, 0 /* turn */ , ms.fCrawford,
1462                   anChequers[ms.bgv], ms.fPostCrawford);
1463         strcat(szBoard, "\n");
1464 
1465         if (ExternalWrite(ap[ms.fTurn].h, szBoard, strlen(szBoard) + 1) < 0)
1466             return -1;
1467 
1468         if (ExternalRead(ap[ms.fTurn].h, szResponse, sizeof(szResponse)) < 0)
1469             return -1;
1470 
1471 #if 0
1472         /* Resignations for external players -- not yet implemented. */
1473         if (ms.fResigned) {
1474             fComputerDecision = TRUE;
1475 
1476             if (tolower(*szResponse) == 'a')    /* accept or agree */
1477                 CommandAgree(NULL);
1478             else if (tolower(*szResponse) == 'd' || tolower(*szResponse) == 'r')        /* decline or reject */
1479                 CommandDecline(NULL);
1480             else {
1481                 outputl(_("Warning: badly formed resignation response from " "external player"));
1482                 fComputerDecision = FALSE;
1483                 return -1;
1484             }
1485 
1486             fComputerDecision = FALSE;
1487 
1488             return ms.fTurn == fTurnOrig ? -1 : 0;
1489         }
1490 #endif
1491 #define OLD_ERROR_START "Error ("
1492 #define ERROR_START "Error: "
1493         if (!strncmp(szResponse, ERROR_START, strlen(ERROR_START)) ||
1494             !strncmp(szResponse, OLD_ERROR_START, strlen(OLD_ERROR_START))) {
1495             outputl(szResponse);
1496             fComputerDecision = FALSE;
1497             return -1;
1498         }
1499 
1500         if (ms.fDoubled) {
1501             fComputerDecision = TRUE;
1502 
1503             if (tolower(*szResponse) == 'a' || tolower(*szResponse) == 't')     /* accept or take */
1504                 CommandTake(NULL);
1505             else if (tolower(*szResponse) == 'd' ||     /* drop */
1506                      tolower(*szResponse) == 'p' ||     /* pass */
1507                      !StrNCaseCmp(szResponse, "rej", 3))        /* reject */
1508                 CommandDrop(NULL);
1509             else if (tolower(*szResponse) == 'b' ||     /* beaver */
1510                      !StrNCaseCmp(szResponse, "red", 3))        /* redouble */
1511                 CommandRedouble(NULL);
1512             else {
1513                 outputl(_("Warning: badly formed cube response from " "external player"));
1514                 fComputerDecision = FALSE;
1515                 return -1;
1516             }
1517 
1518             fComputerDecision = FALSE;
1519 
1520             return ms.fTurn != fTurnOrig || ms.gs >= GAME_OVER ? 0 : -1;
1521         } else if (tolower(szResponse[0]) == 'r' && tolower(szResponse[1]) == 'e') {    /* resign */
1522             char *r = szResponse;
1523             NextToken(&r);
1524 
1525             fComputerDecision = TRUE;
1526             CommandResign(r);
1527             fComputerDecision = FALSE;
1528 
1529             return ms.fTurn == fTurnOrig ? -1 : 0;
1530         } else if (!ms.anDice[0]) {
1531             if (tolower(*szResponse) == 'r') {  /* roll */
1532                 if (RollDice(ms.anDice, &rngCurrent, rngctxCurrent) < 0)
1533                     return -1;
1534 
1535                 DiceRolled();
1536 
1537                 return 0;       /* we'll end up back here to play the roll, but
1538                                  * that's OK */
1539             } else if (tolower(*szResponse) == 'd') {   /* double */
1540                 fComputerDecision = TRUE;
1541                 CommandDouble(NULL);
1542                 fComputerDecision = FALSE;
1543             }
1544 
1545             return ms.fTurn == fTurnOrig ? -1 : 0;
1546         } else {
1547             int an[8];
1548             parse_move_is_legal(szResponse, &ms, an);
1549             pmr = NewMoveRecord();
1550             pmr->mt = MOVE_NORMAL;
1551             pmr->anDice[0] = MAX(ms.anDice[0], ms.anDice[1]);
1552             pmr->anDice[1] = MIN(ms.anDice[0], ms.anDice[1]);
1553             pmr->fPlayer = ms.fTurn;
1554             memcpy(pmr->n.anMove, an, sizeof pmr->n.anMove);
1555             if (pmr->n.anMove[0] < 0) {
1556                 /* illegal or no move */
1557                 playSound(SOUND_BOT_DANCE);
1558             }
1559             AddMoveRecord(pmr);
1560             return 0;
1561         }
1562 #else
1563         /* fall through */
1564 #endif
1565 
1566     case PLAYER_HUMAN:
1567         /* fall through */
1568         ;
1569     }
1570 
1571     g_assert_not_reached();
1572     return -1;
1573 }
1574 
1575 static void
TurnDone(void)1576 TurnDone(void)
1577 {
1578 
1579 #if defined (USE_GTK)
1580     if (fX) {
1581         if (!nNextTurn)
1582             nNextTurn = g_idle_add(NextTurnNotify, NULL);
1583     } else
1584 #endif
1585         fNextTurn = TRUE;
1586 
1587     outputx();
1588 }
1589 
1590 extern void
CancelCubeAction(void)1591 CancelCubeAction(void)
1592 {
1593 
1594     if (ms.fDoubled) {
1595         ms.fDoubled = FALSE;
1596 
1597         if (fDisplay)
1598             ShowBoard();
1599 
1600         /* FIXME should fTurn be set to fMove? */
1601         /* TurnDone(); Removed. Causes problems when called during
1602          * analyse match */
1603         /* FIXME delete all MOVE_DOUBLE records */
1604     }
1605 }
1606 
1607 
1608 static void
StartAutomaticPlay(void)1609 StartAutomaticPlay(void)
1610 {
1611     if (ap[0].pt == PLAYER_GNU && ap[1].pt == PLAYER_GNU) {
1612         automaticTask = TRUE;
1613 #if defined (USE_GTK)
1614         GTKSuspendInput();
1615         ProcessEvents();
1616 #endif
1617     }
1618 }
1619 
1620 extern void
StopAutomaticPlay(void)1621 StopAutomaticPlay(void)
1622 {
1623     if (automaticTask) {
1624 #if defined (USE_GTK)
1625         GTKResumeInput();
1626 #endif
1627         automaticTask = FALSE;
1628     }
1629 }
1630 
1631 /* Try to automatically bear off as many chequers as possible.  Only do it
1632  * if it's a non-contact position, and each die can be used to bear off
1633  * a chequer. */
1634 static int
TryBearoff(void)1635 TryBearoff(void)
1636 {
1637 
1638     movelist ml;
1639     unsigned int i, iMove, cMoves;
1640     moverecord *pmr;
1641 
1642     if (ClassifyPosition(msBoard(), VARIATION_STANDARD) > CLASS_RACE)
1643         /* It's a contact position; don't automatically bear off */
1644         /* note that we use VARIATION_STANDARD instead of ms.bgv in order
1645          * to avoid automatic bearoff in contact positions for hypergammon */
1646         return -1;
1647 
1648     GenerateMoves(&ml, msBoard(), ms.anDice[0], ms.anDice[1], FALSE);
1649 
1650     cMoves = (ms.anDice[0] == ms.anDice[1]) ? 4 : 2;
1651 
1652     for (i = 0; i < ml.cMoves; i++)
1653         for (iMove = 0; iMove < cMoves; iMove++)
1654             if ((ml.amMoves[i].anMove[iMove << 1] < 0) || (ml.amMoves[i].anMove[(iMove << 1) + 1] != -1))
1655                 break;
1656             else if (iMove == cMoves - 1) {
1657                 /* All dice bear off */
1658                 pmr = NewMoveRecord();
1659 
1660                 pmr->mt = MOVE_NORMAL;
1661                 pmr->anDice[0] = MAX(ms.anDice[0], ms.anDice[1]);
1662                 pmr->anDice[1] = MIN(ms.anDice[0], ms.anDice[1]);
1663                 pmr->fPlayer = ms.fTurn;
1664                 memcpy(pmr->n.anMove, ml.amMoves[i].anMove, sizeof(pmr->n.anMove));
1665 
1666                 ShowAutoMove(msBoard(), pmr->n.anMove);
1667 
1668 
1669                 AddMoveRecord(pmr);
1670 
1671 
1672                 return 0;
1673             }
1674 
1675     return -1;
1676 }
1677 
1678 extern int
NextTurn(int fPlayNext)1679 NextTurn(int fPlayNext)
1680 {
1681 
1682     g_assert(!fComputing);
1683 
1684 #if defined (USE_GTK)
1685     if (fX) {
1686         if (nNextTurn) {
1687             g_source_remove(nNextTurn);
1688             nNextTurn = 0;
1689         } else
1690             return -1;
1691     } else
1692 #endif
1693     if (fNextTurn)
1694         fNextTurn = FALSE;
1695     else
1696         return -1;
1697 
1698     if (!plGame)
1699         return -1;
1700 
1701     fComputing = TRUE;
1702 
1703 #if defined (USE_GTK)
1704     if (fX && fDisplay) {
1705         if (nDelay && nTimeout) {
1706             fDelaying = TRUE;
1707             GTKDelay();
1708             fDelaying = FALSE;
1709             ResetDelayTimer();
1710         }
1711 
1712         if (fLastMove) {
1713             board_animate(BOARD(pwBoard), anLastMove, fLastPlayer);
1714             if (fInterrupt && !automaticTask)
1715                 fInterrupt = FALSE;
1716 
1717             playSound(SOUND_MOVE);
1718             fLastMove = FALSE;
1719         }
1720     }
1721 #endif
1722 
1723     UpdateSetting(&ms.nCube);
1724     UpdateSetting(&ms.fCubeOwner);
1725     UpdateSetting(&ms.fTurn);
1726     UpdateSetting(&ms.gs);
1727 
1728     if (GameStatus(msBoard(), ms.bgv) || ms.gs == GAME_DROP || ms.gs == GAME_RESIGNED) {
1729         moverecord *pmr = (moverecord *) plGame->plNext->p;
1730         xmovegameinfo *pmgi = &pmr->g;
1731         int n;
1732 
1733         if (ms.fJacoby && ms.fCubeOwner == -1 && !ms.nMatchTo)
1734             /* gammons do not count on a centred cube during money
1735              * sessions under the Jacoby rule */
1736             n = 1;
1737         else if (ms.gs == GAME_DROP)
1738             n = 1;
1739         else if (ms.gs == GAME_RESIGNED)
1740             /* FIXME: in some circumstances gs==GAME_RESIGNED but fResigned==0
1741              * this causes a read out of bounds in aszGameResult[] below */
1742             n = MAX(ms.fResigned, 1);
1743         else
1744             n = GameStatus(msBoard(), ms.bgv);
1745 
1746         playSound(ap[pmgi->fWinner].pt == PLAYER_HUMAN ? SOUND_HUMAN_WIN_GAME : SOUND_BOT_WIN_GAME);
1747 
1748         outputf(ngettext("%s wins a %s and %d point.\n",
1749                          "%s wins a %s and %d points.\n",
1750                          pmgi->nPoints), ap[pmgi->fWinner].szName, gettext(aszGameResult[n - 1]), pmgi->nPoints);
1751 
1752 #if defined (USE_GTK)
1753         if (fX) {
1754             if (fDisplay) {
1755 #if defined(USE_BOARD3D)
1756                 BoardData *bd = BOARD(pwBoard)->board_data;
1757                 if (ms.fResigned && display_is_3d(bd->rd))
1758                     StopIdle3d(bd, bd->bd3d);   /* Stop flag waving */
1759 
1760 #endif
1761                 /* Clear the resignation flag */
1762                 ms.fResigned = 0;
1763 
1764                 ShowBoard();
1765             } else
1766                 outputx();
1767         }
1768 #endif
1769 
1770         if (ms.nMatchTo && fAutoCrawford) {
1771             ms.fPostCrawford |= ms.fCrawford && ms.anScore[pmgi->fWinner] < ms.nMatchTo;
1772             ms.fCrawford = !ms.fPostCrawford && !ms.fCrawford &&
1773                 ms.anScore[pmgi->fWinner] == ms.nMatchTo - 1 && ms.anScore[!pmgi->fWinner] != ms.nMatchTo - 1;
1774         }
1775 
1776         fCrawfordState = ms.fCrawford | (ms.fPostCrawford << 1);
1777 
1778 #if defined (USE_GTK)
1779         if (!fX || fDisplay)
1780 #endif
1781             CommandShowScore(NULL);
1782 
1783         if (ms.nMatchTo && ms.anScore[pmgi->fWinner] >= ms.nMatchTo) {
1784             playSound(ap[pmgi->fWinner].pt == PLAYER_HUMAN ? SOUND_HUMAN_WIN_MATCH : SOUND_BOT_WIN_MATCH);
1785 
1786             outputf(_("%s has won the match.\n"), ap[pmgi->fWinner].szName);
1787             outputx();
1788             fComputing = FALSE;
1789 
1790             ShowBoard();
1791 
1792             StopAutomaticPlay();
1793 
1794             return -1;
1795         }
1796 
1797         outputx();
1798 
1799         if (fAutoGame) {
1800             if (NewGame() < 0) {
1801                 fComputing = FALSE;
1802                 return -1;
1803             }
1804 
1805             if (ap[ms.fTurn].pt == PLAYER_HUMAN)
1806                 ShowBoard();
1807         } else {
1808             fComputing = FALSE;
1809             StopAutomaticPlay();
1810             return -1;
1811         }
1812     }
1813 
1814     g_assert(ms.gs == GAME_PLAYING);
1815 
1816     if (fDisplay || ap[ms.fTurn].pt == PLAYER_HUMAN) {
1817         ShowBoard();
1818     }
1819 
1820     /* We have reached a safe point to check for interrupts.  Until now,
1821      * the board could have been in an inconsistent state. */
1822     if (fInterrupt || !fPlayNext) {
1823         fComputing = FALSE;
1824         return -1;
1825     }
1826 
1827     if (ap[ms.fTurn].pt == PLAYER_HUMAN) {
1828         /* Roll for them, if:
1829          *
1830          * * "auto roll" is on;
1831          * * they haven't already rolled;
1832          * * they haven't just been doubled;
1833          * * somebody hasn't just resigned;
1834          * * at least one of the following:
1835          * - cube use is disabled;
1836          * - it's the Crawford game;
1837          * - the cube is dead. */
1838         if (fAutoRoll && !ms.anDice[0] && !ms.fDoubled && !ms.fResigned &&
1839             (!ms.fCubeUse || ms.fCrawford ||
1840              (ms.fCubeOwner >= 0 && ms.fCubeOwner != ms.fTurn) ||
1841              (ms.nMatchTo > 0 && ms.anScore[ms.fTurn] + ms.nCube >= ms.nMatchTo)))
1842             CommandRoll(NULL);
1843 
1844         fComputing = FALSE;
1845         return -1;
1846     }
1847 #if defined (USE_GTK)
1848     if (fX) {
1849         if (!ComputerTurn() && !nNextTurn)
1850             nNextTurn = g_idle_add(NextTurnNotify, NULL);
1851     } else
1852 #endif
1853         fNextTurn = !ComputerTurn();
1854 
1855     fComputing = FALSE;
1856     return 0;
1857 }
1858 
1859 #if defined (USE_GTK)
1860 extern int
quick_roll(void)1861 quick_roll(void)
1862 {
1863     if (ms.gs == GAME_NONE && move_is_last_in_match()) {
1864         UserCommand("new match");
1865         return (ms.gs == GAME_PLAYING);
1866     }
1867     if (ms.gs != GAME_PLAYING && move_is_last_in_match()) {
1868         UserCommand("new game");
1869         return (ms.gs == GAME_PLAYING);
1870     }
1871     if (ms.gs != GAME_PLAYING)
1872         /* we are not playing and the move isn't the last in the match */
1873         return 0;
1874     if (!ms.anDice[0]) {
1875         UserCommand("roll");
1876         return (ms.anDice[0]);
1877     }
1878     return 0;
1879 }
1880 #endif
1881 
1882 
1883 extern void
CommandAccept(char * sz)1884 CommandAccept(char *sz)
1885 {
1886 
1887     if (ms.fResigned)
1888         CommandAgree(sz);
1889     else if (ms.fDoubled)
1890         CommandTake(sz);
1891     else
1892         outputl(_("You can only accept if the cube or a resignation has been " "offered."));
1893 }
1894 
1895 extern void
CommandAgree(char * UNUSED (sz))1896 CommandAgree(char *UNUSED(sz))
1897 {
1898 
1899     moverecord *pmr;
1900 
1901     if (ms.gs != GAME_PLAYING) {
1902         outputl(_("No game in progress (type `new game' to start one)."));
1903 
1904         return;
1905     }
1906 
1907     if (ap[ms.fTurn].pt != PLAYER_HUMAN && !fComputerDecision) {
1908         outputl(_("It is the computer's turn -- type `play' to force it to " "move immediately."));
1909         return;
1910     }
1911 
1912     if (!ms.fResigned) {
1913         outputl(_("No resignation was offered."));
1914 
1915         return;
1916     }
1917 
1918     if (!move_not_last_in_match_ok())
1919         return;
1920 
1921     if (fDisplay)
1922         outputf(_("%s accepts and wins a %s.\n"), ap[ms.fTurn].szName, gettext(aszGameResult[ms.fResigned - 1]));
1923 
1924     playSound(SOUND_AGREE);
1925 
1926     pmr = NewMoveRecord();
1927 
1928     pmr->mt = MOVE_RESIGN;
1929     pmr->fPlayer = !ms.fTurn;
1930 
1931     pmr->r.nResigned = ms.fResigned;
1932     pmr->r.esResign.et = EVAL_NONE;
1933 
1934     AddMoveRecord(pmr);
1935 
1936     TurnDone();
1937 }
1938 
1939 static void
AnnotateMove(skilltype st)1940 AnnotateMove(skilltype st)
1941 {
1942 
1943     moverecord *pmr;
1944 
1945     if (!plLastMove || !plLastMove->plNext || (!(pmr = plLastMove->plNext->p))) {
1946         outputl(_("You must select a move to annotate first."));
1947         return;
1948     }
1949 
1950     switch (pmr->mt) {
1951     case MOVE_NORMAL:
1952 
1953         switch (at) {
1954         case ANNOTATE_MOVE:
1955             pmr->n.stMove = st; /* fixme */
1956             break;
1957         case ANNOTATE_CUBE:
1958         case ANNOTATE_DOUBLE:
1959             pmr->stCube = st;   /* fixme */
1960             break;
1961         default:
1962             outputl(_("Invalid annotation"));
1963             break;
1964         }
1965 
1966         break;
1967 
1968     case MOVE_DOUBLE:
1969 
1970         switch (at) {
1971         case ANNOTATE_CUBE:
1972         case ANNOTATE_DOUBLE:
1973             pmr->stCube = st;
1974             break;
1975         default:
1976             outputl(_("Invalid annotation"));
1977             break;
1978         }
1979 
1980         break;
1981 
1982     case MOVE_TAKE:
1983 
1984         switch (at) {
1985         case ANNOTATE_CUBE:
1986         case ANNOTATE_ACCEPT:
1987             pmr->stCube = st;
1988             break;
1989         default:
1990             outputl(_("Invalid annotation"));
1991             break;
1992         }
1993 
1994         break;
1995 
1996     case MOVE_DROP:
1997 
1998         switch (at) {
1999         case ANNOTATE_CUBE:
2000         case ANNOTATE_REJECT:
2001         case ANNOTATE_DROP:
2002             pmr->stCube = st;
2003             break;
2004         default:
2005             outputl(_("Invalid annotation"));
2006             break;
2007         }
2008 
2009         break;
2010 
2011     case MOVE_RESIGN:
2012 
2013         switch (at) {
2014         case ANNOTATE_RESIGN:
2015             pmr->r.stResign = st;
2016             break;
2017         case ANNOTATE_ACCEPT:
2018         case ANNOTATE_REJECT:
2019             pmr->r.stAccept = st;
2020             break;
2021         default:
2022             outputl(_("Invalid annotation"));
2023             break;
2024         }
2025 
2026         break;
2027 
2028     default:
2029         outputl(_("You cannot annotate this move."));
2030         return;
2031     }
2032 
2033     if (st == SKILL_NONE)
2034         outputl(_("Skill annotation cleared."));
2035     else
2036         outputf(_("Move marked as %s.\n"), gettext(aszSkillType[st]));
2037 
2038 #if defined (USE_GTK)
2039     if (fX)
2040         ChangeGame(NULL);
2041 #endif
2042 
2043 }
2044 
2045 static void
AnnotateRoll(lucktype lt)2046 AnnotateRoll(lucktype lt)
2047 {
2048 
2049     moverecord *pmr;
2050 
2051     if (!plLastMove || !plLastMove->plNext || (!(pmr = plLastMove->plNext->p))) {
2052         outputl(_("You must select a move to annotate first."));
2053         return;
2054     }
2055 
2056     switch (pmr->mt) {
2057     case MOVE_NORMAL:
2058         pmr->lt = lt;
2059         break;
2060 
2061     case MOVE_SETDICE:
2062         pmr->lt = lt;
2063         break;
2064 
2065     default:
2066         outputl(_("You cannot annotate this move."));
2067         return;
2068     }
2069 
2070     if (lt == LUCK_NONE)
2071         outputl(_("Luck annotation cleared."));
2072     else
2073         outputf(_("Roll marked as %s.\n"), gettext(aszLuckType[lt]));
2074 
2075 #if defined (USE_GTK)
2076     if (fX)
2077         ChangeGame(NULL);
2078 #endif
2079 
2080 }
2081 
2082 
2083 extern void
CommandAnnotateAccept(char * sz)2084 CommandAnnotateAccept(char *sz)
2085 {
2086 
2087     at = ANNOTATE_ACCEPT;
2088     HandleCommand(sz, acAnnotateMove);
2089 
2090 }
2091 
2092 extern void
CommandAnnotateCube(char * sz)2093 CommandAnnotateCube(char *sz)
2094 {
2095 
2096     at = ANNOTATE_CUBE;
2097     HandleCommand(sz, acAnnotateMove);
2098 
2099 }
2100 
2101 extern void
CommandAnnotateDouble(char * sz)2102 CommandAnnotateDouble(char *sz)
2103 {
2104 
2105     at = ANNOTATE_DOUBLE;
2106     HandleCommand(sz, acAnnotateMove);
2107 
2108 }
2109 
2110 extern void
CommandAnnotateDrop(char * sz)2111 CommandAnnotateDrop(char *sz)
2112 {
2113 
2114     at = ANNOTATE_DROP;
2115     HandleCommand(sz, acAnnotateMove);
2116 
2117 }
2118 
2119 extern void
CommandAnnotateMove(char * sz)2120 CommandAnnotateMove(char *sz)
2121 {
2122 
2123     at = ANNOTATE_MOVE;
2124     HandleCommand(sz, acAnnotateMove);
2125 
2126 }
2127 
2128 extern void
CommandAnnotateReject(char * sz)2129 CommandAnnotateReject(char *sz)
2130 {
2131 
2132     at = ANNOTATE_REJECT;
2133     HandleCommand(sz, acAnnotateMove);
2134 
2135 }
2136 
2137 extern void
CommandAnnotateResign(char * sz)2138 CommandAnnotateResign(char *sz)
2139 {
2140 
2141     at = ANNOTATE_RESIGN;
2142     HandleCommand(sz, acAnnotateMove);
2143 
2144 }
2145 
2146 extern void
CommandAnnotateBad(char * UNUSED (sz))2147 CommandAnnotateBad(char *UNUSED(sz))
2148 {
2149 
2150     AnnotateMove(SKILL_BAD);
2151 }
2152 
2153 extern void
CommandAnnotateAddComment(char * sz)2154 CommandAnnotateAddComment(char *sz)
2155 {
2156 
2157     moverecord *pmr;
2158 
2159     if (!plLastMove || !plLastMove->plNext || (!(pmr = plLastMove->plNext->p))) {
2160         outputl(_("You must select a move to which to add the comment."));
2161         return;
2162     }
2163 
2164     if (pmr->sz)
2165         free(pmr->sz);
2166 
2167     pmr->sz = g_strdup(sz);
2168 
2169     outputl(_("Commentary for this move added."));
2170 
2171 #if defined (USE_GTK)
2172     if (fX)
2173         ChangeGame(NULL);
2174 #endif
2175 
2176 }
2177 
2178 extern void
CommandAnnotateClearComment(char * UNUSED (sz))2179 CommandAnnotateClearComment(char *UNUSED(sz))
2180 {
2181 
2182     moverecord *pmr;
2183 
2184     if (!plLastMove || !plLastMove->plNext || (!(pmr = plLastMove->plNext->p))) {
2185         outputl(_("You must select a move to clear the comment from."));
2186         return;
2187     }
2188 
2189     if (pmr->sz)
2190         free(pmr->sz);
2191 
2192     pmr->sz = NULL;
2193 
2194     outputl(_("Commentary for this move cleared."));
2195 
2196 #if defined (USE_GTK)
2197     if (fX)
2198         ChangeGame(NULL);
2199 #endif
2200 
2201 }
2202 
2203 extern void
CommandAnnotateClearLuck(char * UNUSED (sz))2204 CommandAnnotateClearLuck(char *UNUSED(sz))
2205 {
2206 
2207     AnnotateRoll(LUCK_NONE);
2208 }
2209 
2210 extern void
CommandAnnotateClearSkill(char * UNUSED (sz))2211 CommandAnnotateClearSkill(char *UNUSED(sz))
2212 {
2213 
2214     AnnotateMove(SKILL_NONE);
2215 }
2216 
2217 extern void
CommandAnnotateDoubtful(char * UNUSED (sz))2218 CommandAnnotateDoubtful(char *UNUSED(sz))
2219 {
2220 
2221     AnnotateMove(SKILL_DOUBTFUL);
2222 }
2223 
2224 extern void
CommandAnnotateLucky(char * UNUSED (sz))2225 CommandAnnotateLucky(char *UNUSED(sz))
2226 {
2227 
2228     AnnotateRoll(LUCK_GOOD);
2229 }
2230 
2231 extern void
CommandAnnotateUnlucky(char * UNUSED (sz))2232 CommandAnnotateUnlucky(char *UNUSED(sz))
2233 {
2234 
2235     AnnotateRoll(LUCK_BAD);
2236 }
2237 
2238 extern void
CommandAnnotateVeryBad(char * UNUSED (sz))2239 CommandAnnotateVeryBad(char *UNUSED(sz))
2240 {
2241 
2242     AnnotateMove(SKILL_VERYBAD);
2243 }
2244 
2245 extern void
CommandAnnotateVeryLucky(char * UNUSED (sz))2246 CommandAnnotateVeryLucky(char *UNUSED(sz))
2247 {
2248 
2249     AnnotateRoll(LUCK_VERYGOOD);
2250 }
2251 
2252 extern void
CommandAnnotateVeryUnlucky(char * UNUSED (sz))2253 CommandAnnotateVeryUnlucky(char *UNUSED(sz))
2254 {
2255 
2256     AnnotateRoll(LUCK_VERYBAD);
2257 }
2258 
2259 extern void
CommandDecline(char * UNUSED (sz))2260 CommandDecline(char *UNUSED(sz))
2261 {
2262 
2263     if (ms.gs != GAME_PLAYING) {
2264         outputl(_("No game in progress (type `new game' to start one)."));
2265 
2266         return;
2267     }
2268 
2269     if (ap[ms.fTurn].pt != PLAYER_HUMAN && !fComputerDecision) {
2270         outputl(_("It is the computer's turn -- type `play' to force it to " "move immediately."));
2271         return;
2272     }
2273 
2274     if (!ms.fResigned) {
2275         outputl(_("No resignation was offered."));
2276 
2277         return;
2278     }
2279 
2280     if (fDisplay) {
2281         {
2282             if (ms.fResigned > 0)
2283                 outputf(_("%s declines the %s.\n"), ap[ms.fTurn].szName, gettext(aszGameResult[ms.fResigned - 1]));
2284             else
2285                 outputf(_("%s declines the resignation\n"), ap[ms.fTurn].szName);
2286         }
2287     }
2288 
2289     ms.fResignationDeclined = ms.fResigned;
2290     ms.fResigned = FALSE;
2291     ms.fTurn = !ms.fTurn;
2292 
2293     TurnDone();
2294 }
2295 
2296 static skilltype
tutor_double(int did_double)2297 tutor_double(int did_double)
2298 {
2299     moverecord *pmr;
2300     cubeinfo ci;
2301     GetMatchStateCubeInfo(&ci, &ms);
2302 
2303     /* reasons that doubling is not an issue */
2304     if ((ms.gs != GAME_PLAYING)
2305         || ap[ms.fTurn].pt != PLAYER_HUMAN || !GetDPEq(NULL, NULL, &ci))
2306         return SKILL_NONE;
2307 
2308     hint_double(FALSE, did_double);
2309     pmr = get_current_moverecord(NULL);
2310     return pmr ? pmr->stCube : SKILL_NONE;
2311 }
2312 
2313 extern void
CommandDouble(char * UNUSED (sz))2314 CommandDouble(char *UNUSED(sz))
2315 {
2316 
2317     moverecord *pmr;
2318 
2319     if (ms.gs != GAME_PLAYING) {
2320         outputl(_("No game in progress (type `new game' to start one)."));
2321 
2322         return;
2323     }
2324 
2325     if (ap[ms.fTurn].pt != PLAYER_HUMAN && !fComputerDecision) {
2326         outputl(_("It is the computer's turn -- type `play' to force it to " "move immediately."));
2327         return;
2328     }
2329 
2330     if (ms.fCrawford) {
2331         outputl(_("Doubling is forbidden by the Crawford rule (see `help set " "crawford')."));
2332 
2333         return;
2334     }
2335 
2336     if (!ms.fCubeUse) {
2337         outputl(_("The doubling cube has been disabled (see `help set cube " "use')."));
2338 
2339         return;
2340     }
2341 
2342     if (!move_not_last_in_match_ok())
2343         return;
2344 
2345     if (ms.fDoubled) {
2346         CommandRedouble(NULL);
2347         return;
2348     }
2349 
2350     if (ms.fTurn != ms.fMove) {
2351         outputl(_("You are only allowed to double if you are on roll."));
2352 
2353         return;
2354     }
2355 
2356     if (ms.anDice[0]) {
2357         outputl(_("You can't double after rolling the dice -- wait until your " "next turn."));
2358 
2359         return;
2360     }
2361 
2362     if (ms.fCubeOwner >= 0 && ms.fCubeOwner != ms.fTurn) {
2363         outputl(_("You do not own the cube."));
2364 
2365         return;
2366     }
2367 
2368     if (ms.nCube >= (ms.nMatchTo ? MAXSCORE : MAX_CUBE)) {
2369         outputf(_("The cube is already at its highest supported value ; you can't double any more.\n"));
2370         return;
2371     }
2372 
2373     if (ms.nMatchTo && ms.nCube >= (ms.nMatchTo - ms.anScore[ms.fTurn])) {
2374         outputf(_("The cube is dead; you can't double any more.\n"));
2375         return;
2376     }
2377 
2378     playSound(SOUND_DOUBLE);
2379 
2380     pmr = NewMoveRecord();
2381 
2382     pmr->mt = MOVE_DOUBLE;
2383     pmr->fPlayer = ms.fTurn;
2384     if (fTutor && fTutorCube && !GiveAdvice(tutor_double(TRUE))) {
2385         free(pmr);
2386         return;
2387     }
2388 
2389     if (fDisplay)
2390         outputf(_("%s doubles.\n"), ap[ms.fTurn].szName);
2391 
2392 #if defined (USE_GTK)
2393     /* There's no point delaying here. */
2394     if (nTimeout) {
2395         g_source_remove(nTimeout);
2396         nTimeout = 0;
2397     }
2398 #endif
2399 
2400     AddMoveRecord(pmr);
2401 
2402     TurnDone();
2403 }
2404 
2405 static skilltype
tutor_take(int did_take)2406 tutor_take(int did_take)
2407 {
2408     moverecord *pmr;
2409 
2410     /* reasons that taking is not an issue */
2411     if ((ms.gs != GAME_PLAYING) || ms.anDice[0] || !ms.fDoubled || ms.fResigned || (ap[ms.fTurn].pt != PLAYER_HUMAN))
2412         return (SKILL_NONE);
2413 
2414     hint_take(FALSE, did_take);
2415     pmr = get_current_moverecord(NULL);
2416     return pmr ? pmr->stCube : SKILL_NONE;
2417 }
2418 
2419 
2420 extern void
CommandDrop(char * UNUSED (sz))2421 CommandDrop(char *UNUSED(sz))
2422 {
2423     moverecord *pmr;
2424 
2425     if (ms.gs != GAME_PLAYING || !ms.fDoubled) {
2426         outputl(_("The cube must have been offered before you can drop it."));
2427 
2428         return;
2429     }
2430 
2431     if (ap[ms.fTurn].pt != PLAYER_HUMAN && !fComputerDecision) {
2432         outputl(_("It is the computer's turn -- type `play' to force it to " "move immediately."));
2433         return;
2434     }
2435 
2436     if (!move_not_last_in_match_ok())
2437         return;
2438 
2439     playSound(SOUND_DROP);
2440 
2441     pmr = NewMoveRecord();
2442 
2443     pmr->mt = MOVE_DROP;
2444     pmr->fPlayer = ms.fTurn;
2445     if (!LinkToDouble(pmr)) {
2446         free(pmr);
2447         return;
2448     }
2449 
2450     if (fTutor && fTutorCube && !GiveAdvice(tutor_take(FALSE))) {
2451         free(pmr);              /* garbage collect */
2452         return;
2453     }
2454 
2455     if (fDisplay)
2456         outputf(ngettext
2457                 ("%s refuses the cube and gives up %d point.\n", "%s refuses the cube and gives up %d points.\n",
2458                  ms.nCube), ap[ms.fTurn].szName, ms.nCube);
2459 
2460     AddMoveRecord(pmr);
2461 
2462     TurnDone();
2463 }
2464 
2465 static void
DumpGameList(GString * gsz,listOLD * plGame)2466 DumpGameList(GString * gsz, listOLD * plGame)
2467 {
2468 
2469     listOLD *pl;
2470     moverecord *pmr;
2471     int nFileCube = 1;
2472     TanBoard anBoard;
2473     char tmp[100];
2474     int column = 0;
2475     InitBoard(anBoard, ms.bgv);
2476     for (pl = plGame->plNext; pl != plGame; pl = pl->plNext) {
2477         pmr = pl->p;
2478         if (column == 1)
2479             g_string_append(gsz, "\n");
2480         if (pmr->fPlayer == 1 && column == 1)
2481             g_string_append_printf(gsz, "%42s", "");
2482         column = pmr->fPlayer;
2483         switch (pmr->mt) {
2484         case MOVE_GAMEINFO:
2485             column = 0;
2486             break;
2487         case MOVE_NORMAL:
2488             g_string_append_printf(gsz, "%u%u%-5s: ", pmr->anDice[0], pmr->anDice[1], aszLuckTypeAbbr[pmr->lt]);
2489             FormatMove(tmp, (ConstTanBoard) anBoard, pmr->n.anMove);
2490             g_string_append_printf(gsz, "%-15s", tmp);
2491             g_string_append_printf(gsz, "%-10s%-10s", aszSkillTypeAbbr[pmr->n.stMove], aszSkillTypeAbbr[pmr->stCube]);
2492             ApplyMove(anBoard, pmr->n.anMove, FALSE);
2493             SwapSides(anBoard);
2494             break;
2495         case MOVE_DOUBLE:
2496             g_string_append_printf(gsz, _("Doubles to %4d                              "), nFileCube <<= 1);
2497             break;
2498         case MOVE_TAKE:
2499             g_string_append(gsz, _("Takes                                       "));
2500             break;
2501         case MOVE_DROP:
2502             g_string_append(gsz, _("Drops\n"));
2503             break;
2504         case MOVE_RESIGN:
2505             g_string_append(gsz, _("Resigns\n"));
2506             break;
2507         case MOVE_SETDICE:
2508             g_string_append_printf(gsz, "%u%u%-5s: %-33s",
2509                                    pmr->anDice[0], pmr->anDice[1], aszLuckTypeAbbr[pmr->lt], _("Rolled"));
2510             break;
2511         default:
2512             g_string_append_printf(gsz, _("Unhandled movetype %d                      \n"), (int) pmr->mt);
2513             break;
2514         }
2515     }
2516 
2517 }
2518 
2519 extern void
CommandListGame(char * UNUSED (sz))2520 CommandListGame(char *UNUSED(sz))
2521 {
2522     GString *gsz = g_string_new(NULL);
2523 
2524     if (ms.gs != GAME_PLAYING) {
2525         outputl(_("No game in progress (type `new game' to start one)."));
2526 
2527         return;
2528     }
2529     DumpGameList(gsz, plGame);
2530 #if defined (USE_GTK)
2531     if (fX)
2532         GTKTextWindow(gsz->str, _("Game dump"), DT_INFO, NULL);
2533     else
2534 #endif
2535     {
2536         outputl(gsz->str);
2537         outputx();
2538     }
2539     g_string_free(gsz, TRUE);
2540 }
2541 
2542 extern void
CommandListMatch(char * UNUSED (sz))2543 CommandListMatch(char *UNUSED(sz))
2544 {
2545 #if defined (USE_GTK)
2546     if (fX) {
2547         ShowGameWindow();
2548         return;
2549     }
2550 #endif
2551 
2552     /* FIXME */
2553 }
2554 
2555 /*! \brief finds the new board in the list of moves/
2556  * \param pml the list of moves
2557  * \param old_board the board before move
2558  * \param board the new board
2559  * \return 1 if found, 0 otherwise
2560  */
2561 extern int
board_in_list(const movelist * pml,const TanBoard old_board,const TanBoard board,int * an)2562 board_in_list(const movelist * pml, const TanBoard old_board, const TanBoard board, int *an)
2563 {
2564     guint j;
2565     TanBoard list_board;
2566     g_return_val_if_fail(pml, FALSE);
2567     g_return_val_if_fail(old_board, FALSE);
2568     g_return_val_if_fail(board, FALSE);
2569     for (j = 0; j < pml->cMoves; j++) {
2570         memcpy(list_board, old_board, sizeof(TanBoard));
2571         ApplyMove(list_board, pml->amMoves[j].anMove, FALSE);
2572         if (memcmp(list_board, board, sizeof(TanBoard)) == 0) {
2573             if (an)
2574                 memcpy(an, pml->amMoves[j].anMove, 8 * sizeof(int));
2575             return 1;
2576         }
2577     }
2578     if (an)
2579         *an = -1;
2580     return 0;
2581 }
2582 
2583 extern void
CommandMove(char * sz)2584 CommandMove(char *sz)
2585 {
2586 
2587     int an[8];
2588     movelist ml;
2589     moverecord *pmr;
2590 
2591     if (ms.gs != GAME_PLAYING) {
2592         outputl(_("No game in progress (type `new game' to start one)."));
2593 
2594         return;
2595     }
2596 
2597     if (ap[ms.fTurn].pt != PLAYER_HUMAN) {
2598         outputl(_("It is the computer's turn -- type `play' to force it to " "move immediately."));
2599         return;
2600     }
2601 
2602     if (!ms.anDice[0]) {
2603         outputl(_("You must roll the dice before you can move."));
2604 
2605         return;
2606     }
2607 
2608     if (ms.fResigned) {
2609         outputf(_("Please wait for %s to consider the resignation before " "moving.\n"), ap[ms.fTurn].szName);
2610 
2611         return;
2612     }
2613 
2614     if (ms.fDoubled) {
2615         outputf(_("Please wait for %s to consider the cube before " "moving.\n"), ap[ms.fTurn].szName);
2616 
2617         return;
2618     }
2619 
2620     if (!move_not_last_in_match_ok())
2621         return;
2622 
2623     if (!*sz) {
2624         GenerateMoves(&ml, msBoard(), ms.anDice[0], ms.anDice[1], FALSE);
2625 
2626         if (ml.cMoves <= 1) {
2627 
2628             if (!ml.cMoves)
2629                 playSound(ap[ms.fTurn].pt == PLAYER_HUMAN ? SOUND_HUMAN_DANCE : SOUND_BOT_DANCE);
2630             else
2631                 playSound(SOUND_MOVE);
2632 
2633 
2634             pmr = NewMoveRecord();
2635 
2636             pmr->mt = MOVE_NORMAL;
2637             pmr->sz = NULL;
2638             pmr->anDice[0] = MAX(ms.anDice[0], ms.anDice[1]);
2639             pmr->anDice[1] = MIN(ms.anDice[0], ms.anDice[1]);
2640             pmr->fPlayer = ms.fTurn;
2641             if (ml.cMoves)
2642                 memcpy(pmr->n.anMove, ml.amMoves[0].anMove, sizeof(pmr->n.anMove));
2643 
2644             ShowAutoMove(msBoard(), pmr->n.anMove);
2645 
2646 
2647             AddMoveRecord(pmr);
2648 
2649             TurnDone();
2650 
2651             return;
2652         }
2653 
2654         if (fAutoBearoff && !TryBearoff()) {
2655             TurnDone();
2656 
2657             return;
2658         }
2659 
2660         outputl(_("You must specify a move (type `help move' for " "instructions)."));
2661 
2662         return;
2663     }
2664 
2665     if (!parse_move_is_legal(sz, &ms, an)) {
2666         outputerrf(_("Illegal or unparsable move."));
2667         return;
2668     }
2669 
2670     /* we have a legal move! */
2671     playSound(SOUND_MOVE);
2672     pmr = NewMoveRecord();
2673     pmr->mt = MOVE_NORMAL;
2674     pmr->sz = NULL;
2675     pmr->anDice[0] = MAX(ms.anDice[0], ms.anDice[1]);
2676     pmr->anDice[1] = MIN(ms.anDice[0], ms.anDice[1]);
2677     pmr->fPlayer = ms.fTurn;
2678     memcpy(pmr->n.anMove, an, sizeof pmr->n.anMove);
2679 
2680     if (fTutor && fTutorChequer) {
2681         moverecord *pmr_cur = get_current_moverecord(NULL);
2682         g_assert(pmr_cur);
2683         /* update or set the move */
2684         memcpy(pmr_cur->n.anMove, an, sizeof an);
2685         hint_move("", FALSE, NULL);
2686         if (!GiveAdvice(pmr_cur->n.stMove)) {
2687             free(pmr);
2688             return;
2689         }
2690     }
2691 #if defined (USE_GTK)
2692     /* There's no point delaying here. */
2693     if (nTimeout) {
2694         g_source_remove(nTimeout);
2695         nTimeout = 0;
2696     }
2697 
2698     if (fX) {
2699         outputnew();
2700         ShowAutoMove(msBoard(), pmr->n.anMove);
2701         outputx();
2702     }
2703 #endif
2704 
2705     AddMoveRecord(pmr);
2706 
2707 #if defined (USE_GTK)
2708     /* Don't animate this move. */
2709     fLastMove = FALSE;
2710 #endif
2711     TurnDone();
2712 
2713     return;
2714 }
2715 
2716 static void
StartNewGame(void)2717 StartNewGame(void)
2718 {
2719     fComputing = TRUE;
2720     NewGame();
2721     if (!fInterrupt) {
2722         if (ap[ms.fTurn].pt == PLAYER_HUMAN)
2723             ShowBoard();
2724         else if (!ComputerTurn())
2725             TurnDone();
2726     }
2727     fComputing = FALSE;
2728 }
2729 
2730 extern void
CommandNewGame(char * UNUSED (sz))2731 CommandNewGame(char *UNUSED(sz))
2732 {
2733     if (ms.nMatchTo && (ms.anScore[0] >= ms.nMatchTo || ms.anScore[1] >= ms.nMatchTo)) {
2734         outputl(_("The match is already over."));
2735         return;
2736     }
2737 
2738     if (ms.gs == GAME_PLAYING || !move_is_last_in_match()) {
2739         if (fConfirmNew) {
2740             if (fInterrupt)
2741                 return;
2742 
2743             if (!GetInputYN(_("Are you sure you want to start a new game, " "and discard the rest of the match? ")))
2744                 return;
2745         }
2746 
2747         PopGame(plGame, TRUE);
2748     }
2749 
2750     StartAutomaticPlay();
2751 
2752     StartNewGame();
2753 }
2754 
2755 extern void
ClearMatch(void)2756 ClearMatch(void)
2757 {
2758     char ***pppch;
2759     static char **appch[] = { &mi.pchRating[0], &mi.pchRating[1],
2760         &mi.pchEvent, &mi.pchRound, &mi.pchPlace,
2761         &mi.pchAnnotator, &mi.pchComment, NULL
2762     };
2763 
2764     ms.nMatchTo = 0;
2765 
2766     ms.cGames = ms.anScore[0] = ms.anScore[1] = 0;
2767     ms.fMove = ms.fTurn = -1;
2768     ms.fCrawford = FALSE;
2769     ms.fPostCrawford = FALSE;
2770     fCrawfordState = -1;
2771     ms.gs = GAME_NONE;
2772     ms.fJacoby = fJacoby;
2773 
2774     IniStatcontext(&scMatch);
2775 
2776     for (pppch = appch; *pppch; pppch++)
2777         if (**pppch) {
2778             g_free(**pppch);
2779             **pppch = NULL;
2780         }
2781 
2782     mi.nYear = 0;
2783 }
2784 
2785 extern void
FreeMatch(void)2786 FreeMatch(void)
2787 {
2788     PopGame(lMatch.plNext->p, TRUE);
2789     IniStatcontext(&scMatch);
2790 }
2791 
2792 extern void
SetMatchDate(matchinfo * pmi)2793 SetMatchDate(matchinfo * pmi)
2794 {
2795     time_t t = time(NULL);
2796     struct tm *ptm = localtime(&t);
2797 
2798     pmi->nYear = ptm->tm_year + 1900;;
2799     pmi->nMonth = ptm->tm_mon + 1;
2800     pmi->nDay = ptm->tm_mday;
2801 }
2802 
2803 extern void
CommandNewMatch(char * sz)2804 CommandNewMatch(char *sz)
2805 {
2806     unsigned int n;
2807 
2808     if (!sz || !*sz)
2809         n = nDefaultLength;
2810     else
2811         n = ParseNumber(&sz);
2812 
2813     if (n == 0) {
2814         CommandNewSession(NULL);
2815         return;
2816     }
2817 
2818     /* Check that match equity table is large enough */
2819 
2820     if (n > MAXSCORE) {
2821         outputf(_("GNU Backgammon is compiled with support only for "
2822                   "matches of length %i\n" "and below\n"), MAXSCORE);
2823         return;
2824     }
2825 
2826     if (!get_input_discard())
2827         return;
2828 
2829 #if defined (USE_GTK)
2830     if (fX)
2831         GTKClearMoveRecord();
2832 #endif
2833 
2834     FreeMatch();
2835     ClearMatch();
2836 
2837     strcpy(ap[0].szName, default_names[0]);
2838     strcpy(ap[1].szName, default_names[1]);
2839     plLastMove = NULL;
2840 
2841     ms.nMatchTo = n;
2842     ms.bgv = bgvDefault;
2843     ms.fCubeUse = fCubeUse;
2844     ms.fJacoby = fJacoby;
2845 
2846     SetMatchDate(&mi);
2847 
2848     UpdateSetting(&ms.nMatchTo);
2849     UpdateSetting(&ms.fTurn);
2850     UpdateSetting(&ms.fCrawford);
2851     UpdateSetting(&ms.fJacoby);
2852     UpdateSetting(&ms.gs);
2853 
2854     outputf(_("A new %u point match has been started.\n"), n);
2855 
2856 #if defined (USE_GTK)
2857     if (fX)
2858         GTKSet(ap);
2859 #endif
2860 
2861     CommandNewGame(NULL);
2862 }
2863 
2864 extern void
CommandNewSession(char * UNUSED (sz))2865 CommandNewSession(char *UNUSED(sz))
2866 {
2867     if (!get_input_discard())
2868         return;
2869 
2870 #if defined (USE_GTK)
2871     if (fX)
2872         GTKClearMoveRecord();
2873 #endif
2874 
2875     FreeMatch();
2876     ClearMatch();
2877 
2878     ms.bgv = bgvDefault;
2879     ms.fCubeUse = fCubeUse;
2880     ms.fJacoby = fJacoby;
2881     plLastMove = NULL;
2882 
2883     SetMatchDate(&mi);
2884 
2885     UpdateSetting(&ms.nMatchTo);
2886     UpdateSetting(&ms.fTurn);
2887     UpdateSetting(&ms.fCrawford);
2888     UpdateSetting(&ms.fJacoby);
2889     UpdateSetting(&ms.gs);
2890 
2891     outputl(_("A new session has been started."));
2892 
2893 #if defined (USE_GTK)
2894     if (fX)
2895         ShowBoard();
2896 #endif
2897 
2898     CommandNewGame(NULL);
2899 }
2900 
2901 extern void
UpdateGame(int fShowBoard)2902 UpdateGame(int fShowBoard)
2903 {
2904 
2905     UpdateSetting(&ms.nCube);
2906     UpdateSetting(&ms.fCubeOwner);
2907     UpdateSetting(&ms.fTurn);
2908     UpdateSetting(&ms.gs);
2909     UpdateSetting(&ms.fCrawford);
2910 
2911 #if defined (USE_GTK)
2912     if (fX || (fShowBoard && fDisplay))
2913         ShowBoard();
2914 #else
2915     if (fShowBoard && fDisplay)
2916         ShowBoard();
2917 #endif
2918 }
2919 
2920 #if defined (USE_GTK)
2921 static int
GameIndex(listOLD * plGame)2922 GameIndex(listOLD * plGame)
2923 {
2924 
2925     listOLD *pl;
2926     int i;
2927 
2928     for (i = 0, pl = lMatch.plNext; pl->p != plGame && pl != &lMatch; pl = pl->plNext, i++);
2929 
2930     if (pl == &lMatch)
2931         return -1;
2932     else
2933         return i;
2934 }
2935 #endif
2936 
2937 extern void
ChangeGame(listOLD * plGameNew)2938 ChangeGame(listOLD * plGameNew)
2939 {
2940     moverecord *pmr_cur;
2941     gboolean dice_rolled = FALSE;
2942     movetype reallastmt;
2943     int reallastplayer;
2944 
2945 #if defined (USE_GTK)
2946     listOLD *pl;
2947 #endif
2948 
2949     if (!plGame) {
2950         return;
2951     }
2952 
2953     if (plGameNew) {
2954         plGame = plGameNew;
2955         plLastMove = plGame->plNext;
2956     } else {
2957         if (ms.anDice[0] > 0)
2958             dice_rolled = TRUE;
2959     }
2960 
2961 #if defined (USE_GTK)
2962     if (fX) {
2963         GTKFreeze();
2964         GTKClearMoveRecord();
2965 
2966         for (pl = plGame->plNext; pl->p; pl = pl->plNext) {
2967             GTKAddMoveRecord(pl->p);
2968             FixMatchState(&ms, pl->p);
2969             ApplyMoveRecord(&ms, plGame, pl->p);
2970         }
2971         GTKSetGame(GameIndex(plGame));
2972         /* we need to get the matchstate right before GTKThaw when
2973          * we are updating the current current game */
2974         CalculateBoard();
2975         GTKThaw();
2976     }
2977 #endif
2978     CalculateBoard();
2979     SetMoveRecord(plLastMove->p);
2980 
2981     /* The real last move, before get_current_moverecord()
2982      * possibly adds an empty hint record. If it does and
2983      * last move is a MOVE_SETxxx record, its fPlayer will
2984      * be bogus and we will have to fix it */
2985 
2986     if ((pmr_cur = plLastMove->p)) {
2987         reallastmt = pmr_cur->mt;
2988         reallastplayer = pmr_cur->fPlayer;
2989     } else {
2990         reallastmt = (movetype) - 1;
2991         reallastplayer = -1;
2992     }
2993 
2994     pmr_cur = get_current_moverecord(NULL);
2995 
2996     if (pmr_cur) {
2997         if (reallastmt >= MOVE_SETBOARD && pmr_cur->ml.cMoves == 0)
2998             pmr_cur->fPlayer = reallastplayer;
2999         if (pmr_cur->fPlayer != ms.fTurn) {
3000             SetTurn(pmr_cur->fPlayer);
3001         }
3002         if (dice_rolled) {
3003             ms.anDice[0] = pmr_cur->anDice[0];
3004             ms.anDice[1] = pmr_cur->anDice[1];
3005         }
3006     }
3007     UpdateGame(FALSE);
3008     ShowBoard();
3009 }
3010 
3011 static void
CommandNextGame(char * sz)3012 CommandNextGame(char *sz)
3013 {
3014 
3015     int n;
3016     char *pch;
3017     listOLD *pl;
3018 
3019     if ((pch = NextToken(&sz)))
3020         n = ParseNumber(&pch);
3021     else
3022         n = 1;
3023 
3024     if (n < 1) {
3025         outputl(_("If you specify a parameter to the `next game' command, it "
3026                   "must be a positive number (the count of games to step " "ahead)."));
3027         return;
3028     }
3029 
3030     for (pl = lMatch.plNext; pl->p != plGame && pl != &lMatch; pl = pl->plNext);
3031 
3032     if (pl->p != plGame)
3033         /* current game not found */
3034         return;
3035 
3036     for (; n && pl->plNext->p; n--, pl = pl->plNext);
3037 
3038     if (pl->p == plGame)
3039         return;
3040 
3041     ChangeGame(pl->p);
3042 }
3043 
3044 extern void
CommandFirstMove(char * UNUSED (sz))3045 CommandFirstMove(char *UNUSED(sz))
3046 {
3047     ChangeGame(plGame);
3048 }
3049 
3050 extern void
CommandFirstGame(char * UNUSED (sz))3051 CommandFirstGame(char *UNUSED(sz))
3052 {
3053     if (lMatch.plNext->p)       /* Match not empty */
3054         ChangeGame(lMatch.plNext->p);
3055 }
3056 
3057 static void
CommandNextRoll(char * UNUSED (sz))3058 CommandNextRoll(char *UNUSED(sz))
3059 {
3060     moverecord *pmr;
3061 
3062 #if defined (USE_GTK)
3063     BoardData *bd = NULL;
3064 
3065     if (fX) {
3066         bd = BOARD(pwBoard)->board_data;
3067         /* Make sure dice aren't rolled */
3068         bd->diceShown = DICE_ON_BOARD;
3069     }
3070 #endif
3071 
3072     if (!plLastMove || !plLastMove->plNext || !(pmr = plLastMove->plNext->p))
3073         /* silently ignore */
3074         return;
3075 
3076     if (pmr->mt != MOVE_NORMAL || ms.anDice[0])
3077         /* to skip over the "dice roll" for anything other than a normal
3078          * move, or if the dice are already rolled, just skip the entire
3079          * move */
3080     {
3081         CommandNext(NULL);
3082         return;
3083     }
3084 
3085     CalculateBoard();
3086 
3087     ms.gs = GAME_PLAYING;
3088     ms.fMove = ms.fTurn = pmr->fPlayer;
3089 
3090     ms.anDice[0] = pmr->anDice[0];
3091     ms.anDice[1] = pmr->anDice[1];
3092 
3093 #if defined (USE_GTK)
3094     /* Make sure dice are shown */
3095     if (fX) {
3096         bd->diceRoll[0] = !ms.anDice[0];
3097     }
3098 #endif
3099 
3100     if (plLastMove->plNext && plLastMove->plNext->p)
3101         FixMatchState(&ms, plLastMove->plNext->p);
3102 
3103     UpdateGame(FALSE);
3104 }
3105 
3106 static void
CommandNextRolled(char * UNUSED (sz))3107 CommandNextRolled(char *UNUSED(sz))
3108 {
3109     moverecord *pmr;
3110 
3111     /* goto next move */
3112 
3113     CommandNext(NULL);
3114 
3115     if (plLastMove && plLastMove->plNext && (pmr = plLastMove->plNext->p) && pmr->mt == MOVE_NORMAL)
3116         CommandNextRoll(NULL);
3117 }
3118 
3119 static int
MoveIsCMarked(moverecord * pmr)3120 MoveIsCMarked(moverecord * pmr)
3121 {
3122     unsigned int j;
3123 
3124     if (pmr == 0)
3125         return FALSE;
3126 
3127     switch (pmr->mt) {
3128     case MOVE_NORMAL:
3129         if (pmr->CubeDecPtr->cmark != CMARK_NONE)
3130             return TRUE;
3131         for (j = 0; j < pmr->ml.cMoves; j++) {
3132             if (pmr->ml.amMoves[j].cmark != CMARK_NONE)
3133                 return TRUE;
3134         }
3135         break;
3136     case MOVE_DOUBLE:
3137         if (pmr->CubeDecPtr->cmark != CMARK_NONE)
3138             return TRUE;
3139         break;
3140 
3141     default:
3142         break;
3143     }
3144 
3145     return FALSE;
3146 }
3147 
3148 static int
MoveIsMarked(moverecord * pmr)3149 MoveIsMarked(moverecord * pmr)
3150 {
3151     if (pmr == 0)
3152         return FALSE;
3153 
3154     switch (pmr->mt) {
3155     case MOVE_NORMAL:
3156         return badSkill(pmr->n.stMove) || badSkill(pmr->stCube);
3157 
3158     case MOVE_DOUBLE:
3159     case MOVE_TAKE:
3160     case MOVE_DROP:
3161         return badSkill(pmr->stCube);
3162 
3163     default:
3164         break;
3165     }
3166 
3167     return FALSE;
3168 }
3169 
3170 static void
ShowMark(moverecord * pmr)3171 ShowMark(moverecord * pmr)
3172 {
3173 
3174 #if defined (USE_GTK)
3175     BoardData *bd = NULL;
3176 
3177     if (fX)
3178         bd = BOARD(pwBoard)->board_data;
3179 #endif
3180     /* Show the dice roll, if the chequer play is marked but the cube
3181      * decision is not. */
3182     if (pmr->mt == MOVE_NORMAL && (pmr->stCube == SKILL_NONE) && pmr->n.stMove != SKILL_NONE) {
3183         ms.gs = GAME_PLAYING;
3184         ms.fMove = ms.fTurn = pmr->fPlayer;
3185 
3186         ms.anDice[0] = pmr->anDice[0];
3187         ms.anDice[1] = pmr->anDice[1];
3188     }
3189 #if defined (USE_GTK)
3190     if (fX) {
3191         /* Don't roll dice */
3192         bd->diceShown = DICE_ON_BOARD;
3193         /* Make sure dice are shown */
3194         bd->diceRoll[0] = !ms.anDice[0];
3195     }
3196 #endif
3197 
3198 }
3199 
3200 extern int
InternalCommandNext(int mark,int cmark,int n)3201 InternalCommandNext(int mark, int cmark, int n)
3202 {
3203     int done = 0;
3204 
3205     if (mark || cmark) {
3206         listOLD *pgame;
3207         moverecord *pmr = NULL;
3208         listOLD *p = plLastMove->plNext;
3209 
3210         for (pgame = lMatch.plNext; pgame->p != plGame && pgame != &lMatch; pgame = pgame->plNext);
3211         if (pgame->p != plGame)
3212             /* current game not found */
3213             return 0;
3214 
3215         /* we need to increment the count if we're pointing to a marked move */
3216         if (p->p && (mark && MoveIsMarked((moverecord *) p->p)))
3217             ++n;
3218         if (p->p && (cmark && MoveIsCMarked((moverecord *) p->p)))
3219             ++n;
3220 
3221         while (p->p) {
3222             pmr = (moverecord *) p->p;
3223             if (mark && MoveIsMarked(pmr) && (--n <= 0))
3224                 break;
3225             if (cmark && MoveIsCMarked(pmr) && (--n <= 0))
3226                 break;
3227             p = p->plNext;
3228             if (!(p->p)) {
3229                 if (pgame->plNext == pgame)
3230                     return 0;
3231                 pgame = pgame->plNext;
3232                 if (!(pgame->p))
3233                     return 0;
3234                 p = ((listOLD *) pgame->p)->plNext;
3235             }
3236         }
3237 
3238         if (p->p == 0)
3239             return 0;
3240 
3241         g_assert(pmr);
3242 
3243         if (pgame->p != plGame)
3244             ChangeGame(pgame->p);
3245 
3246         plLastMove = p->plPrev;
3247         CalculateBoard();
3248         ShowMark(pmr);
3249     } else {
3250         while (n-- && plLastMove->plNext->p) {
3251             plLastMove = plLastMove->plNext;
3252             FixMatchState(&ms, plLastMove->p);
3253             ApplyMoveRecord(&ms, plGame, plLastMove->p);
3254 
3255             ++done;
3256         }
3257     }
3258 
3259     UpdateGame(FALSE);
3260 
3261     if (plLastMove->plNext && plLastMove->plNext->p)
3262         FixMatchState(&ms, plLastMove->plNext->p);
3263 
3264     SetMoveRecord(plLastMove->p);
3265 
3266     return done;
3267 }
3268 
3269 extern void
CommandNext(char * sz)3270 CommandNext(char *sz)
3271 {
3272     int n;
3273     char *pch;
3274     int mark = FALSE;
3275     int cmark = FALSE;
3276 
3277     if (!plGame) {
3278         outputl(_("No game in progress (type `new game' to start one)."));
3279         return;
3280     }
3281 #if defined(USE_BOARD3D)
3282     RestrictiveRedraw();
3283 #endif
3284     n = 1;
3285 
3286     if ((pch = NextToken(&sz))) {
3287         if (!StrCaseCmp(pch, "game")) {
3288             CommandNextGame(sz);
3289             return;
3290         } else if (!StrCaseCmp(pch, "roll")) {
3291             CommandNextRoll(sz);
3292             return;
3293         } else if (!StrCaseCmp(pch, "rolled")) {
3294             CommandNextRolled(sz);
3295             return;
3296         } else if (!StrCaseCmp(pch, "marked")) {
3297             mark = TRUE;
3298             if ((pch = NextToken(&sz))) {
3299                 n = ParseNumber(&pch);
3300             }
3301         } else if (!StrCaseCmp(pch, "cmarked")) {
3302             cmark = TRUE;
3303             if ((pch = NextToken(&sz))) {
3304                 n = ParseNumber(&pch);
3305             }
3306         } else if (!StrCaseCmp(pch, "anymarked")) {
3307             mark = TRUE;
3308             cmark = TRUE;
3309             if ((pch = NextToken(&sz))) {
3310                 n = ParseNumber(&pch);
3311             }
3312         } else
3313             n = ParseNumber(&pch);
3314     }
3315 
3316     if (n < 1) {
3317         outputl(_("If you specify a parameter to the `next' command, it must "
3318                   "be a positive number (the count of moves to step ahead)."));
3319         return;
3320     }
3321 
3322     InternalCommandNext(mark, cmark, n);
3323 }
3324 
3325 extern void
CommandEndGame(char * UNUSED (sz))3326 CommandEndGame(char *UNUSED(sz))
3327 {
3328     playertype pt_store[2] = { ap[0].pt, ap[1].pt };
3329     int fAutoGame_store = fAutoGame;
3330     int fDisplay_store = fDisplay;
3331     int fQuiet_store = fQuiet;
3332     const evalcontext ec_quick = { FALSE, 0, FALSE, TRUE, 0.0 };
3333     int manual_dice = (rngCurrent == RNG_MANUAL);
3334     evalcontext ec_cheq_store[2];
3335     evalcontext ec_cube_store[2];
3336 #if defined(USE_BOARD3D)
3337     BoardData *bd = NULL;
3338     if (fX && pwBoard)
3339         bd = BOARD(pwBoard)->board_data;
3340 #endif
3341     ec_cheq_store[0] = ap[0].esChequer.ec;
3342     ec_cheq_store[1] = ap[1].esChequer.ec;
3343     ec_cube_store[0] = ap[0].esCube.ec;
3344     ec_cube_store[1] = ap[1].esCube.ec;
3345 
3346     if (ms.gs != GAME_PLAYING) {
3347         outputl(_("No game in progress (type `new game' to start one)."));
3348         return;
3349     }
3350 
3351     if (!move_not_last_in_match_ok())
3352         return;
3353 
3354 #if defined (USE_GTK)
3355     else if (!GTKShowWarning(WARN_ENDGAME, NULL))
3356         return;
3357 #endif
3358     ap[0].pt = PLAYER_GNU;
3359     ap[1].pt = PLAYER_GNU;
3360     ap[0].esChequer.ec = ec_quick;
3361     ap[1].esChequer.ec = ec_quick;
3362     ap[0].esCube.ec = ec_quick;
3363     ap[1].esCube.ec = ec_quick;
3364 
3365     if (manual_dice) {
3366         outputoff();
3367         SetRNG(&rngCurrent, rngctxCurrent, RNG_MERSENNE, "");
3368         outputon();
3369     }
3370 #if defined(USE_BOARD3D)
3371     if (fX && bd)
3372         SuspendDiceRolling(bd->rd);
3373 #endif
3374 
3375     fAutoGame = FALSE;
3376     fQuiet = TRUE;
3377     fDisplay = FALSE;
3378     fInterrupt = FALSE;
3379     fEndGame = TRUE;
3380     outputnew();
3381 
3382     do {
3383 #if defined (USE_GTK)
3384         UserCommand("play");
3385         while (nNextTurn && automaticTask)
3386             NextTurnNotify(NULL);
3387 #else
3388         {
3389             char *line = g_strdup("play");
3390             HandleCommand(line, acTop);
3391             g_free(line);
3392             while (fNextTurn)
3393                 NextTurn(TRUE);
3394         }
3395 #endif
3396     } while (ms.gs == GAME_PLAYING && automaticTask);
3397 
3398     outputx();
3399     ap[0].pt = pt_store[0];
3400     ap[1].pt = pt_store[1];
3401     ap[0].esChequer.ec = ec_cheq_store[0];
3402     ap[1].esChequer.ec = ec_cheq_store[1];
3403     ap[0].esCube.ec = ec_cube_store[0];
3404     ap[1].esCube.ec = ec_cube_store[1];
3405 
3406     fAutoGame = fAutoGame_store;
3407     fDisplay = fDisplay_store;
3408     fQuiet = fQuiet_store;
3409     fEndGame = FALSE;
3410 
3411     /* If the game ended in a resign then make sure the
3412      * state for the user truly reflects the game is over
3413      * and that no further action from them is necessary */
3414     if (ms.fResigned) {
3415         ms.fResigned = 0;
3416         ShowBoard();
3417     }
3418 
3419     if (manual_dice) {
3420         outputoff();
3421         SetRNG(&rngCurrent, rngctxCurrent, RNG_MANUAL, "");
3422         outputon();
3423     }
3424 #if defined(USE_BOARD3D)
3425     if (fX && bd)
3426         ResumeDiceRolling(bd->rd);
3427 #endif
3428 
3429     if (!automaticTask)
3430         return;
3431     StopAutomaticPlay();
3432 
3433     if (fAutoGame && (!ms.nMatchTo || (ms.anScore[0] < ms.nMatchTo && ms.anScore[1] < ms.nMatchTo)))
3434         StartNewGame();
3435 }
3436 
3437 extern void
CommandPlay(char * UNUSED (sz))3438 CommandPlay(char *UNUSED(sz))
3439 {
3440     if (ms.gs != GAME_PLAYING) {
3441         outputl(_("No game in progress (type `new game' to start one)."));
3442         return;
3443     }
3444 
3445     if (ap[ms.fTurn].pt == PLAYER_HUMAN) {
3446         outputl(_("It's not the computer's turn to play."));
3447         return;
3448     }
3449 
3450     StartAutomaticPlay();
3451 
3452     fComputing = TRUE;
3453 
3454     if (!ComputerTurn())
3455         TurnDone();
3456 
3457     fComputing = FALSE;
3458 #if defined (USE_GTK)
3459     NextTurnNotify(NULL);
3460 #endif
3461 }
3462 
3463 static void
CommandPreviousGame(char * sz)3464 CommandPreviousGame(char *sz)
3465 {
3466 
3467     int n;
3468     char *pch;
3469     listOLD *pl;
3470 
3471     if ((pch = NextToken(&sz)))
3472         n = ParseNumber(&pch);
3473     else
3474         n = 1;
3475 
3476     if (n < 1) {
3477         outputl(_("If you specify a parameter to the `previous game' command, "
3478                   "it must be a positive number (the count of games to step " "back)."));
3479         return;
3480     }
3481 
3482     for (pl = lMatch.plNext; pl->p != plGame && pl != &lMatch; pl = pl->plNext);
3483 
3484     if (pl->p != plGame)
3485         /* current game not found */
3486         return;
3487 
3488     for (; n && pl->plPrev->p; n--, pl = pl->plPrev);
3489 
3490     if (pl->p == plGame)
3491         return;
3492 
3493     ChangeGame(pl->p);
3494 }
3495 
3496 static void
CommandPreviousRoll(char * UNUSED (sz))3497 CommandPreviousRoll(char *UNUSED(sz))
3498 {
3499     moverecord *pmr;
3500 
3501     if (!plLastMove || !plLastMove->p)
3502         /* silently ignore */
3503         return;
3504     pmr = plLastMove->plNext->p;
3505     if (!ms.anDice[0]) {
3506         /* if the dice haven't been rolled skip the entire move */
3507         CommandPrevious(NULL);
3508 
3509         g_assert(plLastMove->plNext);
3510 
3511         if (plLastMove->plNext && plLastMove->plNext->p != pmr) {
3512             pmr = plLastMove->plNext->p;
3513             if (pmr && (pmr->mt == MOVE_NORMAL || pmr->mt == MOVE_GAMEINFO))
3514                 /* We've stepped back a whole move; now we need to recover the
3515                  * previous dice roll. */
3516                 CommandNextRoll(NULL);
3517         }
3518 
3519     } else {
3520 
3521         CalculateBoard();
3522 
3523         ms.anDice[0] = 0;
3524         ms.anDice[1] = 0;
3525 #if defined (USE_GTK)
3526         if (fX) {
3527             BoardData *bd = BOARD(pwBoard)->board_data;
3528             bd->diceRoll[0] = bd->diceRoll[1] = 0;
3529         }
3530 #endif
3531 
3532         if (pmr)
3533             FixMatchState(&ms, pmr);
3534 
3535         UpdateGame(FALSE);
3536     }
3537 }
3538 
3539 static void
CommandPreviousRolled(char * UNUSED (sz))3540 CommandPreviousRolled(char *UNUSED(sz))
3541 {
3542     moverecord *pmr;
3543 
3544     if (!plLastMove || !plLastMove->p)
3545         /* silently ignore */
3546         return;
3547 
3548     /* step back and boogie */
3549 
3550     CommandPrevious(NULL);
3551 
3552     if (plLastMove && plLastMove->plNext && (pmr = plLastMove->plNext->p) && pmr->mt == MOVE_NORMAL)
3553         CommandNextRoll(NULL);
3554 
3555 }
3556 
3557 extern void
CommandPrevious(char * sz)3558 CommandPrevious(char *sz)
3559 {
3560 
3561     int n;
3562     char *pch;
3563     int mark = FALSE;
3564     int cmark = FALSE;
3565     listOLD *p;
3566     moverecord *pmr = NULL;
3567 
3568     if (!plGame) {
3569         outputl(_("No game in progress (type `new game' to start one)."));
3570         return;
3571     }
3572 #if defined(USE_BOARD3D)
3573     RestrictiveRedraw();
3574 #endif
3575 
3576     n = 1;
3577 
3578     if ((pch = NextToken(&sz))) {
3579         if (!StrCaseCmp(pch, "game")) {
3580             CommandPreviousGame(sz);
3581             return;
3582         } else if (!StrCaseCmp(pch, "rolled")) {
3583             CommandPreviousRolled(sz);
3584             return;
3585         } else if (!StrCaseCmp(pch, "roll")) {
3586             CommandPreviousRoll(sz);
3587             return;
3588         } else if (!StrCaseCmp(pch, "marked")) {
3589             mark = TRUE;
3590             if ((pch = NextToken(&sz))) {
3591                 n = ParseNumber(&pch);
3592             }
3593         } else if (!StrCaseCmp(pch, "cmarked")) {
3594             cmark = TRUE;
3595             if ((pch = NextToken(&sz))) {
3596                 n = ParseNumber(&pch);
3597             }
3598         } else if (!StrCaseCmp(pch, "anymarked")) {
3599             mark = TRUE;
3600             cmark = TRUE;
3601             if ((pch = NextToken(&sz))) {
3602                 n = ParseNumber(&pch);
3603             }
3604         } else
3605             n = ParseNumber(&pch);
3606     }
3607 
3608     if (n < 1) {
3609         outputl(_("If you specify a parameter to the `previous' command, it "
3610                   "must be a positive number (the count of moves to step " "back)."));
3611         return;
3612     }
3613 
3614     if (mark || cmark) {
3615         listOLD *pgame;
3616         p = plLastMove;
3617 
3618         for (pgame = lMatch.plNext; pgame->p != plGame && pgame != &lMatch; pgame = pgame->plNext);
3619 
3620         if (pgame->p != plGame)
3621             return;
3622 
3623         while ((p->p) != 0) {
3624             pmr = (moverecord *) p->p;
3625             if (mark && MoveIsMarked(pmr) && (--n <= 0))
3626                 break;
3627             if (cmark && MoveIsCMarked(pmr) && (--n <= 0))
3628                 break;
3629 
3630             p = p->plPrev;
3631             if (!(p->p)) {
3632                 if (pgame->plPrev == pgame)
3633                     return;
3634                 pgame = pgame->plPrev;
3635                 if (!(pgame->p))
3636                     return;
3637                 p = ((listOLD *) pgame->p)->plNext;
3638                 while (p->plNext->p)
3639                     p = p->plNext;
3640                 ;
3641             }
3642         }
3643 
3644         if (p->p == 0)
3645             return;
3646 
3647         if (pgame->p != plGame)
3648             ChangeGame(pgame->p);
3649 
3650         plLastMove = p->plPrev;
3651     } else {
3652         while (n-- && plLastMove->plPrev->p)
3653             plLastMove = plLastMove->plPrev;
3654     }
3655 
3656     CalculateBoard();
3657 
3658     if (pmr)
3659         ShowMark(pmr);
3660 
3661     UpdateGame(FALSE);
3662 
3663     if (plLastMove->plNext && plLastMove->plNext->p)
3664         FixMatchState(&ms, plLastMove->plNext->p);
3665 
3666     SetMoveRecord(plLastMove->p);
3667 }
3668 
3669 extern void
CommandRedouble(char * UNUSED (sz))3670 CommandRedouble(char *UNUSED(sz))
3671 {
3672     moverecord *pmr;
3673 
3674     if (ms.nMatchTo > 0) {
3675         outputl(_("Beavers and Raccoons are not permitted during match play."));
3676 
3677         return;
3678     }
3679 
3680     if (!nBeavers) {
3681         outputl(_("Beavers are disabled (see `help set beavers')."));
3682 
3683         return;
3684     }
3685 
3686     if (ms.cBeavers >= nBeavers) {
3687         if (nBeavers == 1)
3688             outputl(_("Only one beaver is permitted (see `help set " "beavers')."));
3689         else
3690             outputf(_("Only %u beavers are permitted (see `help set " "beavers').\n"), nBeavers);
3691 
3692         return;
3693     }
3694 
3695     if (ms.gs != GAME_PLAYING || !ms.fDoubled) {
3696         outputl(_("The cube must have been offered before you can redouble " "it."));
3697 
3698         return;
3699     }
3700 
3701     if (ap[ms.fTurn].pt != PLAYER_HUMAN && !fComputerDecision) {
3702         outputl(_("It is the computer's turn -- type `play' to force it to " "move immediately."));
3703         return;
3704     }
3705 
3706     if (ms.nCube >= (MAX_CUBE >> 1)) {
3707         outputf(_("The cube is already at its highest supported value ; you can't double any more.\n"));
3708         return;
3709     }
3710 
3711     if (!move_not_last_in_match_ok())
3712         return;
3713 
3714     pmr = NewMoveRecord();
3715 
3716     playSound(SOUND_REDOUBLE);
3717 
3718     if (fDisplay)
3719         outputf(_("%s accepts and immediately redoubles to %d.\n"), ap[ms.fTurn].szName, ms.nCube << 2);
3720 
3721     ms.fCubeOwner = !ms.fMove;
3722     UpdateSetting(&ms.fCubeOwner);
3723 
3724     pmr->mt = MOVE_DOUBLE;
3725     pmr->fPlayer = ms.fTurn;
3726     LinkToDouble(pmr);
3727     AddMoveRecord(pmr);
3728 
3729     TurnDone();
3730 }
3731 
3732 extern void
CommandReject(char * sz)3733 CommandReject(char *sz)
3734 {
3735 
3736     if (ms.fResigned)
3737         CommandDecline(sz);
3738     else if (ms.fDoubled)
3739         CommandDrop(sz);
3740     else
3741         outputl(_("You can only reject if the cube or a resignation has been " "offered."));
3742 }
3743 
3744 extern void
CommandResign(char * sz)3745 CommandResign(char *sz)
3746 {
3747 
3748     char *pch;
3749 
3750     if (ms.gs != GAME_PLAYING) {
3751         outputl(_("You must be playing a game if you want to resign it."));
3752 
3753         return;
3754     }
3755 
3756     if (ap[ms.fTurn].pt != PLAYER_HUMAN && !fComputerDecision) {
3757         outputl(_("It is the computer's turn -- type `play' to force it to " "move immediately."));
3758         return;
3759     }
3760 
3761     /* FIXME cancel cube action?  or refuse resignations while doubled?
3762      * or treat resignations while doubled as drops? */
3763 
3764     if ((pch = NextToken(&sz))) {
3765         const size_t cch = strlen(pch);
3766 
3767         if (!StrNCaseCmp("single", pch, cch) || !StrNCaseCmp("normal", pch, cch))
3768             ms.fResigned = 1;
3769         else if (!StrNCaseCmp("gammon", pch, cch))
3770             ms.fResigned = 2;
3771         else if (!StrNCaseCmp("backgammon", pch, cch))
3772             ms.fResigned = 3;
3773         else
3774             ms.fResigned = atoi(pch);
3775     } else
3776         ms.fResigned = 1;
3777 
3778     if (ms.fResigned != -1 && (ms.fResigned < 1 || ms.fResigned > 3)) {
3779         ms.fResigned = 0;
3780 
3781         outputf(_("Unknown keyword `%s' (see `help resign').\n"), pch);
3782 
3783         return;
3784     }
3785 
3786     if (ms.fResigned >= 0 && ms.fResigned <= ms.fResignationDeclined) {
3787         ms.fResigned = 0;
3788 
3789         outputf(_("%s has already declined your offer of a %s.\n"),
3790                 ap[!ms.fTurn].szName, gettext(aszGameResult[ms.fResignationDeclined - 1]));
3791 
3792         return;
3793     }
3794 
3795     if (fDisplay) {
3796         if (ms.fResigned > 0)
3797             outputf(_("%s offers to resign a %s.\n"), ap[ms.fTurn].szName, gettext(aszGameResult[ms.fResigned - 1]));
3798         else
3799             outputf(_("%s offers to resign.\n"), ap[ms.fTurn].szName);
3800     }
3801 
3802 
3803     ms.fTurn = !ms.fTurn;
3804     playSound(SOUND_RESIGN);
3805 
3806     TurnDone();
3807 }
3808 
3809 /* evaluate wisdom of not having doubled/redoubled */
3810 
3811 extern void
CommandRoll(char * UNUSED (sz))3812 CommandRoll(char *UNUSED(sz))
3813 {
3814     movelist ml;
3815     moverecord *pmr;
3816 
3817     if (ms.gs != GAME_PLAYING) {
3818         outputl(_("No game in progress (type `new game' to start one)."));
3819 
3820         return;
3821     }
3822 
3823     if (ap[ms.fTurn].pt != PLAYER_HUMAN) {
3824         outputl(_("It is the computer's turn -- type `play' to force it to " "move immediately."));
3825         return;
3826     }
3827 
3828     if (ms.fDoubled) {
3829         outputf(_("Please wait for %s to consider the cube before " "moving.\n"), ap[ms.fTurn].szName);
3830 
3831         return;
3832     }
3833 
3834     if (ms.fResigned) {
3835         outputl(_("Please resolve the resignation first."));
3836 
3837         return;
3838     }
3839 
3840     if (ms.anDice[0]) {
3841         outputl(_("You have already rolled the dice."));
3842 
3843         return;
3844     }
3845 
3846     if (!move_not_last_in_match_ok())
3847         return;
3848 
3849     if (fTutor && fTutorCube && !GiveAdvice(tutor_double(FALSE)))
3850         return;
3851 
3852     if (!fCheat || CheatDice(ms.anDice, &ms, afCheatRoll[ms.fMove]))
3853         if (RollDice(ms.anDice, &rngCurrent, rngctxCurrent) < 0)
3854             return;
3855 
3856     pmr = NewMoveRecord();
3857 
3858     pmr->mt = MOVE_SETDICE;
3859     pmr->anDice[0] = MAX(ms.anDice[0], ms.anDice[1]);
3860     pmr->anDice[1] = MIN(ms.anDice[0], ms.anDice[1]);
3861     pmr->fPlayer = ms.fTurn;
3862 
3863     AddMoveRecord(pmr);
3864 
3865     DiceRolled();
3866 
3867 #if defined (USE_GTK)
3868     if (fX) {
3869 
3870         outputnew();
3871         outputf(_("%s rolls %1u and %1u.\n"), ap[ms.fTurn].szName, ms.anDice[0], ms.anDice[1]);
3872         outputx();
3873 
3874     }
3875 #endif
3876 
3877     ResetDelayTimer();
3878 
3879     if (!GenerateMoves(&ml, msBoard(), ms.anDice[0], ms.anDice[1], FALSE)) {
3880 
3881         playSound(ap[ms.fTurn].pt == PLAYER_HUMAN ? SOUND_HUMAN_DANCE : SOUND_BOT_DANCE);
3882 
3883         pmr = NewMoveRecord();
3884 
3885         pmr->mt = MOVE_NORMAL;
3886         pmr->anDice[0] = MAX(ms.anDice[0], ms.anDice[1]);
3887         pmr->anDice[1] = MIN(ms.anDice[0], ms.anDice[1]);
3888         pmr->fPlayer = ms.fTurn;
3889 
3890         ShowAutoMove(msBoard(), pmr->n.anMove);
3891 
3892         AddMoveRecord(pmr);
3893 
3894         TurnDone();
3895     } else if (ml.cMoves == 1 && (fAutoMove || (ClassifyPosition(msBoard(), ms.bgv)
3896                                                 <= CLASS_BEAROFF1 && fAutoBearoff))) {
3897 
3898         pmr = NewMoveRecord();
3899 
3900         pmr->mt = MOVE_NORMAL;
3901         pmr->anDice[0] = MAX(ms.anDice[0], ms.anDice[1]);
3902         pmr->anDice[1] = MIN(ms.anDice[0], ms.anDice[1]);
3903         pmr->fPlayer = ms.fTurn;
3904         memcpy(pmr->n.anMove, ml.amMoves[0].anMove, sizeof(pmr->n.anMove));
3905 
3906         ShowAutoMove(msBoard(), pmr->n.anMove);
3907 
3908         AddMoveRecord(pmr);
3909         TurnDone();
3910     } else if (fAutoBearoff && !TryBearoff())
3911         TurnDone();
3912 }
3913 
3914 
3915 
3916 extern void
CommandTake(char * UNUSED (sz))3917 CommandTake(char *UNUSED(sz))
3918 {
3919     moverecord *pmr;
3920 
3921     if (ms.gs != GAME_PLAYING || !ms.fDoubled) {
3922         outputl(_("The cube must have been offered before you can take it."));
3923 
3924         return;
3925     }
3926 
3927     if (ap[ms.fTurn].pt != PLAYER_HUMAN && !fComputerDecision) {
3928         outputl(_("It is the computer's turn -- type `play' to force it to " "move immediately."));
3929         return;
3930     }
3931 
3932     if (!move_not_last_in_match_ok())
3933         return;
3934 
3935     playSound(SOUND_TAKE);
3936 
3937     pmr = NewMoveRecord();
3938 
3939     pmr->mt = MOVE_TAKE;
3940     pmr->fPlayer = ms.fTurn;
3941     if (!LinkToDouble(pmr)) {
3942         free(pmr);
3943         return;
3944     }
3945 
3946     pmr->stCube = SKILL_NONE;
3947 
3948     if (fTutor && fTutorCube && !GiveAdvice(tutor_take(TRUE))) {
3949         free(pmr);              /* garbage collect */
3950         return;
3951     }
3952 
3953     if (fDisplay)
3954         outputf(_("%s accepts the cube at %d.\n"), ap[ms.fTurn].szName, ms.nCube << 1);
3955 
3956     AddMoveRecord(pmr);
3957 
3958     UpdateSetting(&ms.nCube);
3959     UpdateSetting(&ms.fCubeOwner);
3960 
3961     TurnDone();
3962 }
3963 
3964 extern void
SetMatchID(const char * szMatchID)3965 SetMatchID(const char *szMatchID)
3966 {
3967 
3968     int anScore[2];
3969     unsigned int anDice[2];
3970     int nMatchTo, fCubeOwner, fMove, fCrawford, nCube;
3971     int fTurn, fDoubled, fResigned, oneAway;
3972     int lfJacoby;               /* for local copy of global jacoby variable */
3973 
3974     gamestate gs;
3975     char szID[15];
3976     moverecord *pmr;
3977 
3978     if (!szMatchID || !*szMatchID)
3979         return;
3980 
3981     if (ms.gs == GAME_PLAYING)
3982         strcpy(szID, PositionID(msBoard()));
3983     else
3984         strcpy(szID, "");
3985 
3986     lfJacoby = fJacoby;
3987     if (MatchFromID(anDice, &fTurn, &fResigned, &fDoubled, &fMove, &fCubeOwner, &fCrawford,
3988                     &nMatchTo, anScore, &nCube, &lfJacoby, &gs, szMatchID) < 0) {
3989         outputf(_("Illegal match ID '%s'\n"), szMatchID);
3990 #if 0
3991         /* debugging details */
3992         outputf(_("Dice %u %u, "), anDice[0], anDice[1]);
3993         outputf(_("player on roll %d (turn %d), "), fMove, fTurn);
3994         outputf(_("resigned %d,\n"), fResigned);
3995         outputf(_("doubled %d, "), fDoubled);
3996         outputf(_("cube owner %d, "), fCubeOwner);
3997         outputf(_("crawford game %d,\n"), fCrawford);
3998         outputf(_("jacoby %d,\n"), lfJacoby);
3999         outputf(_("match length %d, "), nMatchTo);
4000         outputf(_("score %d-%d, "), anScore[0], anScore[1]);
4001         outputf(_("cube %d, "), nCube);
4002         outputf(_("game state %d\n"), (int) gs);
4003 #endif
4004         outputx();
4005         return;
4006     }
4007 
4008     if (fDoubled) {
4009         outputl(_("SetMatchID cannot handle positions where a double has been offered."));
4010         outputf(_("Stepping back to the offering of the cube. "));
4011         fMove = fTurn = !fTurn;
4012         fDoubled = 0;
4013     }
4014 
4015     if (nMatchTo == 1)
4016         fCrawford = 0;
4017 
4018     fCrawfordState = -1;
4019 
4020     /* start new match or session */
4021 
4022 #if defined (USE_GTK)
4023     if (fX)
4024         GTKClearMoveRecord();
4025 #endif
4026 
4027     FreeMatch();
4028 
4029     ms.cGames = 0;
4030     ms.nMatchTo = nMatchTo;
4031     ms.anScore[0] = anScore[0];
4032     ms.anScore[1] = anScore[1];
4033     oneAway = (anScore[0] == nMatchTo - 1) || (anScore[1] == nMatchTo - 1);
4034     ms.fCrawford = fCrawford && oneAway;
4035     ms.fPostCrawford = !fCrawford && oneAway;
4036     ms.bgv = bgvDefault;        /* FIXME: include bgv in match ID */
4037     ms.fCubeUse = fCubeUse;     /* FIXME: include cube use in match ID */
4038     ms.fJacoby = lfJacoby;      /* FIXME: include Jacoby in match ID */
4039 
4040     /* start new game */
4041 
4042     PopGame(plGame, TRUE);
4043 
4044     InitBoard(ms.anBoard, ms.bgv);
4045 
4046     ClearMoveRecord();
4047 
4048     ListInsert(&lMatch, plGame);
4049 
4050     pmr = NewMoveRecord();
4051 
4052     pmr->mt = MOVE_GAMEINFO;
4053 
4054     pmr->g.i = ms.cGames;
4055     pmr->g.nMatch = ms.nMatchTo;
4056     pmr->g.anScore[0] = ms.anScore[0];
4057     pmr->g.anScore[1] = ms.anScore[1];
4058     pmr->g.fCrawford = fAutoCrawford && ms.nMatchTo > 1;
4059     pmr->g.fCrawfordGame = ms.fCrawford;
4060     pmr->g.fJacoby = ms.fJacoby && !ms.nMatchTo;
4061     pmr->g.fWinner = -1;
4062     pmr->g.nPoints = 0;
4063     pmr->g.fResigned = FALSE;
4064     pmr->g.nAutoDoubles = 0;
4065     pmr->g.bgv = ms.bgv;
4066     pmr->g.fCubeUse = ms.fCubeUse;
4067     IniStatcontext(&pmr->g.sc);
4068     AddMoveRecord(pmr);
4069     AddGame(pmr);
4070 
4071     ms.gs = gs;
4072     ms.fMove = fMove;
4073     ms.fTurn = fTurn;
4074     ms.fResigned = fResigned;
4075     ms.fDoubled = fDoubled;
4076     ms.fJacoby = lfJacoby;
4077 
4078     if (anDice[0]) {
4079         char sz[10];
4080         sprintf(sz, "%u %u", anDice[0], anDice[1]);
4081         CommandSetDice(sz);
4082 
4083     }
4084 
4085     if (fCubeOwner != -1) {
4086         pmr = NewMoveRecord();
4087         pmr->mt = MOVE_SETCUBEPOS;
4088         pmr->scp.fCubeOwner = fCubeOwner;
4089         AddMoveRecord(pmr);
4090     }
4091 
4092     if (nCube != 1) {
4093         char sz[10];
4094         sprintf(sz, "%d", nCube);
4095         CommandSetCubeValue(sz);
4096     }
4097 
4098     if (strlen(szID))
4099         CommandSetBoard(szID);
4100 
4101     UpdateSetting(&ms.gs);
4102     UpdateSetting(&ms.nCube);
4103     UpdateSetting(&ms.fCubeOwner);
4104     UpdateSetting(&ms.fTurn);
4105     UpdateSetting(&ms.fCrawford);
4106     UpdateSetting(&ms.fJacoby);
4107 
4108     /* make sure that the hint record has the player on turn */
4109     get_current_moverecord(NULL);
4110 
4111 #if defined(USE_GTK)
4112     if (fX) {
4113         BoardData *bd = BOARD(pwBoard)->board_data;
4114         bd->diceRoll[0] = anDice[0];
4115         bd->diceRoll[1] = anDice[1];
4116     }
4117 #endif
4118 
4119     ShowBoard();
4120 }
4121 
4122 
4123 extern void
FixMatchState(matchstate * pms,const moverecord * pmr)4124 FixMatchState(matchstate * pms, const moverecord * pmr)
4125 {
4126     switch (pmr->mt) {
4127     case MOVE_NORMAL:
4128         if (pms->fTurn != pmr->fPlayer) {
4129             /* previous moverecord is missing */
4130             SwapSides(pms->anBoard);
4131             pms->fMove = pms->fTurn = pmr->fPlayer;
4132         }
4133         break;
4134     case MOVE_DOUBLE:
4135         if (pms->fTurn != pmr->fPlayer) {
4136             /* previous record is missing: this must be an normal double */
4137             SwapSides(pms->anBoard);
4138             pms->fMove = pms->fTurn = pmr->fPlayer;
4139         }
4140         break;
4141     default:
4142         /* no-op */
4143         break;
4144     }
4145 
4146 }
4147 
4148 extern void
pmr_cubedata_set(moverecord * pmr,evalsetup * pes,float output[][NUM_ROLLOUT_OUTPUTS],float stddev[][NUM_ROLLOUT_OUTPUTS])4149 pmr_cubedata_set(moverecord * pmr, evalsetup * pes, float output[][NUM_ROLLOUT_OUTPUTS],
4150                  float stddev[][NUM_ROLLOUT_OUTPUTS])
4151 {
4152     pmr->CubeDecPtr->esDouble = *pes;
4153     pmr->stCube = SKILL_NONE;
4154     memcpy(pmr->CubeDecPtr->aarOutput, output, sizeof(pmr->CubeDecPtr->aarOutput));
4155     memcpy(pmr->CubeDecPtr->aarStdDev, stddev, sizeof(pmr->CubeDecPtr->aarStdDev));
4156 }
4157 
4158 extern void
pmr_movelist_set(moverecord * pmr,evalsetup * pes,movelist * pml)4159 pmr_movelist_set(moverecord * pmr, evalsetup * pes, movelist * pml)
4160 {
4161     float skill_score;
4162     pmr->esChequer = *pes;
4163     pmr->stCube = SKILL_NONE;
4164     pmr->ml = *pml;
4165     pmr->n.iMove = locateMove(msBoard(), pmr->n.anMove, &pmr->ml);
4166     if (pmr->ml.cMoves > 0)
4167         skill_score = pmr->ml.amMoves[pmr->n.iMove].rScore - pmr->ml.amMoves[0].rScore;
4168     else
4169         skill_score = 0.0f;
4170     pmr->n.stMove = Skill(skill_score);
4171 }
4172 
4173 extern moverecord *
get_current_moverecord(int * pfHistory)4174 get_current_moverecord(int *pfHistory)
4175 {
4176     if (plLastMove && plLastMove->plNext && plLastMove->plNext->p) {
4177         if (pfHistory)
4178             *pfHistory = TRUE;
4179         return plLastMove->plNext->p;
4180     }
4181 
4182     if (pfHistory)
4183         *pfHistory = FALSE;
4184 
4185     if (ms.gs != GAME_PLAYING) {
4186         pmr_hint_destroy();
4187         return NULL;
4188     }
4189 
4190     /* invalidate on changed dice */
4191     if (ms.anDice[0] > 0 && pmr_hint && pmr_hint->anDice[0] > 0 && (pmr_hint->anDice[0] != ms.anDice[0]
4192                                                                     || pmr_hint->anDice[1] != ms.anDice[1]))
4193         pmr_hint_destroy();
4194 
4195     if (!pmr_hint) {
4196         pmr_hint = NewMoveRecord();
4197         pmr_hint->fPlayer = ms.fTurn;
4198     }
4199 
4200     if (ms.anDice[0] > 0) {
4201         pmr_hint->mt = MOVE_NORMAL;
4202         pmr_hint->anDice[0] = MAX(ms.anDice[0], ms.anDice[1]);
4203         pmr_hint->anDice[1] = MIN(ms.anDice[0], ms.anDice[1]);
4204     } else if (ms.fDoubled) {
4205         pmr_hint->mt = MOVE_TAKE;
4206     } else {
4207         pmr_hint->mt = MOVE_DOUBLE;
4208         pmr_hint->fPlayer = ms.fTurn;
4209     }
4210     return pmr_hint;
4211 }
4212 
4213 extern gboolean
game_is_last(listOLD * plGame)4214 game_is_last(listOLD * plGame)
4215 {
4216     listOLD *pl;
4217     for (pl = lMatch.plNext; pl->p != plGame && pl != &lMatch; pl = pl->plNext) {
4218     }
4219     if (pl->p != plGame || pl->plNext != &lMatch)
4220         return FALSE;
4221     else
4222         return TRUE;
4223 }
4224 
4225 extern listOLD *
game_add_pmr_hint(listOLD * plGame)4226 game_add_pmr_hint(listOLD * plGame)
4227 {
4228     g_return_val_if_fail(plGame, NULL);
4229     g_return_val_if_fail(game_is_last(plGame), NULL);
4230 
4231     if (pmr_hint)
4232         return (ListInsert(plGame, pmr_hint));
4233     else
4234         return NULL;
4235 }
4236 
4237 extern void
game_remove_pmr_hint(listOLD * pl_hint)4238 game_remove_pmr_hint(listOLD * pl_hint)
4239 {
4240     g_return_if_fail(pl_hint);
4241     g_return_if_fail(pl_hint->p == pmr_hint);
4242     ListDelete(pl_hint);
4243 }
4244 
4245 extern void
pmr_hint_destroy(void)4246 pmr_hint_destroy(void)
4247 {
4248     if (!pmr_hint)
4249         return;
4250     FreeMoveRecord(pmr_hint);
4251     pmr_hint = NULL;
4252 }
4253 
4254 static void
4255  OptimumRoll(TanBoard anBoard, const cubeinfo * pci, const evalcontext * pec, const int fBest, unsigned int anDice[2]);
4256 
4257 static int
CheatDice(unsigned int anDice[2],matchstate * pms,const int fBest)4258 CheatDice(unsigned int anDice[2], matchstate * pms, const int fBest)
4259 {
4260 
4261     static evalcontext ec0ply = { FALSE, 0, FALSE, TRUE, 0.0 };
4262     static cubeinfo ci;
4263 
4264     GetMatchStateCubeInfo(&ci, pms);
4265 
4266     OptimumRoll(pms->anBoard, &ci, &ec0ply, fBest, anDice);
4267 
4268     if (!anDice[0])
4269         return -1;
4270 
4271     return 0;
4272 
4273 }
4274 
4275 /*
4276  * find best (or worst roll possible)
4277  *
4278  */
4279 
4280 typedef struct _rollequity {
4281     float r;
4282     int anDice[2];
4283 } rollequity;
4284 
4285 static int
CompareRollEquity(const void * p1,const void * p2)4286 CompareRollEquity(const void *p1, const void *p2)
4287 {
4288 
4289     const rollequity *per1 = (const rollequity *) p1;
4290     const rollequity *per2 = (const rollequity *) p2;
4291 
4292     if (per1->r > per2->r)
4293         return -1;
4294     else if (per1->r < per2->r)
4295         return +1;
4296     else
4297         return 0;
4298 
4299 }
4300 
4301 static void
OptimumRoll(TanBoard anBoard,const cubeinfo * pci,const evalcontext * pec,const int fBest,unsigned int anDice[2])4302 OptimumRoll(TanBoard anBoard, const cubeinfo * pci, const evalcontext * pec, const int fBest, unsigned int anDice[2])
4303 {
4304 
4305     int i, j, k;
4306     float ar[NUM_ROLLOUT_OUTPUTS];
4307     rollequity are[21];
4308     float r;
4309 
4310     anDice[0] = anDice[1] = 0;
4311 
4312     for (i = 1, k = 0; i <= 6; i++)
4313         for (j = 1; j <= i; j++, ++k) {
4314 
4315             EvaluateRoll(ar, i, j, (ConstTanBoard) anBoard, pci, pec);
4316 
4317             r = (pec->fCubeful) ? ar[OUTPUT_CUBEFUL_EQUITY] : ar[OUTPUT_EQUITY];
4318 
4319             if (pci->nMatchTo)
4320                 r = 1.0f - r;
4321             else
4322                 r = -r;
4323 
4324             are[k].r = r;
4325             are[k].anDice[0] = i;
4326             are[k].anDice[1] = j;
4327 
4328         }
4329 
4330     qsort(are, 21, sizeof(rollequity), CompareRollEquity);
4331 
4332     anDice[0] = are[fBest].anDice[0];
4333     anDice[1] = are[fBest].anDice[1];
4334 
4335 }
4336 
4337 extern void
EvaluateRoll(float ar[NUM_ROLLOUT_OUTPUTS],int nDie1,int nDie2,const TanBoard anBoard,const cubeinfo * pci,const evalcontext * pec)4338 EvaluateRoll(float ar[NUM_ROLLOUT_OUTPUTS], int nDie1, int nDie2, const TanBoard anBoard,
4339              const cubeinfo * pci, const evalcontext * pec)
4340 {
4341     TanBoard anBoardTemp;
4342     cubeinfo ciOpp;
4343 
4344     memcpy(&ciOpp, pci, sizeof(cubeinfo));
4345     ciOpp.fMove = !pci->fMove;
4346 
4347     memcpy(&anBoardTemp[0][0], &anBoard[0][0], 2 * 25 * sizeof(int));
4348 
4349     if (FindBestMove(NULL, nDie1, nDie2, anBoardTemp, pci, NULL, defaultFilters) < 0)
4350         g_assert_not_reached();
4351 
4352     SwapSides(anBoardTemp);
4353 
4354     GeneralEvaluationE(ar, (ConstTanBoard) anBoardTemp, &ciOpp, pec);
4355 
4356     return;
4357 }
4358 
4359 /* routine to link doubles/beavers/raccoons/etc and their eventual
4360  * take/drop decisions into a single evaluation data block
4361  * returns NULL if there is no preceding double record to link to
4362  */
4363 
4364 moverecord *
LinkToDouble(moverecord * pmr)4365 LinkToDouble(moverecord * pmr)
4366 {
4367     moverecord *prev;
4368 
4369     if (!plLastMove || ((prev = plLastMove->p) == NULL) || prev->mt != MOVE_DOUBLE)
4370         return NULL;
4371 
4372     /* link the evaluation data */
4373     pmr->CubeDecPtr = prev->CubeDecPtr;
4374 
4375     /* if this is part of a chain of doubles, add to the count
4376      * nAnimals will be 0 for the first double, 1 for the beaver,
4377      * 2 for the racoon, etc.
4378      */
4379     if (pmr->mt == MOVE_DOUBLE)
4380         pmr->nAnimals = 1 + prev->nAnimals;
4381 
4382     /* We used to return pmr here and callers merely tested if the return
4383      * value is null (above) or not (here).
4384      * On the other hand, prev can be used by the caller (that already
4385      * knows pmr) in the case of import of games with beavers.
4386      */
4387     return prev;
4388 }
4389 
4390 /*
4391  * getFinalScore:
4392  * fills anScore[ 2 ] with the final score of the match/session
4393  */
4394 
4395 extern int
getFinalScore(int * anScore)4396 getFinalScore(int *anScore)
4397 {
4398     listOLD *plGame;
4399 
4400     /* find last game */
4401     for (plGame = lMatch.plNext; plGame->plNext->p; plGame = plGame->plNext);
4402 
4403     if (plGame->p && ((listOLD *) plGame->p)->plNext &&
4404         ((listOLD *) plGame->p)->plNext->p &&
4405         ((moverecord *) ((listOLD *) plGame->p)->plNext->p)->mt == MOVE_GAMEINFO) {
4406         anScore[0] = ((moverecord *) ((listOLD *) plGame->p)->plNext->p)->g.anScore[0];
4407         anScore[1] = ((moverecord *) ((listOLD *) plGame->p)->plNext->p)->g.anScore[1];
4408         if (((moverecord *) ((listOLD *) plGame->p)->plNext->p)->g.fWinner != -1)
4409             anScore[((moverecord *) ((listOLD *) plGame->p)->plNext->p)->g.fWinner] +=
4410                 ((moverecord *) ((listOLD *) plGame->p)->plNext->p)->g.nPoints;
4411         return TRUE;
4412     }
4413 
4414     return FALSE;
4415 }
4416 
4417 extern const char *
GetMoveString(moverecord * pmr,int * pPlayer,gboolean addSkillMarks)4418 GetMoveString(moverecord * pmr, int *pPlayer, gboolean addSkillMarks)
4419 {
4420     doubletype dt;
4421     static char sz[40];
4422     const char *pch = NULL;
4423     TanBoard anBoard;
4424     *pPlayer = 0;
4425 
4426     switch (pmr->mt) {
4427     case MOVE_GAMEINFO:
4428         /* no need to list this */
4429         break;
4430 
4431     case MOVE_NORMAL:
4432         *pPlayer = pmr->fPlayer;
4433         pch = sz;
4434         sz[0] = (char) (MAX(pmr->anDice[0], pmr->anDice[1]) + '0');
4435         sz[1] = (char) (MIN(pmr->anDice[0], pmr->anDice[1]) + '0');
4436         sz[2] = ':';
4437         sz[3] = ' ';
4438         FormatMove(sz + 4, msBoard(), pmr->n.anMove);
4439         if (addSkillMarks) {
4440             strcat(sz, aszSkillTypeAbbr[pmr->n.stMove]);
4441             strcat(sz, aszSkillTypeAbbr[pmr->stCube]);
4442         }
4443         break;
4444 
4445     case MOVE_DOUBLE:
4446         *pPlayer = pmr->fPlayer;
4447         pch = sz;
4448 
4449         switch ((dt = DoubleType(ms.fDoubled, ms.fMove, ms.fTurn))) {
4450         case DT_NORMAL:
4451             sprintf(sz, (ms.fCubeOwner == -1) ? _("Double to %d") : _("Redouble to %d"), ms.nCube << 1);
4452             break;
4453         case DT_BEAVER:
4454         case DT_RACCOON:
4455             sprintf(sz, (dt == DT_BEAVER) ? _("Beaver to %d") : _("Raccoon to %d"), ms.nCube << 2);
4456             break;
4457         default:
4458             g_assert_not_reached();
4459         }
4460         if (addSkillMarks)
4461             strcat(sz, aszSkillTypeAbbr[pmr->stCube]);
4462         break;
4463 
4464     case MOVE_TAKE:
4465         *pPlayer = pmr->fPlayer;
4466         strcpy(sz, _("Take"));
4467         pch = sz;
4468         if (addSkillMarks)
4469             strcat(sz, aszSkillTypeAbbr[pmr->stCube]);
4470         break;
4471 
4472     case MOVE_DROP:
4473         *pPlayer = pmr->fPlayer;
4474         strcpy(sz, _("Drop"));
4475         pch = sz;
4476         if (addSkillMarks)
4477             strcat(sz, aszSkillTypeAbbr[pmr->stCube]);
4478         break;
4479 
4480     case MOVE_RESIGN:
4481         *pPlayer = pmr->fPlayer;
4482         pch = _(" Resigns");    /* FIXME show value */
4483         break;
4484 
4485     case MOVE_SETDICE:
4486         *pPlayer = pmr->fPlayer;
4487         sprintf(sz, _("Rolled %u%u"), MAX(pmr->anDice[0], pmr->anDice[1]), MIN(pmr->anDice[0], pmr->anDice[1]));
4488         pch = sz;
4489         break;
4490 
4491     case MOVE_SETBOARD:
4492         *pPlayer = -1;
4493         /* PositionID assumes player 0 on roll. If this is not
4494          * the case, swap board first and restore it when done */
4495         if (pmr->fPlayer) {
4496             PositionFromKey(anBoard, &pmr->sb.key);
4497             SwapSides(anBoard);
4498             PositionKey((ConstTanBoard) anBoard, &pmr->sb.key);
4499         }
4500         sprintf(sz, " (set board %s)", PositionIDFromKey(&pmr->sb.key));
4501         if (pmr->fPlayer) {
4502             SwapSides(anBoard);
4503             PositionKey((ConstTanBoard) anBoard, &pmr->sb.key);
4504         }
4505         pch = sz;
4506         break;
4507 
4508     case MOVE_SETCUBEPOS:
4509         *pPlayer = -1;
4510         if (pmr->scp.fCubeOwner < 0)
4511             sprintf(sz, " (set cube centre)");
4512         else
4513             sprintf(sz, " (set cube owner %s)", ap[pmr->scp.fCubeOwner].szName);
4514         pch = sz;
4515         break;
4516 
4517     case MOVE_SETCUBEVAL:
4518         *pPlayer = -1;
4519         sprintf(sz, " (set cube value %d)", pmr->scv.nCube);
4520         pch = sz;
4521         break;
4522     default:
4523         g_assert_not_reached();
4524     }
4525     return pch;
4526 }
4527 
4528 #define STRBUF_SIZE 1024
4529 
4530 static void
AddString(listOLD * buffers,char * str)4531 AddString(listOLD * buffers, char *str)
4532 {
4533     listOLD *pCurrent = buffers->plPrev;
4534     if (!pCurrent->p || strlen(str) + strlen(pCurrent->p) > STRBUF_SIZE) {
4535         char *newBuf = malloc(STRBUF_SIZE + 1);
4536         *newBuf = '\0';
4537         pCurrent = ListInsert(buffers, newBuf);
4538     }
4539     strcat(pCurrent->p, str);
4540 }
4541 
4542 extern char *
GetMatchCheckSum(void)4543 GetMatchCheckSum(void)
4544 {
4545     static char auchHex[33];    /* static buffer that holds result, not ideal but not hurting anyone */
4546     unsigned char auch[16];
4547     int i;
4548     /* Work out md5 checksum */
4549     char *gameStr;
4550     size_t size;
4551     char buf[1024];
4552     listOLD *pList, *plGame, *plMove;
4553     listOLD buffers;
4554     ListCreate(&buffers);
4555 
4556     sprintf(buf, "%s vs %s (%d)", ap[0].szName, ap[1].szName, ms.nMatchTo);
4557     AddString(&buffers, buf);
4558 
4559     for (plGame = lMatch.plNext; plGame->p; plGame = plGame->plNext) {
4560         listOLD *plStart = plGame->p;
4561         int move = 1;
4562         for (plMove = plStart->plNext; plMove->p; plMove = plMove->plNext) {
4563             char playerStr[4] = ".AB";
4564             int player;
4565             moverecord *pmr = plMove->p;
4566             const char *moveString = GetMoveString(pmr, &player, FALSE);
4567             if (moveString) {
4568                 sprintf(buf, " %d%c %s", move, playerStr[player + 1], moveString);
4569                 AddString(&buffers, buf);
4570                 if (player == 1)
4571                     move++;
4572             }
4573         }
4574     }
4575 
4576     /* Create single string from buffers */
4577     size = 0;
4578     for (pList = buffers.plNext; pList->p; pList = pList->plNext)
4579         size += strlen(pList->p);
4580 
4581     gameStr = malloc(size + 1);
4582     *gameStr = '\0';
4583     for (pList = buffers.plNext; pList->p; pList = pList->plNext)
4584         strcat(gameStr, pList->p);
4585 
4586     ListDeleteAll(&buffers);
4587 
4588     md5_buffer(gameStr, strlen(gameStr), auch);
4589     free(gameStr);
4590     /* Convert to hex so stores easily in database - is there a better way? */
4591     for (i = 0; i < 16; i++)
4592         sprintf(auchHex + (i * 2), "%02x", auch[i]);
4593 
4594     return auchHex;
4595 }
4596 
4597 /*
4598  * get name number
4599  *
4600  * Input
4601  *    plGame: the game for which the game number is requested
4602  *
4603  * Returns:
4604  *    the game number
4605  *
4606  */
4607 
4608 extern int
getGameNumber(const listOLD * plGame)4609 getGameNumber(const listOLD * plGame)
4610 {
4611 
4612     listOLD *pl;
4613     int iGame;
4614 
4615     for (pl = lMatch.plNext, iGame = 0; pl != &lMatch; pl = pl->plNext, iGame++)
4616         if (pl->p == plGame)
4617             return iGame;
4618 
4619     return -1;
4620 
4621 }
4622 
4623 
4624 /*
4625  * get move number
4626  *
4627  * Input
4628  *    plGame: the game
4629  *    p: the move
4630  *
4631  * Returns:
4632  *    the move number
4633  *
4634  */
4635 
4636 extern int
getMoveNumber(const listOLD * plGame,const void * p)4637 getMoveNumber(const listOLD * plGame, const void *p)
4638 {
4639 
4640     listOLD *pl;
4641     int iMove;
4642 
4643     for (pl = plGame->plNext, iMove = 0; pl != plGame; pl = pl->plNext, iMove++)
4644         if (p == pl->p)
4645             return iMove;
4646 
4647     if (p == pmr_hint)
4648         return iMove;
4649 
4650     return -1;
4651 
4652 }
4653 
4654 
4655 extern void
CommandClearTurn(char * UNUSED (sz))4656 CommandClearTurn(char *UNUSED(sz))
4657 {
4658     moverecord *pmr;
4659     if (ms.gs != GAME_PLAYING) {
4660         outputl(_("There must be a game in progress to set a player on roll."));
4661         return;
4662     }
4663 
4664     if (ms.fResigned) {
4665         outputl(_("Please resolve the resignation first."));
4666         return;
4667     }
4668 
4669     if (!move_not_last_in_match_ok())
4670         return;
4671 
4672     while ((pmr = plLastMove->p) && (pmr->mt == MOVE_DOUBLE || pmr->mt == MOVE_SETDICE))
4673         PopMoveRecord(plLastMove);
4674 
4675     pmr_hint_destroy();
4676     fNextTurn = FALSE;
4677 #if defined (USE_GTK)
4678     if (fX) {
4679         BoardData *bd = BOARD(pwBoard)->board_data;
4680         bd->diceRoll[0] = bd->diceRoll[1] = 0;
4681     }
4682 #endif
4683     ms.anDice[0] = ms.anDice[1] = 0;
4684 
4685     UpdateSetting(&ms.fTurn);
4686 
4687 #if defined (USE_GTK)
4688     if (fX)
4689         ShowBoard();
4690 #endif                          /* USE_GTK */
4691 }
4692