Home | History | Annotate | Line # | Download | only in boggle
      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
     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
    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
    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
    153 prword(const char *const base[], int indx)
    154 {
    155 	printw("%s", base[indx]);
    156 }
    157 
    158 static int
    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 *
    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
    265 inputch(void)
    266 {
    267 	return (getch() & 0177);
    268 }
    269 
    270 void
    271 redraw(void)
    272 {
    273 	clearok(stdscr, 1);
    274 	refresh();
    275 }
    276 
    277 void
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    543 showboard(const char *b)
    544 {
    545 	tty_showboard(b);
    546 }
    547 
    548 void
    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
    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
    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
    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
    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
    630 tty_cleanup(void)
    631 {
    632 	move(nlines - 1, 0);
    633 	refresh();
    634 	noraw();
    635 	echo();
    636 	endwin();
    637 }
    638 
    639 static void
    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