cgram.c revision 1.8 1 /* $NetBSD */
2
3 /*-
4 * Copyright (c) 2013 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.
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 xstrdup(const char *s)
48 {
49 char *ret;
50
51 ret = malloc(strlen(s) + 1);
52 if (ret == NULL) {
53 errx(1, "Out of memory");
54 }
55 strcpy(ret, s);
56 return ret;
57 }
58
59 static char
60 ch_toupper(char ch)
61 {
62 return (char)toupper((unsigned char)ch);
63 }
64
65 static char
66 ch_tolower(char ch)
67 {
68 return (char)tolower((unsigned char)ch);
69 }
70
71 static bool
72 ch_isalpha(char ch)
73 {
74 return isalpha((unsigned char)ch);
75 }
76
77 static bool
78 ch_islower(char ch)
79 {
80 return islower((unsigned char)ch);
81 }
82
83 static bool
84 ch_isupper(char ch)
85 {
86 return isupper((unsigned char)ch);
87 }
88
89 ////////////////////////////////////////////////////////////
90
91 struct stringarray {
92 char **v;
93 int num;
94 };
95
96 static void
97 stringarray_init(struct stringarray *a)
98 {
99 a->v = NULL;
100 a->num = 0;
101 }
102
103 static void
104 stringarray_cleanup(struct stringarray *a)
105 {
106 free(a->v);
107 }
108
109 static void
110 stringarray_add(struct stringarray *a, const char *s)
111 {
112 a->v = realloc(a->v, (a->num + 1) * sizeof(a->v[0]));
113 if (a->v == NULL) {
114 errx(1, "Out of memory");
115 }
116 a->v[a->num] = xstrdup(s);
117 a->num++;
118 }
119
120 ////////////////////////////////////////////////////////////
121
122 static struct stringarray lines;
123 static struct stringarray sollines;
124 static bool hinting;
125 static int scrolldown;
126 static unsigned curx;
127 static int cury;
128
129 static void
130 readquote(void)
131 {
132 FILE *f = popen(_PATH_FORTUNE, "r");
133 if (f == NULL) {
134 err(1, "%s", _PATH_FORTUNE);
135 }
136
137 char buf[128], buf2[8 * sizeof(buf)];
138 while (fgets(buf, sizeof buf, f) != NULL) {
139 char *s = strrchr(buf, '\n');
140 assert(s != NULL);
141 assert(strlen(s) == 1);
142 *s = '\0';
143
144 int i, j;
145 for (i = j = 0; buf[i] != '\0'; i++) {
146 if (buf[i] == '\t') {
147 buf2[j++] = ' ';
148 while (j % 8 != 0)
149 buf2[j++] = ' ';
150 } else if (buf[i] == '\b') {
151 if (j > 0)
152 j--;
153 } else {
154 buf2[j++] = buf[i];
155 }
156 }
157 buf2[j] = '\0';
158
159 stringarray_add(&lines, buf2);
160 stringarray_add(&sollines, buf2);
161 }
162
163 pclose(f);
164 }
165
166 static void
167 encode(void)
168 {
169 int key[26];
170 for (int i = 0; i < 26; i++)
171 key[i] = i;
172 for (int i = 26; i > 1; i--) {
173 int c = random() % i;
174 int t = key[i - 1];
175 key[i - 1] = key[c];
176 key[c] = t;
177 }
178
179 for (int y = 0; y < lines.num; y++) {
180 for (unsigned x = 0; lines.v[y][x] != '\0'; x++) {
181 if (ch_islower(lines.v[y][x])) {
182 int q = lines.v[y][x] - 'a';
183 lines.v[y][x] = 'a' + key[q];
184 }
185 if (ch_isupper(lines.v[y][x])) {
186 int q = lines.v[y][x] - 'A';
187 lines.v[y][x] = 'A' + key[q];
188 }
189 }
190 }
191 }
192
193 static bool
194 substitute(int ch)
195 {
196 assert(cury >= 0 && cury < lines.num);
197 if (curx >= strlen(lines.v[cury])) {
198 beep();
199 return false;
200 }
201
202 char och = lines.v[cury][curx];
203 if (!ch_isalpha(och)) {
204 beep();
205 return false;
206 }
207
208 char loch = ch_tolower(och);
209 char uoch = ch_toupper(och);
210 char lch = ch_tolower(ch);
211 char uch = ch_toupper(ch);
212
213 for (int y = 0; y < lines.num; y++) {
214 for (unsigned x = 0; lines.v[y][x] != '\0'; x++) {
215 if (lines.v[y][x] == loch) {
216 lines.v[y][x] = lch;
217 } else if (lines.v[y][x] == uoch) {
218 lines.v[y][x] = uch;
219 } else if (lines.v[y][x] == lch) {
220 lines.v[y][x] = loch;
221 } else if (lines.v[y][x] == uch) {
222 lines.v[y][x] = uoch;
223 }
224 }
225 }
226 return true;
227 }
228
229 ////////////////////////////////////////////////////////////
230
231 static void
232 redraw(void)
233 {
234 erase();
235 bool won = true;
236 for (int i = 0; i < LINES - 1; i++) {
237 move(i, 0);
238 int ln = i + scrolldown;
239 if (ln < lines.num) {
240 for (unsigned j = 0; lines.v[i][j] != '\0'; j++) {
241 char ch = lines.v[i][j];
242 if (ch != sollines.v[i][j] && ch_isalpha(ch)) {
243 won = false;
244 }
245 bool bold = false;
246 if (hinting && ch == sollines.v[i][j] &&
247 ch_isalpha(ch)) {
248 bold = true;
249 attron(A_BOLD);
250 }
251 addch(lines.v[i][j]);
252 if (bold) {
253 attroff(A_BOLD);
254 }
255 }
256 }
257 clrtoeol();
258 }
259
260 move(LINES - 1, 0);
261 if (won) {
262 addstr("*solved* ");
263 }
264 addstr("~ to quit, * to cheat, ^pnfb to move");
265
266 move(LINES - 1, 0);
267
268 move(cury - scrolldown, curx);
269
270 refresh();
271 }
272
273 static void
274 opencurses(void)
275 {
276 initscr();
277 cbreak();
278 noecho();
279 }
280
281 static void
282 closecurses(void)
283 {
284 endwin();
285 }
286
287 ////////////////////////////////////////////////////////////
288
289 static void
290 loop(void)
291 {
292 bool done = false;
293 while (!done) {
294 redraw();
295 int ch = getch();
296 switch (ch) {
297 case 1: /* ^A */
298 case KEY_HOME:
299 curx = 0;
300 break;
301 case 2: /* ^B */
302 case KEY_LEFT:
303 if (curx > 0) {
304 curx--;
305 } else if (cury > 0) {
306 cury--;
307 curx = strlen(lines.v[cury]);
308 }
309 break;
310 case 5: /* ^E */
311 case KEY_END:
312 curx = strlen(lines.v[cury]);
313 break;
314 case 6: /* ^F */
315 case KEY_RIGHT:
316 if (curx < strlen(lines.v[cury])) {
317 curx++;
318 } else if (cury < lines.num - 1) {
319 cury++;
320 curx = 0;
321 }
322 break;
323 case 12: /* ^L */
324 clear();
325 break;
326 case 14: /* ^N */
327 case KEY_DOWN:
328 if (cury < lines.num - 1) {
329 cury++;
330 }
331 if (curx > strlen(lines.v[cury])) {
332 curx = strlen(lines.v[cury]);
333 }
334 if (scrolldown < cury - (LINES - 2)) {
335 scrolldown = cury - (LINES - 2);
336 }
337 break;
338 case 16: /* ^P */
339 case KEY_UP:
340 if (cury > 0) {
341 cury--;
342 }
343 if (curx > strlen(lines.v[cury])) {
344 curx = strlen(lines.v[cury]);
345 }
346 if (scrolldown > cury) {
347 scrolldown = cury;
348 }
349 break;
350 case '*':
351 hinting = !hinting;
352 break;
353 case '~':
354 done = true;
355 break;
356 default:
357 if (isascii(ch) && isalpha(ch)) {
358 if (substitute(ch)) {
359 if (curx < strlen(lines.v[cury])) {
360 curx++;
361 }
362 if (curx == strlen(lines.v[cury]) &&
363 cury < lines.num - 1) {
364 curx = 0;
365 cury++;
366 }
367 }
368 } else if (curx < strlen(lines.v[cury]) &&
369 ch == lines.v[cury][curx]) {
370 curx++;
371 if (curx == strlen(lines.v[cury]) &&
372 cury < lines.num - 1) {
373 curx = 0;
374 cury++;
375 }
376 } else {
377 beep();
378 }
379 break;
380 }
381 }
382 }
383
384 ////////////////////////////////////////////////////////////
385
386 int
387 main(void)
388 {
389
390 stringarray_init(&lines);
391 stringarray_init(&sollines);
392 srandom(time(NULL));
393 readquote();
394 encode();
395 opencurses();
396
397 keypad(stdscr, true);
398 loop();
399
400 closecurses();
401 stringarray_cleanup(&sollines);
402 stringarray_cleanup(&lines);
403 }
404