// 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);
}