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