#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "bog.h"
#include "extern.h"
static void init(void);
static void init_adjacencies(void);
static int compar(const void *, const void *);
struct dictindex dictindex[26];
static int **adjacency, **letter_map;
char *board;
int wordpath[MAXWORDLEN + 1];
int wordlen;
int usedbits;
int ncubes;
int grid = 4;
char **pword, *pwords, *pwordsp;
int npwords, maxpwords = MAXPWORDS, maxpspace = MAXPSPACE;
char **mword, *mwords, *mwordsp;
int nmwords, maxmwords = MAXMWORDS, maxmspace = MAXMSPACE;
int ngames = 0;
int tnmwords = 0, tnpwords = 0;
jmp_buf env;
time_t start_t;
static FILE *dictfp;
int batch;
int challenge;
int debug;
int minlength;
int reuse;
int selfuse;
int tlimit;
int
main(int argc, char *argv[])
{
int ch, done;
char *bspec, *p;
batch = debug = reuse = selfuse;
bspec = NULL;
minlength = -1;
tlimit = 180;
while ((ch = getopt(argc, argv, "Bbcdht:w:")) != -1)
switch(ch) {
case 'B':
grid = 5;
break;
case 'b':
batch = 1;
break;
case 'c':
challenge = 1;
break;
case 'd':
debug = 1;
break;
case 't':
if ((tlimit = atoi(optarg)) < 1)
errx(1, "bad time limit");
break;
case 'w':
if ((minlength = atoi(optarg)) < 3)
errx(1, "min word length must be > 2");
break;
case 'h':
default:
usage();
}
argc -= optind;
argv += optind;
ncubes = grid * grid;
if (argc > 0) {
if (strcmp(argv[0], "+") == 0)
reuse = 1;
else if (strcmp(argv[0], "++") == 0)
selfuse = 1;
}
if (reuse || selfuse) {
argc -= 1;
argv += 1;
}
if (argc == 1) {
if (strlen(argv[0]) != ncubes)
usage();
for (p = argv[0]; *p != '\0'; p++)
if (!islower((unsigned char)*p))
errx(1, "only lower case letters are allowed "
"in boardspec");
bspec = argv[0];
} else if (argc != 0)
usage();
if (batch && bspec == NULL)
errx(1, "must give both -b and a board setup");
init();
if (batch) {
newgame(bspec);
while ((p = batchword(stdin)) != NULL)
(void) printf("%s\n", p);
return 0;
}
setup();
if (pledge("stdio rpath tty", NULL) == -1)
err(1, "pledge");
prompt("Loading the dictionary...");
if ((dictfp = opendict(DICT)) == NULL) {
warn("%s", DICT);
cleanup();
return 1;
}
#ifdef LOADDICT
if (loaddict(dictfp) < 0) {
warnx("can't load %s", DICT);
cleanup();
return 1;
}
(void)fclose(dictfp);
dictfp = NULL;
#endif
if (loadindex(DICTINDEX) < 0) {
warnx("can't load %s", DICTINDEX);
cleanup();
return 1;
}
prompt("Type <space> to begin...");
while (inputch() != ' ');
for (done = 0; !done;) {
newgame(bspec);
bspec = NULL;
playgame();
prompt("Type <space> to continue, any cap to quit...");
delay(10);
flushin(stdin);
for (;;) {
ch = inputch();
if (ch == '\033')
findword();
else if (ch == '\014' || ch == '\022')
redraw();
else {
if (isupper(ch)) {
done = 1;
break;
}
if (ch == ' ')
break;
}
}
}
cleanup();
return 0;
}
char *
batchword(FILE *fp)
{
int *p, *q;
char *w;
q = &wordpath[MAXWORDLEN + 1];
p = wordpath;
while (p < q)
*p++ = -1;
while ((w = nextword(fp)) != NULL) {
if (wordlen < minlength)
continue;
p = wordpath;
while (p < q && *p != -1)
*p++ = -1;
usedbits = 0;
if (checkword(w, -1, wordpath) != -1)
return (w);
}
return (NULL);
}
void
playgame(void)
{
int i, *p, *q;
time_t t;
char buf[MAXWORDLEN + 1];
ngames++;
npwords = 0;
pwordsp = pwords;
nmwords = 0;
mwordsp = mwords;
time(&start_t);
q = &wordpath[MAXWORDLEN + 1];
p = wordpath;
while (p < q)
*p++ = -1;
showboard(board);
startwords();
if (setjmp(env)) {
badword();
goto timesup;
}
while (1) {
if (get_line(buf) == NULL) {
if (feof(stdin))
clearerr(stdin);
break;
}
time(&t);
if (t - start_t >= tlimit) {
badword();
break;
}
if (buf[0] == '\0') {
int remaining;
remaining = tlimit - (int) (t - start_t);
(void)snprintf(buf, sizeof(buf),
"%d:%02d", remaining / 60, remaining % 60);
showstr(buf, 1);
continue;
}
if (strlen(buf) < (size_t)minlength) {
badword();
continue;
}
p = wordpath;
while (p < q && *p != -1)
*p++ = -1;
usedbits = 0;
if (checkword(buf, -1, wordpath) < 0)
badword();
else {
if (debug) {
(void) printf("[");
for (i = 0; wordpath[i] != -1; i++)
(void) printf(" %d", wordpath[i]);
(void) printf(" ]\n");
}
for (i = 0; i < npwords; i++) {
if (strcmp(pword[i], buf) == 0)
break;
}
if (i != npwords) {
badword();
showword(i);
}
else if (!validword(buf))
badword();
else {
int len;
if (npwords == maxpwords - 1) {
maxpwords += MAXPWORDS;
pword = reallocarray(pword, maxpwords,
sizeof(char *));
if (pword == NULL) {
cleanup();
errx(1, "%s", strerror(ENOMEM));
}
}
len = strlen(buf) + 1;
if (pwordsp + len >= &pwords[maxpspace]) {
maxpspace += MAXPSPACE;
pwords = realloc(pwords, maxpspace);
if (pwords == NULL) {
cleanup();
errx(1, "%s", strerror(ENOMEM));
}
}
pword[npwords++] = pwordsp;
memcpy(pwordsp, buf, len);
pwordsp += len;
addword(buf);
}
}
}
timesup: ;
qsort(pword, npwords, sizeof(pword[0]), compar);
pword[npwords] = NULL;
checkdict();
tnmwords += nmwords;
tnpwords += npwords;
results();
}
int
checkword(char *word, int prev, int *path)
{
char *p, *q;
int i, *lm;
if (debug) {
(void) printf("checkword(%s, %d, [", word, prev);
for (i = 0; wordpath[i] != -1; i++)
(void) printf(" %d", wordpath[i]);
(void) printf(" ]\n");
}
if (*word == '\0')
return (1);
lm = letter_map[*word - 'a'];
if (prev == -1) {
char subword[MAXWORDLEN + 1];
p = word;
q = subword;
while (*p != '\0') {
if (*letter_map[*p - 'a'] == -1)
return (-1);
*q++ = *p;
if (*p++ == 'q') {
if (*p++ != 'u')
return (-1);
}
}
*q = '\0';
while (*lm != -1) {
*path = *lm;
usedbits |= (1 << *lm);
if (checkword(subword + 1, *lm, path + 1) > 0)
return (1);
usedbits &= ~(1 << *lm);
lm++;
}
return (-1);
}
for (i = 0; lm[i] != -1; i++) {
if (adjacency[prev][lm[i]]) {
int used;
used = 1 << lm[i];
if (!reuse && !selfuse && (usedbits & used))
continue;
*path = lm[i];
usedbits |= used;
if (checkword(word + 1, lm[i], path + 1) > 0)
return (1);
usedbits &= ~used;
}
}
*path = -1;
return (-1);
}
int
validword(char *word)
{
int j;
char *q, *w;
j = word[0] - 'a';
if (dictseek(dictfp, dictindex[j].start, SEEK_SET) < 0) {
cleanup();
errx(1, "seek error in validword()");
}
while ((w = nextword(dictfp)) != NULL) {
int ch;
if (*w != word[0])
break;
q = word;
while ((ch = *w++) == *q++ && ch != '\0')
;
if (*(w - 1) == '\0' && *(q - 1) == '\0')
return (1);
}
if (dictfp != NULL && feof(dictfp))
clearerr(dictfp);
return (0);
}
void
checkdict(void)
{
char **pw, *w;
int i;
int prevch, previndex, *pi, *qi, st;
mwordsp = mwords;
nmwords = 0;
pw = pword;
prevch ='a';
qi = &wordpath[MAXWORDLEN + 1];
(void) dictseek(dictfp, 0L, SEEK_SET);
while ((w = nextword(dictfp)) != NULL) {
if (wordlen < minlength)
continue;
if (*w != prevch) {
i = (int) (*w - 'a');
while (i < 26 && letter_map[i][0] == -1)
i++;
if (i == 26)
break;
previndex = prevch - 'a';
prevch = i + 'a';
if (i != previndex + 1) {
if (dictseek(dictfp,
dictindex[i].start, SEEK_SET) < 0) {
cleanup();
errx(1, "seek error in checkdict()");
}
continue;
}
}
pi = wordpath;
while (pi < qi && *pi != -1)
*pi++ = -1;
usedbits = 0;
if (checkword(w, -1, wordpath) == -1)
continue;
st = 1;
while (*pw != NULL && (st = strcmp(*pw, w)) < 0)
pw++;
if (st == 0)
continue;
if (nmwords == maxmwords - 1) {
maxmwords += MAXMWORDS;
mword = reallocarray(mword, maxmwords, sizeof(char *));
if (mword == NULL) {
cleanup();
errx(1, "%s", strerror(ENOMEM));
}
}
if (mwordsp + wordlen + 1 >= &mwords[maxmspace]) {
maxmspace += MAXMSPACE;
mwords = realloc(mwords, maxmspace);
if (mwords == NULL) {
cleanup();
errx(1, "%s", strerror(ENOMEM));
}
}
mword[nmwords++] = mwordsp;
memcpy(mwordsp, w, wordlen + 1);
mwordsp += wordlen + 1;
}
}
void
newgame(char *b)
{
int i, p, q;
char *tmp, **cubes;
int *lm[26];
char chal_cube[] = "iklmqu";
static char *cubes4[] = {
"ednosw", "aaciot", "acelrs", "ehinps",
"eefhiy", "elpstu", "acdemp", "gilruw",
"egkluy", "ahmors", "abilty", "adenvz",
"bfiorx", "dknotu", "abjmoq", "egintv"
};
static char *cubes5[] = {
"aaafrs", "aaeeee", "aafirs", "adennn", "aeeeem",
"aeegmu", "aegmnn", "afirsy", "bjkqxz", "ccnstw",
"ceiilt", "ceilpt", "ceipst", "ddlnor", "dhhlor",
"dhhnot", "dhlnor", "eiiitt", "emottt", "ensssu",
"fiprsy", "gorrvw", "hiprry", "nootuw", "ooottu"
};
cubes = grid == 4 ? cubes4 : cubes5;
if (b == NULL) {
p = ncubes;
while (--p) {
q = (int)arc4random_uniform(p + 1);
tmp = cubes[p];
cubes[p] = cubes[q];
cubes[q] = tmp;
}
for (i = 0; i < ncubes; i++)
board[i] = cubes[i][arc4random_uniform(6)];
if (challenge) {
i = arc4random_uniform(ncubes);
board[i] = SETHI(chal_cube[arc4random_uniform(6)]);
}
} else {
for (i = 0; i < ncubes; i++)
board[i] = b[i];
}
board[ncubes] = '\0';
for (i = 0; i < 26; i++) {
lm[i] = letter_map[i];
*lm[i] = -1;
}
for (i = 0; i < ncubes; i++) {
int j;
j = (int) (SEVENBIT(board[i]) - 'a');
*lm[j] = i;
*(++lm[j]) = -1;
}
if (debug) {
for (i = 0; i < 26; i++) {
int ch, j;
(void) printf("%c:", 'a' + i);
for (j = 0; (ch = letter_map[i][j]) != -1; j++)
(void) printf(" %d", ch);
(void) printf("\n");
}
}
}
static int
compar(const void *p, const void *q)
{
return (strcmp(*(char **)p, *(char **)q));
}
static void
init(void)
{
int i;
if (minlength == -1)
minlength = grid - 1;
init_adjacencies();
board = malloc(ncubes + 1);
if (board == NULL)
err(1, NULL);
letter_map = calloc(26, sizeof(int *));
if (letter_map == NULL)
err(1, NULL);
for (i = 0; i < 26; i++) {
letter_map[i] = calloc(ncubes, sizeof(int));
if (letter_map[i] == NULL)
err(1, NULL);
}
pword = calloc(maxpwords, sizeof(char *));
if (pword == NULL)
err(1, NULL);
pwords = malloc(maxpspace);
if (pwords == NULL)
err(1, NULL);
mword = calloc(maxmwords, sizeof(char *));
if (mword == NULL)
err(1, NULL);
mwords = malloc(maxmspace);
if (mwords == NULL)
err(1, NULL);
}
#define SET_ADJ(r) do { \
if (col > 0) \
adj[r - 1] = 1; \
adj[r] = 1; \
if (col + 1 < grid) \
adj[r + 1] = 1; \
} while(0)
static void
init_adjacencies(void)
{
int cube, row, col, *adj;
adjacency = calloc(ncubes, sizeof(int *));
if (adjacency == NULL)
err(1, NULL);
for (cube = 0; cube < ncubes; cube++) {
adj = adjacency[cube] = calloc(ncubes, sizeof(int));
if (adj == NULL)
err(1, NULL);
row = cube / grid;
col = cube % grid;
SET_ADJ(cube);
if (!selfuse)
adj[cube] = 0;
if (row > 0)
SET_ADJ(cube - grid);
if (row + 1 < grid)
SET_ADJ(cube + grid);
}
}
void
usage(void)
{
extern char *__progname;
(void) fprintf(stderr, "usage: "
"%s [-Bbcd] [-t time] [-w length] [+[+]] [boardspec]\n",
__progname);
exit(1);
}