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