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