1: /* 2: * Copyright (c) 1980 Regents of the University of California. 3: * All rights reserved. The Berkeley software License Agreement 4: * specifies the terms and conditions for redistribution. 5: */ 6: 7: #ifndef lint 8: char copyright[] = 9: "@(#) Copyright (c) 1980 Regents of the University of California.\n\ 10: All rights reserved.\n"; 11: #endif not lint 12: 13: #ifndef lint 14: static char sccsid[] = "@(#)boggle.c 5.1 (Berkeley) 5/30/85"; 15: #endif not lint 16: 17: #include <ctype.h> 18: #include <errno.h> 19: #include <setjmp.h> 20: #include <sgtty.h> 21: #include <signal.h> 22: #include <stdio.h> 23: 24: /* basic parameters */ 25: #define N 4 26: #define SSIZE 200 27: #define MAXWORDS 1000 28: #define CWIDTH 10 29: #define LWIDTH 80 30: 31: /* parameters defined in terms of above */ 32: #define BSIZE (N*N) 33: #define row(x) (x/N) 34: #define col(x) (x%N) 35: 36: /* word being searched for */ 37: int wlength; 38: int numsame; 39: char wbuff [BSIZE+1]; 40: 41: /* tty and process control */ 42: extern int errno; 43: int status; 44: int pipefd[2]; 45: int super = 0; 46: int delct = 1; 47: int zero = 0; 48: int master = 1; 49: int column; 50: int *timept; 51: int timeint[] = {60,60,50,7,1,1,1,0}; 52: long timein; 53: extern long int time(); 54: struct sgttyb origttyb, tempttyb; 55: int ctlecho = 0; 56: int lctlech = LCTLECH; 57: jmp_buf env; 58: 59: /* monitoring variables */ 60: int games; 61: int logfile = -1; 62: long logloc; 63: char logbuff[100] = {"inst\t"}; 64: extern char *ctime(), *getlogin(); 65: extern long lseek(); 66: 67: /* dictionary interface */ 68: char defname[] = "/usr/games/bogdict"; 69: char *dictname = &defname[0]; 70: FILE *dict; 71: 72: /* structures for doing matching */ 73: struct frame { 74: struct frame *parent; 75: int place; 76: }; 77: struct frame stack [SSIZE]; 78: struct frame *level [BSIZE+1]; 79: 80: /* the board and subsidiary structures */ 81: char present [BSIZE+1]; 82: char board [BSIZE]; 83: char olink [BSIZE]; 84: char adj [BSIZE+1][BSIZE]; 85: char occurs [26]; 86: 87: /* the boggle cubes */ 88: char *cube [BSIZE] = { 89: "forixb", "moqabj", "gurilw", "setupl", 90: "cmpdae", "acitao", "slcrae", "romash", 91: "nodesw", "hefiye", "onudtk", "tevign", 92: "anedvz", "pinesh", "abilyt", "gkyleu" 93: }; 94: 95: 96: /* storage for words found */ 97: int ubotch, ustart, wcount; 98: char *word [MAXWORDS]; 99: char *freesp; 100: char space[10000]; 101: 102: endline () 103: { 104: if (column != 0) { 105: putchar('\n'); 106: column = 0; 107: } 108: } 109: 110: timeout () 111: { 112: if (*timept > 0) { 113: signal (SIGALRM, timeout); 114: alarm(*timept++); 115: } 116: putchar('\007'); 117: } 118: 119: interrupt () 120: { 121: signal(SIGINT, interrupt); 122: if (delct++ >= 1) 123: longjmp(env, 1); 124: timept = &zero; 125: } 126: 127: goodbye (stat) 128: int stat; 129: { 130: if (master != 0) { 131: wait(&status); 132: if ( ctlecho & LCTLECH ) { 133: ioctl( fileno(stdin), TIOCLBIS, &lctlech ); 134: } 135: stty(fileno(stdin), &origttyb); 136: } 137: exit(stat); 138: } 139: 140: clearscreen () 141: { 142: stty (fileno(stdin), &tempttyb); 143: printf("\n\033\f\r"); 144: } 145: 146: compare (a, b) 147: char **a, **b; 148: { 149: return(wordcomp(*a, *b)); 150: } 151: 152: wordcomp (p, q) 153: register char *p, *q; 154: { 155: if (*p=='0' && *q!='0') 156: return(-1); 157: if (*p!='0' && *q=='0') 158: return(1); 159: while (*++p == *++q && isalpha(*p)) ; 160: if (!isalpha(*p)) 161: return(-isalpha(*q)); 162: if (!isalpha(*q)) 163: return(1); 164: return(*p-*q); 165: } 166: 167: printinst () 168: { 169: stty (fileno(stdin), &tempttyb); 170: printf("instructions?"); 171: if (getchar() == 'y') { 172: clearscreen(); 173: printf(" The object of Boggle (TM Parker Bros.) is to find, within 3\n"); 174: printf("minutes, as many words as possible in a 4 by 4 grid of letters. Words\n"); 175: printf("may be formed from any sequence of 3 or more adjacent letters in the\n"); 176: printf("grid. The letters may join horizontally, vertically, or diagonally.\n"); 177: printf("However, no position in the grid may be used more than once within any\n"); 178: printf("one word. In competitive play amongst humans, each player is given\n"); 179: printf("credit for those of his words which no other player has found.\n"); 180: printf(" This program is intended for people wishing to sharpen their\n"); 181: printf("skills at Boggle. If you invoke the program with 4 arguments of 4\n"); 182: printf("letters each, (e.g. \"boggle appl epie moth erhd\") the program forms the\n"); 183: printf("obvious Boggle grid and lists all the words from /usr/dict/words found\n"); 184: printf("therein. If you invoke the program without arguments, it will generate\n"); 185: printf("a board for you, let you enter words for 3 minutes, and then tell you\n"); 186: printf("how well you did relative to /usr/dict/words.\n"); 187: printf(" In interactive play, enter your words separated by spaces, tabs,\n"); 188: printf("or newlines. A bell will ring when there is 2:00, 1:00, 0:10, 0:02,\n"); 189: printf("0:01, and 0:00 time left. You may complete any word started before the\n"); 190: printf("expiration of time. You can surrender before time is up by hitting\n"); 191: printf("'break'. While entering words, your erase character is only effective\n"); 192: printf("within the current word and your line kill character is ignored.\n"); 193: printf(" Advanced players may wish to invoke the program with 1 or 2 +'s as\n"); 194: printf("the first argument. The first + removes the restriction that positions\n"); 195: printf("can only be used once in each word. The second + causes a position to\n"); 196: printf("be considered adjacent to itself as well as its (up to) 8 neighbors.\n"); 197: printf("Hit any key to begin.\n"); 198: stty (fileno(stdin), &tempttyb); 199: getchar(); 200: } 201: stty (fileno(stdin), &tempttyb); 202: } 203: 204: setup () 205: { 206: register int i, j; 207: int rd, cd, k; 208: for (i=0; i<BSIZE; i++) { 209: adj[i][i] = super>=2 ? 1 : 0; 210: adj[BSIZE][i] = 1; 211: for (j=0; j<i; j++) { 212: rd = row(i)-row(j); 213: cd = col(i)-col(j); 214: k = 0; 215: switch (rd) { 216: case -1: 217: case 1: 218: if (-1<=cd && cd<=1) 219: k = 1; 220: break; 221: case 0: 222: if (cd==-1 || cd==1) 223: k = 1; 224: break; 225: } 226: adj[i][j] = adj[j][i] = k; 227: } 228: } 229: stack[0].parent = &stack[0]; 230: stack[0].place = BSIZE; 231: level[0] = &stack[0]; 232: level[1] = &stack[1]; 233: } 234: 235: makelists () 236: { 237: register int i, c; 238: for (i=0; i<26; i++) 239: occurs[i] = BSIZE; 240: for (i=0; i<BSIZE; i++) { 241: c = board[i]; 242: olink[i] = occurs[c-'a']; 243: occurs[c-'a'] = i; 244: } 245: } 246: 247: genboard () 248: { 249: register int i, j; 250: for (i=0; i<BSIZE; i++) 251: board[i] = 0; 252: for (i=0; i<BSIZE; i++) { 253: j = rand()%BSIZE; 254: while (board[j] != 0) 255: j = (j+1)%BSIZE; 256: board[j] = cube[i][rand()%6]; 257: } 258: } 259: 260: printboard () 261: { 262: register int i, j; 263: for (i=0; i<N; i++) { 264: printf("\t\t\t\t\b\b"); 265: for (j=0; j<N; j++) { 266: putchar ((putchar(board[i*N+j]) == 'q') ? 'u' : ' '); 267: putchar(' '); 268: } 269: putchar('\n'); 270: } 271: putchar('\n'); 272: } 273: 274: getdword () 275: { 276: /* input: numsame = # chars same as last word */ 277: /* output: numsame = # same chars for next word */ 278: /* word in wbuff[0]...wbuff[wlength-1] */ 279: register int c; 280: register char *p; 281: if (numsame == EOF) 282: return (0); 283: p = &wbuff[numsame]+1; 284: while ((*p++ = c = getc(dict)) != EOF && isalpha(c)) ; 285: numsame = c; 286: wlength = p - &wbuff[2]; 287: return (1); 288: } 289: 290: getuword () 291: { 292: int c; 293: register char *p, *q, *r; 294: numsame = 0; 295: while (*timept>0 && (isspace(c=getchar()) || c==EOF)); 296: if (*timept == 0) 297: return(0); 298: word[wcount++] = freesp; 299: *freesp++ = '0'; 300: r = &wbuff[1]; 301: q = p = freesp; 302: *p++ = c; 303: while (!isspace(c = getchar())) { 304: if (c == EOF) 305: continue; 306: if (c == origttyb.sg_erase) { 307: if (p > q) 308: p--; 309: continue; 310: } 311: *p++ = c; 312: } 313: freesp = p; 314: for (p=q; p<freesp && r<&wbuff[BSIZE]; ) 315: if (islower(c = *p++) && (*r++ = *q++ = c) == 'q' && *p == 'u') 316: p++; 317: *(freesp = q) = '0'; 318: wlength = r-&wbuff[1]; 319: return(1); 320: } 321: 322: aputuword (ways) 323: int ways; 324: { 325: *word[wcount-1] = ways>=10 ? '*' : '0'+ways; 326: } 327: 328: aputword (ways) 329: int ways; 330: { 331: /* store (wbuff, ways) in next slot in space */ 332: register int i; 333: *freesp++ = ways>=10 ? '*' : '0'+ways; 334: for (i=1; i<= wlength; i++) 335: *freesp++ = wbuff[i]; 336: word[++wcount] = freesp; 337: } 338: 339: tputword (ways) 340: int ways; 341: { 342: /* print (wbuff, ways) on terminal */ 343: wbuff[wlength+1] = '0'; 344: wbuff[0] = ways>=10 ? '*' : '0'+ways; 345: outword(&wbuff[0]); 346: } 347: 348: outword (p) 349: register char *p; 350: { 351: register int newcol; 352: register char *q; 353: for (q=p+1; isalpha(*q); ) { 354: putchar(*q); 355: if (*q++ == 'q') { 356: putchar('u'); 357: column++; 358: } 359: } 360: column += q-p-1; 361: if (column > LWIDTH-CWIDTH) { 362: putchar('\n'); 363: column = 0; 364: return; 365: } 366: newcol = ((column+CWIDTH)/CWIDTH)*CWIDTH; 367: while (((column+8)/8)*8 <= newcol) { 368: putchar('\t'); 369: column = ((column+8)/8)*8; 370: } 371: while (column < newcol) { 372: putchar(' '); 373: column++; 374: } 375: } 376: 377: printdiff () 378: { 379: register int c, d, u; 380: char both, donly, uonly; 381: word[wcount] = freesp; 382: *freesp = '0'; 383: both = donly = uonly = column = d = 0; 384: u = ustart; 385: while (d < ubotch) { 386: c = u<wcount ? wordcomp (word[d], word[u]) : -1; 387: if (c == 0) { 388: /* dict and user found same word */ 389: if (both == 0) { 390: both = 1; 391: printf("\t\t\t we both found:\n"); 392: } 393: outword(word[d]); 394: word[d++] = NULL; 395: word[u++] = NULL; 396: } else if (c < 0) { 397: /* dict found it, user didn't */ 398: donly = 1; 399: d++; 400: } else { 401: /* user found it, dict didn't */ 402: uonly = 1; 403: u++; 404: } 405: } 406: endline(); 407: if (donly) { 408: printf("\n\t\t\tI alone found these:\n"); 409: for (d=0; d<ubotch; d++) 410: if (word[d] != NULL) 411: outword(word[d]); 412: endline(); 413: } 414: if (uonly) { 415: printf("\n\t\t\tyou alone found these:\n"); 416: for (u=ustart; u<wcount; u++) 417: if (word[u] != NULL) 418: outword(word[u]); 419: endline(); 420: } 421: if (ubotch < ustart) { 422: printf("\n\t\t\t you botched these:\n"); 423: for (u=ubotch; u<ustart; u++) 424: outword(word[u]); 425: endline(); 426: } 427: } 428: 429: numways (leaf, last) 430: register struct frame *leaf; 431: struct frame *last; 432: { 433: int count; 434: register char *p; 435: register struct frame *node; 436: if (super > 0) 437: return(last-leaf); 438: count = 0; 439: present[BSIZE] = 1; 440: while (leaf < last) { 441: for (p = &present[0]; p < &present[BSIZE]; *p++ = 0); 442: for (node = leaf; present[node->place]++ == 0; node = node->parent); 443: if (node == &stack[0]) 444: count++; 445: leaf++; 446: } 447: return(count); 448: } 449: 450: evalboard (getword, putword) 451: int (*getword)(), (*putword)(); 452: { 453: register struct frame *top; 454: register int l, q; 455: int fo, found; 456: struct frame *parent, *lastparent; 457: char *padj; 458: 459: numsame = found = 0; 460: makelists (); 461: 462: while (1) { 463: l = numsame; 464: if (!(*getword) ()) 465: break; 466: top = level[l+1]; 467: 468: while (1) { 469: level[l+1] = lastparent = top; 470: /* wbuff[1]...wbuff[l] have been matched */ 471: /* level[0],...,level[l] of tree built */ 472: if (l == wlength) { 473: if (wlength >= 3 && (q = numways(level[l], top)) != 0) { 474: (*putword) (q); 475: found++; 476: } 477: l = BSIZE+1; 478: break; 479: } 480: if ((fo = occurs[wbuff[++l]-'a']) == BSIZE) 481: break; 482: /* wbuff[1]...wbuff[l-1] have been matched */ 483: /* level[0],...,level[l-1] of tree built */ 484: for (parent=level[l-1]; parent<lastparent; parent++) { 485: padj = &adj[parent->place][0]; 486: for (q=fo; q!=BSIZE; q=olink[q]) 487: if (padj[q]) { 488: top->parent = parent; 489: top->place = q; 490: if (++top >= &stack[SSIZE]) { 491: printf("stack overflow\n"); 492: goodbye(1); 493: } 494: } 495: } 496: /* were any nodes added? */ 497: if (top == lastparent) 498: break; 499: } 500: 501: /* advance until first l characters of next word are different */ 502: while (numsame >= l && (*getword)()) ; 503: } 504: return(found); 505: } 506: 507: main (argc, argv) 508: int argc; 509: char **argv; 510: { 511: char *q; 512: register char *p; 513: register int i, c; 514: 515: gtty (fileno(stdin), &origttyb); 516: setbuf(stdin, NULL); 517: tempttyb = origttyb; 518: if (setjmp(env) != 0) 519: goodbye(0); 520: signal (SIGINT, interrupt); 521: timein = time(0L); 522: if (argv[0][0] != 'a' && (logfile = open("/usr/games/boglog", 1)) >= 0) { 523: p = &logbuff[5]; 524: q = getlogin(); 525: while (*p++ = *q++); 526: p[-1] = '\t'; 527: q = ctime(&timein); 528: while (*p++ = *q++); 529: logloc = lseek(logfile, 0L, 2); 530: write(logfile, &logbuff[0], p-&logbuff[1]); 531: } 532: if ((dict = fopen(dictname, "r")) == NULL) { 533: printf("can't open %s\n", dictname); 534: goodbye (2); 535: } 536: while ( argc > 1 && ( argv[1][0] == '+' || argv[1][0] == '-' ) ) { 537: if (argv[1][0]=='+') { 538: while(*(argv[1]++) == '+') 539: super++; 540: argc--; 541: argv++; 542: } 543: if ( argv[1][0] == '-' ) { 544: timeint[0] = 60 * ( atol( &argv[1][1] ) - 2 ); 545: if ( timeint[0] <= 0 ) { 546: timeint[0] = 60; 547: } 548: argc--; 549: argv++; 550: } 551: } 552: setup (); 553: switch (argc) { 554: default: punt: 555: printf("usage: boggle [+[+]] [row1 row2 row3 row4]\n"); 556: goodbye (3); 557: case 5: 558: for (i=0; i<BSIZE; i++) { 559: board[i] = c = argv[row(i)+1][col(i)]; 560: if (!islower(c)) { 561: printf("bad board\n"); 562: goto punt; 563: } 564: } 565: printboard(); 566: column = 0; 567: evalboard(getdword, tputword); 568: endline(); 569: if (logfile >= 0) { 570: strncpy(&logbuff[0], "eval", 4); 571: lseek(logfile, logloc, 0); 572: write(logfile, &logbuff[0], 4); 573: } 574: goodbye(0); 575: case 1: 576: tempttyb.sg_flags |= CBREAK; 577: if ( ioctl( fileno(stdin), TIOCLGET, &ctlecho ) == 0 ) { 578: if ( ctlecho & LCTLECH ) { 579: ioctl( fileno(stdin), TIOCLBIC, &lctlech ); 580: } 581: } 582: printinst(); 583: srand((int) timein); 584: while (setjmp(env) == 0) { 585: errno = 0; 586: if (pipe(&pipefd[0]) != 0) { 587: printf("can't create pipe\n"); 588: goodbye(4); 589: } 590: genboard(); 591: delct = wcount = 0; 592: word[0] = freesp = &space[0]; 593: if ((master = fork()) == 0) { 594: close(pipefd[0]); 595: clearscreen(); 596: printboard(); 597: signal(SIGALRM, timeout); 598: timept = &timeint[0]; 599: alarm(*timept++); 600: evalboard(getuword, aputuword); 601: clearscreen(); 602: qsort(&word[0], wcount, sizeof (int), compare); 603: for (i=0; i<wcount; i++) 604: if (i==0 || wordcomp(word[i], word[i-1])!=0) { 605: p = word[i]; 606: while (isalpha(*++p)) ; 607: write (pipefd[1], word[i], p-word[i]); 608: } 609: close(pipefd[1]); 610: goodbye(0); 611: } 612: close(pipefd[1]); 613: rewind(dict); 614: getc(dict); 615: evalboard(getdword, aputword); 616: p = freesp; 617: while ((i = read(pipefd[0], freesp, 512)) != 0) { 618: if (i < 0) 619: if (errno != EINTR) 620: break; 621: else 622: i = 0; 623: freesp += i; 624: } 625: close(pipefd[0]); 626: ustart = ubotch = wcount; 627: while (p < freesp) { 628: word[wcount++] = p; 629: if (*p == '0') 630: ustart = wcount; 631: while (isalpha(*++p)); 632: } 633: wait(&status); 634: if (status != 0) 635: goodbye (5); 636: delct = 1; 637: printdiff(); 638: printboard(); 639: games++; 640: if (logfile >= 0) { 641: sprintf(&logbuff[0], "%4d", games); 642: lseek(logfile, logloc, 0); 643: write(logfile, &logbuff[0], 4); 644: } 645: stty (fileno(stdin), &tempttyb); 646: printf("\nanother game?"); 647: if (getchar() != 'y') { 648: putchar('\n'); 649: break; 650: } 651: stty (fileno(stdin), &tempttyb); 652: } 653: goodbye(0); 654: } 655: }