// boggle.c
// --------
// Copyright 2002, Chuong Do
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define SIZE 6 // sets boggle board size
#define NUM_TOP 20
#define NUM_KEEP 25
#define UPDATE (1000 / NUM_KEEP)
#define NUM_BOARDS (NUM_KEEP * 2)
#define PATIENCE (100000 / NUM_KEEP)
#define INCREMENT (PATIENCE / 100 / NUM_KEEP)
#define ITERATIONS 100
#define NUM_PERTURBATIONS 4
#define PSEUDOCOUNTS 1
#define MAX_TRYS 3
int hist[20];
typedef char grid[SIZE][SIZE];
typedef struct {
grid data;
long score;
} boardType;
struct node {
struct node *child[26];
int isTerminal;
char *used;
};
inline int min (int a, int b){
if (a < b) return a;
return b;
}
inline int max (int a, int b){
if (a > b) return a;
return b;
}
int score1 (int len, char *buffer){
if (len <= 2) return 0;
if (len <= 4) return 1;
if (len == 5) return 2;
if (len == 6) return 3;
if (len == 7) return 5;
return 11;
}
int score2 (int len, char *buffer){
if (len <= 2) return 0;
return 1;
}
int score3 (int len, char *buffer){
if (len <= 2) return 0;
hist[len]++;
buffer[len] = '\0';
printf ("%s\n", buffer);
return 1;
}
int score4 (int len, char *buffer){
return len*len*len;
}
void addWord (struct node **root, char *word){
int i;
if (*root == NULL){
*root = (struct node *) malloc (sizeof (struct node));
for (i = 0; i < 26; i++) (*root)->child[i] = NULL;
(*root)->isTerminal = 0;
}
if (strlen (word) == 0)
(*root)->isTerminal = 1;
else
addWord (&((*root)->child[(int)(tolower(word[0]) - 'a')]), word + 1);
}
int makeTrie (struct node **root, char *filename, char **wordused){
int i = 0;
char buffer[256];
FILE *data = fopen (filename, "r");
*root = NULL;
while (!feof (data)){
if (fscanf (data, "%s", buffer) == 1){
addWord (root, buffer);
i++;
}
}
*wordused = (char *) malloc (sizeof (char) * i);
while (i >= 0){
(*wordused)[i] = 0;
i--;
}
i = 0;
threadTrie (*root, *wordused, &i);
fclose (data);
return i;
}
int threadTrie (struct node *root, char *wordused, int *ct){
int i;
if (root != NULL){
if (root->isTerminal)
root->used = wordused + (*ct)++;
for (i = 0; i < 26; i++) threadTrie (root->child[i], wordused, ct);
}
}
int clearTrie (struct node *root){
int i;
if (root){
if (root->isTerminal) root->used = 0;
for (i = 0; i < 26; i++) clearTrie (root->child[i]);
}
}
int count (struct node *root, grid board, grid used,
int (*fn)(int, char*), int r, int c, int len, char *buffer){
int score = 0;
if (r >= 0 && c >= 0 && r < SIZE && c < SIZE && !used[r][c]){
root = root->child[(int)(board[r][c] - 'a')];
buffer[len] = board[r][c];
if (root != NULL){
len++;
used[r][c] = 1;
if (root->isTerminal && !*(root->used)){
score = fn(len, buffer);
*(root->used) = 1;
}
score +=
count (root, board, used, fn, r + 1, c, len, buffer) +
count (root, board, used, fn, r - 1, c, len, buffer) +
count (root, board, used, fn, r, c + 1, len, buffer) +
count (root, board, used, fn, r, c - 1, len, buffer) +
count (root, board, used, fn, r + 1, c + 1, len, buffer) +
count (root, board, used, fn, r - 1, c - 1, len, buffer) +
count (root, board, used, fn, r - 1, c + 1, len, buffer) +
count (root, board, used, fn, r + 1, c - 1, len, buffer);
used[r][c] = 0;
}
}
return score;
}
int getScore (struct node *root, grid board, int (*fn)(int, char*), char *wordused, int numwords){
int i, j, score = 0;
grid used;
char buffer[256];
memset (wordused, 0, sizeof (char) * numwords);
for (i = 0; i < SIZE; i++)
for (j = 0; j < SIZE; j++)
used[i][j] = 0;
for (i = 0; i < SIZE; i++)
for (j = 0; j < SIZE; j++)
score += count (root, board, used, fn, i, j, 0, buffer);
return score;
}
typedef void (*perturbFn)(grid board, int num);
void perturb0 (grid board, int num){
int i;
for (i = random() % (num + 1); i >= 0; i--)
board[random() % SIZE][random() % SIZE] = 'a' + (random() % 26);
}
void perturb1 (grid board, int num){
int i, r1, c1, r2, c2;
char ch;
for (i = random() % (num + 1); i >= 0; i--){
r1 = random() % SIZE;
c1 = random() % SIZE;
switch (random() % 8){
case 0: r2 = r1 + 1; c2 = c1; break;
case 1: r2 = r1 - 1; c2 = c1; break;
case 2: r2 = r1; c2 = c1 + 1; break;
case 3: r2 = r1; c2 = c1 - 1; break;
case 4: r2 = r1 + 1; c2 = c1 + 1; break;
case 5: r2 = r1 - 1; c2 = c1 - 1; break;
case 6: r2 = r1 - 1; c2 = c1 + 1; break;
case 7: r2 = r1 + 1; c2 = c1 - 1; break;
}
if (r2 >= 0 && c2 >= 0 && r2 < SIZE && c2 < SIZE){
ch = board[r1][c1];
board[r1][c1] = board[r2][c2];
board[r2][c2] = ch;
}
else
i--;
}
}
void perturb2 (grid board, int num){
int i, r1, c1, r2, c2;
char ch;
for (i = random() % (num + 1); i >= 0; i--){
r1 = random() % SIZE;
c1 = random() % SIZE;
r2 = random() % SIZE;
c2 = random() % SIZE;
if (r1 != r2 || c1 != c2){
ch = board[r1][c1];
board[r1][c1] = board[r2][c2];
board[r2][c2] = ch;
}
else
i--;
}
}
void perturb3 (grid board, int num){
char temp[SIZE][SIZE];
int i, j;
for (i = 0; i < SIZE; i++){
for (j = 0; j < SIZE; j++){
temp[i][j] = board[(i + num / SIZE) % SIZE][(i + num) % SIZE];
}
}
memcpy (board, temp, sizeof (grid));
}
int makeShot (int attempts[NUM_PERTURBATIONS * SIZE * SIZE], int hits[NUM_PERTURBATIONS * SIZE * SIZE]){
double shot = (double) (random() % INT_MAX) / (double) INT_MAX;
double total = 0, subtotal = 0, next;
int i;
for (i = 0; i < NUM_PERTURBATIONS * SIZE * SIZE; i++) total += (double) hits[i] / (double) attempts[i];
shot *= total;
for (i = 0; i < NUM_PERTURBATIONS * SIZE * SIZE; i++){
next = subtotal + (double) hits[i] / (double) attempts[i];
if (subtotal <= shot && shot <= next){
return i;
}
subtotal = next;
}
return 0;
}
void makeRandomBoard (boardType *board){
int i, j;
for (i = 0; i < SIZE; i++){
for (j = 0; j < SIZE; j++){
board->data[i][j] = 'a' + (random() % 26);
}
}
}
void printUpdate (int pass, int pat, boardType *boards, boardType *best, int *hits, int *attempts,
struct node *root, char *wordused, int numwords){
int i, j, k;
fprintf (stderr, "%d passes (%d remaining) -- \"", pass, pat);
for (i = 0; i < SIZE; i++){
for (j = 0; j < SIZE; j++)
fprintf (stderr, "%c", boards[0].data[i][j]);
if (i < SIZE - 1) fprintf (stderr, "/");
}
fprintf (stderr, "\":%d/%d (\"",
getScore (root, boards[0].data, score1, wordused, numwords),
getScore (root, boards[0].data, score2, wordused, numwords));
for (i = 0; i < SIZE; i++){
for (j = 0; j < SIZE; j++)
fprintf (stderr, "%c", best->data[i][j]);
if (i < SIZE - 1) fprintf (stderr, "/");
}
fprintf (stderr, "\":%d/%d)\n",
getScore (root, best->data, score1, wordused, numwords),
getScore (root, best->data, score2, wordused, numwords));
for (i = 0; i < NUM_PERTURBATIONS; i++){
for (j = 0; j < SIZE * SIZE; j++)
fprintf (stderr, " %4.2lf", (double) hits[j * NUM_PERTURBATIONS + i] * 100 /
(double) attempts[j * NUM_PERTURBATIONS + i]);
fprintf (stderr, "\n");
}
for (i = 0; i < NUM_TOP; i++){
for (j = 0; j < SIZE; j++){
for (k = 0; k < SIZE; k++)
fprintf (stderr, "%c", boards[i].data[j][k]);
if (j < SIZE - 1) fprintf (stderr, "/");
}
fprintf (stderr, " %5d/%4d\n",
getScore (root, boards[i].data, score1, wordused, numwords),
getScore (root, boards[i].data, score2, wordused, numwords));
}
fprintf (stderr, "\n");
}
int compFn (const void *a, const void *b){
return ((boardType *) b)->score - ((boardType *) a)->score;
}
int duplicateFound (boardType *boards, int i){
int j;
for (j = 0; j < i; j++){
if (boards[i].score == boards[j].score &&
memcmp (boards[i].data, boards[j].data, sizeof (grid)) == 0)
return 1;
}
return 0;
}
void genBoard (struct node *root, char *wordused, int numwords){
boardType best, boards[NUM_BOARDS];
int i, iter, pat, pass = 0;
long prevscore;
perturbFn perturb[NUM_PERTURBATIONS];
int attempts[NUM_PERTURBATIONS * SIZE * SIZE];
int hits[NUM_PERTURBATIONS * SIZE * SIZE];
int shot, trys;
for (i = 0; i < NUM_PERTURBATIONS * SIZE * SIZE; i++)
attempts[i] = hits[i] = PSEUDOCOUNTS;
perturb[0] = perturb0;
perturb[1] = perturb1;
perturb[2] = perturb2;
perturb[3] = perturb3;
best.score = -999;
for (iter = 0; iter < ITERATIONS; iter++){
for (i = 0; i < NUM_BOARDS; i++){
do {
makeRandomBoard (&boards[i]);
boards[i].score = getScore (root, boards[i].data, score1, wordused, numwords);
} while (duplicateFound (boards, i));
}
qsort (boards, NUM_BOARDS, sizeof (boardType), compFn);
pass = 0;
pat = PATIENCE;
while (pat){
for (i = 0; i < NUM_KEEP; i++){
shot = makeShot (attempts, hits);
attempts[shot]++;
memcpy (boards[i + NUM_KEEP].data, boards[i].data, sizeof (grid));
trys = 0;
do {
if (trys > MAX_TRYS)
makeRandomBoard (&boards[i + NUM_KEEP]);
else
perturb[shot % NUM_PERTURBATIONS](boards[i + NUM_KEEP].data, shot / NUM_PERTURBATIONS);
boards[i + NUM_KEEP].score = getScore (root, boards[i + NUM_KEEP].data, score1, wordused, numwords);
trys++;
} while (duplicateFound (boards, i + NUM_KEEP));
if (boards[i + NUM_KEEP].score > boards[i].score) hits[shot]++;
}
pat--;
pass++;
prevscore = boards[0].score;
qsort (boards, NUM_BOARDS, sizeof (boardType), compFn);
if (boards[0].score > best.score){
best.score = boards[0].score;
memcpy (best.data, boards[0].data, sizeof (grid));
}
if (prevscore < boards[0].score)
pat = PATIENCE;
if (pass % UPDATE == 0)
printUpdate (pass, pat, boards, &best, hits, attempts, root, wordused, numwords);
}
fprintf (stderr, "%d attempts completed.\n\n", iter + 1);
}
}
void freeTrie (struct node **root){
int i;
if (*root){
for (i = 0; i < 26; i++) freeTrie (&((*root)->child[i]));
free (*root);
*root = NULL;
}
}
void printWords (struct node *root, char *wordused, int numwords, char *s){
int i, j, k;
char board[SIZE][SIZE];
for (i = 0; i < 20; i++) hist[i] = 0;
for (i = 0; i < SIZE; i++)
for (j = 0; j < SIZE; j++)
board[i][j] = s[i * SIZE + j];
getScore (root, board, score3, wordused, numwords);
j = k = 0;
for (i = 3; i < 20; i++){
if (hist[i]){
fprintf (stderr, "%d %d-letter words (%d points)\n", hist[i], i, hist[i] * score1 (i, NULL));
j += hist[i] * score1 (i, NULL);
k += hist[i];
}
}
fprintf (stderr, "%d total points, %d total words\n", j, k);
}
int main (int argc, char **argv){
struct node *root;
char *wordused;
int numwords;
srandom (time (NULL));
if (argc != 2 && argc != 3){
fprintf (stderr, "Usage: boggle wordlist [configuration]\n");
return 1;
}
fprintf (stderr, "Reading word list...\n");
numwords = makeTrie (&root, argv[1], &wordused);
if (argc == 3)
printWords (root, wordused, numwords, argv[2]);
else
genBoard (root, wordused, numwords);
fprintf (stderr, "Freeing word list...\n");
freeTrie (&root);
free (wordused);
}