bridge(4): document net.link.bridge.pfil_onlyip
[dragonfly.git] / games / cgram / cgram.c
1 /* $NetBSD: cgram.c,v 1.17 2021/02/26 15:18:40 rillig Exp $ */
2
3 /*-
4  * Copyright (c) 2013, 2021 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by David A. Holland and Roland Illig.
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  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include <assert.h>
33 #include <ctype.h>
34 #include <curses.h>
35 #include <err.h>
36 #include <stdbool.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <time.h>
41
42 #include "pathnames.h"
43
44 ////////////////////////////////////////////////////////////
45
46 static char
47 ch_toupper(char ch)
48 {
49         return (char)toupper((unsigned char)ch);
50 }
51
52 static char
53 ch_tolower(char ch)
54 {
55         return (char)tolower((unsigned char)ch);
56 }
57
58 static bool
59 ch_isalpha(char ch)
60 {
61         return isalpha((unsigned char)ch) != 0;
62 }
63
64 static bool
65 ch_islower(char ch)
66 {
67         return islower((unsigned char)ch) != 0;
68 }
69
70 static bool
71 ch_isspace(char ch)
72 {
73         return isspace((unsigned char)ch) != 0;
74 }
75
76 static bool
77 ch_isupper(char ch)
78 {
79         return isupper((unsigned char)ch) != 0;
80 }
81
82 static int
83 imax(int a, int b)
84 {
85         return a > b ? a : b;
86 }
87
88 static int
89 imin(int a, int b)
90 {
91         return a < b ? a : b;
92 }
93
94 ////////////////////////////////////////////////////////////
95
96 struct string {
97         char *s;
98         size_t len;
99         size_t cap;
100 };
101
102 struct stringarray {
103         struct string *v;
104         size_t num;
105 };
106
107 static void
108 string_init(struct string *s)
109 {
110         s->s = NULL;
111         s->len = 0;
112         s->cap = 0;
113 }
114
115 static void
116 string_add(struct string *s, char ch)
117 {
118         if (s->len >= s->cap) {
119                 s->cap = 2 * s->cap + 16;
120                 s->s = realloc(s->s, s->cap);
121                 if (s->s == NULL)
122                         errx(1, "Out of memory");
123         }
124         s->s[s->len++] = ch;
125 }
126
127 static void
128 string_finish(struct string *s)
129 {
130         string_add(s, '\0');
131         s->len--;
132 }
133
134 static void
135 stringarray_init(struct stringarray *a)
136 {
137         a->v = NULL;
138         a->num = 0;
139 }
140
141 static void
142 stringarray_cleanup(struct stringarray *a)
143 {
144         for (size_t i = 0; i < a->num; i++)
145                 free(a->v[i].s);
146         free(a->v);
147 }
148
149 static void
150 stringarray_add(struct stringarray *a, struct string *s)
151 {
152         size_t num = a->num++;
153         a->v = realloc(a->v, a->num * sizeof a->v[0]);
154         if (a->v == NULL)
155                 errx(1, "Out of memory");
156         a->v[num] = *s;
157 }
158
159 static void
160 stringarray_dup(struct stringarray *dst, const struct stringarray *src)
161 {
162         assert(dst->num == 0);
163         for (size_t i = 0; i < src->num; i++) {
164                 struct string str;
165                 string_init(&str);
166                 for (const char *p = src->v[i].s; *p != '\0'; p++)
167                         string_add(&str, *p);
168                 string_finish(&str);
169                 stringarray_add(dst, &str);
170         }
171 }
172
173 ////////////////////////////////////////////////////////////
174
175 static struct stringarray lines;
176 static struct stringarray sollines;
177 static bool hinting;
178 static int extent_x;
179 static int extent_y;
180 static int offset_x;
181 static int offset_y;
182 static int cursor_x;
183 static int cursor_y;
184
185 static int
186 cur_max_x(void)
187 {
188         return (int)lines.v[cursor_y].len;
189 }
190
191 static int
192 cur_max_y(void)
193 {
194         return extent_y - 1;
195 }
196
197 static char
198 char_left_of_cursor(void)
199 {
200         if (cursor_x > 0)
201                 return lines.v[cursor_y].s[cursor_x - 1];
202         assert(cursor_y > 0);
203         return '\n'; /* eol of previous line */
204 }
205
206 static char
207 char_at_cursor(void)
208 {
209         if (cursor_x == cur_max_x())
210                 return '\n';
211         return lines.v[cursor_y].s[cursor_x];
212 }
213
214 static void
215 readquote(void)
216 {
217         FILE *f = popen(_PATH_FORTUNE, "r");
218         if (f == NULL)
219                 err(1, "%s", _PATH_FORTUNE);
220
221         struct string line;
222         string_init(&line);
223
224         int ch;
225         while ((ch = fgetc(f)) != EOF) {
226                 if (ch == '\n') {
227                         string_finish(&line);
228                         stringarray_add(&lines, &line);
229                         string_init(&line);
230                 } else if (ch == '\t') {
231                         string_add(&line, ' ');
232                         while (line.len % 8 != 0)
233                                 string_add(&line, ' ');
234                 } else if (ch == '\b') {
235                         if (line.len > 0)
236                                 line.len--;
237                 } else {
238                         string_add(&line, (char)ch);
239                 }
240         }
241
242         stringarray_dup(&sollines, &lines);
243
244         extent_y = (int)lines.num;
245         for (int i = 0; i < extent_y; i++)
246                 extent_x = imax(extent_x, (int)lines.v[i].len);
247
248         if (pclose(f) != 0)
249                 exit(1); /* error message must come from child process */
250 }
251
252 static void
253 encode(void)
254 {
255         int key[26];
256
257         for (int i = 0; i < 26; i++)
258                 key[i] = i;
259
260         for (int i = 26; i > 1; i--) {
261                 int c = (int)(random() % i);
262                 int t = key[i - 1];
263                 key[i - 1] = key[c];
264                 key[c] = t;
265         }
266
267         for (int y = 0; y < extent_y; y++) {
268                 for (char *p = lines.v[y].s; *p != '\0'; p++) {
269                         if (ch_islower(*p))
270                                 *p = (char)('a' + key[*p - 'a']);
271                         if (ch_isupper(*p))
272                                 *p = (char)('A' + key[*p - 'A']);
273                 }
274         }
275 }
276
277 static void
278 substitute(char a, char b)
279 {
280         char la = ch_tolower(a);
281         char ua = ch_toupper(a);
282         char lb = ch_tolower(b);
283         char ub = ch_toupper(b);
284
285         for (int y = 0; y < (int)lines.num; y++) {
286                 for (char *p = lines.v[y].s; *p != '\0'; p++) {
287                         if (*p == la)
288                                 *p = lb;
289                         else if (*p == ua)
290                                 *p = ub;
291                         else if (*p == lb)
292                                 *p = la;
293                         else if (*p == ub)
294                                 *p = ua;
295                 }
296         }
297 }
298
299 static bool
300 is_solved(void)
301 {
302         for (size_t i = 0; i < lines.num; i++)
303                 if (strcmp(lines.v[i].s, sollines.v[i].s) != 0)
304                         return false;
305         return true;
306 }
307
308 ////////////////////////////////////////////////////////////
309
310 static void
311 redraw(void)
312 {
313         erase();
314
315         int max_y = imin(LINES - 1, extent_y - offset_y);
316         for (int y = 0; y < max_y; y++) {
317                 move(y, 0);
318
319                 int len = (int)lines.v[offset_y + y].len;
320                 int max_x = imin(COLS - 1, len - offset_x);
321                 const char *line = lines.v[offset_y + y].s;
322                 const char *solline = sollines.v[offset_y + y].s;
323
324                 for (int x = 0; x < max_x; x++) {
325                         char ch = line[offset_x + x];
326                         bool bold = hinting &&
327                             ch == solline[offset_x + x] &&
328                             ch_isalpha(ch);
329
330                         if (bold)
331                                 attron(A_BOLD);
332                         addch(ch);
333                         if (bold)
334                                 attroff(A_BOLD);
335                 }
336                 clrtoeol();
337         }
338
339         move(LINES - 1, 0);
340         addstr("~ to quit, * to cheat, ^pnfb to move");
341
342         if (is_solved()) {
343                 if (extent_y + 1 - offset_y < LINES - 2)
344                         move(extent_y + 1 - offset_y, 0);
345                 else
346                         addch(' ');
347                 attron(A_BOLD | A_STANDOUT);
348                 addstr("*solved*");
349                 attroff(A_BOLD | A_STANDOUT);
350         }
351
352         move(cursor_y - offset_y, cursor_x - offset_x);
353
354         refresh();
355 }
356
357 ////////////////////////////////////////////////////////////
358
359 static void
360 saturate_cursor(void)
361 {
362         cursor_y = imax(cursor_y, 0);
363         cursor_y = imin(cursor_y, cur_max_y());
364
365         assert(cursor_x >= 0);
366         cursor_x = imin(cursor_x, cur_max_x());
367 }
368
369 static void
370 scroll_into_view(void)
371 {
372         if (cursor_x < offset_x)
373                 offset_x = cursor_x;
374         if (cursor_x > offset_x + COLS - 1)
375                 offset_x = cursor_x - (COLS - 1);
376
377         if (cursor_y < offset_y)
378                 offset_y = cursor_y;
379         if (cursor_y > offset_y + LINES - 2)
380                 offset_y = cursor_y - (LINES - 2);
381 }
382
383 static bool
384 can_go_left(void)
385 {
386         return cursor_y > 0 ||
387             (cursor_y == 0 && cursor_x > 0);
388 }
389
390 static bool
391 can_go_right(void)
392 {
393         return cursor_y < cur_max_y() ||
394             (cursor_y == cur_max_y() && cursor_x < cur_max_x());
395 }
396
397 static void
398 go_to_prev_line(void)
399 {
400         cursor_y--;
401         cursor_x = cur_max_x();
402 }
403
404 static void
405 go_to_next_line(void)
406 {
407         cursor_x = 0;
408         cursor_y++;
409 }
410
411 static void
412 go_left(void)
413 {
414         if (cursor_x > 0)
415                 cursor_x--;
416         else if (cursor_y > 0)
417                 go_to_prev_line();
418 }
419
420 static void
421 go_right(void)
422 {
423         if (cursor_x < cur_max_x())
424                 cursor_x++;
425         else if (cursor_y < cur_max_y())
426                 go_to_next_line();
427 }
428
429 static void
430 go_to_prev_word(void)
431 {
432         while (can_go_left() && ch_isspace(char_left_of_cursor()))
433                 go_left();
434
435         while (can_go_left() && !ch_isspace(char_left_of_cursor()))
436                 go_left();
437 }
438
439 static void
440 go_to_next_word(void)
441 {
442         while (can_go_right() && !ch_isspace(char_at_cursor()))
443                 go_right();
444
445         while (can_go_right() && ch_isspace(char_at_cursor()))
446                 go_right();
447 }
448
449 static bool
450 can_substitute_here(int ch)
451 {
452         return isascii(ch) &&
453             ch_isalpha((char)ch) &&
454             cursor_x < cur_max_x() &&
455             ch_isalpha(char_at_cursor());
456 }
457
458 static void
459 handle_char_input(int ch)
460 {
461         if (ch == char_at_cursor())
462                 go_right();
463         else if (can_substitute_here(ch)) {
464                 substitute(char_at_cursor(), (char)ch);
465                 go_right();
466         } else
467                 beep();
468 }
469
470 static bool
471 handle_key(void)
472 {
473         int ch = getch();
474
475         switch (ch) {
476         case 1:                 /* ^A */
477         case KEY_HOME:
478                 cursor_x = 0;
479                 break;
480         case 2:                 /* ^B */
481         case KEY_LEFT:
482                 go_left();
483                 break;
484         case 5:                 /* ^E */
485         case KEY_END:
486                 cursor_x = cur_max_x();
487                 break;
488         case 6:                 /* ^F */
489         case KEY_RIGHT:
490                 go_right();
491                 break;
492         case '\t':
493                 go_to_next_word();
494                 break;
495         case KEY_BTAB:
496                 go_to_prev_word();
497                 break;
498         case '\n':
499                 go_to_next_line();
500                 break;
501         case 12:                /* ^L */
502                 clear();
503                 break;
504         case 14:                /* ^N */
505         case KEY_DOWN:
506                 cursor_y++;
507                 break;
508         case 16:                /* ^P */
509         case KEY_UP:
510                 cursor_y--;
511                 break;
512         case KEY_PPAGE:
513                 cursor_y -= LINES - 2;
514                 break;
515         case KEY_NPAGE:
516                 cursor_y += LINES - 2;
517                 break;
518         case '*':
519                 hinting = !hinting;
520                 break;
521         case '~':
522                 return false;
523         case KEY_RESIZE:
524                 break;
525         default:
526                 handle_char_input(ch);
527                 break;
528         }
529         return true;
530 }
531
532 static void
533 init(void)
534 {
535         stringarray_init(&lines);
536         stringarray_init(&sollines);
537         srandom((unsigned int)time(NULL));
538         readquote();
539         encode();
540
541         initscr();
542         cbreak();
543         noecho();
544         keypad(stdscr, true);
545 }
546
547 static void
548 loop(void)
549 {
550         for (;;) {
551                 redraw();
552                 if (!handle_key())
553                         break;
554                 saturate_cursor();
555                 scroll_into_view();
556         }
557 }
558
559 static void
560 clean_up(void)
561 {
562         endwin();
563
564         stringarray_cleanup(&sollines);
565         stringarray_cleanup(&lines);
566 }
567
568 ////////////////////////////////////////////////////////////
569
570 int
571 main(void)
572 {
573         init();
574         loop();
575         clean_up();
576 }