Home | History | Annotate | Line # | Download | only in cgram
cgram.c revision 1.8
      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 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 	int 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 unsigned 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 	for (int i = 0; i < 26; i++)
    171 		key[i] = i;
    172 	for (int i = 26; i > 1; i--) {
    173 		int c = random() % i;
    174 		int t = key[i - 1];
    175 		key[i - 1] = key[c];
    176 		key[c] = t;
    177 	}
    178 
    179 	for (int y = 0; y < lines.num; y++) {
    180 		for (unsigned x = 0; lines.v[y][x] != '\0'; x++) {
    181 			if (ch_islower(lines.v[y][x])) {
    182 				int q = lines.v[y][x] - 'a';
    183 				lines.v[y][x] = 'a' + key[q];
    184 			}
    185 			if (ch_isupper(lines.v[y][x])) {
    186 				int q = lines.v[y][x] - 'A';
    187 				lines.v[y][x] = 'A' + key[q];
    188 			}
    189 		}
    190 	}
    191 }
    192 
    193 static bool
    194 substitute(int ch)
    195 {
    196 	assert(cury >= 0 && cury < lines.num);
    197 	if (curx >= strlen(lines.v[cury])) {
    198 		beep();
    199 		return false;
    200 	}
    201 
    202 	char och = lines.v[cury][curx];
    203 	if (!ch_isalpha(och)) {
    204 		beep();
    205 		return false;
    206 	}
    207 
    208 	char loch = ch_tolower(och);
    209 	char uoch = ch_toupper(och);
    210 	char lch = ch_tolower(ch);
    211 	char uch = ch_toupper(ch);
    212 
    213 	for (int y = 0; y < lines.num; y++) {
    214 		for (unsigned x = 0; lines.v[y][x] != '\0'; x++) {
    215 			if (lines.v[y][x] == loch) {
    216 				lines.v[y][x] = lch;
    217 			} else if (lines.v[y][x] == uoch) {
    218 				lines.v[y][x] = uch;
    219 			} else if (lines.v[y][x] == lch) {
    220 				lines.v[y][x] = loch;
    221 			} else if (lines.v[y][x] == uch) {
    222 				lines.v[y][x] = uoch;
    223 			}
    224 		}
    225 	}
    226 	return true;
    227 }
    228 
    229 ////////////////////////////////////////////////////////////
    230 
    231 static void
    232 redraw(void)
    233 {
    234 	erase();
    235 	bool won = true;
    236 	for (int i = 0; i < LINES - 1; i++) {
    237 		move(i, 0);
    238 		int ln = i + scrolldown;
    239 		if (ln < lines.num) {
    240 			for (unsigned j = 0; lines.v[i][j] != '\0'; j++) {
    241 				char ch = lines.v[i][j];
    242 				if (ch != sollines.v[i][j] && ch_isalpha(ch)) {
    243 					won = false;
    244 				}
    245 				bool bold = false;
    246 				if (hinting && ch == sollines.v[i][j] &&
    247 				    ch_isalpha(ch)) {
    248 					bold = true;
    249 					attron(A_BOLD);
    250 				}
    251 				addch(lines.v[i][j]);
    252 				if (bold) {
    253 					attroff(A_BOLD);
    254 				}
    255 			}
    256 		}
    257 		clrtoeol();
    258 	}
    259 
    260 	move(LINES - 1, 0);
    261 	if (won) {
    262 		addstr("*solved* ");
    263 	}
    264 	addstr("~ to quit, * to cheat, ^pnfb to move");
    265 
    266 	move(LINES - 1, 0);
    267 
    268 	move(cury - scrolldown, curx);
    269 
    270 	refresh();
    271 }
    272 
    273 static void
    274 opencurses(void)
    275 {
    276 	initscr();
    277 	cbreak();
    278 	noecho();
    279 }
    280 
    281 static void
    282 closecurses(void)
    283 {
    284 	endwin();
    285 }
    286 
    287 ////////////////////////////////////////////////////////////
    288 
    289 static void
    290 loop(void)
    291 {
    292 	bool done = false;
    293 	while (!done) {
    294 		redraw();
    295 		int ch = getch();
    296 		switch (ch) {
    297 		case 1:		/* ^A */
    298 		case KEY_HOME:
    299 			curx = 0;
    300 			break;
    301 		case 2:		/* ^B */
    302 		case KEY_LEFT:
    303 			if (curx > 0) {
    304 				curx--;
    305 			} else if (cury > 0) {
    306 				cury--;
    307 				curx = strlen(lines.v[cury]);
    308 			}
    309 			break;
    310 		case 5:		/* ^E */
    311 		case KEY_END:
    312 			curx = strlen(lines.v[cury]);
    313 			break;
    314 		case 6:		/* ^F */
    315 		case KEY_RIGHT:
    316 			if (curx < strlen(lines.v[cury])) {
    317 				curx++;
    318 			} else if (cury < lines.num - 1) {
    319 				cury++;
    320 				curx = 0;
    321 			}
    322 			break;
    323 		case 12:	/* ^L */
    324 			clear();
    325 			break;
    326 		case 14:	/* ^N */
    327 		case KEY_DOWN:
    328 			if (cury < lines.num - 1) {
    329 				cury++;
    330 			}
    331 			if (curx > strlen(lines.v[cury])) {
    332 				curx = strlen(lines.v[cury]);
    333 			}
    334 			if (scrolldown < cury - (LINES - 2)) {
    335 				scrolldown = cury - (LINES - 2);
    336 			}
    337 			break;
    338 		case 16:	/* ^P */
    339 		case KEY_UP:
    340 			if (cury > 0) {
    341 				cury--;
    342 			}
    343 			if (curx > strlen(lines.v[cury])) {
    344 				curx = strlen(lines.v[cury]);
    345 			}
    346 			if (scrolldown > cury) {
    347 				scrolldown = cury;
    348 			}
    349 			break;
    350 		case '*':
    351 			hinting = !hinting;
    352 			break;
    353 		case '~':
    354 			done = true;
    355 			break;
    356 		default:
    357 			if (isascii(ch) && isalpha(ch)) {
    358 				if (substitute(ch)) {
    359 					if (curx < strlen(lines.v[cury])) {
    360 						curx++;
    361 					}
    362 					if (curx == strlen(lines.v[cury]) &&
    363 					    cury < lines.num - 1) {
    364 						curx = 0;
    365 						cury++;
    366 					}
    367 				}
    368 			} else if (curx < strlen(lines.v[cury]) &&
    369 			    ch == lines.v[cury][curx]) {
    370 				curx++;
    371 				if (curx == strlen(lines.v[cury]) &&
    372 				    cury < lines.num - 1) {
    373 					curx = 0;
    374 					cury++;
    375 				}
    376 			} else {
    377 				beep();
    378 			}
    379 			break;
    380 		}
    381 	}
    382 }
    383 
    384 ////////////////////////////////////////////////////////////
    385 
    386 int
    387 main(void)
    388 {
    389 
    390 	stringarray_init(&lines);
    391 	stringarray_init(&sollines);
    392 	srandom(time(NULL));
    393 	readquote();
    394 	encode();
    395 	opencurses();
    396 
    397 	keypad(stdscr, true);
    398 	loop();
    399 
    400 	closecurses();
    401 	stringarray_cleanup(&sollines);
    402 	stringarray_cleanup(&lines);
    403 }
    404