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