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