Bring in OpenBSD's boggle(6).
[dragonfly.git] / games / boggle / boggle / mach.c
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 }