1 /* $NetBSD: cgram.c,v 1.11 2021/02/21 22:21:56 rillig Exp $ */ 2 3 /*- 4 * Copyright (c) 2013, 2021 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 and Roland Illig. 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 46 static char 47 ch_toupper(char ch) 48 { 49 return (char)toupper((unsigned char)ch); 50 } 51 52 static char 53 ch_tolower(char ch) 54 { 55 return (char)tolower((unsigned char)ch); 56 } 57 58 static bool 59 ch_isalpha(char ch) 60 { 61 return isalpha((unsigned char)ch) != 0; 62 } 63 64 static bool 65 ch_islower(char ch) 66 { 67 return islower((unsigned char)ch) != 0; 68 } 69 70 static bool 71 ch_isupper(char ch) 72 { 73 return isupper((unsigned char)ch) != 0; 74 } 75 76 static int 77 imax(int a, int b) 78 { 79 return a > b ? a : b; 80 } 81 82 static int 83 imin(int a, int b) 84 { 85 return a < b ? a : b; 86 } 87 88 //////////////////////////////////////////////////////////// 89 90 struct string { 91 char *s; 92 size_t len; 93 size_t cap; 94 }; 95 96 struct stringarray { 97 struct string *v; 98 size_t num; 99 }; 100 101 static void 102 string_init(struct string *s) 103 { 104 s->s = NULL; 105 s->len = 0; 106 s->cap = 0; 107 } 108 109 static void 110 string_add(struct string *s, char ch) 111 { 112 if (s->len >= s->cap) { 113 s->cap = 2 * s->cap + 16; 114 s->s = realloc(s->s, s->cap); 115 if (s->s == NULL) 116 errx(1, "Out of memory"); 117 } 118 s->s[s->len++] = ch; 119 } 120 121 static void 122 string_finish(struct string *s) 123 { 124 string_add(s, '\0'); 125 s->len--; 126 } 127 128 static void 129 stringarray_init(struct stringarray *a) 130 { 131 a->v = NULL; 132 a->num = 0; 133 } 134 135 static void 136 stringarray_cleanup(struct stringarray *a) 137 { 138 for (size_t i = 0; i < a->num; i++) 139 free(a->v[i].s); 140 free(a->v); 141 } 142 143 static void 144 stringarray_add(struct stringarray *a, struct string *s) 145 { 146 size_t num = a->num++; 147 a->v = realloc(a->v, a->num * sizeof a->v[0]); 148 if (a->v == NULL) 149 errx(1, "Out of memory"); 150 a->v[num] = *s; 151 } 152 153 static void 154 stringarray_dup(struct stringarray *dst, const struct stringarray *src) 155 { 156 assert(dst->num == 0); 157 for (size_t i = 0; i < src->num; i++) { 158 struct string str; 159 string_init(&str); 160 for (const char *p = src->v[i].s; *p != '\0'; p++) 161 string_add(&str, *p); 162 string_finish(&str); 163 stringarray_add(dst, &str); 164 } 165 } 166 167 //////////////////////////////////////////////////////////// 168 169 static struct stringarray lines; 170 static struct stringarray sollines; 171 static bool hinting; 172 static int extent_x; 173 static int extent_y; 174 static int offset_x; 175 static int offset_y; 176 static int cursor_x; 177 static int cursor_y; 178 179 static int 180 cur_max_x(void) 181 { 182 return (int)lines.v[cursor_y].len; 183 } 184 185 static int 186 cur_max_y(void) 187 { 188 return extent_y - 1; 189 } 190 191 static void 192 readquote(void) 193 { 194 FILE *f = popen(_PATH_FORTUNE, "r"); 195 if (f == NULL) 196 err(1, "%s", _PATH_FORTUNE); 197 198 struct string line; 199 string_init(&line); 200 201 int ch; 202 while ((ch = fgetc(f)) != EOF) { 203 if (ch == '\n') { 204 string_finish(&line); 205 stringarray_add(&lines, &line); 206 string_init(&line); 207 } else if (ch == '\t') { 208 string_add(&line, ' '); 209 while (line.len % 8 != 0) 210 string_add(&line, ' '); 211 } else if (ch == '\b') { 212 if (line.len > 0) 213 line.len--; 214 } else { 215 string_add(&line, (char)ch); 216 } 217 } 218 219 stringarray_dup(&sollines, &lines); 220 221 extent_y = (int)lines.num; 222 for (int i = 0; i < extent_y; i++) 223 extent_x = imax(extent_x, (int)lines.v[i].len); 224 225 pclose(f); 226 } 227 228 static void 229 encode(void) 230 { 231 int key[26]; 232 233 for (int i = 0; i < 26; i++) 234 key[i] = i; 235 236 for (int i = 26; i > 1; i--) { 237 int c = (int)(random() % i); 238 int t = key[i - 1]; 239 key[i - 1] = key[c]; 240 key[c] = t; 241 } 242 243 for (int y = 0; y < extent_y; y++) { 244 for (char *p = lines.v[y].s; *p != '\0'; p++) { 245 if (ch_islower(*p)) 246 *p = (char)('a' + key[*p - 'a']); 247 if (ch_isupper(*p)) 248 *p = (char)('A' + key[*p - 'A']); 249 } 250 } 251 } 252 253 static bool 254 substitute(char ch) 255 { 256 assert(cursor_x >= 0 && cursor_x < extent_x); 257 assert(cursor_y >= 0 && cursor_y < extent_y); 258 if (cursor_x >= cur_max_x()) { 259 beep(); 260 return false; 261 } 262 263 char och = lines.v[cursor_y].s[cursor_x]; 264 if (!ch_isalpha(och)) { 265 beep(); 266 return false; 267 } 268 269 char loch = ch_tolower(och); 270 char uoch = ch_toupper(och); 271 char lch = ch_tolower(ch); 272 char uch = ch_toupper(ch); 273 274 for (int y = 0; y < (int)lines.num; y++) { 275 for (char *p = lines.v[y].s; *p != '\0'; p++) { 276 if (*p == loch) 277 *p = lch; 278 else if (*p == uoch) 279 *p = uch; 280 else if (*p == lch) 281 *p = loch; 282 else if (*p == uch) 283 *p = uoch; 284 } 285 } 286 return true; 287 } 288 289 //////////////////////////////////////////////////////////// 290 291 static bool 292 is_solved(void) 293 { 294 for (size_t i = 0; i < lines.num; i++) 295 if (strcmp(lines.v[i].s, sollines.v[i].s) != 0) 296 return false; 297 return true; 298 } 299 300 static void 301 redraw(void) 302 { 303 erase(); 304 305 int max_y = imin(LINES - 1, extent_y - offset_y); 306 for (int y = 0; y < max_y; y++) { 307 move(y, 0); 308 309 int len = (int)lines.v[offset_y + y].len; 310 int max_x = imin(COLS - 1, len - offset_x); 311 const char *line = lines.v[offset_y + y].s; 312 const char *solline = sollines.v[offset_y + y].s; 313 314 for (int x = 0; x < max_x; x++) { 315 char ch = line[offset_x + x]; 316 bool bold = hinting && 317 ch == solline[offset_x + x] && 318 ch_isalpha(ch); 319 320 if (bold) 321 attron(A_BOLD); 322 addch(ch); 323 if (bold) 324 attroff(A_BOLD); 325 } 326 clrtoeol(); 327 } 328 329 move(LINES - 1, 0); 330 if (is_solved()) 331 addstr("*solved* "); 332 addstr("~ to quit, * to cheat, ^pnfb to move"); 333 334 move(cursor_y - offset_y, cursor_x - offset_x); 335 336 refresh(); 337 } 338 339 static void 340 opencurses(void) 341 { 342 initscr(); 343 cbreak(); 344 noecho(); 345 keypad(stdscr, true); 346 } 347 348 static void 349 closecurses(void) 350 { 351 endwin(); 352 } 353 354 //////////////////////////////////////////////////////////// 355 356 static void 357 saturate_cursor(void) 358 { 359 cursor_y = imax(cursor_y, 0); 360 cursor_y = imin(cursor_y, cur_max_y()); 361 362 assert(cursor_x >= 0); 363 cursor_x = imin(cursor_x, cur_max_x()); 364 } 365 366 static void 367 scroll_into_view(void) 368 { 369 if (cursor_x < offset_x) 370 offset_x = cursor_x; 371 if (cursor_x > offset_x + COLS - 1) 372 offset_x = cursor_x - (COLS - 1); 373 374 if (cursor_y < offset_y) 375 offset_y = cursor_y; 376 if (cursor_y > offset_y + LINES - 2) 377 offset_y = cursor_y - (LINES - 2); 378 } 379 380 static void 381 handle_char_input(int ch) 382 { 383 if (isascii(ch) && ch_isalpha((char)ch)) { 384 if (substitute((char)ch)) { 385 if (cursor_x < cur_max_x()) 386 cursor_x++; 387 if (cursor_x == cur_max_x() && 388 cursor_y < cur_max_y()) { 389 cursor_x = 0; 390 cursor_y++; 391 } 392 } 393 } else if (cursor_x < cur_max_x() && 394 ch == lines.v[cursor_y].s[cursor_x]) { 395 cursor_x++; 396 if (cursor_x == cur_max_x() && 397 cursor_y < cur_max_y()) { 398 cursor_x = 0; 399 cursor_y++; 400 } 401 } else { 402 beep(); 403 } 404 } 405 406 static bool 407 handle_key(void) 408 { 409 int ch = getch(); 410 411 switch (ch) { 412 case 1: /* ^A */ 413 case KEY_HOME: 414 cursor_x = 0; 415 break; 416 case 2: /* ^B */ 417 case KEY_LEFT: 418 if (cursor_x > 0) { 419 cursor_x--; 420 } else if (cursor_y > 0) { 421 cursor_y--; 422 cursor_x = cur_max_x(); 423 } 424 break; 425 case 5: /* ^E */ 426 case KEY_END: 427 cursor_x = cur_max_x(); 428 break; 429 case 6: /* ^F */ 430 case KEY_RIGHT: 431 if (cursor_x < cur_max_x()) { 432 cursor_x++; 433 } else if (cursor_y < cur_max_y()) { 434 cursor_y++; 435 cursor_x = 0; 436 } 437 break; 438 case 12: /* ^L */ 439 clear(); 440 break; 441 case 14: /* ^N */ 442 case KEY_DOWN: 443 cursor_y++; 444 break; 445 case 16: /* ^P */ 446 case KEY_UP: 447 cursor_y--; 448 break; 449 case KEY_PPAGE: 450 cursor_y -= LINES - 2; 451 break; 452 case KEY_NPAGE: 453 cursor_y += LINES - 2; 454 break; 455 case '*': 456 hinting = !hinting; 457 break; 458 case '~': 459 return false; 460 default: 461 handle_char_input(ch); 462 break; 463 } 464 return true; 465 } 466 467 static void 468 init(void) 469 { 470 stringarray_init(&lines); 471 stringarray_init(&sollines); 472 srandom((unsigned int)time(NULL)); 473 readquote(); 474 encode(); 475 opencurses(); 476 } 477 478 static void 479 loop(void) 480 { 481 for (;;) { 482 redraw(); 483 if (!handle_key()) 484 break; 485 saturate_cursor(); 486 scroll_into_view(); 487 } 488 } 489 490 static void 491 clean_up(void) 492 { 493 closecurses(); 494 stringarray_cleanup(&sollines); 495 stringarray_cleanup(&lines); 496 } 497 498 //////////////////////////////////////////////////////////// 499 500 int 501 main(void) 502 { 503 init(); 504 loop(); 505 clean_up(); 506 } 507