Home | History | Annotate | Line # | Download | only in cgram
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 
     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 	size_t 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 int 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 
    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 
    191 static bool
    192 substitute(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 
    228 static void
    229 redraw(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 
    270 static void
    271 opencurses(void)
    272 {
    273 	initscr();
    274 	cbreak();
    275 	noecho();
    276 }
    277 
    278 static void
    279 closecurses(void)
    280 {
    281 	endwin();
    282 }
    283 
    284 ////////////////////////////////////////////////////////////
    285 
    286 static void
    287 loop(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 
    383 int
    384 main(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