diff options
Diffstat (limited to 'cgram/cgram.c')
-rw-r--r-- | cgram/cgram.c | 465 |
1 files changed, 286 insertions, 179 deletions
diff --git a/cgram/cgram.c b/cgram/cgram.c index 9f7a096e..e9e5813e 100644 --- a/cgram/cgram.c +++ b/cgram/cgram.c @@ -1,11 +1,11 @@ -/* $NetBSD: cgram.c,v 1.9 2021/02/21 17:16:00 rillig Exp $ */ +/* $NetBSD: cgram.c,v 1.10 2021/02/21 20:33:42 rillig Exp $ */ /*- - * Copyright (c) 2013 The NetBSD Foundation, Inc. + * Copyright (c) 2013, 2021 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation - * by David A. Holland. + * by David A. Holland and Roland Illig. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -29,6 +29,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include <sys/cdefs.h> +#if defined(__RCSID) && !defined(lint) +__RCSID("$NetBSD: cgram.c,v 1.10 2021/02/21 20:33:42 rillig Exp $"); +#endif + #include <assert.h> #include <ctype.h> #include <curses.h> @@ -43,19 +48,6 @@ //////////////////////////////////////////////////////////// -static char * -xstrdup(const char *s) -{ - char *ret; - - ret = malloc(strlen(s) + 1); - if (ret == NULL) { - errx(1, "Out of memory"); - } - strcpy(ret, s); - return ret; -} - static char ch_toupper(char ch) { @@ -71,29 +63,74 @@ ch_tolower(char ch) static bool ch_isalpha(char ch) { - return isalpha((unsigned char)ch); + return isalpha((unsigned char)ch) != 0; } static bool ch_islower(char ch) { - return islower((unsigned char)ch); + return islower((unsigned char)ch) != 0; } static bool ch_isupper(char ch) { - return isupper((unsigned char)ch); + return isupper((unsigned char)ch) != 0; +} + +static int +imax(int a, int b) +{ + return a > b ? a : b; +} + +static int +imin(int a, int b) +{ + return a < b ? a : b; } //////////////////////////////////////////////////////////// +struct string { + char *s; + size_t len; + size_t cap; +}; + struct stringarray { - char **v; + struct string *v; size_t num; }; static void +string_init(struct string *s) +{ + s->s = NULL; + s->len = 0; + s->cap = 0; +} + +static void +string_add(struct string *s, char ch) +{ + if (s->len >= s->cap) { + s->cap = 2 * s->cap + 16; + s->s = realloc(s->s, s->cap); + if (s->s == NULL) + errx(1, "Out of memory"); + } + s->s[s->len++] = ch; +} + +static void +string_finish(struct string *s) +{ + string_add(s, '\0'); + s->len--; +} + +static void stringarray_init(struct stringarray *a) { a->v = NULL; @@ -103,18 +140,33 @@ stringarray_init(struct stringarray *a) static void stringarray_cleanup(struct stringarray *a) { + for (size_t i = 0; i < a->num; i++) + free(a->v[i].s); free(a->v); } static void -stringarray_add(struct stringarray *a, const char *s) +stringarray_add(struct stringarray *a, struct string *s) { - a->v = realloc(a->v, (a->num + 1) * sizeof(a->v[0])); - if (a->v == NULL) { + size_t num = a->num++; + a->v = realloc(a->v, a->num * sizeof a->v[0]); + if (a->v == NULL) errx(1, "Out of memory"); + a->v[num] = *s; +} + +static void +stringarray_dup(struct stringarray *dst, const struct stringarray *src) +{ + assert(dst->num == 0); + for (size_t i = 0; i < src->num; i++) { + struct string str; + string_init(&str); + for (const char *p = src->v[i].s; *p != '\0'; p++) + string_add(&str, *p); + string_finish(&str); + stringarray_add(dst, &str); } - a->v[a->num] = xstrdup(s); - a->num++; } //////////////////////////////////////////////////////////// @@ -122,44 +174,59 @@ stringarray_add(struct stringarray *a, const char *s) static struct stringarray lines; static struct stringarray sollines; static bool hinting; -static int scrolldown; -static int curx; -static int cury; +static int extent_x; +static int extent_y; +static int offset_x; +static int offset_y; +static int cursor_x; +static int cursor_y; + +static int +cur_max_x(void) +{ + return (int)lines.v[cursor_y].len; +} + +static int +cur_max_y(void) +{ + return extent_y - 1; +} static void readquote(void) { FILE *f = popen(_PATH_FORTUNE, "r"); - if (f == NULL) { + if (f == NULL) err(1, "%s", _PATH_FORTUNE); - } - char buf[128], buf2[8 * sizeof(buf)]; - while (fgets(buf, sizeof buf, f) != NULL) { - char *s = strrchr(buf, '\n'); - assert(s != NULL); - assert(strlen(s) == 1); - *s = '\0'; - - int i, j; - for (i = j = 0; buf[i] != '\0'; i++) { - if (buf[i] == '\t') { - buf2[j++] = ' '; - while (j % 8 != 0) - buf2[j++] = ' '; - } else if (buf[i] == '\b') { - if (j > 0) - j--; - } else { - buf2[j++] = buf[i]; - } + struct string line; + string_init(&line); + + int ch; + while ((ch = fgetc(f)) != EOF) { + if (ch == '\n') { + string_finish(&line); + stringarray_add(&lines, &line); + string_init(&line); + } else if (ch == '\t') { + string_add(&line, ' '); + while (line.len % 8 != 0) + string_add(&line, ' '); + } else if (ch == '\b') { + if (line.len > 0) + line.len--; + } else { + string_add(&line, (char)ch); } - buf2[j] = '\0'; - - stringarray_add(&lines, buf2); - stringarray_add(&sollines, buf2); } + stringarray_dup(&sollines, &lines); + + extent_y = (int)lines.num; + for (int i = 0; i < extent_y; i++) + extent_x = imax(extent_x, (int)lines.v[i].len); + pclose(f); } @@ -178,8 +245,8 @@ encode(void) key[c] = t; } - for (int y = 0; y < (int)lines.num; y++) { - for (char *p = lines.v[y]; *p != '\0'; p++) { + for (int y = 0; y < extent_y; y++) { + for (char *p = lines.v[y].s; *p != '\0'; p++) { if (ch_islower(*p)) *p = (char)('a' + key[*p - 'a']); if (ch_isupper(*p)) @@ -191,13 +258,14 @@ encode(void) static bool substitute(char ch) { - assert(cury >= 0 && cury < (int)lines.num); - if (curx >= (int)strlen(lines.v[cury])) { + assert(cursor_x >= 0 && cursor_x < extent_x); + assert(cursor_y >= 0 && cursor_y < extent_y); + if (cursor_x >= cur_max_x()) { beep(); return false; } - char och = lines.v[cury][curx]; + char och = lines.v[cursor_y].s[cursor_x]; if (!ch_isalpha(och)) { beep(); return false; @@ -209,7 +277,7 @@ substitute(char ch) char uch = ch_toupper(ch); for (int y = 0; y < (int)lines.num; y++) { - for (char *p = lines.v[y]; *p != '\0'; p++) { + for (char *p = lines.v[y].s; *p != '\0'; p++) { if (*p == loch) *p = lch; else if (*p == uoch) @@ -225,44 +293,50 @@ substitute(char ch) //////////////////////////////////////////////////////////// +static bool +is_solved(void) +{ + for (size_t i = 0; i < lines.num; i++) + if (strcmp(lines.v[i].s, sollines.v[i].s) != 0) + return false; + return true; +} + static void redraw(void) { erase(); - bool won = true; - for (int i = 0; i < LINES - 1; i++) { - move(i, 0); - int ln = i + scrolldown; - if (ln < (int)lines.num) { - for (unsigned j = 0; lines.v[i][j] != '\0'; j++) { - char ch = lines.v[i][j]; - if (ch != sollines.v[i][j] && ch_isalpha(ch)) { - won = false; - } - bool bold = false; - if (hinting && ch == sollines.v[i][j] && - ch_isalpha(ch)) { - bold = true; - attron(A_BOLD); - } - addch(lines.v[i][j]); - if (bold) { - attroff(A_BOLD); - } - } + + int max_y = imin(LINES - 1, extent_y - offset_y); + for (int y = 0; y < max_y; y++) { + move(y, 0); + + int len = (int)lines.v[offset_y + y].len; + int max_x = imin(COLS - 1, len - offset_x); + const char *line = lines.v[offset_y + y].s; + const char *solline = sollines.v[offset_y + y].s; + + for (int x = 0; x < max_x; x++) { + char ch = line[offset_x + x]; + bool bold = hinting && + ch == solline[offset_x + x] && + ch_isalpha(ch); + + if (bold) + attron(A_BOLD); + addch(ch); + if (bold) + attroff(A_BOLD); } clrtoeol(); } move(LINES - 1, 0); - if (won) { + if (is_solved()) addstr("*solved* "); - } addstr("~ to quit, * to cheat, ^pnfb to move"); - move(LINES - 1, 0); - - move(cury - scrolldown, curx); + move(cursor_y - offset_y, cursor_x - offset_x); refresh(); } @@ -273,6 +347,7 @@ opencurses(void) initscr(); cbreak(); noecho(); + keypad(stdscr, true); } static void @@ -284,117 +359,149 @@ closecurses(void) //////////////////////////////////////////////////////////// static void -loop(void) +saturate_cursor(void) { - bool done = false; - while (!done) { - redraw(); - int ch = getch(); - switch (ch) { - case 1: /* ^A */ - case KEY_HOME: - curx = 0; - break; - case 2: /* ^B */ - case KEY_LEFT: - if (curx > 0) { - curx--; - } else if (cury > 0) { - cury--; - curx = (int)strlen(lines.v[cury]); - } - break; - case 5: /* ^E */ - case KEY_END: - curx = (int)strlen(lines.v[cury]); - break; - case 6: /* ^F */ - case KEY_RIGHT: - if (curx < (int)strlen(lines.v[cury])) { - curx++; - } else if (cury < (int)lines.num - 1) { - cury++; - curx = 0; - } - break; - case 12: /* ^L */ - clear(); - break; - case 14: /* ^N */ - case KEY_DOWN: - if (cury < (int)lines.num - 1) { - cury++; - } - if (curx > (int)strlen(lines.v[cury])) { - curx = (int)strlen(lines.v[cury]); - } - if (scrolldown < cury - (LINES - 2)) { - scrolldown = cury - (LINES - 2); - } - break; - case 16: /* ^P */ - case KEY_UP: - if (cury > 0) { - cury--; - } - if (curx > (int)strlen(lines.v[cury])) { - curx = (int)strlen(lines.v[cury]); - } - if (scrolldown > cury) { - scrolldown = cury; - } - break; - case '*': - hinting = !hinting; - break; - case '~': - done = true; - break; - default: - if (isascii(ch) && ch_isalpha((char)ch)) { - if (substitute((char)ch)) { - if (curx < (int)strlen(lines.v[cury])) { - curx++; - } - if (curx == (int)strlen(lines.v[cury]) && - cury < (int)lines.num - 1) { - curx = 0; - cury++; - } - } - } else if (curx < (int)strlen(lines.v[cury]) && - ch == lines.v[cury][curx]) { - curx++; - if (curx == (int)strlen(lines.v[cury]) && - cury < (int)lines.num - 1) { - curx = 0; - cury++; - } - } else { - beep(); + assert(cursor_y >= 0); + assert(cursor_y <= cur_max_y()); + + assert(cursor_x >= 0); + cursor_x = imin(cursor_x, cur_max_x()); +} + +static void +scroll_into_view(void) +{ + if (cursor_x < offset_x) + offset_x = cursor_x; + if (cursor_x > offset_x + COLS - 1) + offset_x = cursor_x - (COLS - 1); + + if (cursor_y < offset_y) + offset_y = cursor_y; + if (cursor_y > offset_y + LINES - 2) + offset_y = cursor_y - (LINES - 2); +} + +static void +handle_char_input(int ch) +{ + if (isascii(ch) && ch_isalpha((char)ch)) { + if (substitute((char)ch)) { + if (cursor_x < cur_max_x()) + cursor_x++; + if (cursor_x == cur_max_x() && + cursor_y < cur_max_y()) { + cursor_x = 0; + cursor_y++; } - break; } + } else if (cursor_x < cur_max_x() && + ch == lines.v[cursor_y].s[cursor_x]) { + cursor_x++; + if (cursor_x == cur_max_x() && + cursor_y < cur_max_y()) { + cursor_x = 0; + cursor_y++; + } + } else { + beep(); } } -//////////////////////////////////////////////////////////// - -int -main(void) +static bool +handle_key(void) { + int ch = getch(); + + switch (ch) { + case 1: /* ^A */ + case KEY_HOME: + cursor_x = 0; + break; + case 2: /* ^B */ + case KEY_LEFT: + if (cursor_x > 0) { + cursor_x--; + } else if (cursor_y > 0) { + cursor_y--; + cursor_x = cur_max_x(); + } + break; + case 5: /* ^E */ + case KEY_END: + cursor_x = cur_max_x(); + break; + case 6: /* ^F */ + case KEY_RIGHT: + if (cursor_x < cur_max_x()) { + cursor_x++; + } else if (cursor_y < cur_max_y()) { + cursor_y++; + cursor_x = 0; + } + break; + case 12: /* ^L */ + clear(); + break; + case 14: /* ^N */ + case KEY_DOWN: + if (cursor_y < cur_max_y()) + cursor_y++; + break; + case 16: /* ^P */ + case KEY_UP: + if (cursor_y > 0) + cursor_y--; + break; + case '*': + hinting = !hinting; + break; + case '~': + return false; + default: + handle_char_input(ch); + break; + } + return true; +} +static void +init(void) +{ stringarray_init(&lines); stringarray_init(&sollines); srandom((unsigned int)time(NULL)); readquote(); encode(); opencurses(); +} - keypad(stdscr, true); - loop(); +static void +loop(void) +{ + for (;;) { + redraw(); + if (!handle_key()) + break; + saturate_cursor(); + scroll_into_view(); + } +} +static void +clean_up(void) +{ closecurses(); stringarray_cleanup(&sollines); stringarray_cleanup(&lines); } + +//////////////////////////////////////////////////////////// + +int +main(void) +{ + init(); + loop(); + clean_up(); +} |