xref: /original-bsd/games/boggle/boggle/bog.c (revision 3705696b)
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