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