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