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