sudoku/main.c
2024-03-07 13:19:48 +01:00

449 lines
11 KiB
C

#include "board.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
/**
* ANSI control codes
*/
#define COLOUR_RED "\e[31m"
#define COLOUR_RESET "\e[0m"
#define CLEAR "\033[2J"
/**
* How many boards to allocate by default for board spec
* Average depth for highly complex boards is between 10 and 12
*/
#define DEFAULT_DEPTH 10
/**
* How many boards to allocate per depth increase
* Higher values mean fewer reallocations, by with greater resource costs
*/
#define DEPTH_INCREMENT 3
struct args {
bool valid;
unsigned verbosity : 2;
char *file_name;
};
struct boards_table {
struct board **board_specs;
unsigned long long max_depth;
};
void tables_ensure_depth(struct boards_table *board_spec,
unsigned long long depth) {
if (board_spec->max_depth <= depth) {
/* Compute new max depth */
unsigned long long new_max;
if (board_spec->max_depth == 0)
new_max = DEFAULT_DEPTH;
else
new_max = board_spec->max_depth + DEPTH_INCREMENT;
/* Disregard NULL return value. What's it gonna do, segfault? :P */
board_spec->board_specs =
realloc(board_spec->board_specs, sizeof(struct board *) * new_max);
/* Allocate boards */
for (unsigned long long l = board_spec->max_depth; l < new_max; ++l) {
board_spec->board_specs[l] = malloc(sizeof(struct board));
board_make_links(board_spec->board_specs[l]);
}
/* Update max depth */
board_spec->max_depth = new_max;
}
}
bool simplify(struct boards_table *board_spec, unsigned long long depth,
unsigned long long *counter, unsigned verbosity);
struct board_file {
int fd;
void *data;
};
/**
* Determines if the given char is a valid character for defining
* a board element
*/
static inline bool is_valid_def(char def) {
return def == ' ' || (def >= '0' && def <= '9');
}
/**
* Load a board-definition file and return file descriptor
*/
static struct board_file load_board_file(const char *path) {
struct board_file file;
file.fd = -1;
file.data = NULL;
/* Open file */
int fd = open(path, 0);
if (fd < 0)
return file;
/* Map 89 bytes of file to memory */
void *region = mmap(NULL, 89, PROT_READ, MAP_SHARED, fd, 0);
if (region == (void *)-1) {
close(fd);
return file;
}
/*
Ensure data in file is formatted correctly:
1) Every 10th character is ignored (row-terminator)
2) Each character must be in range '0'-'9' or ' '
*/
char *data = region;
for (unsigned i = 1; i <= 89; ++i)
if (!((i % 10) == 0) && !is_valid_def(data[i - 1])) {
munmap(region, 89);
close(fd);
return file;
}
/* Save file data to struct and return */
file.fd = fd;
file.data = region;
return file;
}
/**
* Free all resources linked to an opened board definition file
*/
static void close_board_file(struct board_file file) {
close(file.fd);
munmap(file.data, 89);
}
static void copy_to_board(struct board_file file, struct board *board) {
// Clear board
board_init(board);
char *data = file.data;
for (unsigned i = 1; i <= 89; ++i)
if ((i % 10) != 0) {
board_pos x = i % 10;
board_pos y = i / 10;
if (data[i - 1] != ' ')
board_place(board, x - 1, y, data[i - 1] - '0' - 1);
}
}
static void ansi_set_cursor(unsigned y, unsigned x) {
printf("\033[%u;%uH", x + 1, y + 1);
}
static void ansi_clear_screen() { puts(CLEAR); }
void ansi_cursor_show(bool show) {
if (show)
fputs("\e[?25h", stdout);
else
fputs("\e[?25l", stdout);
}
static void print_board_verbose(const struct board *board, unsigned whence_x,
unsigned whence_y) {
for (board_pos y = 0; y < 9; ++y) {
for (board_pos x = 0; x < 9; ++x) {
for (element_value vy = 0; vy < 3; ++vy)
for (element_value vx = 0; vx < 3; ++vx) {
element_value check = vx + (vy * 3);
ansi_set_cursor(whence_x + (x * 4) + vx, whence_y + (y * 4) + vy);
if (board_has_value(board, x, y))
printf("%u", board_get_value(board, x, y) + 1);
else if (board_is_marked(board, x, y, check))
printf(COLOUR_RED "%u" COLOUR_RESET, check + 1);
else
fputs(" ", stdout);
if (vx == 2 && x != 8)
fputs("|", stdout);
}
}
ansi_set_cursor(0, (y * 4) + 3);
if (y != 8) {
for (unsigned i = 0; i < (4 * 9) - 1; ++i)
if ((i + 1) % 4 == 0)
fputs("+", stdout);
else
fputs("-", stdout);
}
}
fflush(stdout);
}
static void print_board(const struct board *board, const struct board *compare,
unsigned whence_x, unsigned whence_y) {
for (board_pos y = 0; y < 9; ++y) {
/* Print row */
for (board_pos x = 0; x < 9; ++x) {
ansi_set_cursor(whence_x + (x * 2), whence_y + (y * 2));
/* Print board element */
if (board_has_value(board, x, y)) {
if (compare != NULL && !board_has_value(compare, x, y))
printf(COLOUR_RED "%u" COLOUR_RESET,
board_get_value(board, x, y) + 1);
else
printf("%u", board_get_value(board, x, y) + 1);
} else
fputs(" ", stdout);
/* Print column element delimiter */
if (x < 8)
fputs("|", stdout);
}
/* Print row element delimiter */
if (y < 8) {
for (board_pos x = 0; x < 17; ++x) {
ansi_set_cursor(whence_x + x, whence_y + (y * 2 + 1));
if ((x & 1) == 0)
fputs("-", stdout);
else
fputs("+", stdout);
}
}
}
}
/**
* Compute first potential value of a given element
*/
element_value first_potential_value(struct board_element *element,
struct board *board, bool *error) {
unsigned short potential = element->potential;
if (potential == 0) {
*error = true;
return 0;
}
*error = false;
element_value value = 0;
while (potential != 0) {
if ((potential & 1) == 1)
return value;
++value;
potential >>= 1;
}
/* Unreachable */
abort();
}
/**
* 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 simplify(struct boards_table *board_specs, unsigned long long depth,
unsigned long long *counter, unsigned verbosity)
#endif
{
/* Get current table */
struct board *board = board_specs->board_specs[depth];
#ifndef NOVERB
if (verbosity > 0) {
if (((*counter) & (0xFFFF >> (4 * (4 - verbosity)))) == 0) {
print_board_verbose(board, 0, 0);
ansi_set_cursor(0, 35);
printf("Iteration: %llu", *counter);
}
*counter += 1;
}
#endif
bool error;
unsigned count = 0;
/* Reduce using low-complexity computation */
while (board->complexity == 1) {
for (board_pos y = 0; y < 9; ++y)
for (board_pos x = 0; x < 9; ++x)
if (!board_has_value(board, x, y)) {
struct board_element *elem = BOARD_ELEM(board, x, y);
if (elem->complexity == 1) {
element_value value = first_potential_value(elem, board, &error);
if (error)
return false;
++count;
if (!board_place(board, x, y, value))
return false;
}
}
board_refresh_complexity(board);
}
/* Attempt to reduce with speculative placement */
if (board->complexity > 1) {
for (board_pos y = 0; y < 9; ++y)
for (board_pos x = 0; x < 9; ++x) {
struct board_element *elem = BOARD_ELEM(board, x, y);
/* Find a simplest element on the board */
if (!elem->has_value && elem->complexity == board->complexity)
for (element_value value = 0; value < 9; ++value) {
/* Try speculative placement of each potential value and recurse */
tables_ensure_depth(board_specs, depth + 1);
if (elem_is_marked(elem, value)) {
struct board *board_spec = board_place_speculative(
board, board_specs->board_specs[depth + 1], x, y, value);
/* If speculative placement failed, try another value */
if (board_spec == NULL) {
if (!elem_unmark(elem, value))
return false;
continue;
}
/* Found solution */
if (
#ifdef NOVERB
simplify(board_specs, depth + 1) &&
#else
simplify(board_specs, depth + 1, counter, verbosity) &&
#endif
board_spec->complexity == 0) {
board_copy(board_spec, board);
x = 9;
y = 9;
value = 9;
} else if (!elem_unmark(elem, value))
return false;
}
}
}
}
return true;
}
struct args argparse(int argc, char **argv) {
struct args result;
result.file_name = NULL;
result.valid = true;
result.verbosity = 0;
if (argc < 2) {
result.valid = false;
return result;
}
for (int i = 1; i < argc; ++i)
if (strncmp(argv[i], "-", 1) == 0) {
if (result.verbosity != 0) {
result.valid = false;
return result;
}
if (strcmp(argv[i], "-v") == 0)
result.verbosity = 1;
else if (strcmp(argv[i], "-vv") == 0)
result.verbosity = 2;
else {
result.valid = false;
return result;
}
} else if (result.file_name == NULL)
result.file_name = argv[i];
else {
result.valid = false;
return result;
}
return result;
}
int main(int argc, char **argv, char **env) {
struct args args = argparse(argc, argv);
if (!args.valid) {
fputs("Badly formatted arguments! Usage:\n\t./sudoku [-v[v]] {file name}\n",
stderr);
return 1;
}
struct board_file file = load_board_file(args.file_name);
if (file.fd == -1 || file.data == NULL)
return -1;
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);
board_copy(&original, boards.board_specs[0]);
close_board_file(file);
ansi_clear_screen();
if (!board_is_valid(root_board)) {
fputs("Supplied board is not valid!\n", stderr);
ansi_cursor_show(true);
return 1;
}
if (args.verbosity == 0)
puts("Simplifying...");
board_refresh_complexity(root_board);
/* Profiler start time */
clock_t start_clk = clock();
unsigned long long counter = 0;
simplify(&boards, 0, &counter, args.verbosity);
/* Profiler end time */
clock_t end_clk = clock();
ansi_clear_screen();
if (root_board->complexity == 0) {
print_board(&original, NULL, 0, 0);
print_board(root_board, &original, 21, 0);
ansi_set_cursor(0, 18);
} else {
print_board_verbose(root_board, 0, 0);
ansi_set_cursor(0, 36);
}
printf("Simplification took %Lf seconds\n",
((long double)(end_clk - start_clk)) / CLOCKS_PER_SEC);
ansi_cursor_show(true);
return 0;
}