423749f89d47e8b9c3c3ed17e7bb57ee30cdf0a3
[gnushogi.git] / gnushogi / book.c
1 /*
2  * FILE: book.c
3  *
4  * ----------------------------------------------------------------------
5  * Copyright (c) 1993, 1994, 1995 Matthias Mutz
6  * Copyright (c) 1999 Michael Vanier and the Free Software Foundation
7  * Copyright (c) 2008, 2013, 2014 Yann Dirson and the Free Software Foundation
8  *
9  * GNU SHOGI is based on GNU CHESS
10  *
11  * Copyright (c) 1988, 1989, 1990 John Stanback
12  * Copyright (c) 1992 Free Software Foundation
13  *
14  * This file is part of GNU SHOGI.
15  *
16  * GNU Shogi is free software; you can redistribute it and/or modify it
17  * under the terms of the GNU General Public License as published by the
18  * Free Software Foundation; either version 3 of the License,
19  * or (at your option) any later version.
20  *
21  * GNU Shogi is distributed in the hope that it will be useful, but WITHOUT
22  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
23  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
24  * for more details.
25  *
26  * You should have received a copy of the GNU General Public License along
27  * with GNU Shogi; see the file COPYING. If not, see
28  * <http://www.gnu.org/licenses/>.
29  * ----------------------------------------------------------------------
30  *
31  */
32
33 #include "gnushogi.h"
34
35 #define O_BINARY 0
36
37 #if HAVE_UNISTD_H
38 /* Declarations of read(), write(), close(), and lseek(). */
39 #include <unistd.h>
40 #endif
41
42 #if HAVE_FCNTL_H
43 #include <fcntl.h>
44 #endif
45
46 #include "book.h"
47
48 unsigned booksize = BOOKSIZE;
49 unsigned short bookmaxply = BOOKMAXPLY;
50 unsigned bookcount = 0;
51
52 #ifdef BOOK
53 char *bookfile = BOOK;
54 #else
55 char *bookfile = NULL;
56 #endif
57
58 #ifdef BINBOOK
59 char *binbookfile = BINBOOK;
60 #else
61 char *binbookfile = NULL;
62 #endif
63
64 static char bmvstr[3][7];
65
66 static ULONG bhashbd;
67 static ULONG bhashkey;
68
69
70 /*
71  * Balgbr(f, t, flag)
72  *
73  * Generate move strings in different formats.
74  */
75
76 static void
77 Balgbr(short f, short t, short flag)
78 {
79     short promoted = false;
80
81     if ((f & 0x80) != 0)
82     {
83         f &= 0x7f;
84         promoted = true;
85     }
86
87     if (f > NO_SQUARES)
88     {
89         short piece;
90         piece = f - NO_SQUARES;
91
92         if (f > (NO_SQUARES + NO_PIECES))
93             piece -= NO_PIECES;
94
95         flag = (dropmask | piece);
96     }
97
98     if ((t & 0x80) != 0)
99     {
100         flag |= promote;
101         t &= 0x7f;
102     }
103
104     if ((f == t) && ((f != 0) || (t != 0)))
105     {
106         /*
107          * error in algbr: FROM=TO=t
108          */
109
110         bmvstr[0][0] = bmvstr[1][0] = bmvstr[2][0] = '\0';
111     }
112     else
113     {
114         if ((flag & dropmask) != 0)
115         {
116             /* bmvstr[0]: P*3c bmvstr[1]: P'3c */
117             short piece = flag & pmask;
118             bmvstr[0][0] = pxx[piece];
119             bmvstr[0][1] = '*';
120             bmvstr[0][2] = COL_NAME(column(t));
121             bmvstr[0][3] = ROW_NAME(row(t));
122             bmvstr[0][4] = bmvstr[2][0] = '\0';
123             strcpy(bmvstr[1], bmvstr[0]);
124             bmvstr[1][1] = '\'';
125         }
126         else
127         {
128             if ((f != 0) || (t != 0))
129             {
130                 /* algebraic notation */
131                 /* bmvstr[0]: 7g7f bmvstr[1]:
132                  * (+)P7g7f(+) bmvstr[2]: (+)P7f(+) */
133                 bmvstr[0][0] = COL_NAME(column(f));
134                 bmvstr[0][1] = ROW_NAME(row(f));
135                 bmvstr[0][2] = COL_NAME(column(t));
136                 bmvstr[0][3] = ROW_NAME(row(t));
137                 bmvstr[0][4] = '\0';
138
139                 if (promoted)
140                 {
141                     bmvstr[1][0] = bmvstr[2][0] = '+';
142                     bmvstr[1][1] = bmvstr[2][1] = pxx[board[f]];
143                     strcpy(&bmvstr[1][2], &bmvstr[0][0]);
144                     strcpy(&bmvstr[2][2], &bmvstr[0][2]);
145                 }
146                 else
147                 {
148                     bmvstr[1][0] = bmvstr[2][0] = pxx[board[f]];
149                     strcpy(&bmvstr[1][1], &bmvstr[0][0]);
150                     strcpy(&bmvstr[2][1], &bmvstr[0][2]);
151                 }
152
153                 if (flag & promote)
154                 {
155                     strcat(bmvstr[0], "+");
156                     strcat(bmvstr[1], "+");
157                     strcat(bmvstr[2], "+");
158                 }
159             }
160             else
161             {
162                 bmvstr[0][0] = bmvstr[1][0] = bmvstr[2][0] = '\0';
163             }
164         }
165     }
166 }
167
168
169 #ifndef QUIETBOOKGEN
170 static void
171 bkdisplay(char *s, int cnt, int moveno)
172 {
173     static short pnt;
174     struct leaf  *node;
175     int r, c, l;
176
177     pnt = TrPnt[2];
178     printf("matches = %d\n", cnt);
179     printf("inout move is :%s: move number %d side %s\n",
180             s, moveno / 2 + 1, (moveno & 1) ? "white" : "black");
181
182 #ifndef SEMIQUIETBOOKGEN
183     printf("legal moves are \n");
184
185     while (pnt < TrPnt[3])
186     {
187         node = &Tree[pnt++];
188
189         if (is_promoted[board[node->f]] )
190             Balgbr(node->f | 0x80, node->t, (short) node->flags);
191         else
192             Balgbr(node->f, node->t, (short) node->flags);
193
194         printf("%s %s %s\n",
195                bmvstr[0], bmvstr[1], bmvstr[2]);
196     }
197
198     printf("\n current board is\n");
199
200     for (r = (NO_ROWS - 1); r >= 0; r--)
201     {
202         for (c = 0; c <= (NO_COLS - 1); c++)
203         {
204             char pc;
205
206             l = locn(r, c);
207             pc = (is_promoted[board[l]] ? '+' : ' ');
208
209             if (color[l] == neutral)
210                 printf(" -");
211             else if (color[l] == black)
212                 printf("%c%c", pc, qxx[board[l]]);
213             else
214                 printf("%c%c", pc, pxx[board[l]]);
215         }
216
217         printf("\n");
218     }
219
220     printf("\n");
221     {
222         short color;
223
224         for (color = black; color <= white; color++)
225         {
226             short piece, c;
227
228             printf((color == black) ? "black " : "white ");
229
230             for (piece = pawn; piece <= king; piece++)
231             {
232                 if ((c = Captured[color][piece]))
233                     printf("%i%c ", c, pxx[piece]);
234             }
235
236             printf("\n");
237         };
238     }
239 #endif /* SEMIQUIETBOOKGEN */
240 }
241 #endif /* QUIETBOOKGEN */
242
243
244 /*
245  * BVerifyMove(s, mv, moveno)
246  *
247  * Compare the string 's' to the list of legal moves available for the
248  * opponent. If a match is found, make the move on the board.
249  */
250
251 static int
252 BVerifyMove(char *s, unsigned short *mv, int moveno)
253 {
254     static short pnt, tempb, tempc, tempsf, tempst, cnt;
255     static struct leaf xnode;
256     struct leaf  *node;
257
258     *mv = 0;
259     cnt = 0;
260     MoveList(opponent, 2, -2, true);
261     pnt = TrPnt[2];
262
263     while (pnt < TrPnt[3])
264     {
265         node = &Tree[pnt++];
266
267         if (is_promoted[board[node->f]] )
268             Balgbr(node->f | 0x80, node->t, (short) node->flags);
269         else
270             Balgbr(node->f, node->t, (short) node->flags);
271
272         if (strcmp(s, bmvstr[0]) == 0 || strcmp(s, bmvstr[1]) == 0 ||
273             strcmp(s, bmvstr[2]) == 0)
274         {
275             cnt++;
276             xnode = *node;
277         }
278     }
279
280     if (cnt == 1)
281     {
282         short blockable;
283
284         MakeMove(opponent, &xnode, &tempb,
285                  &tempc, &tempsf, &tempst, &INCscore);
286
287         if (SqAttacked(PieceList[opponent][0], computer, &blockable))
288         {
289             UnmakeMove(opponent, &xnode, &tempb, &tempc, &tempsf, &tempst);
290             /* Illegal move in check */
291 #if !defined QUIETBOOKGEN
292             puts("Illegal move (in check): %s");
293             bkdisplay(s, cnt, moveno);
294 #endif
295             return false;
296         }
297         else
298         {
299             *mv = (xnode.f << 8) | xnode.t;
300
301             if (is_promoted[board[xnode.t]] )
302                 Balgbr(xnode.f | 0x80, xnode.t, false);
303             else
304                 Balgbr(xnode.f, xnode.t, false);
305
306             return true;
307         }
308     }
309
310     /* Illegal move */
311 #if !defined QUIETBOOKGEN
312     printf("Illegal move (no match): %s\n", s);
313     bkdisplay(s, cnt, moveno);
314 #endif
315     return false;
316 }
317
318
319 /*
320  * RESET()
321  *
322  * Reset the board and other variables to start a new game.
323  *
324  */
325
326 static void
327 RESET(void)
328 {
329     short l;
330
331     flag.illegal = flag.mate = flag.quit
332         = flag.reverse = flag.bothsides = flag.onemove = flag.force
333         = false;
334
335     flag.post &= xboard; /* [HGM] xboard: do not clear in XBoard mode */
336
337     flag.material = flag.coords = flag.hash = flag.easy
338         = flag.beep = flag.rcptr
339         = true;
340
341     flag.stars = flag.shade = flag.back = flag.musttimeout = false;
342     flag.gamein = false;
343     GenCnt   = 0;
344     GameCnt  = 0;
345     CptrFlag[0] = TesujiFlag[0] = false;
346     opponent = black;
347     computer = white;
348
349     for (l = 0; l < NO_SQUARES; l++)
350     {
351         board[l]   = Stboard[l];
352         color[l]   = Stcolor[l];
353         Mvboard[l] = 0;
354     }
355
356     ClearCaptured();
357     InitializeStats();
358     hashbd = hashkey = 0;
359 }
360
361
362 static int
363 Vparse (FILE * fd, USHORT *mv, USHORT *flags, int moveno)
364 {
365     int c, i;
366     char s[255];
367
368     *flags = 0;
369
370     while (true)
371     {
372         while (((c = getc(fd)) == ' ')
373                || (c == '!') || (c == '/') || (c == '\n'));
374
375         if (c == '(')
376         {
377             /* amount of time spent for the last move */
378             while (((c = getc(fd)) != ')') && (c != EOF));
379
380             if (c == ')')
381             {
382                 while (((c = getc(fd)) == ' ') || (c == '\n'));
383             }
384         }
385
386         if (c == '[')
387         {
388             /* comment for the actual game */
389             while (((c = getc(fd))) != ']' && (c != EOF));
390
391             if (c == ']')
392             {
393                 while (((c = getc(fd))) == ' ' || (c == '\n'));
394             }
395         }
396
397         if (c == '\r')
398             continue;
399
400         if (c == '#')
401         {
402             /* comment */
403             do
404             {
405                 c = getc(fd);
406
407                 if (c == '\r')
408                     continue;
409                 /* goes to end of line */
410
411                 if (c == '\n')
412                     return 0;
413
414                 if (c == EOF)
415                     return -1;
416             }
417             while (true);
418         }
419
420         s[i = 0] = (char) c;
421
422         while ((c >= '0') && (c <= '9'))
423         {
424             c = getc(fd);
425             s[++i] = (char) c;
426         }
427
428         if (c == '.')
429         {
430             while (((c = getc(fd)) == ' ') || (c == '.') || (c == '\n'));
431             s[i = 0] = (char) c;
432         }
433
434         while (((c = getc(fd)) != '?') && (c != '!') && (c != ' ')
435                && (c != '(') && (c != '\n') && (c != '\t') && (c != EOF))
436         {
437             if (c == '\r')
438                 continue;
439
440             if ((c != 'x') && (c != '-') && (c != ',')
441                 && (c != ';') && (c != '='))
442             {
443                 s[++i] = (char) c;
444             }
445         }
446
447         s[++i] = '\0';
448
449         if (c == '(')
450         {
451             while (((c = getc(fd)) != ')') && (c != EOF));
452
453             if (c == ')')
454                 c = getc(fd);
455         }
456
457         if (c == EOF)
458             return -1;
459
460         if (s[0] == '#')
461         {
462             while ((c != '\n') && (c != EOF))
463                 c = getc(fd);
464
465             if (c == EOF)
466                 return -1;
467             else
468                 return 0;
469         }
470
471         if (strcmp(s, "draw") == 0)
472             continue;
473         else if (strcmp(s, "1-0") == 0)
474             continue;
475         else if (strcmp(s, "0-1") == 0)
476             continue;
477         else if (strcmp(s, "Resigns") == 0)
478             continue;
479         else if (strcmp(s, "Resigns.") == 0)
480             continue;
481         else if (strcmp(s, "Sennichite") == 0)
482             continue;
483         else if (strcmp(s, "Sennichite.") == 0)
484             continue;
485         else if (strcmp(s, "Jishogi") == 0)
486             continue;
487         else if (strcmp(s, "Jishogi.") == 0)
488             continue;
489
490         bhashkey = hashkey;
491         bhashbd  = hashbd;
492
493         i = BVerifyMove(s, mv, moveno);
494
495         if (c == '?')
496         {
497             /* Bad move, not for the program to play */
498             *flags |= BADMOVE;  /* Flag it ! */
499             while (((c = getc(fd)) == '?') || (c == '!') || (c == '/'));
500         }
501 #ifdef EASY_OPENINGS
502         else if (c == '~')
503         {
504             /* Do not use by computer */
505             *flags |= BADMOVE;  /* Flag it ! */
506
507             while (((c = getc(fd)) == '?') || (c == '!') || (c == '/'));
508         }
509 #endif
510         else if (c == '!')
511         {
512             /* Good move */
513             *flags |= GOODMOVE; /* Flag it ! */
514
515             while (((c = getc(fd)) == '?') || (c == '!') || (c == '/'));
516         }
517         else if (c == '\r')
518         {
519             c = getc(fd);
520         }
521
522         if (c == '(' )
523             while (((c = getc(fd)) != ')') && (c != EOF));
524
525         if (!i)
526         {
527             /* flush to start of next */
528             while (((c = getc(fd)) != '#') && (c != EOF));
529
530             if (c == EOF)
531             {
532                 return -1;
533             }
534             else
535             {
536                 ungetc(c, fd);
537                 return i;
538             }
539         }
540
541         return i;
542     }
543 }
544
545
546 static struct gdxadmin ADMIN;
547 struct gdxadmin B;
548 static struct gdxdata DATA;
549
550 /* lts(l) returns most significant 16 bits of l */
551
552 #if SIZEOF_LONG == 8  /* 64-bit long i.e. 8 bytes */
553 #  define lts(x) (USHORT)(((x >> 48) & 0xfffe) | side)
554 #else
555 #  if defined USE_LTSIMP
556 static USHORT ltsimp(long x)
557 {
558     USHORT n;
559     n = (((x >> 16) & 0xfffe));
560     return n;
561 }
562 #    define lts(x) (USHORT)(ltsimp(x) | side)
563 #  else
564 #    define lts(x) (USHORT)(((x >> 16)&0xfffe) | side)
565 #  endif
566 #endif
567
568
569 /* #define HashValue(l) lts(l) */
570 #define HashValue(l) (USHORT)(l & 0xffff)
571
572
573 static int gfd;
574 static ULONG currentoffset;
575
576
577 #define MAXOFFSET(B) ((B.booksize - 1) * sizeof_gdxdata + sizeof_gdxadmin)
578
579 #define HashOffset(hashkey, B) \
580 { \
581   currentoffset = ((ULONG)hashkey % B.booksize) \
582     * sizeof_gdxdata + sizeof_gdxadmin; \
583 }
584
585
586 #define NextOffset(B) \
587 { \
588   currentoffset += sizeof_gdxdata; \
589   if (currentoffset > B.maxoffset) \
590     currentoffset = sizeof_gdxadmin; \
591 }
592
593
594 #define WriteAdmin() \
595 { \
596   lseek(gfd, 0, SEEK_SET); \
597   write(gfd, (char *)&ADMIN, sizeof_gdxadmin); \
598 }
599
600 #define WriteData() \
601 { \
602   if (mustwrite ) \
603   { \
604     lseek(gfd, currentoffset, SEEK_SET); \
605     write(gfd, (char *)&DATA, sizeof_gdxdata); \
606     mustwrite = false; \
607   } \
608 }
609
610 static int ReadAdmin(void)
611 {
612     lseek(gfd, 0, SEEK_SET);
613     return (sizeof_gdxadmin == read(gfd, (char *)&ADMIN, sizeof_gdxadmin));
614 }
615
616 static int ReadData(struct gdxdata *DATA)
617 {
618     lseek(gfd, currentoffset, SEEK_SET);
619     return (sizeof_gdxdata == read(gfd, (char *)DATA, sizeof_gdxdata));
620 }
621
622
623 /*
624  * GetOpenings()
625  *
626  * CHECKME: is this still valid esp. wrt gnushogi.book?
627  *
628  * Read in the Opening Book file and parse the algebraic notation for a move
629  * into an unsigned integer format indicating the from and to square. Create
630  * a linked list of opening lines of play, with entry->next pointing to the
631  * next line and entry->move pointing to a chunk of memory containing the
632  * moves. More Opening lines of up to 100 half moves may be added to
633  * gnushogi.book. But now it's a hashed table by position which yields a move
634  * or moves for each position. It no longer knows about openings per se only
635  * positions and recommended moves in those positions.
636  *
637  */
638
639 void
640 GetOpenings(void)
641 {
642     short i;
643     int mustwrite = false, first;
644     unsigned short side;
645     short c;
646     USHORT mv, flags;
647     unsigned int x;
648     unsigned int games = 0;
649     LONG collisions = 0;
650     char msg[80];
651
652     FILE *fd;
653
654     if ((fd = fopen(bookfile, "r")) == NULL)
655         fd = fopen("gnushogi.tbk", "r");
656
657     if (fd != NULL)
658     {
659         /* yes add to book */
660         /* open book as writer */
661         gfd = open(binbookfile, O_RDONLY | O_BINARY);
662
663         if (gfd >= 0)
664         {
665             if (ReadAdmin())
666             {
667                 B.bookcount = ADMIN.bookcount;
668                 B.booksize = ADMIN.booksize;
669                 B.maxoffset = ADMIN.maxoffset;
670
671                 if (B.booksize && !(B.maxoffset == MAXOFFSET(B)))
672                 {
673                     printf("bad format %s\n", binbookfile);
674                     exit(1);
675                 }
676             }
677             else
678             {
679                 printf("bad format %s\n", binbookfile);
680                 exit(1);
681             }
682             close(gfd);
683             gfd = open(binbookfile, O_RDWR | O_BINARY);
684
685         }
686         else
687         {
688             gfd = open(binbookfile, O_RDWR | O_CREAT | O_BINARY, 0644);
689
690             ADMIN.bookcount = B.bookcount = 0;
691             ADMIN.booksize = B.booksize = booksize;
692             B.maxoffset = ADMIN.maxoffset = MAXOFFSET(B);
693             DATA.hashbd = 0;
694             DATA.hashkey = 0;
695             DATA.bmove = 0;
696             DATA.flags = 0;
697             DATA.hint = 0;
698             DATA.count = 0;
699             write(gfd, (char *)&ADMIN, sizeof_gdxadmin);
700             printf("creating bookfile %s %ld %ld\n",
701                     binbookfile, B.maxoffset, B.booksize);
702
703             for (x = 0; x < B.booksize; x++)
704             {
705                 write(gfd, (char *)&DATA, sizeof_gdxdata);
706             }
707         }
708
709         if (gfd >= 0)
710         {
711             /* setvbuf(fd, buffr, _IOFBF, 2048); */
712             side = black;
713             hashbd = hashkey = 0;
714             i = 0;
715
716             while ((c = Vparse(fd, &mv, &flags, i)) >= 0)
717             {
718                 if (c == 1)
719                 {
720                     /*
721                      * If this is not the first move of an opening and
722                      * if it's the first time we have seen it then
723                      * save the next move as a hint.
724                      */
725                     i++;
726
727                     if (i < bookmaxply + 2)
728                     {
729                         if (i > 1 && !(flags & BADMOVE))
730                             DATA.hint = mv;
731
732                         if (i < bookmaxply + 1)
733                         {
734                             /*
735                              * See if this position and move already
736                              * exist from some other opening.
737                              */
738
739                             WriteData();
740                             HashOffset(bhashkey, B);
741                             first = true;
742
743                             while (true)
744                             {
745                                 if (!ReadData(&DATA))
746                                     break; /* corrupted binbook file */
747
748                                 if (DATA.bmove == 0)
749                                     break;  /* free entry */
750
751                                 if (DATA.hashkey == HashValue(bhashkey)
752                                     && DATA.hashbd == bhashbd)
753                                 {
754                                     if (DATA.bmove == mv)
755                                     {
756                                         /*
757                                          * Yes, so just bump count - count
758                                          * is used to choose the opening
759                                          * move in proportion to its
760                                          * presence in the book.
761                                          */
762
763                                         DATA.count++;
764                                         DATA.flags |= flags;
765                                         mustwrite = true;
766                                         break;
767                                     }
768                                     else
769                                     {
770                                         if (first)
771                                             collisions++;
772
773                                         if (DATA.flags & LASTMOVE)
774                                         {
775                                             DATA.flags &= (~LASTMOVE);
776                                             mustwrite = true;
777                                             WriteData();
778                                         }
779                                     }
780                                 }
781
782                                 NextOffset(B);
783                                 first = false;
784                             }
785
786                             /*
787                              * Doesn't exist so add it to the book.
788                              */
789
790                             if (!mustwrite)
791                             {
792                                 B.bookcount++;
793
794                                 if ((B.bookcount % 1000) == 0)
795                                 {
796                                     /* CHECKME: may want to get rid of this,
797                                      * especially for xshogi. */
798                                     printf("%ld rec %d openings "
799                                            "processed\n",
800                                            B.bookcount, games);
801                                 }
802
803                                 /* initialize a record */
804                                 DATA.hashbd = bhashbd;
805                                 DATA.hashkey = HashValue(bhashkey);
806                                 DATA.bmove = mv;
807                                 DATA.flags = flags | LASTMOVE;
808                                 DATA.count = 1;
809                                 DATA.hint = 0;
810                                 mustwrite = true;
811                             }
812                         }
813                     }
814
815                     computer = opponent;
816                     opponent = computer ^ 1;
817
818                     side = side ^ 1;
819                 }
820                 else if (i > 0)
821                 {
822                     /* reset for next opening */
823                     games++;
824                     WriteData();
825                     RESET();
826                     i = 0;
827                     side = black;
828                 }
829             }
830
831             WriteData();
832             fclose(fd);
833             /* write admin rec with counts */
834             ADMIN.bookcount = B.bookcount;
835             WriteAdmin();
836
837             close(gfd);
838         }
839     }
840
841     if (binbookfile != NULL)
842     {
843         /* open book as reader */
844         gfd = open(binbookfile, O_RDONLY | O_BINARY);
845
846         if (gfd >= 0)
847         {
848             if (ReadAdmin() && (!ADMIN.booksize
849                                 || (ADMIN.maxoffset == MAXOFFSET(ADMIN))))
850             {
851                 B.bookcount = ADMIN.bookcount;
852                 B.booksize  = ADMIN.booksize;
853                 B.maxoffset = ADMIN.maxoffset;
854             }
855             else
856             {
857                 printf("bad format %s\n", binbookfile);
858                 exit(1);
859             }
860
861         }
862         else
863         {
864             B.bookcount = 0;
865             B.booksize = booksize;
866
867         }
868
869         sprintf(msg, "Book used %lu(%lu).", B.bookcount, B.booksize);
870         dsp->ShowMessage(msg);
871     }
872
873     /* Set everything back to start the game. */
874     Book = BOOKFAIL;
875     RESET();
876
877     /* Now get ready to play .*/
878     if (!B.bookcount)
879     {
880         dsp->ShowMessage("Can't find book.");
881         Book = 0;
882     }
883 }
884
885
886 /*
887  * OpeningBook(hint)
888  *
889  * Go through each of the opening lines of play and check for a match with
890  * the current game listing. If a match occurs, generate a random
891  * number. If this number is the largest generated so far then the next
892  * move in this line becomes the current "candidate".  After all lines are
893  * checked, the candidate move is put at the top of the Tree[] array and
894  * will be played by the program.  Note that the program does not handle
895  * book transpositions.
896  */
897
898 int
899 OpeningBook(unsigned short *hint)
900 {
901     unsigned short r, m;
902     int possibles = TrPnt[2] - TrPnt[1];
903
904     gsrand((unsigned int) time((long *) 0));
905     m = 0;
906
907     /*
908      * Find all the moves for this position  - count them and get their
909      * total count.
910      */
911
912     {
913         USHORT i, x;
914         USHORT rec = 0;
915         USHORT summ = 0;
916         USHORT h = 0, b = 0;
917         struct gdxdata OBB[128];
918
919         if (B.bookcount == 0)
920         {
921             Book--;
922             return false;
923         }
924
925         x = 0;
926         HashOffset(hashkey, B);
927 #ifdef BOOKTEST
928         printf("looking for book move, bhashbd = 0x%lx bhashkey = 0x%x\n",
929                (ULONG)hashbd, HashValue(hashkey));
930 #endif
931         while (true)
932         {
933             if (!ReadData(&OBB[x]))
934                 break;
935
936             if (OBB[x].bmove == 0)
937                 break;
938
939 #ifdef BOOKTEST
940             printf("compare with bhashbd = 0x%lx bhashkey = 0x%x\n",
941                    OBB[x].hashbd, OBB[x].hashkey);
942 #endif
943             if ((OBB[x].hashkey == HashValue(hashkey))
944                 && (OBB[x].hashbd == (ULONG)hashbd))
945             {
946                 x++;
947
948                 if (OBB[x-1].flags & LASTMOVE)
949                     break;
950             }
951
952             NextOffset(B);
953         }
954
955 #ifdef BOOKTEST
956         printf("%d book move(s) found.\n", x);
957 #endif
958
959         if (x == 0)
960         {
961             Book--;
962             return false;
963         }
964
965         for (i = 0; i < x; i++)
966         {
967             if (OBB[i].flags & BADMOVE)
968             {
969                 m = OBB[i].bmove;
970
971                 /* Is the move in the MoveList? */
972                 for (b = TrPnt[1]; b < (unsigned) TrPnt[2]; b++)
973                 {
974                     if (((Tree[b].f << 8) | Tree[b].t) == m)
975                     {
976                         if (--possibles)
977                             Tree[b].score = DONTUSE;
978                         break;
979                     }
980                 }
981             }
982             else
983             {
984 #if defined BOOKTEST
985                 char s[20];
986                 movealgbr(m = OBB[i].bmove, s);
987                 printf("finding book move: %s\n", s);
988 #endif
989                 summ += OBB[i].count;
990             }
991         }
992
993         if (summ == 0)
994         {
995             Book--;
996             return false;
997         }
998
999         r = (urand() % summ);
1000
1001         for (i = 0; i < x; i++)
1002         {
1003             if (!(OBB[i].flags & BADMOVE))
1004             {
1005                 if (r < OBB[i].count)
1006                 {
1007                     rec = i;
1008                     break;
1009                 }
1010                 else
1011                 {
1012                     r -= OBB[i].count;
1013                 }
1014             }
1015         }
1016
1017         h = OBB[rec].hint;
1018         m = OBB[rec].bmove;
1019
1020         /* Make sure the move is in the MoveList. */
1021         for (b = TrPnt[1]; b < (unsigned) TrPnt[2]; b++)
1022         {
1023             if (((Tree[b].f << 8) | Tree[b].t) == m)
1024             {
1025                 Tree[b].flags |= book;
1026                 Tree[b].score = 0;
1027                 break;
1028             }
1029         }
1030
1031         /* Make sure it's the best. */
1032
1033         pick(TrPnt[1], TrPnt[2] - 1);
1034
1035         if (Tree[TrPnt[1]].score)
1036         {
1037             /* no! */
1038             Book--;
1039             return false;
1040         }
1041
1042         /* Ok, pick up the hint and go. */
1043         *hint = h;
1044         return true;
1045     }
1046
1047     Book--;
1048     return false;
1049 }