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 (unsigned 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 = lines.v[cury][curx]; 169 if (!isalpha((unsigned char)och)) { 170 beep(); 171 return -1; 172 } 173 174 int loch = tolower((unsigned char)och); 175 int uoch = toupper((unsigned char)och); 176 int lch = tolower((unsigned char)ch); 177 int uch = toupper((unsigned char)ch); 178 179 for (int y = 0; y < lines.num; y++) { 180 for (unsigned 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 for (int i = 0; i < LINES - 1; i++) { 204 move(i, 0); 205 int ln = i + scrolldown; 206 if (ln < lines.num) { 207 for (unsigned j = 0; lines.v[i][j]; j++) { 208 int ch = lines.v[i][j]; 209 if (ch != sollines.v[i][j] && 210 isalpha((unsigned char)ch)) { 211 won = false; 212 } 213 bool bold = false; 214 if (hinting && ch == sollines.v[i][j] && 215 isalpha((unsigned char)ch)) { 216 bold = true; 217 attron(A_BOLD); 218 } 219 addch(lines.v[i][j]); 220 if (bold) 221 attroff(A_BOLD); 222 } 223 } 224 clrtoeol(); 225 } 226 227 move(LINES - 1, 0); 228 if (won) 229 addstr("*solved* "); 230 addstr("~ to quit, * to cheat, ^pnfb to move"); 231 232 move(LINES - 1, 0); 233 move(cury - scrolldown, curx); 234 235 refresh(); 236 } 237 238 static void 239 opencurses(void) 240 { 241 initscr(); 242 cbreak(); 243 noecho(); 244 } 245 246 static void 247 closecurses(void) 248 { 249 endwin(); 250 } 251 252 //////////////////////////////////////////////////////////// 253 254 static void 255 loop(void) 256 { 257 bool done = false; 258 259 while (!done) { 260 redraw(); 261 262 int ch = getch(); 263 switch (ch) { 264 case 1: /* ^A */ 265 case KEY_BEG: 266 curx = 0; 267 break; 268 case 2: /* ^B */ 269 case KEY_LEFT: 270 if (curx > 0) { 271 curx--; 272 } else if (cury > 0) { 273 cury--; 274 curx = strlen(lines.v[cury]); 275 } 276 break; 277 case 5: /* ^E */ 278 case KEY_END: 279 curx = strlen(lines.v[cury]); 280 break; 281 case 6: /* ^F */ 282 case KEY_RIGHT: 283 if (curx < strlen(lines.v[cury])) { 284 curx++; 285 } else if (cury < lines.num - 1) { 286 cury++; 287 curx = 0; 288 } 289 break; 290 case 12: /* ^L */ 291 clear(); 292 break; 293 case 14: /* ^N */ 294 case KEY_DOWN: 295 if (cury < lines.num - 1) 296 cury++; 297 if (curx > strlen(lines.v[cury])) 298 curx = strlen(lines.v[cury]); 299 if (scrolldown < cury - (LINES - 2)) 300 scrolldown = cury - (LINES - 2); 301 break; 302 case 16: /* ^P */ 303 case KEY_UP: 304 if (cury > 0) 305 cury--; 306 if (curx > strlen(lines.v[cury])) 307 curx = strlen(lines.v[cury]); 308 if (scrolldown > cury) 309 scrolldown = cury; 310 break; 311 case '*': 312 hinting = !hinting; 313 break; 314 case '~': 315 done = true; 316 break; 317 default: 318 if (isalpha(ch)) { 319 if (!substitute(ch)) { 320 if (curx < strlen(lines.v[cury])) 321 curx++; 322 if (curx == strlen(lines.v[cury]) && 323 cury < lines.num - 1) { 324 curx = 0; 325 cury++; 326 } 327 } 328 } else if (curx < strlen(lines.v[cury]) && 329 ch == lines.v[cury][curx]) { 330 curx++; 331 if (curx == strlen(lines.v[cury]) && 332 cury < lines.num - 1) { 333 curx = 0; 334 cury++; 335 } 336 } else { 337 beep(); 338 } 339 break; 340 } 341 } 342 } 343 344 //////////////////////////////////////////////////////////// 345 346 int 347 main(void) 348 { 349 stringarray_init(&lines); 350 stringarray_init(&sollines); 351 srandom(time(NULL)); 352 readquote(); 353 encode(); 354 355 opencurses(); 356 keypad(stdscr, TRUE); 357 loop(); 358 closecurses(); 359 360 stringarray_cleanup(&sollines); 361 stringarray_cleanup(&lines); 362 363 return 0; 364 } 365