1/*	$NetBSD: mach.c,v 1.20 2009/08/12 05:29:40 dholland 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
38static char sccsid[] = "@(#)mach.c	8.1 (Berkeley) 6/11/93";
39#else
40__RCSID("$NetBSD: mach.c,v 1.20 2009/08/12 05:29:40 dholland 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
64static int ccol, crow, maxw;
65static int colstarts[MAXCOLS], ncolstarts;
66static int lastline;
67static int ncols;
68int nlines;
69
70extern const char *pword[], *mword[];
71extern int ngames, nmwords, npwords, tnmwords, tnpwords;
72extern char board[];
73extern int usedbits, wordpath[];
74extern time_t start_t;
75extern int debug;
76
77static void	cont_catcher(int);
78static int	prwidth(const char *const [], int);
79static void	prword(const char *const [], int);
80static void	stop_catcher(int);
81static void	tty_cleanup(void);
82static int	tty_setup(void);
83static void	tty_showboard(const char *);
84static void	winch_catcher(int);
85static void	getword(char *);
86static void	starttime(void);
87static void	stoptime(void);
88
89
90/*
91 * Do system dependent initialization
92 * This is called once, when the program starts
93 */
94int
95setup(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 */
112void
113cleanup(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 */
122void
123results(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
152static void
153prword(const char *const base[], int indx)
154{
155	printw("%s", base[indx]);
156}
157
158static int
159prwidth(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 */
169char *
170get_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
264int
265inputch(void)
266{
267	return (getch() & 0177);
268}
269
270void
271redraw(void)
272{
273	clearok(stdscr, 1);
274	refresh();
275}
276
277void
278flushin(FILE *fp)
279{
280
281	(void) tcflush(fileno(fp), TCIFLUSH);
282}
283
284static int gone;
285
286/*
287 * Stop the game timer
288 */
289static void
290stoptime(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 */
301static void
302starttime(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 */
317void
318startwords(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 */
334void
335addword(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 */
357void
358badword(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 */
370void
371showword(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 */
398void
399findword(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 */
471void
472showstr(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 */
485static void
486getword(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
542void
543showboard(const char *b)
544{
545	tty_showboard(b);
546}
547
548void
549prompt(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
557static int
558tty_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
581static void
582stop_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
601static void
602cont_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 */
616static void
617winch_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
629static void
630tty_cleanup(void)
631{
632	move(nlines - 1, 0);
633	refresh();
634	noraw();
635	echo();
636	endwin();
637}
638
639static void
640tty_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