first commit
commit
1a3e746c2e
|
@ -0,0 +1,497 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
A boggle solver,
|
||||||
|
by Allen Webster
|
||||||
|
|
||||||
|
03.07.2016 (dd.mm.yyyy)
|
||||||
|
|
||||||
|
How to use:
|
||||||
|
Call the application with two text files.
|
||||||
|
|
||||||
|
The first should be a dictionary containing one
|
||||||
|
word per line. This system will check to make
|
||||||
|
sure the dictionary is sorted, but if it isn't
|
||||||
|
the sorting is not particularly good right now.
|
||||||
|
The dictionary is not provided but they are not
|
||||||
|
hard to find with a google search.
|
||||||
|
|
||||||
|
The second text file is the boggle board. It should
|
||||||
|
be a 4 by 4 matrix of capital letters with no spaces
|
||||||
|
and newlines only at the end of each row. Here is
|
||||||
|
a particularly interesting board I rolled for testing:
|
||||||
|
|
||||||
|
EYRL
|
||||||
|
OQOS
|
||||||
|
VWET
|
||||||
|
LYUS
|
||||||
|
|
||||||
|
TODO(allen):
|
||||||
|
Better sorting
|
||||||
|
Add break down commands for querying:
|
||||||
|
-All the words of a particular length
|
||||||
|
-The number of words that start on each grid position
|
||||||
|
-The number of words that use each grid position
|
||||||
|
-The number of ways to form each word
|
||||||
|
Options for:
|
||||||
|
-Output sorted by length
|
||||||
|
-Output sorted alphabetically
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TOP
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
struct Data{
|
||||||
|
char *data;
|
||||||
|
int32_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct Data
|
||||||
|
file_dump(char *filename){
|
||||||
|
struct Data data = {0};
|
||||||
|
FILE *file = fopen(filename, "rb");
|
||||||
|
|
||||||
|
if (file){
|
||||||
|
fseek(file, 0, SEEK_END);
|
||||||
|
data.size = ftell(file);
|
||||||
|
fseek(file, 0, SEEK_SET);
|
||||||
|
|
||||||
|
data.data = (char*)malloc(data.size+1);
|
||||||
|
fread(data.data, 1, data.size, file);
|
||||||
|
data.data[data.size] = 0;
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BOGGLE_BOARD_W 4
|
||||||
|
#define BOGGLE_BOARD_H 4
|
||||||
|
#define BOGGLE_BOARD_SIZE BOGGLE_BOARD_W*BOGGLE_BOARD_H
|
||||||
|
|
||||||
|
struct Boggle_Board{
|
||||||
|
char letter[BOGGLE_BOARD_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
static int32_t
|
||||||
|
is_letter(char c){
|
||||||
|
int32_t result = 0;
|
||||||
|
if ((c >= 'A' && c <= 'Z') ||
|
||||||
|
(c >= 'a' && c <= 'z')){
|
||||||
|
result = 1;
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char
|
||||||
|
to_upper(char c){
|
||||||
|
if (c >= 'a' && c <= 'z'){
|
||||||
|
c += 'A' - 'a';
|
||||||
|
}
|
||||||
|
return(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Integer_Pos{
|
||||||
|
int32_t x, y;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define BPOS(x,y) {x, y}
|
||||||
|
|
||||||
|
static struct Integer_Pos moore[] = {
|
||||||
|
BPOS(-1, -1),
|
||||||
|
BPOS( 0, -1),
|
||||||
|
BPOS( 1, -1),
|
||||||
|
BPOS( 1, 0),
|
||||||
|
BPOS( 1, 1),
|
||||||
|
BPOS( 0, 1),
|
||||||
|
BPOS(-1, 1),
|
||||||
|
BPOS(-1, 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct Integer_Pos
|
||||||
|
pos_add(struct Integer_Pos a, struct Integer_Pos b){
|
||||||
|
struct Integer_Pos r = {0};
|
||||||
|
r.x = a.x + b.x;
|
||||||
|
r.y = a.y + b.y;
|
||||||
|
return(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t
|
||||||
|
pos_i(struct Integer_Pos p){
|
||||||
|
int32_t i = (p.x + p.y*BOGGLE_BOARD_W);
|
||||||
|
return(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Visit_Record{
|
||||||
|
int32_t visit[BOGGLE_BOARD_W * BOGGLE_BOARD_H];
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct Visit_Record
|
||||||
|
make_visit_record(){
|
||||||
|
struct Visit_Record visit_record = {0};
|
||||||
|
return(visit_record);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Dictionary{
|
||||||
|
char **word;
|
||||||
|
int32_t *len;
|
||||||
|
int32_t *already_used;
|
||||||
|
int32_t count;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct Dictionary
|
||||||
|
make_dictionary(int32_t word_count){
|
||||||
|
struct Dictionary dict = {0};
|
||||||
|
int32_t mem_size = (word_count*(sizeof(char*) + sizeof(int)*2));
|
||||||
|
dict.word = (char**)malloc(mem_size);
|
||||||
|
dict.len = (int*)(dict.word + word_count);
|
||||||
|
dict.already_used = (int*)(dict.len + word_count);
|
||||||
|
memset(dict.word, 0, mem_size);
|
||||||
|
dict.count = 0;
|
||||||
|
return(dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define Swap(t,a,b) do { t T = a; a = b; b = T; } while (0)
|
||||||
|
|
||||||
|
static void
|
||||||
|
bubble_sort(struct Dictionary dict){
|
||||||
|
int32_t start = 0;
|
||||||
|
int32_t end = dict.count-1;
|
||||||
|
int32_t last_swap = -1;
|
||||||
|
do{
|
||||||
|
last_swap = -1;
|
||||||
|
for (int32_t i = start;
|
||||||
|
i < end;
|
||||||
|
++i){
|
||||||
|
if (strcmp(dict.word[i], dict.word[i+1]) > 0){
|
||||||
|
Swap(int32_t, dict.len[i], dict.len[i+1]);
|
||||||
|
Swap(char*, dict.word[i], dict.word[i+1]);
|
||||||
|
last_swap = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end = last_swap;
|
||||||
|
}while(last_swap != -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Match_Range{
|
||||||
|
int32_t first;
|
||||||
|
int32_t last;
|
||||||
|
int32_t index;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Match_Range
|
||||||
|
make_range(struct Dictionary dict){
|
||||||
|
struct Match_Range range;
|
||||||
|
range.first = 0;
|
||||||
|
range.last = dict.count;
|
||||||
|
range.index = 0;
|
||||||
|
return(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Match_Range
|
||||||
|
narrow_range(struct Match_Range range,
|
||||||
|
struct Dictionary dict,
|
||||||
|
char letter){
|
||||||
|
int32_t str_index = range.index;
|
||||||
|
|
||||||
|
int32_t s = range.first;
|
||||||
|
int32_t e = range.last;
|
||||||
|
|
||||||
|
int32_t new_s = range.first;
|
||||||
|
int32_t new_e = range.last;
|
||||||
|
int32_t i = 0;
|
||||||
|
|
||||||
|
if (s != e){
|
||||||
|
for(;s < e;){
|
||||||
|
i = (s+e)/2;
|
||||||
|
|
||||||
|
if (i > range.first){
|
||||||
|
if (dict.len[i-1] > str_index){
|
||||||
|
if (dict.word[i-1][str_index] >= letter){
|
||||||
|
e = i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dict.len[i] > str_index){
|
||||||
|
if (dict.word[i][str_index] >= letter){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s = i+1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_s = i;
|
||||||
|
|
||||||
|
if (s >= e){
|
||||||
|
new_e = new_s;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
s = new_s;
|
||||||
|
e = range.last + 1;
|
||||||
|
|
||||||
|
for(;s < e;){
|
||||||
|
i = (s+e)/2;
|
||||||
|
|
||||||
|
if (i > range.first){
|
||||||
|
if (dict.len[i-1] > str_index){
|
||||||
|
if (dict.word[i-1][str_index] > letter){
|
||||||
|
e = i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dict.len[i] > str_index){
|
||||||
|
if (dict.word[i][str_index] > letter){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s = i+1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_e = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Match_Range new_range;
|
||||||
|
new_range.first = new_s;
|
||||||
|
new_range.last = new_e;
|
||||||
|
new_range.index = str_index + 1;
|
||||||
|
return(new_range);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(allen): Boggle has no 'Q' but it has a die with a 'Qu'.
|
||||||
|
static struct Match_Range
|
||||||
|
narrow_range_boggle(struct Match_Range range,
|
||||||
|
struct Dictionary dict,
|
||||||
|
char letter){
|
||||||
|
struct Match_Range new_range = narrow_range(range, dict, letter);
|
||||||
|
if (letter == 'Q'){
|
||||||
|
new_range = narrow_range(new_range, dict, 'U');
|
||||||
|
}
|
||||||
|
return(new_range);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t
|
||||||
|
can_visit_pos(struct Visit_Record *visit_record, struct Integer_Pos pos){
|
||||||
|
int32_t result = 0;
|
||||||
|
if (pos.x >= 0 && pos.x < BOGGLE_BOARD_W &&
|
||||||
|
pos.y >= 0 && pos.y < BOGGLE_BOARD_H &&
|
||||||
|
visit_record->visit[pos_i(pos)] == 0){
|
||||||
|
result = 1;
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Solution{
|
||||||
|
char **words;
|
||||||
|
int32_t word_count;
|
||||||
|
int32_t total_score;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
explore_neighborhood(struct Dictionary dict, struct Match_Range range,
|
||||||
|
struct Boggle_Board board, struct Visit_Record *visit_record,
|
||||||
|
struct Integer_Pos pos, struct Solution *solution){
|
||||||
|
if (dict.len[range.first] == range.index){
|
||||||
|
if (dict.already_used[range.first] == 0){
|
||||||
|
dict.already_used[range.first] = 1;
|
||||||
|
|
||||||
|
solution->words[solution->word_count++] = dict.word[range.first];
|
||||||
|
|
||||||
|
int32_t score = 0;
|
||||||
|
if (range.index == 3){
|
||||||
|
score = 1;
|
||||||
|
}
|
||||||
|
else if (range.index > 3){
|
||||||
|
score = range.index - 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
solution->total_score += score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t count = (sizeof(moore)/sizeof(*moore));
|
||||||
|
for (int32_t i = 0;
|
||||||
|
i < count;
|
||||||
|
++i){
|
||||||
|
struct Integer_Pos relative_pos = pos_add(pos, moore[i]);
|
||||||
|
if (can_visit_pos(visit_record, relative_pos)){
|
||||||
|
char letter = board.letter[pos_i(relative_pos)];
|
||||||
|
|
||||||
|
struct Match_Range new_range = narrow_range_boggle(range, dict, letter);
|
||||||
|
|
||||||
|
if (new_range.first < new_range.last){
|
||||||
|
visit_record->visit[pos_i(relative_pos)] = 1;
|
||||||
|
explore_neighborhood(dict, new_range, board,
|
||||||
|
visit_record, relative_pos, solution);
|
||||||
|
visit_record->visit[pos_i(relative_pos)] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv){
|
||||||
|
if (argc != 3){
|
||||||
|
fprintf(stderr, "usaged:\n\t%s <dictionary> <board>\n", argv[0]);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Data dictionary_file = file_dump(argv[1]);
|
||||||
|
struct Data board_file = file_dump(argv[2]);
|
||||||
|
|
||||||
|
struct Boggle_Board board = {0};
|
||||||
|
int32_t board_error = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
int32_t row = 0;
|
||||||
|
int32_t col = 0;
|
||||||
|
for (int32_t i = 0;
|
||||||
|
i < board_file.size;
|
||||||
|
++i){
|
||||||
|
char c = board_file.data[i];
|
||||||
|
if (is_letter(c)){
|
||||||
|
if (col < 4){
|
||||||
|
if (row < 4){
|
||||||
|
board.letter[col + row*BOGGLE_BOARD_W] = to_upper(c);
|
||||||
|
++col;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
board_error = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
board_error = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c == '\r'){
|
||||||
|
// NOTE(allen): do nothing
|
||||||
|
}
|
||||||
|
else if (c == '\n'){
|
||||||
|
if (col == 4){
|
||||||
|
col = 0;
|
||||||
|
++row;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
board_error = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
board_error = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (board_error){
|
||||||
|
fprintf(stderr, "error in boggle board format\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t line_count = 1;
|
||||||
|
|
||||||
|
for (int32_t i = 0;
|
||||||
|
i < dictionary_file.size;
|
||||||
|
++i){
|
||||||
|
if (dictionary_file.data[i] == '\n'){
|
||||||
|
++line_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Dictionary dict = make_dictionary(line_count);
|
||||||
|
|
||||||
|
for (int32_t i = 0;
|
||||||
|
i < dictionary_file.size;
|
||||||
|
++i){
|
||||||
|
|
||||||
|
int32_t word_start = i;
|
||||||
|
|
||||||
|
for (; i < dictionary_file.size;
|
||||||
|
++i){
|
||||||
|
if (dictionary_file.data[i] == '\n'){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t word_end = i;
|
||||||
|
|
||||||
|
int32_t early_end = word_end;
|
||||||
|
int32_t good_word = 1;
|
||||||
|
for (int32_t j = word_start;
|
||||||
|
j < word_end;
|
||||||
|
++j){
|
||||||
|
char c = dictionary_file.data[j];
|
||||||
|
|
||||||
|
if (is_letter(c)){
|
||||||
|
if (early_end != word_end){
|
||||||
|
good_word = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
dictionary_file.data[j] = to_upper(c);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
early_end = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (good_word && (early_end - word_start) > 2){
|
||||||
|
dictionary_file.data[early_end] = 0;
|
||||||
|
dict.len[dict.count] = (early_end - word_start);
|
||||||
|
dict.word[dict.count] = dictionary_file.data + word_start;
|
||||||
|
++dict.count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bubble_sort(dict);
|
||||||
|
|
||||||
|
struct Solution solution = {0};
|
||||||
|
solution.words = (char**)malloc(sizeof(char*)*dict.count);
|
||||||
|
|
||||||
|
struct Visit_Record visit_record = make_visit_record();
|
||||||
|
struct Integer_Pos pos = {0};
|
||||||
|
|
||||||
|
for (pos.y = 0;
|
||||||
|
pos.y < BOGGLE_BOARD_H;
|
||||||
|
++pos.y){
|
||||||
|
for (pos.x = 0;
|
||||||
|
pos.x < BOGGLE_BOARD_H;
|
||||||
|
++pos.x){
|
||||||
|
|
||||||
|
struct Match_Range range = make_range(dict);
|
||||||
|
|
||||||
|
visit_record.visit[pos_i(pos)] = 1;
|
||||||
|
range = narrow_range_boggle(range, dict, board.letter[pos_i(pos)]);
|
||||||
|
explore_neighborhood(dict, range, board, &visit_record, pos, &solution);
|
||||||
|
visit_record.visit[pos_i(pos)] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = 0;
|
||||||
|
i < solution.word_count;
|
||||||
|
++i){
|
||||||
|
printf("%s\n", solution.words[i]);
|
||||||
|
}
|
||||||
|
printf("word count: %d\n", solution.word_count);
|
||||||
|
printf("total score: %d\n", solution.total_score);
|
||||||
|
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BOTTOM
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
@echo off
|
||||||
|
|
||||||
|
set WARNINGOPS=/W4 /wd4310 /wd4100 /wd4201 /wd4505 /wd4996 /wd4127 /wd4510 /wd4512 /wd4610 /wd4390 /WX
|
||||||
|
set WARNINGOPS=%WARNINGOPS% /GR- /EHa- /nologo /FC
|
||||||
|
set WIN_LIBS=user32.lib winmm.lib gdi32.lib
|
||||||
|
|
||||||
|
pushd w:\boggler\build
|
||||||
|
cl %WARNINGOPS% ..\code\boggler.c /Feboggler /Zi %*
|
||||||
|
popd
|
Loading…
Reference in New Issue