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