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