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