Compare commits

..

3 Commits
master ... wasm

4 changed files with 163 additions and 204 deletions

View File

@ -1,6 +1,14 @@
# Sudoku solver # Sudoku solver
A simple sudoku problem solver because I got bored of solving them manually. A simple sudoku problem solver because I got bored of solving them manually.
## WASM Adaptation
This branch has been specially adapted to work with WebAssembly: specifically,
Emcripten. To compile, simply run `emcc main.c board.c -s WASM=1 -o sudoku.html`
and then open the generated html file in a browser that supports WASM.
**NOTE:** Emscripten has a terrible default *stdin*, so to enter a board into
the program, one must enter it as one line, press **OK**, then, when the second
input text box appears, press **Cancel**.
## File format ## File format
The sudoku solver accepts a file for the following format: The sudoku solver accepts a file for the following format:
* Each row consists of 9 characters (+ newline) * Each row consists of 9 characters (+ newline)
@ -36,27 +44,21 @@ or
912345678 912345678
``` ```
## Compiling and running
To test the functionality, simply run `gcc -o sudoku main.c board.c`, then
`./sudoku [-v[v]] {filename}`, where the given file is formatted according to
the aforementioned specifications.
## Optimization ## Optimization
An optimization directive has been included in `board.c` to allow for the near An optimization directive has been included in `board.c` to allow for the near
complete removal of boundary checks and error conditions. To enable this complete removal of boundary checks and error conditions. To enable this
optimization, simply add `-DOPTIMIZE` to your compiler flags. For insane speeds, optimization, simply add `-DOPTIMIZE` to your compiler flags.
don't forget the `-O3` gcc flag.
## Live status output
If you would like to get a live status output of speculative value placement,
include the `-v` for verbose-mode solving (or `-vv` for more verbosity) when
running the sudoku program. **Please note:** Most boards will be solved in a
matter of milliseconds (at most) on modern machines, so these verbosity options
are really only useful on much slower devices.
## TODO ## TODO
* Optimizations
* Packed structures
* Better structure layouts
* Optimize speculative value placement error-condition checks
* Cleaner code * Cleaner code
* Move printing to separate file * Move printing to separate file

119
board.c
View File

@ -72,37 +72,6 @@ meta_init (struct metadata *meta)
} }
void
board_make_links (struct board *board)
{
for (board_pos y = 0; y < 9; ++y)
for (board_pos x = 0; x < 9; ++x)
{
unsigned pos = ELEM_POS (x, y);
unsigned link_count = -1;
/* Link column adjacents */
for (board_pos lx = 0; lx < 9; ++lx)
if (lx != x)
board->links[pos][++link_count] = BOARD_ELEM (board, lx, y);
/* Link column adjacents */
for (board_pos ly = 0; ly < 9; ++ly)
if (ly != y)
board->links[pos][++link_count] = BOARD_ELEM (board, x, ly);
/* Link quadrant adjacents */
board_pos qx = TO_QUAD (x);
board_pos qy = TO_QUAD (y);
for (board_pos lqy = 0; lqy < 3; ++lqy)
for (board_pos lqx = 0; lqx < 3; ++lqx)
if ((lqx + qx) != x && (lqy + qy) != y)
board->links[pos][++link_count] = BOARD_ELEM (board, lqx + qx, lqy + qy);
}
}
void void
board_init (struct board *board) board_init (struct board *board)
{ {
@ -111,17 +80,8 @@ board_init (struct board *board)
defval.potential = 0x1FF; defval.potential = 0x1FF;
defval.complexity = 9; defval.complexity = 9;
for (board_pos y = 0; y < 9; ++y) for (size_t t = 0; t < (sizeof (board->elements) / sizeof (struct board_element)); ++t)
for (board_pos x = 0; x < 9; ++x) memcpy (&board->elements[t], &defval, sizeof (struct board_element));
{
struct board_element *elem = BOARD_ELEM (board, x, y);
/* Set default state */
memcpy (elem, &defval, sizeof (struct board_element));
}
board_make_links (board);
board->complexity = 9; board->complexity = 9;
@ -264,7 +224,6 @@ board_set (
); );
struct board_element *elem = BOARD_ELEM (board, x, y); struct board_element *elem = BOARD_ELEM (board, x, y);
elem->has_value = true; elem->has_value = true;
elem->value = value; elem->value = value;
} }
@ -298,7 +257,7 @@ board_mark (
} }
bool void
board_unmark ( board_unmark (
struct board *board, struct board *board,
board_pos x, board_pos x,
@ -313,26 +272,16 @@ board_unmark (
"Attempt to mark element with value" "Attempt to mark element with value"
); );
return elem_unmark (BOARD_ELEM (board, x, y), value); struct board_element *elem = BOARD_ELEM (board, x, y);
}
else ERROR("Invalid parameters to function board_unmark()");
}
if (board_is_marked (board, x, y, value))
bool
elem_unmark (
struct board_element *elem,
element_value value
)
{
if (elem_is_marked (elem, value))
{ {
/* Shift bit to correct place and then invert first 9 bits */ /* Shift bit to correct place and then invert first 9 bits */
elem->potential ^= (1 << value); elem->potential ^= (1 << value);
--(elem->complexity); --(elem->complexity);
} }
}
return elem->potential != 0; else ERROR("Invalid parameters to function board_unmark()");
} }
@ -376,22 +325,12 @@ board_is_marked (
{ {
if (is_in_bounds (x, y) && is_valid_value (value)) if (is_in_bounds (x, y) && is_valid_value (value))
{ {
return elem_is_marked(BOARD_ELEM (board, x, y), value); return BOARD_ELEM (board, x, y)->potential & (1 << value);
} }
else ERROR("Invalid parameters to function board_is_marked()"); else ERROR("Invalid parameters to function board_is_marked()");
} }
bool
elem_is_marked (
const struct board_element *elem,
element_value value
)
{
return elem->potential & (1 << value);
}
bool bool
board_is_valid (struct board *board) board_is_valid (struct board *board)
{ {
@ -585,17 +524,32 @@ board_place (
{ {
if (board_meta_can_set (board, x, y, value)) if (board_meta_can_set (board, x, y, value))
{ {
unsigned pos = ELEM_POS (x, y); /* Unmark x-axis */
for (board_pos unmark_x = 0; unmark_x < 9; ++unmark_x)
if (unmark_x != x && ! board_has_value (board, unmark_x, y))
board_unmark (board, unmark_x, y, value);
/* Unmark all adjacent elements */ /* Unmark y-axis */
for (unsigned i = 0; i < 20; ++i) for (board_pos unmark_y = 0; unmark_y < 9; ++unmark_y)
if ( if (unmark_y != y && ! board_has_value (board, x, unmark_y))
! board->links[pos][i]->has_value && board_unmark (board, x, unmark_y, value);
! elem_unmark (board->links[pos][i], value)
) /* Update quad */
board_pos quad_x = TO_QUAD (x);
board_pos quad_y = TO_QUAD (y);
for (board_pos unmark_y = 0; unmark_y < 3; ++unmark_y)
for (board_pos unmark_x = 0; unmark_x < 3; ++unmark_x)
{ {
/* Unmarking potential caused element to have no potential */ board_pos target_x = quad_x + unmark_x;
return false; board_pos target_y = quad_y + unmark_y;
/* Unmark value for all unset elements in quad */
if (
(target_x != x || target_y != y) &&
!board_has_value (board, target_x, target_y)
)
board_unmark (board, target_x, target_y, value);
} }
/* Set value */ /* Set value */
@ -631,7 +585,7 @@ board_place_speculative (
/* Create duplicate and place value */ /* Create duplicate and place value */
board_copy (board, board_duplicate); board_copy (board, board_duplicate);
if (! board_place (board_duplicate, x, y, value)) if (! board_place (board_duplicate, x, y, value) || ! board_is_valid (board_duplicate))
return NULL; return NULL;
board_refresh_complexity (board_duplicate); board_refresh_complexity (board_duplicate);
@ -678,10 +632,5 @@ board_refresh_complexity (struct board *board)
void void
board_copy (const struct board *board_from, struct board *board_to) board_copy (const struct board *board_from, struct board *board_to)
{ {
/* Copy everything except the links */ memcpy (board_to, board_from, sizeof(struct board));
memcpy (
board_to,
board_from,
sizeof(struct board) - sizeof (((struct board *)0)->links)
);
} }

45
board.h
View File

@ -6,13 +6,10 @@
#include <stdbool.h> #include <stdbool.h>
#define ELEM_POS(x, y) (((y) * 9) + (x))
/** /**
* Get a `struct board_element`-entry from a specified location on the board * Get a `struct board_element`-entry from a specified location on the board
*/ */
#define BOARD_ELEM(board_ptr, x, y) (&(board_ptr)->elements[ELEM_POS (x, y)]) #define BOARD_ELEM(board_ptr, x, y) (&(board_ptr)->elements[((y) * 9) + (x)])
/** /**
* Get a `struct metadata`-entry from a specified location on the quadrand grid * Get a `struct metadata`-entry from a specified location on the quadrand grid
@ -35,8 +32,6 @@
*/ */
#define TO_QUAD(pos) (((pos) / 3) * 3) #define TO_QUAD(pos) (((pos) / 3) * 3)
struct board_element;
typedef unsigned short int board_pos; typedef unsigned short int board_pos;
typedef unsigned char element_value; typedef unsigned char element_value;
@ -50,9 +45,13 @@ typedef unsigned char element_value;
struct board_element { struct board_element {
bool has_value : 1; /* Whether element has a decided value */ bool has_value : 1; /* Whether element has a decided value */
union {
element_value value : 4; /* Value of element */ element_value value : 4; /* Value of element */
unsigned short potential : 9; /* Bitfield of possible values*/ struct {
unsigned short potential : 9; /* Bitfield of possible values */
unsigned char complexity : 4; /* Complexity tracker */ unsigned char complexity : 4; /* Complexity tracker */
};
};
}; };
/** /**
@ -81,9 +80,6 @@ struct board {
struct metadata meta_quad [9]; /* Quadrant metadata */ struct metadata meta_quad [9]; /* Quadrant metadata */
struct metadata meta_row [9]; /* Row metadata */ struct metadata meta_row [9]; /* Row metadata */
struct metadata meta_col [9]; /* Column metadata */ struct metadata meta_col [9]; /* Column metadata */
struct board_element *links [81][20];/* All "connected", "adjacent" elements */
}; };
@ -94,13 +90,6 @@ void
meta_init (struct metadata *meta); meta_init (struct metadata *meta);
/*
* Just generate board element adjacency links
*/
void
board_make_links (struct board *board);
/** /**
* Initialize a board to a blank state with maximum complexity * Initialize a board to a blank state with maximum complexity
*/ */
@ -195,7 +184,7 @@ board_mark (
* *
* NOTE: Unmarking an element with a decied value is undefined * NOTE: Unmarking an element with a decied value is undefined
*/ */
bool void
board_unmark ( board_unmark (
struct board *board, struct board *board,
board_pos x, board_pos x,
@ -204,16 +193,6 @@ board_unmark (
); );
/**
* Removes a marking of a potential value of an element
*/
bool
elem_unmark (
struct board_element *elem,
element_value value
);
/** /**
* Get whether or not an element has a decided value * Get whether or not an element has a decided value
*/ */
@ -252,16 +231,6 @@ board_is_marked (
); );
/**
* Get whether or not a given element is marked with a particular value
*/
bool
elem_is_marked (
const struct board_element *elem,
element_value value
);
/** /**
* Checks if there are any pairs of board elements that share a value and * Checks if there are any pairs of board elements that share a value and
* also share either a row or a column * also share either a row or a column

143
main.c
View File

@ -35,6 +35,7 @@ struct args {
bool valid; bool valid;
unsigned verbosity : 2; unsigned verbosity : 2;
char *file_name; char *file_name;
bool print_simple;
}; };
@ -64,10 +65,7 @@ tables_ensure_depth (struct boards_table *board_spec, unsigned long long depth)
/* Allocate boards */ /* Allocate boards */
for (unsigned long long l = board_spec->max_depth; l < new_max; ++l) for (unsigned long long l = board_spec->max_depth; l < new_max; ++l)
{
board_spec->board_specs[l] = malloc (sizeof (struct board)); board_spec->board_specs[l] = malloc (sizeof (struct board));
board_make_links (board_spec->board_specs[l]);
}
/* Update max depth */ /* Update max depth */
board_spec->max_depth = new_max; board_spec->max_depth = new_max;
@ -246,42 +244,50 @@ print_board_verbose (
static void static void
print_board (const struct board *board, const struct board *compare, unsigned whence_x, unsigned whence_y) print_board (const struct board *board, const struct board *compare, unsigned whence_x, unsigned whence_y, bool simple)
{ {
for (board_pos y = 0; y < 9; ++y) for (board_pos y = 0; y < 9; ++y)
{ {
/* Print row */ /* Print row */
for (board_pos x = 0; x < 9; ++x) for (board_pos x = 0; x < 9; ++x)
{ {
if (! simple)
ansi_set_cursor (whence_x + (x * 2), whence_y + (y * 2)); ansi_set_cursor (whence_x + (x * 2), whence_y + (y * 2));
/* Print board element */ /* Print board element */
if (board_has_value (board, x, y)) if (board_has_value (board, x, y))
{ {
if (compare != NULL && ! board_has_value (compare, x, y)) if (compare != NULL && ! board_has_value (compare, x, y) && ! simple)
printf (COLOUR_RED "%u" COLOUR_RESET, board_get_value (board, x, y) + 1); printf (COLOUR_RED "%u" COLOUR_RESET, board_get_value (board, x, y) + 1);
else else
printf ("%u", board_get_value (board, x, y) + 1); printf ("%u", board_get_value (board, x, y) + 1);
} }
else else
fputs (" ", stdout); printf (" ");
/* Print column element delimiter */ /* Print column element delimiter */
if (x < 8) if (x < 8)
fputs("|", stdout); printf ("|");
} }
if (simple)
puts ("");
else
ansi_set_cursor (whence_x, whence_y + (y * 2 + 1));
/* Print row element delimiter */ /* Print row element delimiter */
if (y < 8) if (y < 8)
{ {
for (board_pos x = 0; x < 17; ++x) for (board_pos x = 0; x < 17; ++x)
{ {
ansi_set_cursor (whence_x + x, whence_y + (y * 2 + 1));
if ((x & 1) == 0) if ((x & 1) == 0)
fputs ("-", stdout); printf ("-");
else else
fputs ("+", stdout); printf ("+");
} }
if (simple)
puts ("");
} }
} }
} }
@ -319,13 +325,6 @@ first_potential_value (struct board_element *element, struct board *board, bool
/** /**
* Reduce away all elements on board with complexity=1 until none remain * Reduce away all elements on board with complexity=1 until none remain
*/ */
#ifdef NOVERB
bool
simplify (
struct boards_table *board_specs,
unsigned long long depth
)
#else
bool bool
simplify ( simplify (
struct boards_table *board_specs, struct boards_table *board_specs,
@ -333,12 +332,10 @@ simplify (
unsigned long long *counter, unsigned long long *counter,
unsigned verbosity unsigned verbosity
) )
#endif
{ {
/* Get current table */ /* Get current table */
struct board *board = board_specs->board_specs[depth]; struct board *board = board_specs->board_specs[depth];
#ifndef NOVERB
if (verbosity > 0) if (verbosity > 0)
{ {
if (((*counter) & (0xFFFF >> (4 * (4 - verbosity)))) == 0) if (((*counter) & (0xFFFF >> (4 * (4 - verbosity)))) == 0)
@ -349,7 +346,6 @@ simplify (
} }
*counter += 1; *counter += 1;
} }
#endif
bool error; bool error;
@ -371,8 +367,15 @@ simplify (
++count; ++count;
//fprintf (stderr, "Placing (%u, %u)=%u (potential=%u)\n", x, y, value, elem->potential);
if (! board_place (board, x, y, value)) if (! board_place (board, x, y, value))
return false; {
bool rerr = meta_has_value (BOARD_ROW (board, y), value);
bool cerr = meta_has_value (BOARD_COL (board, x), value);
bool qerr = meta_has_value (BOARD_QUAD (board, TO_QUAD (x), TO_QUAD (y)), value);
fprintf (stderr, "Error (%u, %u, %u)\n", rerr, cerr, qerr);
}
} }
} }
@ -388,14 +391,14 @@ simplify (
struct board_element *elem = BOARD_ELEM (board, x, y); struct board_element *elem = BOARD_ELEM (board, x, y);
/* Find a simplest element on the board */ /* Find a simplest element on the board */
if ( if (
! elem->has_value && ! board_has_value (board, x, y) &&
elem->complexity == board->complexity elem->complexity == board->complexity
) )
for (element_value value = 0; value < 9; ++value) for (element_value value = 0; value < 9; ++value)
{ {
/* Try speculative placement of each potential value and recurse */ /* Try speculative placement of each potential value and recurse */
tables_ensure_depth (board_specs, depth + 1); tables_ensure_depth (board_specs, depth + 1);
if (elem_is_marked (elem, value)) if ((elem->potential & (1 << value)) != 0)
{ {
struct board *board_spec = struct board *board_spec =
board_place_speculative ( board_place_speculative (
@ -409,27 +412,18 @@ simplify (
/* If speculative placement failed, try another value */ /* If speculative placement failed, try another value */
if (board_spec == NULL) if (board_spec == NULL)
{ {
if (! elem_unmark (elem, value)) board_unmark (board, x, y, value);
return false;
continue; continue;
} }
/* Found solution */ /* Found solution */
if ( if (
#ifdef NOVERB
simplify (
board_specs,
depth + 1
) &&
#else
simplify ( simplify (
board_specs, board_specs,
depth + 1, depth + 1,
counter, counter,
verbosity verbosity
) && ) &&
#endif
board_spec->complexity == 0) board_spec->complexity == 0)
{ {
board_copy (board_spec, board); board_copy (board_spec, board);
@ -437,8 +431,7 @@ simplify (
y = 9; y = 9;
value = 9; value = 9;
} }
else if (! elem_unmark (elem, value)) else board_unmark (board, x, y, value);
return false;
} }
} }
} }
@ -493,7 +486,57 @@ argparse (int argc, char **argv)
int int
main (int argc, char **argv, char **env) main (int argc, char **argv, char **env)
{ {
struct args args = argparse (argc, argv); /* Allocate boards */
struct board original;
struct boards_table boards;
boards.max_depth = 0;
boards.board_specs = NULL;
tables_ensure_depth (&boards, 0);
struct board *root_board = boards.board_specs[0];
struct args args;
args.print_simple = false;
if (argc == 1)
{
FILE *input = fopen ("/dev/stdin", "r");
args.print_simple = true;
puts ("No file specified! Using stdin:");
fflush (stdout);
char read;
puts ("Please enter a board definition without line separators:");
char board[89];
int track = 0;
for (int i = 0; i < 81; ++i)
{
fread (&read, 1, 1, input);
if ((read < '1' || read > '9') && read != ' ')
--i;
else
{
board[track] = read;
++track;
if (track % 10 == 9) ++track;
}
}
struct board_file fake;
fake.data = board;
copy_to_board (fake, &original);
args.valid = true;
args.verbosity = 0;
}
else
{
args = argparse (argc, argv);
if (! args.valid) if (! args.valid)
{ {
fputs ("Badly formatted arguments! Usage:\n\t./sudoku [-v[v]] {file name}\n", stderr); fputs ("Badly formatted arguments! Usage:\n\t./sudoku [-v[v]] {file name}\n", stderr);
@ -506,28 +549,20 @@ main (int argc, char **argv, char **env)
ansi_cursor_show (false); ansi_cursor_show (false);
/* Allocate boards */
struct board original;
struct boards_table boards;
boards.max_depth = 0;
boards.board_specs = NULL;
tables_ensure_depth (&boards, 0);
struct board *root_board = boards.board_specs[0];
copy_to_board (file, &original); copy_to_board (file, &original);
board_copy (&original, boards.board_specs[0]);
close_board_file (file); close_board_file (file);
}
board_copy (&original, boards.board_specs[0]);
if (! args.print_simple)
ansi_clear_screen (); ansi_clear_screen ();
if (! board_is_valid (root_board)) if (! board_is_valid (root_board))
{ {
fputs ("Supplied board is not valid!\n", stderr); puts ("Supplied board is not valid!\n");
ansi_cursor_show (true); ansi_cursor_show (true);
@ -551,12 +586,16 @@ main (int argc, char **argv, char **env)
/* Profiler end time */ /* Profiler end time */
clock_t end_clk = clock (); clock_t end_clk = clock ();
if (! args.print_simple)
ansi_clear_screen (); ansi_clear_screen ();
if (root_board->complexity == 0) if (root_board->complexity == 0 || args.print_simple)
{ {
print_board (&original, NULL, 0, 0);
print_board (root_board, &original, 21, 0); if (! args.print_simple)
print_board (&original, NULL, 0, 0, args.print_simple);
print_board (root_board, &original, 21, 0, args.print_simple);
if (! args.print_simple)
ansi_set_cursor (0, 18); ansi_set_cursor (0, 18);
} }
else else