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