NetBSD/games/cgram/cgram.c
dholland 0e7c031ea9 Add a curses gizmo for solving Sunday-paper-type cryptograms based on
substitution ciphers. It gets the cleartext from fortune. I wrote this
some years ago for my own amusement; a couple people have suggested
that I should import it.

Approved only by groo, so I'm going to wait a couple days to hook it
to the build in case anyone demands it be removed again...
2013-08-04 05:42:47 +00:00

345 lines
7.3 KiB
C

/*-
* Copyright (c) 2013 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by David A. Holland.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
#include <err.h>
#include <assert.h>
#include <curses.h>
#include "pathnames.h"
////////////////////////////////////////////////////////////
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;
}
////////////////////////////////////////////////////////////
struct stringarray {
char **v;
int num;
};
static void stringarray_init(struct stringarray *a) {
a->v = NULL;
a->num = 0;
}
static void stringarray_cleanup(struct stringarray *a) {
free(a->v);
}
static void stringarray_add(struct stringarray *a, const char *s) {
a->v = realloc(a->v, (a->num + 1) * sizeof(a->v[0]));
if (a->v == NULL) {
errx(1, "Out of memory");
}
a->v[a->num] = xstrdup(s);
a->num++;
}
////////////////////////////////////////////////////////////
static struct stringarray lines;
static struct stringarray sollines;
static bool hinting;
static int scrolldown;
static unsigned curx;
static int cury;
static void readquote(void) {
FILE *f = popen(_PATH_FORTUNE, "r");
if (!f) {
err(1, "%s", _PATH_FORTUNE);
}
char buf[128], buf2[8*sizeof(buf)];
while (fgets(buf, sizeof(buf), f)) {
char *s = strrchr(buf, '\n');
assert(s);
assert(strlen(s)==1);
*s = 0;
int i,j;
for (i=j=0; buf[i]; i++) {
if (buf[i]=='\t') {
buf2[j++] = ' ';
while (j%8) buf2[j++] = ' ';
}
else if (buf[i]=='\b') {
if (j>0) j--;
}
else {
buf2[j++] = buf[i];
}
}
buf2[j] = 0;
stringarray_add(&lines, buf2);
stringarray_add(&sollines, buf2);
}
pclose(f);
}
static void encode(void) {
int used[26];
for (int i=0; i<26; i++) used[i] = 0;
int key[26];
int keypos=0;
while (keypos < 26) {
int c = random()%26;
if (used[c]) continue;
key[keypos++] = c;
used[c] = 1;
}
for (int y=0; y<lines.num; y++) {
for (unsigned x=0; lines.v[y][x]; x++) {
if (islower((unsigned char)lines.v[y][x])) {
int q = lines.v[y][x]-'a';
lines.v[y][x] = 'a'+key[q];
}
if (isupper((unsigned char)lines.v[y][x])) {
int q = lines.v[y][x]-'A';
lines.v[y][x] = 'A'+key[q];
}
}
}
}
static int substitute(int ch) {
assert(cury>=0 && cury<lines.num);
if (curx >= strlen(lines.v[cury])) {
beep();
return -1;
}
int och = lines.v[cury][curx];
if (!isalpha((unsigned char)och)) {
beep();
return -1;
}
int loch = tolower((unsigned char)och);
int uoch = toupper((unsigned char)och);
int lch = tolower((unsigned char)ch);
int uch = toupper((unsigned char)ch);
for (int y=0; y<lines.num; y++) {
for (unsigned x=0; lines.v[y][x]; x++) {
if (lines.v[y][x]==loch) {
lines.v[y][x] = lch;
}
else if (lines.v[y][x]==uoch) {
lines.v[y][x] = uch;
}
else if (lines.v[y][x]==lch) {
lines.v[y][x] = loch;
}
else if (lines.v[y][x]==uch) {
lines.v[y][x] = uoch;
}
}
}
return 0;
}
////////////////////////////////////////////////////////////
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 < lines.num) {
for (unsigned j=0; lines.v[i][j]; j++) {
int ch = lines.v[i][j];
if (ch != sollines.v[i][j] && isalpha((unsigned char)ch)) {
won = false;
}
bool bold=false;
if (hinting && ch==sollines.v[i][j] &&
isalpha((unsigned char)ch)) {
bold = true;
attron(A_BOLD);
}
addch(lines.v[i][j]);
if (bold) {
attroff(A_BOLD);
}
}
}
clrtoeol();
}
move(LINES-1, 0);
if (won) {
addstr("*solved* ");
}
addstr("~ to quit, * to cheat, ^pnfb to move");
move(LINES-1, 0);
move(cury-scrolldown, curx);
refresh();
}
static void opencurses(void) {
initscr();
cbreak();
noecho();
}
static void closecurses(void) {
endwin();
}
////////////////////////////////////////////////////////////
static void loop(void) {
bool done=false;
while (!done) {
redraw();
int ch = getch();
switch (ch) {
case 1: /* ^A */
curx=0;
break;
case 2: /* ^B */
if (curx > 0) {
curx--;
}
else if (cury > 0) {
cury--;
curx = strlen(lines.v[cury]);
}
break;
case 5: /* ^E */
curx = strlen(lines.v[cury]);
break;
case 6: /* ^F */
if (curx < strlen(lines.v[cury])) {
curx++;
}
else if (cury < lines.num - 1) {
cury++;
curx = 0;
}
break;
case 12: /* ^L */
clear();
break;
case 14: /* ^N */
if (cury < lines.num-1) {
cury++;
}
if (curx > strlen(lines.v[cury])) {
curx = strlen(lines.v[cury]);
}
if (scrolldown < cury - (LINES-2)) {
scrolldown = cury - (LINES-2);
}
break;
case 16: /* ^P */
if (cury > 0) {
cury--;
}
if (curx > strlen(lines.v[cury])) {
curx = strlen(lines.v[cury]);
}
if (scrolldown > cury) {
scrolldown = cury;
}
break;
case '*':
hinting = !hinting;
break;
case '~':
done = true;
break;
default:
if (isalpha(ch)) {
if (!substitute(ch)) {
if (curx < strlen(lines.v[cury])) {
curx++;
}
if (curx==strlen(lines.v[cury]) && cury < lines.num-1) {
curx=0;
cury++;
}
}
}
else if (curx < strlen(lines.v[cury]) && ch==lines.v[cury][curx]) {
curx++;
if (curx==strlen(lines.v[cury]) && cury < lines.num-1) {
curx=0;
cury++;
}
}
else {
beep();
}
break;
}
}
}
////////////////////////////////////////////////////////////
int main(void) {
stringarray_init(&lines);
stringarray_init(&sollines);
srandom(time(NULL));
readquote();
encode();
opencurses();
loop();
closecurses();
stringarray_cleanup(&sollines);
stringarray_cleanup(&lines);
return 0;
}