summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrillig <rillig@NetBSD.org>2021-02-21 20:33:42 +0000
committerCameron Katri <me@cameronkatri.com>2021-04-13 15:28:33 -0400
commit6535b2e014adf4ec8033eaf18a55e8476d124cda (patch)
tree0358a9a7bd9da6d6150fb2abe872d48b5effe236
parenteddba41ef6625aba9839b88ff828073e89008051 (diff)
downloadbsdgames-darwin-6535b2e014adf4ec8033eaf18a55e8476d124cda.tar.gz
bsdgames-darwin-6535b2e014adf4ec8033eaf18a55e8476d124cda.tar.zst
bsdgames-darwin-6535b2e014adf4ec8033eaf18a55e8476d124cda.zip
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.
-rw-r--r--cgram/cgram.c465
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();
+}