cgram.c revision 1.9
1/* $NetBSD: cgram.c,v 1.9 2021/02/21 17:16:00 rillig Exp $ */ 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 59static char 60ch_toupper(char ch) 61{ 62 return (char)toupper((unsigned char)ch); 63} 64 65static char 66ch_tolower(char ch) 67{ 68 return (char)tolower((unsigned char)ch); 69} 70 71static bool 72ch_isalpha(char ch) 73{ 74 return isalpha((unsigned char)ch); 75} 76 77static bool 78ch_islower(char ch) 79{ 80 return islower((unsigned char)ch); 81} 82 83static bool 84ch_isupper(char ch) 85{ 86 return isupper((unsigned char)ch); 87} 88 89//////////////////////////////////////////////////////////// 90 91struct stringarray { 92 char **v; 93 size_t num; 94}; 95 96static void 97stringarray_init(struct stringarray *a) 98{ 99 a->v = NULL; 100 a->num = 0; 101} 102 103static void 104stringarray_cleanup(struct stringarray *a) 105{ 106 free(a->v); 107} 108 109static void 110stringarray_add(struct stringarray *a, const char *s) 111{ 112 a->v = realloc(a->v, (a->num + 1) * sizeof(a->v[0])); 113 if (a->v == NULL) { 114 errx(1, "Out of memory"); 115 } 116 a->v[a->num] = xstrdup(s); 117 a->num++; 118} 119 120//////////////////////////////////////////////////////////// 121 122static struct stringarray lines; 123static struct stringarray sollines; 124static bool hinting; 125static int scrolldown; 126static int curx; 127static int cury; 128 129static void 130readquote(void) 131{ 132 FILE *f = popen(_PATH_FORTUNE, "r"); 133 if (f == NULL) { 134 err(1, "%s", _PATH_FORTUNE); 135 } 136 137 char buf[128], buf2[8 * sizeof(buf)]; 138 while (fgets(buf, sizeof buf, f) != NULL) { 139 char *s = strrchr(buf, '\n'); 140 assert(s != NULL); 141 assert(strlen(s) == 1); 142 *s = '\0'; 143 144 int i, j; 145 for (i = j = 0; buf[i] != '\0'; i++) { 146 if (buf[i] == '\t') { 147 buf2[j++] = ' '; 148 while (j % 8 != 0) 149 buf2[j++] = ' '; 150 } else if (buf[i] == '\b') { 151 if (j > 0) 152 j--; 153 } else { 154 buf2[j++] = buf[i]; 155 } 156 } 157 buf2[j] = '\0'; 158 159 stringarray_add(&lines, buf2); 160 stringarray_add(&sollines, buf2); 161 } 162 163 pclose(f); 164} 165 166static void 167encode(void) 168{ 169 int key[26]; 170 171 for (int i = 0; i < 26; i++) 172 key[i] = i; 173 174 for (int i = 26; i > 1; i--) { 175 int c = (int)(random() % i); 176 int t = key[i - 1]; 177 key[i - 1] = key[c]; 178 key[c] = t; 179 } 180 181 for (int y = 0; y < (int)lines.num; y++) { 182 for (char *p = lines.v[y]; *p != '\0'; p++) { 183 if (ch_islower(*p)) 184 *p = (char)('a' + key[*p - 'a']); 185 if (ch_isupper(*p)) 186 *p = (char)('A' + key[*p - 'A']); 187 } 188 } 189} 190 191static bool 192substitute(char ch) 193{ 194 assert(cury >= 0 && cury < (int)lines.num); 195 if (curx >= (int)strlen(lines.v[cury])) { 196 beep(); 197 return false; 198 } 199 200 char och = lines.v[cury][curx]; 201 if (!ch_isalpha(och)) { 202 beep(); 203 return false; 204 } 205 206 char loch = ch_tolower(och); 207 char uoch = ch_toupper(och); 208 char lch = ch_tolower(ch); 209 char uch = ch_toupper(ch); 210 211 for (int y = 0; y < (int)lines.num; y++) { 212 for (char *p = lines.v[y]; *p != '\0'; p++) { 213 if (*p == loch) 214 *p = lch; 215 else if (*p == uoch) 216 *p = uch; 217 else if (*p == lch) 218 *p = loch; 219 else if (*p == uch) 220 *p = uoch; 221 } 222 } 223 return true; 224} 225 226//////////////////////////////////////////////////////////// 227 228static void 229redraw(void) 230{ 231 erase(); 232 bool won = true; 233 for (int i = 0; i < LINES - 1; i++) { 234 move(i, 0); 235 int ln = i + scrolldown; 236 if (ln < (int)lines.num) { 237 for (unsigned j = 0; lines.v[i][j] != '\0'; j++) { 238 char ch = lines.v[i][j]; 239 if (ch != sollines.v[i][j] && ch_isalpha(ch)) { 240 won = false; 241 } 242 bool bold = false; 243 if (hinting && ch == sollines.v[i][j] && 244 ch_isalpha(ch)) { 245 bold = true; 246 attron(A_BOLD); 247 } 248 addch(lines.v[i][j]); 249 if (bold) { 250 attroff(A_BOLD); 251 } 252 } 253 } 254 clrtoeol(); 255 } 256 257 move(LINES - 1, 0); 258 if (won) { 259 addstr("*solved* "); 260 } 261 addstr("~ to quit, * to cheat, ^pnfb to move"); 262 263 move(LINES - 1, 0); 264 265 move(cury - scrolldown, curx); 266 267 refresh(); 268} 269 270static void 271opencurses(void) 272{ 273 initscr(); 274 cbreak(); 275 noecho(); 276} 277 278static void 279closecurses(void) 280{ 281 endwin(); 282} 283 284//////////////////////////////////////////////////////////// 285 286static void 287loop(void) 288{ 289 bool done = false; 290 while (!done) { 291 redraw(); 292 int ch = getch(); 293 switch (ch) { 294 case 1: /* ^A */ 295 case KEY_HOME: 296 curx = 0; 297 break; 298 case 2: /* ^B */ 299 case KEY_LEFT: 300 if (curx > 0) { 301 curx--; 302 } else if (cury > 0) { 303 cury--; 304 curx = (int)strlen(lines.v[cury]); 305 } 306 break; 307 case 5: /* ^E */ 308 case KEY_END: 309 curx = (int)strlen(lines.v[cury]); 310 break; 311 case 6: /* ^F */ 312 case KEY_RIGHT: 313 if (curx < (int)strlen(lines.v[cury])) { 314 curx++; 315 } else if (cury < (int)lines.num - 1) { 316 cury++; 317 curx = 0; 318 } 319 break; 320 case 12: /* ^L */ 321 clear(); 322 break; 323 case 14: /* ^N */ 324 case KEY_DOWN: 325 if (cury < (int)lines.num - 1) { 326 cury++; 327 } 328 if (curx > (int)strlen(lines.v[cury])) { 329 curx = (int)strlen(lines.v[cury]); 330 } 331 if (scrolldown < cury - (LINES - 2)) { 332 scrolldown = cury - (LINES - 2); 333 } 334 break; 335 case 16: /* ^P */ 336 case KEY_UP: 337 if (cury > 0) { 338 cury--; 339 } 340 if (curx > (int)strlen(lines.v[cury])) { 341 curx = (int)strlen(lines.v[cury]); 342 } 343 if (scrolldown > cury) { 344 scrolldown = cury; 345 } 346 break; 347 case '*': 348 hinting = !hinting; 349 break; 350 case '~': 351 done = true; 352 break; 353 default: 354 if (isascii(ch) && ch_isalpha((char)ch)) { 355 if (substitute((char)ch)) { 356 if (curx < (int)strlen(lines.v[cury])) { 357 curx++; 358 } 359 if (curx == (int)strlen(lines.v[cury]) && 360 cury < (int)lines.num - 1) { 361 curx = 0; 362 cury++; 363 } 364 } 365 } else if (curx < (int)strlen(lines.v[cury]) && 366 ch == lines.v[cury][curx]) { 367 curx++; 368 if (curx == (int)strlen(lines.v[cury]) && 369 cury < (int)lines.num - 1) { 370 curx = 0; 371 cury++; 372 } 373 } else { 374 beep(); 375 } 376 break; 377 } 378 } 379} 380 381//////////////////////////////////////////////////////////// 382 383int 384main(void) 385{ 386 387 stringarray_init(&lines); 388 stringarray_init(&sollines); 389 srandom((unsigned int)time(NULL)); 390 readquote(); 391 encode(); 392 opencurses(); 393 394 keypad(stdscr, true); 395 loop(); 396 397 closecurses(); 398 stringarray_cleanup(&sollines); 399 stringarray_cleanup(&lines); 400} 401