1 /*- 2 * Copyright (c) 2013 The NetBSD Foundation, Inc. 3 * All rights reserved. 4 * 5 * This code is derived from software contributed to The NetBSD Foundation 6 * by David A. Holland. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 * POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include <assert.h> 31 #include <ctype.h> 32 #include <err.h> 33 #include <curses.h> 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <time.h> 38 39 #include "pathnames.h" 40 41 //////////////////////////////////////////////////////////// 42 43 static char * 44 xstrdup(const char *s) 45 { 46 char *ret; 47 48 ret = malloc(strlen(s) + 1); 49 if (ret == NULL) 50 errx(1, "Out of memory"); 51 52 strcpy(ret, s); 53 return ret; 54 } 55 56 //////////////////////////////////////////////////////////// 57 58 struct stringarray { 59 char **v; 60 int num; 61 }; 62 63 static void 64 stringarray_init(struct stringarray *a) 65 { 66 a->v = NULL; 67 a->num = 0; 68 } 69 70 static void 71 stringarray_cleanup(struct stringarray *a) 72 { 73 free(a->v); 74 } 75 76 static void 77 stringarray_add(struct stringarray *a, const char *s) 78 { 79 a->v = realloc(a->v, (a->num + 1) * sizeof(a->v[0])); 80 if (a->v == NULL) 81 errx(1, "Out of memory"); 82 83 a->v[a->num] = xstrdup(s); 84 a->num++; 85 } 86 87 //////////////////////////////////////////////////////////// 88 89 static struct stringarray lines; 90 static struct stringarray sollines; 91 static bool hinting; 92 static int scrolldown; 93 static unsigned curx; 94 static int cury; 95 96 static void 97 readquote(void) 98 { 99 FILE *f = popen(_PATH_FORTUNE, "r"); 100 if (!f) 101 err(1, "%s", _PATH_FORTUNE); 102 103 char buf[128], buf2[8 * sizeof(buf)]; 104 while (fgets(buf, sizeof(buf), f)) { 105 char *s = strrchr(buf, '\n'); 106 assert(s && strlen(s) == 1); 107 *s = '\0'; 108 109 int i, j; 110 for (i = j = 0; buf[i]; i++) { 111 if (buf[i] == '\t') { 112 buf2[j++] = ' '; 113 while (j % 8) 114 buf2[j++] = ' '; 115 } else if (buf[i] == '\b') { 116 if (j > 0) 117 j--; 118 } else { 119 buf2[j++] = buf[i]; 120 } 121 } 122 buf2[j] = 0; 123 124 stringarray_add(&lines, buf2); 125 stringarray_add(&sollines, buf2); 126 } 127 128 pclose(f); 129 } 130 131 static void 132 encode(void) 133 { 134 int key[26]; 135 136 for (int i = 0; i < 26; i++) 137 key[i] = i; 138 for (int i = 26; i > 1; i--) { 139 int c = random() % i; 140 int t = key[i - 1]; 141 key[i - 1] = key[c]; 142 key[c] = t; 143 } 144 145 for (int y = 0; y < lines.num; y++) { 146 for (int x = 0; lines.v[y][x]; x++) { 147 if (islower((unsigned char)lines.v[y][x])) { 148 int q = lines.v[y][x] - 'a'; 149 lines.v[y][x] = 'a' + key[q]; 150 } 151 if (isupper((unsigned char)lines.v[y][x])) { 152 int q = lines.v[y][x] - 'A'; 153 lines.v[y][x] = 'A' + key[q]; 154 } 155 } 156 } 157 } 158 159 static int 160 substitute(int ch) 161 { 162 assert(cury >= 0 && cury < lines.num); 163 if (curx >= strlen(lines.v[cury])) { 164 beep(); 165 return -1; 166 } 167 168 int och = (unsigned char)lines.v[cury][curx]; 169 if (!isalpha(och)) { 170 beep(); 171 return -1; 172 } 173 174 int loch = tolower(och); 175 int uoch = toupper(och); 176 int lch = tolower(ch); 177 int uch = toupper(ch); 178 179 for (int y = 0; y < lines.num; y++) { 180 for (int x = 0; lines.v[y][x]; x++) { 181 if (lines.v[y][x] == loch) 182 lines.v[y][x] = lch; 183 else if (lines.v[y][x] == uoch) 184 lines.v[y][x] = uch; 185 else if (lines.v[y][x] == lch) 186 lines.v[y][x] = loch; 187 else if (lines.v[y][x] == uch) 188 lines.v[y][x] = uoch; 189 } 190 } 191 192 return 0; 193 } 194 195 //////////////////////////////////////////////////////////// 196 197 static void 198 redraw(void) 199 { 200 erase(); 201 202 bool won = true; 203 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 (int j = 0; lines.v[i][j]; j++) { 209 int ch = (unsigned char)lines.v[i][j]; 210 int solch = (unsigned char)sollines.v[i][j]; 211 if (ch != solch && isalpha(ch)) 212 won = false; 213 214 int attr = 0; 215 if (hinting && ch == solch && isalpha(ch)) 216 attr = A_BOLD; 217 addch(ch | attr); 218 } 219 } 220 clrtoeol(); 221 } 222 223 move(LINES - 1, 0); 224 if (won) 225 addstr("*solved* "); 226 addstr("~ to quit, * to cheat, ^pnfb to move"); 227 228 move(cury - scrolldown, curx); 229 230 refresh(); 231 } 232 233 static void 234 opencurses(void) 235 { 236 initscr(); 237 cbreak(); 238 noecho(); 239 keypad(stdscr, TRUE); 240 } 241 242 static void 243 closecurses(void) 244 { 245 endwin(); 246 } 247 248 //////////////////////////////////////////////////////////// 249 250 static void 251 loop(void) 252 { 253 bool done = false; 254 255 while (!done) { 256 redraw(); 257 258 int ch = getch(); 259 switch (ch) { 260 case 1: /* ^A */ 261 case KEY_HOME: 262 curx = 0; 263 break; 264 case 2: /* ^B */ 265 case KEY_LEFT: 266 if (curx > 0) { 267 curx--; 268 } else if (cury > 0) { 269 cury--; 270 curx = strlen(lines.v[cury]); 271 } 272 break; 273 case 5: /* ^E */ 274 case KEY_END: 275 curx = strlen(lines.v[cury]); 276 break; 277 case 6: /* ^F */ 278 case KEY_RIGHT: 279 if (curx < strlen(lines.v[cury])) { 280 curx++; 281 } else if (cury < lines.num - 1) { 282 cury++; 283 curx = 0; 284 } 285 break; 286 case 12: /* ^L */ 287 clear(); 288 break; 289 case 14: /* ^N */ 290 case KEY_DOWN: 291 if (cury < lines.num - 1) 292 cury++; 293 if (curx > strlen(lines.v[cury])) 294 curx = strlen(lines.v[cury]); 295 if (scrolldown < cury - (LINES - 2)) 296 scrolldown = cury - (LINES - 2); 297 break; 298 case 16: /* ^P */ 299 case KEY_UP: 300 if (cury > 0) 301 cury--; 302 if (curx > strlen(lines.v[cury])) 303 curx = strlen(lines.v[cury]); 304 if (scrolldown > cury) 305 scrolldown = cury; 306 break; 307 case '*': 308 hinting = !hinting; 309 break; 310 case '~': 311 done = true; 312 break; 313 default: 314 if (isalpha(ch)) { 315 if (!substitute(ch)) { 316 if (curx < strlen(lines.v[cury])) 317 curx++; 318 if (curx == strlen(lines.v[cury]) && 319 cury < lines.num - 1) { 320 curx = 0; 321 cury++; 322 } 323 } 324 } else if (curx < strlen(lines.v[cury]) && 325 ch == lines.v[cury][curx]) { 326 curx++; 327 if (curx == strlen(lines.v[cury]) && 328 cury < lines.num - 1) { 329 curx = 0; 330 cury++; 331 } 332 } else { 333 beep(); 334 } 335 break; 336 } 337 } 338 } 339 340 //////////////////////////////////////////////////////////// 341 342 int 343 main(void) 344 { 345 stringarray_init(&lines); 346 stringarray_init(&sollines); 347 srandom(time(NULL)); 348 readquote(); 349 encode(); 350 351 opencurses(); 352 loop(); 353 closecurses(); 354 355 stringarray_cleanup(&sollines); 356 stringarray_cleanup(&lines); 357 358 return 0; 359 } 360