Home | History | Annotate | Line # | Download | only in cgram
cgram.c revision 1.6
      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 ////////////////////////////////////////////////////////////
     60 
     61 struct stringarray {
     62 	char **v;
     63 	int num;
     64 };
     65 
     66 static void
     67 stringarray_init(struct stringarray *a)
     68 {
     69 	a->v = NULL;
     70 	a->num = 0;
     71 }
     72 
     73 static void
     74 stringarray_cleanup(struct stringarray *a)
     75 {
     76 	free(a->v);
     77 }
     78 
     79 static void
     80 stringarray_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 
     92 static struct stringarray lines;
     93 static struct stringarray sollines;
     94 static bool hinting;
     95 static int scrolldown;
     96 static unsigned curx;
     97 static int cury;
     98 
     99 static void
    100 readquote(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 
    136 static void
    137 encode(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 
    163 static bool
    164 substitute(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 
    201 static void
    202 redraw(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 
    244 static void
    245 opencurses(void)
    246 {
    247 	initscr();
    248 	cbreak();
    249 	noecho();
    250 }
    251 
    252 static void
    253 closecurses(void)
    254 {
    255 	endwin();
    256 }
    257 
    258 ////////////////////////////////////////////////////////////
    259 
    260 static void
    261 loop(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 (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 
    357 int
    358 main(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