xref: /netbsd/games/boggle/boggle/mach.c (revision bf9ec67e)
1 /*	$NetBSD: mach.c,v 1.11 2001/02/05 00:27:35 christos Exp $	*/
2 
3 /*-
4  * Copyright (c) 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Barry Brachman.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *	This product includes software developed by the University of
21  *	California, Berkeley and its contributors.
22  * 4. Neither the name of the University nor the names of its contributors
23  *    may be used to endorse or promote products derived from this software
24  *    without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36  * SUCH DAMAGE.
37  */
38 
39 #include <sys/cdefs.h>
40 #ifndef lint
41 #if 0
42 static char sccsid[] = "@(#)mach.c	8.1 (Berkeley) 6/11/93";
43 #else
44 __RCSID("$NetBSD: mach.c,v 1.11 2001/02/05 00:27:35 christos Exp $");
45 #endif
46 #endif /* not lint */
47 
48 /*
49  * Terminal interface
50  *
51  * Input is raw and unechoed
52  */
53 #include <sys/ioctl.h>
54 
55 #include <ctype.h>
56 #include <curses.h>
57 #include <fcntl.h>
58 #include <signal.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <termios.h>
63 #include <time.h>
64 
65 #include "bog.h"
66 #include "extern.h"
67 
68 static int ccol, crow, maxw;
69 static int colstarts[MAXCOLS], ncolstarts;
70 static int lastline;
71 int ncols, nlines;
72 
73 extern const char *pword[], *mword[];
74 extern int ngames, nmwords, npwords, tnmwords, tnpwords;
75 extern char board[];
76 extern int usedbits, wordpath[];
77 extern time_t start_t;
78 extern int debug;
79 
80 static void	cont_catcher __P((int));
81 static int	prwidth __P((const char *const [], int));
82 static void	prword __P((const char *const [], int));
83 static void	stop_catcher __P((int));
84 static void	tty_cleanup __P((void));
85 static int	tty_setup __P((void));
86 static void	tty_showboard __P((const char *));
87 static void	winch_catcher __P((int));
88 
89 /*
90  * Do system dependent initialization
91  * This is called once, when the program starts
92  */
93 int
94 setup(sflag, seed)
95 	int sflag;
96 	time_t seed;
97 {
98 	if (tty_setup() < 0)
99 		return(-1);
100 
101 	if (!sflag)
102 		time(&seed);
103 	srandom(seed);
104 	if (debug)
105 		(void) printf("seed = %ld\n", (long) seed);
106 	return(0);
107 }
108 
109 /*
110  * Do system dependent clean up
111  * This is called once, just before the program terminates
112  */
113 void
114 cleanup()
115 {
116 	tty_cleanup();
117 }
118 
119 /*
120  * Display the player's word list, the list of words not found, and the running
121  * stats
122  */
123 void
124 results()
125 {
126 	int col, row;
127 	int denom1, denom2;
128 
129 	move(LIST_LINE, LIST_COL);
130 	clrtobot();
131 	printw("Words you found (%d):", npwords);
132 	refresh();
133 	move(LIST_LINE + 1, LIST_COL);
134 	prtable(pword, npwords, 0, ncols, prword, prwidth);
135 
136 	getyx(stdscr, row, col);
137 	move(row + 1, col);
138 	printw("Words you missed (%d):", nmwords);
139 	refresh();
140 	move(row + 2, col);
141 	prtable(mword, nmwords, 0, ncols, prword, prwidth);
142 
143 	denom1 = npwords + nmwords;
144 	denom2 = tnpwords + tnmwords;
145 
146 	move(SCORE_LINE, SCORE_COL);
147 	printw("Percentage: %0.2f%% (%0.2f%% over %d game%s)\n",
148         denom1 ? (100.0 * npwords) / (double) (npwords + nmwords) : 0.0,
149         denom2 ? (100.0 * tnpwords) / (double) (tnpwords + tnmwords) : 0.0,
150         ngames, ngames > 1 ? "s" : "");
151 }
152 
153 static void
154 prword(base, indx)
155 	const char *const base[];
156 	int indx;
157 {
158 	printw("%s", base[indx]);
159 }
160 
161 static int
162 prwidth(base, indx)
163 	const char *const base[];
164 	int indx;
165 {
166 	return (strlen(base[indx]));
167 }
168 
169 /*
170  * Main input routine
171  *
172  * - doesn't accept words longer than MAXWORDLEN or containing caps
173  */
174 char *
175 getline(q)
176 	char *q;
177 {
178 	int ch, done;
179 	char *p;
180 	int row, col;
181 
182 	p = q;
183 	done = 0;
184 	while (!done) {
185 		ch = timerch();
186 		switch (ch) {
187 		case '\n':
188 		case '\r':
189 		case ' ':
190 			done = 1;
191 			break;
192 		case '\033':
193 			findword();
194 			break;
195 		case '\177':			/* <del> */
196 		case '\010':			/* <bs> */
197 			if (p == q)
198 				break;
199 			p--;
200 			getyx(stdscr, row, col);
201 			move(row, col - 1);
202 			clrtoeol();
203 			refresh();
204 			break;
205 		case '\025':			/* <^u> */
206 		case '\027':			/* <^w> */
207 			if (p == q)
208 				break;
209 			getyx(stdscr, row, col);
210 			move(row, col - (int) (p - q));
211 			p = q;
212 			clrtoeol();
213 			refresh();
214 			break;
215 #ifdef SIGTSTP
216 		case '\032':			/* <^z> */
217 			stop_catcher(0);
218 			break;
219 #endif
220 		case '\023':			/* <^s> */
221 			stoptime();
222 			printw("<PAUSE>");
223 			refresh();
224 			while ((ch = inputch()) != '\021' && ch != '\023')
225 				;
226 			move(crow, ccol);
227 			clrtoeol();
228 			refresh();
229 			starttime();
230 			break;
231 		case '\003':			/* <^c> */
232 			cleanup();
233 			exit(0);
234 			/*NOTREACHED*/
235 		case '\004':			/* <^d> */
236 			done = 1;
237 			ch = EOF;
238 			break;
239 		case '\014':			/* <^l> */
240 		case '\022':			/* <^r> */
241 			redraw();
242 			break;
243 		case '?':
244 			stoptime();
245 			if (help() < 0)
246 				showstr("Can't open help file", 1);
247 			starttime();
248 			break;
249 		default:
250 			if (!islower(ch))
251 				break;
252 			if ((int) (p - q) == MAXWORDLEN) {
253 				p = q;
254 				badword();
255 				break;
256 			}
257 			*p++ = ch;
258 			addch(ch);
259 			refresh();
260 			break;
261 		}
262 	}
263 	*p = '\0';
264 	if (ch == EOF)
265 		return((char *) NULL);
266 	return(q);
267 }
268 
269 int
270 inputch()
271 {
272 	return (getch() & 0177);
273 }
274 
275 void
276 redraw()
277 {
278 	clearok(stdscr, 1);
279 	refresh();
280 }
281 
282 void
283 flushin(fp)
284 	FILE *fp;
285 {
286 
287 	(void) tcflush(fileno(fp), TCIFLUSH);
288 }
289 
290 static int gone;
291 
292 /*
293  * Stop the game timer
294  */
295 void
296 stoptime()
297 {
298 	time_t t;
299 
300 	(void)time(&t);
301 	gone = (int) (t - start_t);
302 }
303 
304 /*
305  * Restart the game timer
306  */
307 void
308 starttime()
309 {
310 	time_t t;
311 
312 	(void)time(&t);
313 	start_t = t - (long) gone;
314 }
315 
316 /*
317  * Initialize for the display of the player's words as they are typed
318  * This display starts at (LIST_LINE, LIST_COL) and goes "down" until the last
319  * line.  After the last line a new column is started at LIST_LINE
320  * Keep track of each column position for showword()
321  * There is no check for exceeding COLS
322  */
323 void
324 startwords()
325 {
326 	crow = LIST_LINE;
327 	ccol = LIST_COL;
328 	maxw = 0;
329 	ncolstarts = 1;
330 	colstarts[0] = LIST_COL;
331 	move(LIST_LINE, LIST_COL);
332 	refresh();
333 }
334 
335 /*
336  * Add a word to the list and start a new column if necessary
337  * The maximum width of the current column is maintained so we know where
338  * to start the next column
339  */
340 void
341 addword(w)
342 	const char *w;
343 {
344 	int n;
345 
346 	if (crow == lastline) {
347 		crow = LIST_LINE;
348 		ccol += (maxw + 5);
349 		colstarts[ncolstarts++] = ccol;
350 		maxw = 0;
351 		move(crow, ccol);
352 	}
353 	else {
354 		move(++crow, ccol);
355 		if ((n = strlen(w)) > maxw)
356 			maxw = n;
357 	}
358 	refresh();
359 }
360 
361 /*
362  * The current word is unacceptable so erase it
363  */
364 void
365 badword()
366 {
367 
368 	move(crow, ccol);
369 	clrtoeol();
370 	refresh();
371 }
372 
373 /*
374  * Highlight the nth word in the list (starting with word 0)
375  * No check for wild arg
376  */
377 void
378 showword(n)
379 	int n;
380 {
381 	int col, row;
382 
383 	row = LIST_LINE + n % (lastline - LIST_LINE + 1);
384 	col = colstarts[n / (lastline - LIST_LINE + 1)];
385 	move(row, col);
386 	standout();
387 	printw("%s", pword[n]);
388 	standend();
389 	move(crow, ccol);
390 	refresh();
391 	delay(15);
392 	move(row, col);
393 	printw("%s", pword[n]);
394 	move(crow, ccol);
395 	refresh();
396 }
397 
398 /*
399  * Get a word from the user and check if it is in either of the two
400  * word lists
401  * If it's found, show the word on the board for a short time and then
402  * erase the word
403  *
404  * Note: this function knows about the format of the board
405  */
406 void
407 findword()
408 {
409 	int c, col, found, i, r, row;
410 	char buf[MAXWORDLEN + 1];
411 
412 	getyx(stdscr, r, c);
413 	getword(buf);
414 	found = 0;
415 	for (i = 0; i < npwords; i++) {
416 		if (strcmp(buf, pword[i]) == 0) {
417 			found = 1;
418 			break;
419 		}
420 	}
421 	if (!found) {
422 		for (i = 0; i < nmwords; i++) {
423 			if (strcmp(buf, mword[i]) == 0) {
424 				found = 1;
425 				break;
426 			}
427 		}
428 	}
429 	for (i = 0; i < MAXWORDLEN; i++)
430 		wordpath[i] = -1;
431 	usedbits = 0;
432 	if (!found || checkword(buf, -1, wordpath) == -1) {
433 		move(r, c);
434 		clrtoeol();
435 		addstr("[???]");
436 		refresh();
437 		delay(10);
438 		move(r, c);
439 		clrtoeol();
440 		refresh();
441 		return;
442 	}
443 
444 	standout();
445 	for (i = 0; wordpath[i] != -1; i++) {
446 		row = BOARD_LINE + (wordpath[i] / 4) * 2 + 1;
447 		col = BOARD_COL + (wordpath[i] % 4) * 4 + 2;
448 		move(row, col);
449 		if (board[wordpath[i]] == 'q')
450 			printw("Qu");
451 		else
452 			printw("%c", toupper(board[wordpath[i]]));
453 		move(r, c);
454 		refresh();
455 		delay(5);
456 	}
457 
458 	standend();
459 
460 	for (i = 0; wordpath[i] != -1; i++) {
461 		row = BOARD_LINE + (wordpath[i] / 4) * 2 + 1;
462 		col = BOARD_COL + (wordpath[i] % 4) * 4 + 2;
463 		move(row, col);
464 		if (board[wordpath[i]] == 'q')
465 			printw("Qu");
466 		else
467 			printw("%c", toupper(board[wordpath[i]]));
468 	}
469 	move(r, c);
470 	clrtoeol();
471 	refresh();
472 }
473 
474 /*
475  * Display a string at the current cursor position for the given number of secs
476  */
477 void
478 showstr(str, delaysecs)
479 	const char *str;
480 	int delaysecs;
481 {
482 	addstr(str);
483 	refresh();
484 	delay(delaysecs * 10);
485 	move(crow, ccol);
486 	clrtoeol();
487 	refresh();
488 }
489 
490 void
491 putstr(s)
492 	const char *s;
493 {
494 	addstr(s);
495 }
496 
497 /*
498  * Get a valid word and put it in the buffer
499  */
500 void
501 getword(q)
502 	char *q;
503 {
504 	int ch, col, done, i, row;
505 	char *p;
506 
507 	done = 0;
508 	i = 0;
509 	p = q;
510 	addch('[');
511 	refresh();
512 	while (!done && i < MAXWORDLEN - 1) {
513 		ch = getch() & 0177;
514 		switch (ch) {
515 		case '\177':			/* <del> */
516 		case '\010':			/* <bs> */
517 			if (p == q)
518 				break;
519 			p--;
520 			getyx(stdscr, row, col);
521 			move(row, col - 1);
522 			clrtoeol();
523 			break;
524 		case '\025':			/* <^u> */
525 		case '\027':			/* <^w> */
526 			if (p == q)
527 				break;
528 			getyx(stdscr, row, col);
529 			move(row, col - (int) (p - q));
530 			p = q;
531 			clrtoeol();
532 			break;
533 		case ' ':
534 		case '\n':
535 		case '\r':
536 			done = 1;
537 			break;
538 		case '\014':			/* <^l> */
539 		case '\022':			/* <^r> */
540 			clearok(stdscr, 1);
541 			refresh();
542 			break;
543 		default:
544 			if (islower(ch)) {
545 				*p++ = ch;
546 				addch(ch);
547 				i++;
548 			}
549 			break;
550 		}
551 		refresh();
552 	}
553 	*p = '\0';
554 	addch(']');
555 	refresh();
556 }
557 
558 void
559 showboard(b)
560 	const char *b;
561 {
562 	tty_showboard(b);
563 }
564 
565 void
566 prompt(mesg)
567 	const char *mesg;
568 {
569 	move(PROMPT_LINE, PROMPT_COL);
570 	printw("%s", mesg);
571 	move(PROMPT_LINE + 1, PROMPT_COL);
572 	refresh();
573 }
574 
575 static int
576 tty_setup()
577 {
578 	initscr();
579 	raw();
580 	noecho();
581 
582 	/*
583 	 * Does curses look at the winsize structure?
584 	 * Should handle SIGWINCH ...
585 	 */
586 	nlines = LINES;
587 	lastline = nlines - 1;
588 	ncols = COLS;
589 
590 	signal(SIGTSTP, stop_catcher);
591 	signal(SIGCONT, cont_catcher);
592 	signal(SIGWINCH, winch_catcher);
593 	return(0);
594 }
595 
596 static void
597 stop_catcher(signo)
598 	int signo __attribute__((__unused__));
599 {
600 	sigset_t sigset, osigset;
601 
602 	stoptime();
603 	noraw();
604 	echo();
605 	move(nlines - 1, 0);
606 	refresh();
607 
608 	signal(SIGTSTP, SIG_DFL);
609 	sigemptyset(&sigset);
610 	sigaddset(&sigset, SIGTSTP);
611 	sigprocmask(SIG_UNBLOCK, &sigset, &osigset);
612 	kill(0, SIGTSTP);
613 	sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0);
614 	signal(SIGTSTP, stop_catcher);
615 }
616 
617 static void
618 cont_catcher(signo)
619 	int signo __attribute__((__unused__));
620 {
621 	noecho();
622 	raw();
623 	clearok(stdscr, 1);
624 	move(crow, ccol);
625 	refresh();
626 	starttime();
627 }
628 
629 /*
630  * The signal is caught but nothing is done about it...
631  * It would mean reformatting the entire display
632  */
633 static void
634 winch_catcher(signo)
635 	int signo __attribute__((__unused__));
636 {
637 	struct winsize win;
638 
639 	(void) signal(SIGWINCH, winch_catcher);
640 	(void) ioctl(fileno(stdout), TIOCGWINSZ, &win);
641 	/*
642 	LINES = win.ws_row;
643 	COLS = win.ws_col;
644 	*/
645 }
646 
647 static void
648 tty_cleanup()
649 {
650 	move(nlines - 1, 0);
651 	refresh();
652 	noraw();
653 	echo();
654 	endwin();
655 }
656 
657 static void
658 tty_showboard(b)
659 	const char *b;
660 {
661 	int i;
662 	int line;
663 
664 	clear();
665 	move(BOARD_LINE, BOARD_COL);
666 	line = BOARD_LINE;
667 	printw("+---+---+---+---+");
668 	move(++line, BOARD_COL);
669 	for (i = 0; i < 16; i++) {
670 		if (b[i] == 'q')
671 			printw("| Qu");
672 		else
673 			printw("| %c ", toupper(b[i]));
674 		if ((i + 1) % 4 == 0) {
675 			printw("|");
676 			move(++line, BOARD_COL);
677 			printw("+---+---+---+---+");
678 			move(++line, BOARD_COL);
679 		}
680 	}
681 	move(SCORE_LINE, SCORE_COL);
682 	printw("Type '?' for help");
683 	refresh();
684 }
685