cgram(6): Sync the rewritten version from NetBSD
[dragonfly.git] / games / cgram / cgram.c
1 /* $NetBSD: cgram.c,v 1.11 2021/02/21 22:21:56 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_isupper(char ch)
72 {
73         return isupper((unsigned char)ch) != 0;
74 }
75
76 static int
77 imax(int a, int b)
78 {
79         return a > b ? a : b;
80 }
81
82 static int
83 imin(int a, int b)
84 {
85         return a < b ? a : b;
86 }
87
88 ////////////////////////////////////////////////////////////
89
90 struct string {
91         char *s;
92         size_t len;
93         size_t cap;
94 };
95
96 struct stringarray {
97         struct string *v;
98         size_t num;
99 };
100
101 static void
102 string_init(struct string *s)
103 {
104         s->s = NULL;
105         s->len = 0;
106         s->cap = 0;
107 }
108
109 static void
110 string_add(struct string *s, char ch)
111 {
112         if (s->len >= s->cap) {
113                 s->cap = 2 * s->cap + 16;
114                 s->s = realloc(s->s, s->cap);
115                 if (s->s == NULL)
116                         errx(1, "Out of memory");
117         }
118         s->s[s->len++] = ch;
119 }
120
121 static void
122 string_finish(struct string *s)
123 {
124         string_add(s, '\0');
125         s->len--;
126 }
127
128 static void
129 stringarray_init(struct stringarray *a)
130 {
131         a->v = NULL;
132         a->num = 0;
133 }
134
135 static void
136 stringarray_cleanup(struct stringarray *a)
137 {
138         for (size_t i = 0; i < a->num; i++)
139                 free(a->v[i].s);
140         free(a->v);
141 }
142
143 static void
144 stringarray_add(struct stringarray *a, struct string *s)
145 {
146         size_t num = a->num++;
147         a->v = realloc(a->v, a->num * sizeof a->v[0]);
148         if (a->v == NULL)
149                 errx(1, "Out of memory");
150         a->v[num] = *s;
151 }
152
153 static void
154 stringarray_dup(struct stringarray *dst, const struct stringarray *src)
155 {
156         assert(dst->num == 0);
157         for (size_t i = 0; i < src->num; i++) {
158                 struct string str;
159                 string_init(&str);
160                 for (const char *p = src->v[i].s; *p != '\0'; p++)
161                         string_add(&str, *p);
162                 string_finish(&str);
163                 stringarray_add(dst, &str);
164         }
165 }
166
167 ////////////////////////////////////////////////////////////
168
169 static struct stringarray lines;
170 static struct stringarray sollines;
171 static bool hinting;
172 static int extent_x;
173 static int extent_y;
174 static int offset_x;
175 static int offset_y;
176 static int cursor_x;
177 static int cursor_y;
178
179 static int
180 cur_max_x(void)
181 {
182         return (int)lines.v[cursor_y].len;
183 }
184
185 static int
186 cur_max_y(void)
187 {
188         return extent_y - 1;
189 }
190
191 static void
192 readquote(void)
193 {
194         FILE *f = popen(_PATH_FORTUNE, "r");
195         if (f == NULL)
196                 err(1, "%s", _PATH_FORTUNE);
197
198         struct string line;
199         string_init(&line);
200
201         int ch;
202         while ((ch = fgetc(f)) != EOF) {
203                 if (ch == '\n') {
204                         string_finish(&line);
205                         stringarray_add(&lines, &line);
206                         string_init(&line);
207                 } else if (ch == '\t') {
208                         string_add(&line, ' ');
209                         while (line.len % 8 != 0)
210                                 string_add(&line, ' ');
211                 } else if (ch == '\b') {
212                         if (line.len > 0)
213                                 line.len--;
214                 } else {
215                         string_add(&line, (char)ch);
216                 }
217         }
218
219         stringarray_dup(&sollines, &lines);
220
221         extent_y = (int)lines.num;
222         for (int i = 0; i < extent_y; i++)
223                 extent_x = imax(extent_x, (int)lines.v[i].len);
224
225         pclose(f);
226 }
227
228 static void
229 encode(void)
230 {
231         int key[26];
232
233         for (int i = 0; i < 26; i++)
234                 key[i] = i;
235
236         for (int i = 26; i > 1; i--) {
237                 int c = (int)(random() % i);
238                 int t = key[i - 1];
239                 key[i - 1] = key[c];
240                 key[c] = t;
241         }
242
243         for (int y = 0; y < extent_y; y++) {
244                 for (char *p = lines.v[y].s; *p != '\0'; p++) {
245                         if (ch_islower(*p))
246                                 *p = (char)('a' + key[*p - 'a']);
247                         if (ch_isupper(*p))
248                                 *p = (char)('A' + key[*p - 'A']);
249                 }
250         }
251 }
252
253 static bool
254 substitute(char ch)
255 {
256         assert(cursor_x >= 0 && cursor_x < extent_x);
257         assert(cursor_y >= 0 && cursor_y < extent_y);
258         if (cursor_x >= cur_max_x()) {
259                 beep();
260                 return false;
261         }
262
263         char och = lines.v[cursor_y].s[cursor_x];
264         if (!ch_isalpha(och)) {
265                 beep();
266                 return false;
267         }
268
269         char loch = ch_tolower(och);
270         char uoch = ch_toupper(och);
271         char lch = ch_tolower(ch);
272         char uch = ch_toupper(ch);
273
274         for (int y = 0; y < (int)lines.num; y++) {
275                 for (char *p = lines.v[y].s; *p != '\0'; p++) {
276                         if (*p == loch)
277                                 *p = lch;
278                         else if (*p == uoch)
279                                 *p = uch;
280                         else if (*p == lch)
281                                 *p = loch;
282                         else if (*p == uch)
283                                 *p = uoch;
284                 }
285         }
286         return true;
287 }
288
289 ////////////////////////////////////////////////////////////
290
291 static bool
292 is_solved(void)
293 {
294         for (size_t i = 0; i < lines.num; i++)
295                 if (strcmp(lines.v[i].s, sollines.v[i].s) != 0)
296                         return false;
297         return true;
298 }
299
300 static void
301 redraw(void)
302 {
303         erase();
304
305         int max_y = imin(LINES - 1, extent_y - offset_y);
306         for (int y = 0; y < max_y; y++) {
307                 move(y, 0);
308
309                 int len = (int)lines.v[offset_y + y].len;
310                 int max_x = imin(COLS - 1, len - offset_x);
311                 const char *line = lines.v[offset_y + y].s;
312                 const char *solline = sollines.v[offset_y + y].s;
313
314                 for (int x = 0; x < max_x; x++) {
315                         char ch = line[offset_x + x];
316                         bool bold = hinting &&
317                             ch == solline[offset_x + x] &&
318                             ch_isalpha(ch);
319
320                         if (bold)
321                                 attron(A_BOLD);
322                         addch(ch);
323                         if (bold)
324                                 attroff(A_BOLD);
325                 }
326                 clrtoeol();
327         }
328
329         move(LINES - 1, 0);
330         if (is_solved())
331                 addstr("*solved* ");
332         addstr("~ to quit, * to cheat, ^pnfb to move");
333
334         move(cursor_y - offset_y, cursor_x - offset_x);
335
336         refresh();
337 }
338
339 static void
340 opencurses(void)
341 {
342         initscr();
343         cbreak();
344         noecho();
345         keypad(stdscr, true);
346 }
347
348 static void
349 closecurses(void)
350 {
351         endwin();
352 }
353
354 ////////////////////////////////////////////////////////////
355
356 static void
357 saturate_cursor(void)
358 {
359         cursor_y = imax(cursor_y, 0);
360         cursor_y = imin(cursor_y, cur_max_y());
361
362         assert(cursor_x >= 0);
363         cursor_x = imin(cursor_x, cur_max_x());
364 }
365
366 static void
367 scroll_into_view(void)
368 {
369         if (cursor_x < offset_x)
370                 offset_x = cursor_x;
371         if (cursor_x > offset_x + COLS - 1)
372                 offset_x = cursor_x - (COLS - 1);
373
374         if (cursor_y < offset_y)
375                 offset_y = cursor_y;
376         if (cursor_y > offset_y + LINES - 2)
377                 offset_y = cursor_y - (LINES - 2);
378 }
379
380 static void
381 handle_char_input(int ch)
382 {
383         if (isascii(ch) && ch_isalpha((char)ch)) {
384                 if (substitute((char)ch)) {
385                         if (cursor_x < cur_max_x())
386                                 cursor_x++;
387                         if (cursor_x == cur_max_x() &&
388                             cursor_y < cur_max_y()) {
389                                 cursor_x = 0;
390                                 cursor_y++;
391                         }
392                 }
393         } else if (cursor_x < cur_max_x() &&
394             ch == lines.v[cursor_y].s[cursor_x]) {
395                 cursor_x++;
396                 if (cursor_x == cur_max_x() &&
397                     cursor_y < cur_max_y()) {
398                         cursor_x = 0;
399                         cursor_y++;
400                 }
401         } else {
402                 beep();
403         }
404 }
405
406 static bool
407 handle_key(void)
408 {
409         int ch = getch();
410
411         switch (ch) {
412         case 1:                 /* ^A */
413         case KEY_HOME:
414                 cursor_x = 0;
415                 break;
416         case 2:                 /* ^B */
417         case KEY_LEFT:
418                 if (cursor_x > 0) {
419                         cursor_x--;
420                 } else if (cursor_y > 0) {
421                         cursor_y--;
422                         cursor_x = cur_max_x();
423                 }
424                 break;
425         case 5:                 /* ^E */
426         case KEY_END:
427                 cursor_x = cur_max_x();
428                 break;
429         case 6:                 /* ^F */
430         case KEY_RIGHT:
431                 if (cursor_x < cur_max_x()) {
432                         cursor_x++;
433                 } else if (cursor_y < cur_max_y()) {
434                         cursor_y++;
435                         cursor_x = 0;
436                 }
437                 break;
438         case 12:                /* ^L */
439                 clear();
440                 break;
441         case 14:                /* ^N */
442         case KEY_DOWN:
443                 cursor_y++;
444                 break;
445         case 16:                /* ^P */
446         case KEY_UP:
447                 cursor_y--;
448                 break;
449         case KEY_PPAGE:
450                 cursor_y -= LINES - 2;
451                 break;
452         case KEY_NPAGE:
453                 cursor_y += LINES - 2;
454                 break;
455         case '*':
456                 hinting = !hinting;
457                 break;
458         case '~':
459                 return false;
460         default:
461                 handle_char_input(ch);
462                 break;
463         }
464         return true;
465 }
466
467 static void
468 init(void)
469 {
470         stringarray_init(&lines);
471         stringarray_init(&sollines);
472         srandom((unsigned int)time(NULL));
473         readquote();
474         encode();
475         opencurses();
476 }
477
478 static void
479 loop(void)
480 {
481         for (;;) {
482                 redraw();
483                 if (!handle_key())
484                         break;
485                 saturate_cursor();
486                 scroll_into_view();
487         }
488 }
489
490 static void
491 clean_up(void)
492 {
493         closecurses();
494         stringarray_cleanup(&sollines);
495         stringarray_cleanup(&lines);
496 }
497
498 ////////////////////////////////////////////////////////////
499
500 int
501 main(void)
502 {
503         init();
504         loop();
505         clean_up();
506 }