games: Bring in cgram(6) from NetBSD
[dragonfly.git] / games / cgram / cgram.c
1 /*-
2  * Copyright (c) 2013 The NetBSD Foundation, Inc.
3  * All rights reserved.
4  *
5  * This code is derived from software contributed to The NetBSD Foundation
6  * by David A. Holland.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include <stdio.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <ctype.h>
34 #include <time.h>
35 #include <err.h>
36 #include <assert.h>
37 #include <curses.h>
38 #include "pathnames.h"
39
40 ////////////////////////////////////////////////////////////
41
42 static char *xstrdup(const char *s) {
43    char *ret;
44
45    ret = malloc(strlen(s) + 1);
46    if (ret == NULL) {
47       errx(1, "Out of memory");
48    }
49    strcpy(ret, s);
50    return ret;
51 }
52
53 ////////////////////////////////////////////////////////////
54
55 struct stringarray {
56    char **v;
57    int num;
58 };
59
60 static void stringarray_init(struct stringarray *a) {
61    a->v = NULL;
62    a->num = 0;
63 }
64
65 static void stringarray_cleanup(struct stringarray *a) {
66    free(a->v);
67 }
68
69 static void stringarray_add(struct stringarray *a, const char *s) {
70    a->v = realloc(a->v, (a->num + 1) * sizeof(a->v[0]));
71    if (a->v == NULL) {
72       errx(1, "Out of memory");
73    }
74    a->v[a->num] = xstrdup(s);
75    a->num++;
76 }
77
78 ////////////////////////////////////////////////////////////
79
80 static struct stringarray lines;
81 static struct stringarray sollines;
82 static bool hinting;
83 static int scrolldown;
84 static unsigned curx;
85 static int cury;
86
87 static void readquote(void) {
88    FILE *f = popen(_PATH_FORTUNE, "r");
89    if (!f) {
90       err(1, "%s", _PATH_FORTUNE);
91    }
92
93    char buf[128], buf2[8*sizeof(buf)];
94    while (fgets(buf, sizeof(buf), f)) {
95       char *s = strrchr(buf, '\n');
96       assert(s);
97       assert(strlen(s)==1);
98       *s = 0;
99
100       int i,j;
101       for (i=j=0; buf[i]; i++) {
102          if (buf[i]=='\t') {
103             buf2[j++] = ' ';
104             while (j%8) buf2[j++] = ' ';
105          }
106          else if (buf[i]=='\b') {
107             if (j>0) j--;
108          }
109          else {
110             buf2[j++] = buf[i];
111          }
112       }
113       buf2[j] = 0;
114
115       stringarray_add(&lines, buf2);
116       stringarray_add(&sollines, buf2);
117    }
118
119    pclose(f);
120 }
121
122 static void encode(void) {
123    int key[26];
124    for (int i=0; i<26; i++) key[i] = i;
125    for (int i=26; i>1; i--) {
126       int c = random() % i;
127       int t = key[i-1];
128       key[i-1] = key[c];
129       key[c] = t;
130    }
131
132    for (int y=0; y<lines.num; y++) {
133       for (unsigned x=0; lines.v[y][x]; x++) {
134          if (islower((unsigned char)lines.v[y][x])) {
135             int q = lines.v[y][x]-'a';
136             lines.v[y][x] = 'a'+key[q];
137          }
138          if (isupper((unsigned char)lines.v[y][x])) {
139             int q = lines.v[y][x]-'A';
140             lines.v[y][x] = 'A'+key[q];
141          }
142       }
143    }
144 }
145
146 static int substitute(int ch) {
147    assert(cury>=0 && cury<lines.num);
148    if (curx >= strlen(lines.v[cury])) {
149       beep();
150       return -1;
151    }
152
153    int och = lines.v[cury][curx];
154    if (!isalpha((unsigned char)och)) {
155       beep();
156       return -1;
157    }
158
159    int loch = tolower((unsigned char)och);
160    int uoch = toupper((unsigned char)och);
161    int lch = tolower((unsigned char)ch);
162    int uch = toupper((unsigned char)ch);
163
164    for (int y=0; y<lines.num; y++) {
165       for (unsigned x=0; lines.v[y][x]; x++) {
166          if (lines.v[y][x]==loch) {
167             lines.v[y][x] = lch;
168          }
169          else if (lines.v[y][x]==uoch) {
170             lines.v[y][x] = uch;
171          }
172          else if (lines.v[y][x]==lch) {
173             lines.v[y][x] = loch;
174          }
175          else if (lines.v[y][x]==uch) {
176             lines.v[y][x] = uoch;
177          }
178       }
179    }
180    return 0;
181 }
182
183 ////////////////////////////////////////////////////////////
184
185 static void redraw(void) {
186    erase();
187    bool won = true;
188    for (int i=0; i<LINES-1; i++) {
189       move(i, 0);
190       int ln = i+scrolldown;
191       if (ln < lines.num) {
192          for (unsigned j=0; lines.v[i][j]; j++) {
193             int ch = lines.v[i][j];
194             if (ch != sollines.v[i][j] && isalpha((unsigned char)ch)) {
195                won = false;
196             }
197             bool bold=false;
198             if (hinting && ch==sollines.v[i][j] &&
199                 isalpha((unsigned char)ch)) {
200                bold = true;
201                attron(A_BOLD);
202             }
203             addch(lines.v[i][j]);
204             if (bold) {
205                attroff(A_BOLD);
206             }
207          }
208       }
209       clrtoeol();
210    }
211
212    move(LINES-1, 0);
213    if (won) {
214       addstr("*solved* ");
215    }
216    addstr("~ to quit, * to cheat, ^pnfb to move");
217
218    move(LINES-1, 0);
219
220    move(cury-scrolldown, curx);
221
222    refresh();
223 }
224
225 static void opencurses(void) {
226     initscr();
227     cbreak();
228     noecho();
229 }
230
231 static void closecurses(void) {
232    endwin();
233 }
234
235 ////////////////////////////////////////////////////////////
236
237 static void loop(void) {
238    bool done=false;
239    while (!done) {
240       redraw();
241       int ch = getch();
242       switch (ch) {
243        case 1: /* ^A */
244        case KEY_BEG:
245         curx=0;
246         break;
247        case 2: /* ^B */
248        case KEY_LEFT:
249         if (curx > 0) {
250            curx--;
251         }
252         else if (cury > 0) {
253            cury--;
254            curx = strlen(lines.v[cury]);
255         }
256         break;
257        case 5: /* ^E */
258        case KEY_END:
259         curx = strlen(lines.v[cury]);
260         break;
261        case 6: /* ^F */
262        case KEY_RIGHT:
263         if (curx < strlen(lines.v[cury])) {
264            curx++;
265         }
266         else if (cury < lines.num - 1) {
267            cury++;
268            curx = 0;
269         }
270         break;
271        case 12: /* ^L */
272         clear();
273         break;
274        case 14: /* ^N */
275        case KEY_DOWN:
276         if (cury < lines.num-1) {
277            cury++;
278         }
279         if (curx > strlen(lines.v[cury])) {
280            curx =  strlen(lines.v[cury]);
281         }
282         if (scrolldown < cury - (LINES-2)) {
283            scrolldown = cury - (LINES-2);
284         }
285         break;
286        case 16: /* ^P */
287        case KEY_UP:
288         if (cury > 0) {
289            cury--;
290         }
291         if (curx > strlen(lines.v[cury])) {
292            curx = strlen(lines.v[cury]);
293         }
294         if (scrolldown > cury) {
295            scrolldown = cury;
296         }
297         break;
298        case '*':
299         hinting = !hinting;
300         break;
301        case '~':
302         done = true;
303         break;
304        default:
305         if (isalpha(ch)) {
306            if (!substitute(ch)) {
307               if (curx < strlen(lines.v[cury])) {
308                  curx++;
309               }
310               if (curx==strlen(lines.v[cury]) && cury < lines.num-1) {
311                  curx=0;
312                  cury++;
313               }
314            }
315         }
316         else if (curx < strlen(lines.v[cury]) && ch==lines.v[cury][curx]) {
317            curx++;
318            if (curx==strlen(lines.v[cury]) && cury < lines.num-1) {
319               curx=0;
320               cury++;
321            }
322         }
323         else {
324            beep();
325         }
326         break;
327       }
328    }
329 }
330
331 ////////////////////////////////////////////////////////////
332
333 int main(void) {
334    stringarray_init(&lines);
335    stringarray_init(&sollines);
336    srandom(time(NULL));
337    readquote();
338    encode();
339    opencurses();
340
341    keypad(stdscr, TRUE);
342    loop();
343
344    closecurses();
345    stringarray_cleanup(&sollines);
346    stringarray_cleanup(&lines);
347    return 0;
348 }