From 6535b2e014adf4ec8033eaf18a55e8476d124cda Mon Sep 17 00:00:00 2001 From: rillig Date: Sun, 21 Feb 2021 20:33:42 +0000 Subject: cgram: rewrite completely, fixing bugs and style Fixed bugs: Do not consider the puzzle solved if all letters in the visible area are substituted correctly. To be properly solved, the whole puzzle must be solved, even those parts that are currently off-screen. Never place the cursor at the very right edge of the screen since that does not work well with some terminals. The maximum valid x coordinate is COLS - 1. Add horizontal scrolling. Make all coordinate handling symmetric in regard to the horizontal and vertical axes. Previously, lines longer than 80 characters could not be seen on the screen. Improvements: Remove the arbitrary limit of 128 characters per line. Even if fortune(6) may never generate such long lines, the code is easy enough to adapt to other sources. Properly clean up the allocated memory. Previously, only the string arrays were freed but not the strings themselves. Stylistic: Add RCS ID. Fix ctype functions in lint's strict bool mode. Avoid excessive calls to strlen whenever the cursor moves. Given that the whole screen is redrawn every time a key is pressed, this is an unnecessary optimization, but the code smelled nevertheless. --- cgram/cgram.c | 465 ++++++++++++++++++++++++++++++++++++---------------------- 1 file 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 +#if defined(__RCSID) && !defined(lint) +__RCSID("$NetBSD: cgram.c,v 1.10 2021/02/21 20:33:42 rillig Exp $"); +#endif + #include #include #include @@ -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,28 +63,73 @@ 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) { @@ -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(); +} -- cgit v1.2.3-56-ge451