/*
This is logically the same program as boggle.c, but I have updated
it as follows:
1) instead of checking each prefix and word with an external
procedure (which requires walking down the DAWG each time),
I walk the DAWG in parallel with doing the maze walk of the
boggle board. This makes it *far* more efficient.
2) I have made the code re-entrant, so that you can check
multiple boggle layouts within one run of the program. This
will make it easy to adapt the code to run inside Phil Goetz's
genetic algorithm boggle-board generator, *without* the overhead
of both loading an external program *and* an external wordlist
for every board evaluation.
3) results are recorded in an internal dynamic trie, rather than
just printed out as soon as they're found. This has two
benefits - the words are both unique, and sorted. This saves
having to pipe the output through sort|uniq.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "splib.h"
/* header for dyntrie.c is missing */
extern void trie_add(NODE **dictp, char *word);
extern int trie_new(NODE **dawgp);
extern int trie_free(NODE **dictp);
#ifndef TRUE
#define TRUE (0==0)
#define FALSE (!TRUE)
#endif
static int score = 0; /* total score */
NODE *dict; /* master word list */
INDEX nedges; /* size of wordlist */
static NODE *results; /* words found */
static void boggle_handle_word(char *word)
{
int len;
/* fprintf(stdout, "%s\n", word); */
/*
See http://www.centralconnector.com/GAMES/boggle.html
Calculate score in accordance with boggle rules:
1 point for 3- or 4-letter words, 2 points for 5-letter words,
3 points for 6, 5 for 7-letter words, and 11 points for 8 or more letters.
QU seems to count as 2 letters.
Note French boggle appears to be:
1 point for 4 letters, 2 for 5 letters, 3 for six letters,
5 for 7 letters, 11 for >= 8 letters
*/
len = strlen(word);
if (len <= 4) {
score += 1;
} else if (len == 5) {
score += 2;
} else if (len == 6) {
score += 3;
} else if (len == 7) {
score += 5;
} else {
score += 11;
}
}
static void dawg_walk(NODE *dawg, INDEX node, int len)
{
static char word[MAX_WORD_LEN];
NODE *edge;
for (edge = (NODE *)&dawg[node]; ; edge++) {
long c;
c = *edge; /* Don't rewrite this - its avoiding a MSC bug */
c = c >> V_LETTER;
c = c & M_LETTER;
word[len] = (char)c;
if ((*edge & M_END_OF_WORD) != 0) {
word[len+1] = '\0';
boggle_handle_word(word);
}
c = *edge & M_NODE_POINTER;
if ((*edge & M_NODE_POINTER) != 0)
dawg_walk(dawg, c, len + 1);
if ((*edge & M_END_OF_NODE) != 0) break; /* End of node */
}
}
static void boggle(int bogsize, int x, int y, char *stem, int stemlen,
int board[5][5], int used[5][5],
NODE *hnode, NODE *rootdawg)
{
int dx, dy, len;
/* pruning illegal moves would be more efficient outside the procedure call,
but not as elegant */
if (x < 0 || x >= bogsize) return;
if (y < 0 || y >= bogsize) return;
if (used[x][y]) return;
if (board[x][y] == 'Q') {
/* Hack for 'qu' tile */
/* Note it wouldn't be much of a hack to *also* invoke the
next step to search for those words with 'q' which *aren't*
followed by a 'u'. However I don't think they're allowed
in boggle... */
stem[stemlen] = 'q';
for (;;) { /* is the first letter of our string valid? */
if ((char)(((*hnode) >> V_LETTER) & M_LETTER) == 'q') {
/* Yes, this letter is valid here! */
break;
}
if (((*hnode) & M_END_OF_NODE) != 0) {
/* we've checked every letter that can be allowed here. */
return; /* PRUNE SEARCH */
}
hnode++;
}
if (((*hnode) & M_NODE_POINTER) == 0) return;
hnode = rootdawg+((*hnode) & M_NODE_POINTER);
stem[stemlen+1] = 'u';
for (;;) { /* is the first letter of our string valid? */
if ((char)(((*hnode) >> V_LETTER) & M_LETTER) == 'u') {
/* Yes, this letter is valid here! */
break;
}
if (((*hnode) & M_END_OF_NODE) != 0) {
/* we've checked every letter that can be allowed here. */
return; /* PRUNE SEARCH */
}
hnode++;
}
if (((*hnode) & M_NODE_POINTER) == 0) return;
hnode = rootdawg+((*hnode) & M_NODE_POINTER);
/* It's a hack, but we'll just assume that no words END in 'qu'.
- I'm lazy. */
len = 2;
} else {
int ch = board[x][y];
stem[stemlen] = ch;
len = 1;
for (;;) { /* is the first letter of our string valid? */
if (ch == (char)(((*hnode) >> V_LETTER) & M_LETTER)) {
/* Yes, this letter is valid here! */
break;
}
if (((*hnode) & M_END_OF_NODE) != 0) {
/* we've checked every letter that can be allowed here. */
return; /* PRUNE SEARCH */
}
hnode++;
}
stem[stemlen+len] = '\0';
if (((*hnode) & M_END_OF_WORD) != 0) { /* Got one */
if (stemlen+len >= 3) trie_add(&results, stem);
}
if (((*hnode) & M_NODE_POINTER) == 0) return;
hnode = rootdawg+((*hnode) & M_NODE_POINTER);
}
used[x][y] = TRUE;
for (dy = -1; dy <= 1; dy++) {
for (dx = -1; dx <= 1; dx++) {
/* The 'if' is optional - would be trimmed inside by used[][]==TRUE */
if (dx != 0 || dy != 0) {
boggle(bogsize, x+dx, y+dy, stem, stemlen+len, board, used,
hnode, rootdawg);
}
}
}
stem[stemlen] = '\0'; /* Backtrack */
used[x][y] = FALSE;
return;
}
int bogglescore(char *rawboard)
{
int board[5][5];
int used[5][5];
char word[51];
int x, y;
int next;
next = 0;
score = 0; /* total score */
for (y = 0; y < 4 /* bogsize */; y++) {
for (x = 0; x < 4 /* bogsize */; x++) {
board[x][y] = rawboard[next++];
if (board[x][y] == 'q') board[x][y] = 'Q';
used[x][y] = FALSE;
}
}
word[0] = '\0';
if (!(trie_new(&results))) {
exit(1);
}
for (y = 0; y < 4 /* bogsize */; y++) {
for (x = 0; x < 4 /* bogsize */; x++) {
boggle(4 /* bogsize */, x, y, word, 0, board, used, dict+ROOT_NODE, dict);
}
}
dawg_walk(results, ROOT_NODE, 0); /* sorted and unique */
trie_free(&results); /* Must free results trie when finished with it */
return(score);
}