1 /* $NetBSD: bog.c,v 1.16 2000/05/08 07:56:02 mycroft Exp $ */ 2 3 /*- 4 * Copyright (c) 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Barry Brachman. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. All advertising materials mentioning features or use of this software 19 * must display the following acknowledgement: 20 * This product includes software developed by the University of 21 * California, Berkeley and its contributors. 22 * 4. Neither the name of the University nor the names of its contributors 23 * may be used to endorse or promote products derived from this software 24 * without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 36 * SUCH DAMAGE. 37 */ 38 39 #include <sys/cdefs.h> 40 #ifndef lint 41 __COPYRIGHT("@(#) Copyright (c) 1993\n\ 42 The Regents of the University of California. All rights reserved.\n"); 43 #endif /* not lint */ 44 45 #ifndef lint 46 #if 0 47 static char sccsid[] = "@(#)bog.c 8.2 (Berkeley) 5/4/95"; 48 #else 49 __RCSID("$NetBSD: bog.c,v 1.16 2000/05/08 07:56:02 mycroft Exp $"); 50 #endif 51 #endif /* not lint */ 52 53 #include <ctype.h> 54 #include <err.h> 55 #include <stdio.h> 56 #include <stdlib.h> 57 #include <string.h> 58 #include <time.h> 59 #include <unistd.h> 60 61 #include "bog.h" 62 #include "extern.h" 63 64 static int compar __P((const void *, const void *)); 65 int main __P((int, char *[])); 66 67 struct dictindex dictindex[26]; 68 69 /* 70 * Cube position numbering: 71 * 72 * 0 1 2 3 73 * 4 5 6 7 74 * 8 9 A B 75 * C D E F 76 */ 77 static int adjacency[16][16] = { 78 /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ 79 { 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* 0 */ 80 { 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* 1 */ 81 { 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, /* 2 */ 82 { 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, /* 3 */ 83 { 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, /* 4 */ 84 { 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0 }, /* 5 */ 85 { 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0 }, /* 6 */ 86 { 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, /* 7 */ 87 { 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0 }, /* 8 */ 88 { 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0 }, /* 9 */ 89 { 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1 }, /* A */ 90 { 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1 }, /* B */ 91 { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0 }, /* C */ 92 { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0 }, /* D */ 93 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1 }, /* E */ 94 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0 } /* F */ 95 }; 96 97 static int letter_map[26][16]; 98 99 char board[17]; 100 int wordpath[MAXWORDLEN + 1]; 101 int wordlen; /* Length of last word returned by nextword() */ 102 int usedbits; 103 104 const char *pword[MAXPWORDS]; 105 char pwords[MAXPSPACE], *pwordsp; 106 int npwords; 107 108 const char *mword[MAXMWORDS]; 109 char mwords[MAXMSPACE], *mwordsp; 110 int nmwords; 111 112 int ngames = 0; 113 int tnmwords = 0, tnpwords = 0; 114 115 #include <setjmp.h> 116 jmp_buf env; 117 118 time_t start_t; 119 120 static FILE *dictfp; 121 122 int batch; 123 int debug; 124 int minlength; 125 int reuse; 126 int tlimit; 127 128 int 129 main(argc, argv) 130 int argc; 131 char *argv[]; 132 { 133 long seed; 134 int ch, done, i, selfuse, sflag; 135 char *bspec, *p; 136 137 /* revoke setgid privileges */ 138 setgid(getgid()); 139 140 seed = 0; 141 batch = debug = reuse = selfuse = sflag = 0; 142 bspec = NULL; 143 minlength = 3; 144 tlimit = 180; /* 3 minutes is standard */ 145 146 while ((ch = getopt(argc, argv, "bds:t:w:")) != -1) 147 switch(ch) { 148 case 'b': 149 batch = 1; 150 break; 151 case 'd': 152 debug = 1; 153 break; 154 case 's': 155 sflag = 1; 156 seed = atol(optarg); 157 break; 158 case 't': 159 if ((tlimit = atoi(optarg)) < 1) 160 errx(1, "bad time limit"); 161 break; 162 case 'w': 163 if ((minlength = atoi(optarg)) < 3) 164 errx(1, "min word length must be > 2"); 165 break; 166 case '?': 167 default: 168 usage(); 169 } 170 argc -= optind; 171 argv += optind; 172 173 /* process final arguments */ 174 if (argc > 0) { 175 if (strcmp(argv[0], "+") == 0) 176 reuse = 1; 177 else if (strcmp(argv[0], "++") == 0) 178 selfuse = 1; 179 } 180 181 if (reuse || selfuse) { 182 argc -= 1; 183 argv += 1; 184 } 185 186 if (argc > 0) { 187 if (islower(argv[0][0])) { 188 if (strlen(argv[0]) != 16) { 189 usage(); 190 } else { 191 /* This board is assumed to be valid... */ 192 bspec = argv[0]; 193 } 194 } else { 195 usage(); 196 } 197 } 198 199 if (batch && bspec == NULL) 200 errx(1, "must give both -b and a board setup"); 201 202 if (selfuse) 203 for (i = 0; i < 16; i++) 204 adjacency[i][i] = 1; 205 206 if (batch) { 207 newgame(bspec); 208 while ((p = batchword(stdin)) != NULL) 209 (void) printf("%s\n", p); 210 exit (0); 211 } 212 setup(sflag, seed); 213 prompt("Loading the dictionary..."); 214 if ((dictfp = opendict(DICT)) == NULL) { 215 warn("%s", DICT); 216 cleanup(); 217 exit(1); 218 } 219 #ifdef LOADDICT 220 if (loaddict(dictfp) < 0) { 221 warnx("can't load %s", DICT); 222 cleanup(); 223 exit(1); 224 } 225 (void)fclose(dictfp); 226 dictfp = NULL; 227 #endif 228 if (loadindex(DICTINDEX) < 0) { 229 warnx("can't load %s", DICTINDEX); 230 cleanup(); 231 exit(1); 232 } 233 234 prompt("Type <space> to begin..."); 235 while (inputch() != ' '); 236 237 for (done = 0; !done;) { 238 newgame(bspec); 239 bspec = NULL; /* reset for subsequent games */ 240 playgame(); 241 #ifdef NEW_STYLE 242 prompt("Type <q>uit, <esc> locate, any other key to continue..."); 243 #else 244 prompt("Type <space> to continue, any cap to quit..."); 245 #endif 246 delay(50); /* wait for user to quit typing */ 247 flushin(stdin); 248 for (;;) { 249 ch = inputch(); 250 #ifdef NEW_STYLE 251 if (ch == '\033') 252 findword(); 253 else if (ch == '\014' || ch == '\022') /* ^l or ^r */ 254 redraw(); 255 else { 256 if (ch == 'q') { 257 done = 1; 258 break; 259 } else { 260 break; 261 } 262 } 263 #else 264 if (ch == '\033') 265 findword(); 266 else if (ch == '\014' || ch == '\022') /* ^l or ^r */ 267 redraw(); 268 else { 269 if (isupper(ch)) { 270 done = 1; 271 break; 272 } 273 if (ch == ' ') 274 break; 275 } 276 #endif 277 } 278 } 279 cleanup(); 280 exit (0); 281 } 282 283 /* 284 * Read a line from the given stream and check if it is legal 285 * Return a pointer to a legal word or a null pointer when EOF is reached 286 */ 287 char * 288 batchword(fp) 289 FILE *fp; 290 { 291 int *p, *q; 292 char *w; 293 294 q = &wordpath[MAXWORDLEN + 1]; 295 p = wordpath; 296 while (p < q) 297 *p++ = -1; 298 while ((w = nextword(fp)) != NULL) { 299 if (wordlen < minlength) 300 continue; 301 p = wordpath; 302 while (p < q && *p != -1) 303 *p++ = -1; 304 usedbits = 0; 305 if (checkword(w, -1, wordpath) != -1) 306 return (w); 307 } 308 return (NULL); 309 } 310 311 /* 312 * Play a single game 313 * Reset the word lists from last game 314 * Keep track of the running stats 315 */ 316 void 317 playgame() 318 { 319 int i, *p, *q; 320 time_t t; 321 char buf[MAXWORDLEN + 1]; 322 323 ngames++; 324 npwords = 0; 325 pwordsp = pwords; 326 nmwords = 0; 327 mwordsp = mwords; 328 329 time(&start_t); 330 331 q = &wordpath[MAXWORDLEN + 1]; 332 p = wordpath; 333 while (p < q) 334 *p++ = -1; 335 showboard(board); 336 startwords(); 337 if (setjmp(env)) { 338 badword(); 339 goto timesup; 340 } 341 342 while (1) { 343 if (getline(buf) == NULL) { 344 if (feof(stdin)) 345 clearerr(stdin); 346 break; 347 } 348 time(&t); 349 if (t - start_t >= tlimit) { 350 badword(); 351 break; 352 } 353 if (buf[0] == '\0') { 354 int remaining; 355 356 remaining = tlimit - (int) (t - start_t); 357 (void)snprintf(buf, sizeof(buf), 358 "%d:%02d", remaining / 60, remaining % 60); 359 showstr(buf, 1); 360 continue; 361 } 362 if (strlen(buf) < (size_t)minlength) { 363 badword(); 364 continue; 365 } 366 367 p = wordpath; 368 while (p < q && *p != -1) 369 *p++ = -1; 370 usedbits = 0; 371 372 if (checkword(buf, -1, wordpath) < 0) 373 badword(); 374 else { 375 if (debug) { 376 (void) printf("["); 377 for (i = 0; wordpath[i] != -1; i++) 378 (void) printf(" %d", wordpath[i]); 379 (void) printf(" ]\n"); 380 } 381 for (i = 0; i < npwords; i++) { 382 if (strcmp(pword[i], buf) == 0) 383 break; 384 } 385 if (i != npwords) { /* already used the word */ 386 badword(); 387 showword(i); 388 } 389 else if (!validword(buf)) 390 badword(); 391 else { 392 int len; 393 394 len = strlen(buf) + 1; 395 if (npwords == MAXPWORDS - 1 || 396 pwordsp + len >= &pwords[MAXPSPACE]) { 397 warnx("Too many words!"); 398 cleanup(); 399 exit(1); 400 } 401 pword[npwords++] = pwordsp; 402 (void) strcpy(pwordsp, buf); 403 pwordsp += len; 404 addword(buf); 405 } 406 } 407 } 408 409 timesup: ; 410 411 /* 412 * Sort the player's words and terminate the list with a null 413 * entry to help out checkdict() 414 */ 415 qsort(pword, npwords, sizeof(pword[0]), compar); 416 pword[npwords] = NULL; 417 418 /* 419 * These words don't need to be sorted since the dictionary is sorted 420 */ 421 checkdict(); 422 423 tnmwords += nmwords; 424 tnpwords += npwords; 425 426 results(); 427 } 428 429 /* 430 * Check if the given word is present on the board, with the constraint 431 * that the first letter of the word is adjacent to square 'prev' 432 * Keep track of the current path of squares for the word 433 * A 'q' must be followed by a 'u' 434 * Words must end with a null 435 * Return 1 on success, -1 on failure 436 */ 437 int 438 checkword(word, prev, path) 439 const char *word; 440 int prev, *path; 441 { 442 const char *p; 443 char *q; 444 int i, *lm; 445 446 if (debug) { 447 (void) printf("checkword(%s, %d, [", word, prev); 448 for (i = 0; wordpath[i] != -1; i++) 449 (void) printf(" %d", wordpath[i]); 450 (void) printf(" ]\n"); 451 } 452 453 if (*word == '\0') 454 return (1); 455 456 lm = letter_map[*word - 'a']; 457 458 if (prev == -1) { 459 char subword[MAXWORDLEN + 1]; 460 461 /* 462 * Check for letters not appearing in the cube to eliminate some 463 * recursive calls 464 * Fold 'qu' into 'q' 465 */ 466 p = word; 467 q = subword; 468 while (*p != '\0') { 469 if (*letter_map[*p - 'a'] == -1) 470 return (-1); 471 *q++ = *p; 472 if (*p++ == 'q') { 473 if (*p++ != 'u') 474 return (-1); 475 } 476 } 477 *q = '\0'; 478 while (*lm != -1) { 479 *path = *lm; 480 usedbits |= (1 << *lm); 481 if (checkword(subword + 1, *lm, path + 1) > 0) 482 return (1); 483 usedbits &= ~(1 << *lm); 484 lm++; 485 } 486 return (-1); 487 } 488 489 /* 490 * A cube is only adjacent to itself in the adjacency matrix if selfuse 491 * was set, so a cube can't be used twice in succession if only the 492 * reuse flag is set 493 */ 494 for (i = 0; lm[i] != -1; i++) { 495 if (adjacency[prev][lm[i]]) { 496 int used; 497 498 used = 1 << lm[i]; 499 /* 500 * If necessary, check if the square has already 501 * been used. 502 */ 503 if (!reuse && (usedbits & used)) 504 continue; 505 *path = lm[i]; 506 usedbits |= used; 507 if (checkword(word + 1, lm[i], path + 1) > 0) 508 return (1); 509 usedbits &= ~used; 510 } 511 } 512 *path = -1; /* in case of a backtrack */ 513 return (-1); 514 } 515 516 /* 517 * A word is invalid if it is not in the dictionary 518 * At this point it is already known that the word can be formed from 519 * the current board 520 */ 521 int 522 validword(word) 523 const char *word; 524 { 525 int j; 526 const char *q, *w; 527 528 j = word[0] - 'a'; 529 if (dictseek(dictfp, dictindex[j].start, SEEK_SET) < 0) { 530 (void) fprintf(stderr, "Seek error\n"); 531 cleanup(); 532 exit(1); 533 } 534 535 while ((w = nextword(dictfp)) != NULL) { 536 int ch; 537 538 if (*w != word[0]) /* end of words starting with word[0] */ 539 break; 540 q = word; 541 while ((ch = *w++) == *q++ && ch != '\0') 542 ; 543 if (*(w - 1) == '\0' && *(q - 1) == '\0') 544 return (1); 545 } 546 if (dictfp != NULL && feof(dictfp)) /* Special case for z's */ 547 clearerr(dictfp); 548 return (0); 549 } 550 551 /* 552 * Check each word in the dictionary against the board 553 * Delete words from the machine list that the player has found 554 * Assume both the dictionary and the player's words are already sorted 555 */ 556 void 557 checkdict() 558 { 559 char *p, *w; 560 const char **pw; 561 int i; 562 int prevch, previndex, *pi, *qi, st; 563 564 mwordsp = mwords; 565 nmwords = 0; 566 pw = pword; 567 prevch ='a'; 568 qi = &wordpath[MAXWORDLEN + 1]; 569 570 (void) dictseek(dictfp, 0L, SEEK_SET); 571 while ((w = nextword(dictfp)) != NULL) { 572 if (wordlen < minlength) 573 continue; 574 if (*w != prevch) { 575 /* 576 * If we've moved on to a word with a different first 577 * letter then we can speed things up by skipping all 578 * words starting with a letter that doesn't appear in 579 * the cube. 580 */ 581 i = (int) (*w - 'a'); 582 while (i < 26 && letter_map[i][0] == -1) 583 i++; 584 if (i == 26) 585 break; 586 previndex = prevch - 'a'; 587 prevch = i + 'a'; 588 /* 589 * Fall through if the word's first letter appears in 590 * the cube (i.e., if we can't skip ahead), otherwise 591 * seek to the beginning of words in the dictionary 592 * starting with the next letter (alphabetically) 593 * appearing in the cube and then read the first word. 594 */ 595 if (i != previndex + 1) { 596 if (dictseek(dictfp, 597 dictindex[i].start, SEEK_SET) < 0) { 598 warnx("seek error in checkdict()"); 599 cleanup(); 600 exit(1); 601 } 602 continue; 603 } 604 } 605 606 pi = wordpath; 607 while (pi < qi && *pi != -1) 608 *pi++ = -1; 609 usedbits = 0; 610 if (checkword(w, -1, wordpath) == -1) 611 continue; 612 613 st = 1; 614 while (*pw != NULL && (st = strcmp(*pw, w)) < 0) 615 pw++; 616 if (st == 0) /* found it */ 617 continue; 618 if (nmwords == MAXMWORDS || 619 mwordsp + wordlen + 1 >= &mwords[MAXMSPACE]) { 620 warnx("too many words!"); 621 cleanup(); 622 exit(1); 623 } 624 mword[nmwords++] = mwordsp; 625 p = w; 626 while ((*mwordsp++ = *p++) != '\0') 627 ; 628 } 629 } 630 631 /* 632 * Crank up a new game 633 * If the argument is non-null then it is assumed to be a legal board spec 634 * in ascending cube order, oth. make a random board 635 */ 636 void 637 newgame(b) 638 const char *b; 639 { 640 int i, p, q; 641 const char *tmp; 642 int *lm[26]; 643 static const char *cubes[16] = { 644 "ednosw", "aaciot", "acelrs", "ehinps", 645 "eefhiy", "elpstu", "acdemp", "gilruw", 646 "egkluy", "ahmors", "abilty", "adenvz", 647 "bfiorx", "dknotu", "abjmoq", "egintv" 648 }; 649 650 if (b == NULL) { 651 /* 652 * Shake the cubes and make the board 653 */ 654 i = 0; 655 while (i < 100) { 656 p = (int) (random() % 16); 657 q = (int) (random() % 16); 658 if (p != q) { 659 tmp = cubes[p]; 660 cubes[p] = cubes[q]; 661 cubes[q] = tmp; 662 i++; 663 } 664 /* else try again */ 665 } 666 667 for (i = 0; i < 16; i++) 668 board[i] = cubes[i][random() % 6]; 669 } 670 else { 671 for (i = 0; i < 16; i++) 672 board[i] = b[i]; 673 } 674 board[16] = '\0'; 675 676 /* 677 * Set up the map from letter to location(s) 678 * Each list is terminated by a -1 entry 679 */ 680 for (i = 0; i < 26; i++) { 681 lm[i] = letter_map[i]; 682 *lm[i] = -1; 683 } 684 685 for (i = 0; i < 16; i++) { 686 int j; 687 688 j = (int) (board[i] - 'a'); 689 *lm[j] = i; 690 *(++lm[j]) = -1; 691 } 692 693 if (debug) { 694 for (i = 0; i < 26; i++) { 695 int ch, j; 696 697 (void) printf("%c:", 'a' + i); 698 for (j = 0; (ch = letter_map[i][j]) != -1; j++) 699 (void) printf(" %d", ch); 700 (void) printf("\n"); 701 } 702 } 703 704 } 705 706 int 707 compar(p, q) 708 const void *p, *q; 709 { 710 return (strcmp(*(const char *const *)p, *(const char *const *)q)); 711 } 712 713 void 714 usage() 715 { 716 (void) fprintf(stderr, 717 "usage: bog [-bd] [-s#] [-t#] [-w#] [+[+]] [boardspec]\n"); 718 exit(1); 719 } 720