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