1 /* vi: set tabstop=4 : */ 2 3 /* 4 * bog - the game of boggle 5 * 6 * 6-Mar-89 changed loaddict() to use a large buffer and check for overflow, 7 * minor cleanup 8 */ 9 10 #include "bog.h" 11 12 #include <ctype.h> 13 #include <stdio.h> 14 15 char *version = "bog V1.3 brachman@ubc.csnet 6-Mar-89"; 16 17 struct dictindex dictindex[26]; 18 19 /* 20 * Cube position numbering: 21 * 22 * 0 1 2 3 23 * 4 5 6 7 24 * 8 9 A B 25 * C D E F 26 */ 27 static int adjacency[16][16] = { 28 /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ 29 { 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* 0 */ 30 { 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* 1 */ 31 { 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, /* 2 */ 32 { 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, /* 3 */ 33 { 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, /* 4 */ 34 { 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0 }, /* 5 */ 35 { 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0 }, /* 6 */ 36 { 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, /* 7 */ 37 { 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0 }, /* 8 */ 38 { 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0 }, /* 9 */ 39 { 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1 }, /* A */ 40 { 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1 }, /* B */ 41 { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0 }, /* C */ 42 { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0 }, /* D */ 43 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1 }, /* E */ 44 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0 } /* F */ 45 }; 46 47 static int letter_map[26][16]; 48 49 char board[17]; 50 int wordpath[MAXWORDLEN + 1]; 51 int wordlen; /* Length of last word returned by nextword() */ 52 int usedbits; 53 54 char *pword[MAXPWORDS], pwords[MAXPSPACE], *pwordsp; 55 int npwords; 56 57 char *mword[MAXMWORDS], mwords[MAXMSPACE], *mwordsp; 58 int nmwords; 59 60 int ngames = 0; 61 int tnmwords = 0, tnpwords = 0; 62 63 #ifdef TIMER 64 #include <setjmp.h> 65 66 jmp_buf env; 67 #endif TIMER 68 69 long start_t; 70 71 static FILE *dictfp = (FILE *) NULL; 72 73 int batch; 74 int debug; 75 int minlength; 76 int reuse; 77 int tlimit; 78 79 char *batchword(), *getline(); 80 81 long atol(); 82 long random(); 83 84 main(argc, argv) 85 int argc; 86 char **argv; 87 { 88 int done, i, selfuse, sflag; 89 char *bspec, *p; 90 long seed; 91 FILE *opendict(); 92 93 debug = 0; 94 bspec = (char *) NULL; 95 reuse = 0; 96 batch = 0; 97 selfuse = 0; 98 minlength = 3; 99 tlimit = 180; /* 3 minutes is standard */ 100 sflag = 0; 101 102 for (i = 1; i < argc; i++) { 103 if (argv[i][0] == '-') { 104 switch (argv[i][1]) { 105 case 'b': 106 batch = 1; 107 break; 108 case 'd': 109 debug = 1; 110 break; 111 case 's': 112 sflag = 1; 113 seed = atol(&argv[i][2]); 114 break; 115 case 't': 116 if ((tlimit = atoi(&argv[i][2])) < 1) { 117 (void) fprintf(stderr, "Bad time limit\n"); 118 exit(1); 119 } 120 break; 121 case 'w': 122 if ((minlength = atoi(&argv[i][2])) < 3) { 123 (void) fprintf(stderr, "Min word length must be > 2\n"); 124 exit(1); 125 } 126 break; 127 default: 128 usage(); 129 /*NOTREACHED*/ 130 } 131 } 132 else if (strcmp(argv[i], "+") == 0) 133 reuse = 1; 134 else if (strcmp(argv[i], "++") == 0) 135 selfuse = 1; 136 else if (islower(argv[i][0])) { 137 if (strlen(argv[i]) != 16) { 138 usage(); 139 /*NOTREACHED*/ 140 } 141 /* This board is assumed to be valid... */ 142 bspec = argv[i]; 143 } 144 else { 145 usage(); 146 /*NOREACHED*/ 147 } 148 } 149 150 if (batch && bspec == (char *) NULL) { 151 (void) fprintf(stderr, "Must give both -b and a board setup\n"); 152 exit(1); 153 } 154 155 if (selfuse) { 156 for (i = 0; i < 16; i++) 157 adjacency[i][i] = 1; 158 } 159 160 if (batch) { 161 newgame(bspec); 162 while ((p = batchword(stdin)) != (char *) NULL) 163 (void) printf("%s\n", p); 164 } 165 else { 166 setup(sflag, seed); 167 prompt("Loading the dictionary..."); 168 if ((dictfp = opendict(DICT)) == (FILE *) NULL) { 169 (void) fprintf(stderr, "Can't load %s\n", DICT); 170 cleanup(); 171 exit(1); 172 } 173 #ifdef LOADDICT 174 if (loaddict(dictfp) < 0) { 175 (void) fprintf(stderr, "Can't load %s\n", DICT); 176 cleanup(); 177 exit(1); 178 } 179 (void) fclose(dictfp); 180 dictfp = (FILE *) NULL; 181 #endif 182 if (loadindex(DICTINDEX) < 0) { 183 (void) fprintf(stderr, "Can't load %s\n", DICTINDEX); 184 cleanup(); 185 exit(1); 186 } 187 188 prompt("Type <space> to begin..."); 189 while (inputch() != ' ') 190 ; 191 192 done = 0; 193 while (!done) { 194 newgame(bspec); 195 bspec = (char *) NULL; /* reset for subsequent games */ 196 playgame(); 197 prompt("Type <space> to continue, any cap to quit..."); 198 delay(50); /* wait for user to quit typing */ 199 flushin(stdin); 200 while (1) { 201 int ch; 202 203 ch = inputch(); 204 if (ch == '\033') 205 findword(); 206 else if (ch == '\014' || ch == '\022') /* ^l or ^r */ 207 redraw(); 208 else { 209 if (isupper(ch)) { 210 done = 1; 211 break; 212 } 213 if (ch == ' ') 214 break; 215 } 216 } 217 } 218 cleanup(); 219 } 220 exit(0); 221 } 222 223 /* 224 * Read a line from the given stream and check if it is legal 225 * Return a pointer to a legal word or a null pointer when EOF is reached 226 */ 227 char * 228 batchword(fp) 229 FILE *fp; 230 { 231 register int *p, *q; 232 register char *w; 233 char *nextword(); 234 235 q = &wordpath[MAXWORDLEN + 1]; 236 p = wordpath; 237 while (p < q) 238 *p++ = -1; 239 while ((w = nextword(fp)) != (char *) NULL) { 240 if (wordlen < minlength) 241 continue; 242 p = wordpath; 243 while (p < q && *p != -1) 244 *p++ = -1; 245 usedbits = 0; 246 if (checkword(w, -1, wordpath) != -1) 247 return(w); 248 } 249 return((char *) NULL); 250 } 251 252 /* 253 * Play a single game 254 * Reset the word lists from last game 255 * Keep track of the running stats 256 */ 257 playgame() 258 { 259 /* Can't use register variables if setjmp() is used! */ 260 int i, *p, *q; 261 long t; 262 char buf[MAXWORDLEN + 1]; 263 int compar(); 264 265 ngames++; 266 npwords = 0; 267 pwordsp = pwords; 268 nmwords = 0; 269 mwordsp = mwords; 270 271 time(&start_t); 272 273 q = &wordpath[MAXWORDLEN + 1]; 274 p = wordpath; 275 while (p < q) 276 *p++ = -1; 277 showboard(board); 278 startwords(); 279 if (setjmp(env)) { 280 badword(); 281 goto timesup; 282 } 283 284 while (1) { 285 if (getline(buf) == (char *) NULL) { 286 if (feof(stdin)) 287 clearerr(stdin); 288 break; 289 } 290 time(&t); 291 if (t - start_t >= tlimit) { 292 badword(); 293 break; 294 } 295 if (buf[0] == '\0') { 296 int remaining; 297 298 remaining = tlimit - (int) (t - start_t); 299 (void) sprintf(buf, "%d:%02d", remaining / 60, remaining % 60); 300 showstr(buf, 1); 301 continue; 302 } 303 if (strlen(buf) < minlength) { 304 badword(); 305 continue; 306 } 307 308 p = wordpath; 309 while (p < q && *p != -1) 310 *p++ = -1; 311 usedbits = 0; 312 313 if (checkword(buf, -1, wordpath) < 0) 314 badword(); 315 else { 316 if (debug) { 317 (void) printf("["); 318 for (i = 0; wordpath[i] != -1; i++) 319 (void) printf(" %d", wordpath[i]); 320 (void) printf(" ]\n"); 321 } 322 for (i = 0; i < npwords; i++) { 323 if (strcmp(pword[i], buf) == 0) 324 break; 325 } 326 if (i != npwords) { /* already used the word */ 327 badword(); 328 showword(i); 329 } 330 else if (!validword(buf)) 331 badword(); 332 else { 333 int len; 334 335 len = strlen(buf) + 1; 336 if (npwords == MAXPWORDS - 1 || 337 pwordsp + len >= &pwords[MAXPSPACE]) { 338 (void) fprintf(stderr, "Too many words!\n"); 339 cleanup(); 340 exit(1); 341 } 342 pword[npwords++] = pwordsp; 343 (void) strcpy(pwordsp, buf); 344 pwordsp += len; 345 addword(buf); 346 } 347 } 348 } 349 350 timesup: ; 351 352 /* 353 * Sort the player's words and terminate the list with a null 354 * entry to help out checkdict() 355 */ 356 qsort(pword, npwords, sizeof(pword[0]), compar); 357 pword[npwords] = (char *) NULL; 358 359 /* 360 * These words don't need to be sorted since the dictionary is sorted 361 */ 362 checkdict(); 363 364 tnmwords += nmwords; 365 tnpwords += npwords; 366 367 results(); 368 } 369 370 /* 371 * Check if the given word is present on the board, with the constraint 372 * that the first letter of the word is adjacent to square 'prev' 373 * Keep track of the current path of squares for the word 374 * A 'q' must be followed by a 'u' 375 * Words must end with a null 376 * Return 1 on success, -1 on failure 377 */ 378 checkword(word, prev, path) 379 char *word; 380 int prev, *path; 381 { 382 register char *p, *q; 383 register int i, *lm; 384 385 if (debug) { 386 (void) printf("checkword(%s, %d, [", word, prev); 387 for (i = 0; wordpath[i] != -1; i++) 388 (void) printf(" %d", wordpath[i]); 389 (void) printf(" ]\n"); 390 } 391 392 if (*word == '\0') 393 return(1); 394 395 lm = letter_map[*word - 'a']; 396 397 if (prev == -1) { 398 char subword[MAXWORDLEN + 1]; 399 400 /* 401 * Check for letters not appearing in the cube to eliminate some 402 * recursive calls 403 * Fold 'qu' into 'q' 404 */ 405 p = word; 406 q = subword; 407 while (*p != '\0') { 408 if (*letter_map[*p - 'a'] == -1) 409 return(-1); 410 *q++ = *p; 411 if (*p++ == 'q') { 412 if (*p++ != 'u') 413 return(-1); 414 } 415 } 416 *q = '\0'; 417 while (*lm != -1) { 418 *path = *lm; 419 usedbits |= (1 << *lm); 420 if (checkword(subword + 1, *lm, path + 1) > 0) 421 return(1); 422 usedbits &= ~(1 << *lm); 423 lm++; 424 } 425 return(-1); 426 } 427 428 /* 429 * A cube is only adjacent to itself in the adjacency matrix if selfuse 430 * was set, so a cube can't be used twice in succession if only the reuse 431 * flag is set 432 */ 433 for (i = 0; lm[i] != -1; i++) { 434 if (adjacency[prev][lm[i]]) { 435 int used; 436 437 used = 1 << lm[i]; 438 /* If necessary, check if the square has already been used */ 439 if (!reuse && (usedbits & used)) 440 continue; 441 *path = lm[i]; 442 usedbits |= used; 443 if (checkword(word + 1, lm[i], path + 1) > 0) 444 return(1); 445 usedbits &= ~used; 446 } 447 } 448 *path = -1; /* in case of a backtrack */ 449 return(-1); 450 } 451 452 /* 453 * A word is invalid if it is not in the dictionary 454 * At this point it is already known that the word can be formed from 455 * the current board 456 */ 457 validword(word) 458 char *word; 459 { 460 register int j; 461 register char *q, *w; 462 char *nextword(); 463 464 j = word[0] - 'a'; 465 if (dictseek(dictfp, dictindex[j].start, 0) < 0) { 466 (void) fprintf(stderr, "Seek error\n"); 467 cleanup(); 468 exit(1); 469 } 470 471 while ((w = nextword(dictfp)) != (char *) NULL) { 472 int ch; 473 474 if (*w != word[0]) /* end of words starting with word[0] */ 475 break; 476 q = word; 477 while ((ch = *w++) == *q++ && ch != '\0') 478 ; 479 if (*(w - 1) == '\0' && *(q - 1) == '\0') 480 return(1); 481 } 482 if (dictfp != (FILE *) NULL && feof(dictfp)) /* Special case for z's */ 483 clearerr(dictfp); 484 return(0); 485 } 486 487 /* 488 * Check each word in the dictionary against the board 489 * Delete words from the machine list that the player has found 490 * Assume both the dictionary and the player's words are already sorted 491 */ 492 checkdict() 493 { 494 register char *p, **pw, *w; 495 register int i; 496 int prevch, previndex, *pi, *qi, st; 497 498 mwordsp = mwords; 499 nmwords = 0; 500 pw = pword; 501 prevch ='a'; 502 qi = &wordpath[MAXWORDLEN + 1]; 503 504 (void) dictseek(dictfp, 0L, 0); 505 while ((w = nextword(dictfp)) != (char *) NULL) { 506 if (wordlen < minlength) 507 continue; 508 if (*w != prevch) { 509 /* 510 * If we've moved on to a word with a different first letter 511 * then we can speed things up by skipping all words starting 512 * with a letter that doesn't appear in the cube 513 */ 514 i = (int) (*w - 'a'); 515 while (i < 26 && letter_map[i][0] == -1) 516 i++; 517 if (i == 26) 518 break; 519 previndex = prevch - 'a'; 520 prevch = i + 'a'; 521 /* 522 * Fall through if the word's first letter appears in the cube 523 * (i.e., if we can't skip ahead), otherwise seek to the 524 * beginning of words in the dictionary starting with the 525 * next letter (alphabetically) appearing in the cube and then 526 * read the first word 527 */ 528 if (i != previndex + 1) { 529 if (dictseek(dictfp, dictindex[i].start, 0) < 0) { 530 (void) fprintf(stderr, "Seek error in checkdict()\n"); 531 cleanup(); 532 exit(1); 533 } 534 continue; 535 } 536 } 537 538 pi = wordpath; 539 while (pi < qi && *pi != -1) 540 *pi++ = -1; 541 usedbits = 0; 542 if (checkword(w, -1, wordpath) == -1) 543 continue; 544 545 st = 1; 546 while (*pw != (char *) NULL && (st = strcmp(*pw, w)) < 0) 547 pw++; 548 if (st == 0) /* found it */ 549 continue; 550 if (nmwords == MAXMWORDS || 551 mwordsp + wordlen + 1 >= &mwords[MAXMSPACE]) { 552 (void) fprintf(stderr, "Too many words!\n"); 553 cleanup(); 554 exit(1); 555 } 556 mword[nmwords++] = mwordsp; 557 p = w; 558 while (*mwordsp++ = *p++) 559 ; 560 } 561 } 562 563 /* 564 * Crank up a new game 565 * If the argument is non-null then it is assumed to be a legal board spec 566 * in ascending cube order, oth. make a random board 567 */ 568 newgame(b) 569 char *b; 570 { 571 register int i, p, q; 572 char *tmp; 573 int *lm[26]; 574 static char *cubes[16] = { 575 "ednosw", "aaciot", "acelrs", "ehinps", 576 "eefhiy", "elpstu", "acdemp", "gilruw", 577 "egkluy", "ahmors", "abilty", "adenvz", 578 "bfiorx", "dknotu", "abjmoq", "egintv" 579 }; 580 581 if (b == (char *) NULL) { 582 /* 583 * Shake the cubes and make the board 584 */ 585 i = 0; 586 while (i < 100) { 587 p = (int) (random() % 16); 588 q = (int) (random() % 16); 589 if (p != q) { 590 tmp = cubes[p]; 591 cubes[p] = cubes[q]; 592 cubes[q] = tmp; 593 i++; 594 } 595 /* else try again */ 596 } 597 598 for (i = 0; i < 16; i++) 599 board[i] = cubes[i][random() % 6]; 600 } 601 else { 602 for (i = 0; i < 16; i++) 603 board[i] = b[i]; 604 } 605 board[16] = '\0'; 606 607 /* 608 * Set up the map from letter to location(s) 609 * Each list is terminated by a -1 entry 610 */ 611 for (i = 0; i < 26; i++) { 612 lm[i] = letter_map[i]; 613 *lm[i] = -1; 614 } 615 616 for (i = 0; i < 16; i++) { 617 register int j; 618 619 j = (int) (board[i] - 'a'); 620 *lm[j] = i; 621 *(++lm[j]) = -1; 622 } 623 624 if (debug) { 625 for (i = 0; i < 26; i++) { 626 int ch, j; 627 628 (void) printf("%c:", 'a' + i); 629 for (j = 0; (ch = letter_map[i][j]) != -1; j++) 630 (void) printf(" %d", ch); 631 (void) printf("\n"); 632 } 633 } 634 635 } 636 637 compar(p, q) 638 char **p, **q; 639 { 640 return(strcmp(*p, *q)); 641 } 642 643 usage() 644 { 645 (void) fprintf(stderr, 646 "Usage: bog [-b] [-d] [-s#] [-t#] [-w#] [+[+]] [boardspec]\n"); 647 (void) fprintf(stderr, "-b: 'batch mode' (boardspec must be present)\n"); 648 (void) fprintf(stderr, "-d: debug\n"); 649 (void) fprintf(stderr, "-s#: use # as the random number seed\n"); 650 (void) fprintf(stderr, "-t#: time limit is # seconds\n"); 651 (void) fprintf(stderr, "-w#: minimum word length is # letters\n"); 652 (void) fprintf(stderr, "+: can reuse a cube, but not twice in succession\n"); 653 (void) fprintf(stderr, "++: can reuse cubes arbitrarily\n"); 654 (void) fprintf(stderr, "boardspec: the first board to use (use 'q' for 'qu')\n"); 655 exit(1); 656 } 657 658