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

475 lines
14 KiB
C

/**
* Sudoku board implementation
*
* Created by Gabriel Tofvesson
*/
#include "board.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* Raises an error by printing it to stderr and stopping execution
*/
#define ERROR(reason) \
{ \
fprintf(stderr, "ERROR: %s\n", reason); \
abort(); \
}
/**
* Raises an error if the given condition is not met
* The error will be the given reason
*/
#ifdef OPTIMIZE
#define ASSERT(cond, reason)
#else
#define ASSERT(cond, reason) \
{ \
if (!cond) \
ERROR((reason)); \
}
#endif
/**
* Check if a given xy-pair is in bounds of a Sudoku board
*/
static inline bool is_in_bounds(board_pos x, board_pos y) {
#ifdef OPTIMIZE
return true;
#else
return x >= 0 && x < 9 && y >= 0 && y < 9;
#endif
}
/**
* Check if a given element value is within acceptable bounds
*/
static inline bool is_valid_value(element_value value) {
#ifdef OPTIMIZE
return true;
#else
return value >= 0 && value < 9;
#endif
}
void meta_init(struct metadata *meta) {
memset(meta, 0, sizeof(struct metadata));
}
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 board_init(struct board *board) {
struct board_element defval;
defval.has_value = false;
defval.potential = 0x1FF;
defval.complexity = 9;
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);
/* Set default state */
memcpy(elem, &defval, sizeof(struct board_element));
}
board_make_links(board);
board->complexity = 9;
for (unsigned i = 0; i < 9; ++i) {
meta_init(&board->meta_quad[i]);
meta_init(&board->meta_row[i]);
meta_init(&board->meta_col[i]);
}
}
bool meta_has_value(const struct metadata *meta, element_value value) {
return ((meta->marked >> value) & 1) == 1;
}
void meta_set_value(struct metadata *meta, element_value value) {
meta->marked |= 1 << value;
}
void meta_clear_values(struct metadata *meta) { meta->marked = 0; }
static inline void meta_mark(struct metadata *meta, element_value value,
unsigned index) {
meta_set_value(meta, value);
unsigned char count = meta->unique[value].count;
if (count == 0) {
meta->unique[value].count = 1;
meta->unique[value].index = index;
} else {
meta->unique[value].count = 2;
}
}
void board_meta_quad_refresh(struct board *board, board_pos qx, board_pos qy) {
struct metadata *meta = BOARD_QUAD(board, qx * 3, qy * 3);
board_pos quad_base_x = qx * 3;
board_pos quad_base_y = qy * 3;
meta_clear_values(meta);
for (board_pos off_y = 0; off_y < 3; ++off_y)
for (board_pos off_x = 0; off_x < 3; ++off_x) {
struct board_element *elem =
BOARD_ELEM(board, quad_base_x + off_x, quad_base_y + off_y);
if (elem->has_value)
meta_mark(meta, elem->value, (off_y * 3) + off_x);
}
}
void board_meta_row_refresh(struct board *board, board_pos y) {
struct metadata *meta = BOARD_ROW(board, y);
meta_clear_values(meta);
for (board_pos x = 0; x < 9; ++x) {
struct board_element *elem = BOARD_ELEM(board, x, y);
if (elem->has_value)
meta_mark(meta, elem->value, x);
}
}
void board_meta_col_refresh(struct board *board, board_pos x) {
struct metadata *meta = BOARD_COL(board, x);
meta_clear_values(meta);
for (board_pos y = 0; y < 9; ++y) {
struct board_element *elem = BOARD_ELEM(board, x, y);
if (elem->has_value)
meta_mark(meta, elem->value, y);
}
}
bool board_meta_can_set(const struct board *board, board_pos x, board_pos y,
element_value value) {
if (is_in_bounds(x, y) && is_valid_value(value)) {
return !(meta_has_value(BOARD_ROW(board, y), value) ||
meta_has_value(BOARD_COL(board, x), value) ||
meta_has_value(BOARD_QUAD(board, x, y), value));
} else
ERROR("Invalid parameters to function board_meta_can_set()");
}
void board_set(struct board *board, board_pos x, board_pos y,
element_value value) {
if (is_in_bounds(x, y) && is_valid_value(value)) {
ASSERT(board_meta_can_set(board, x, y, value),
"Attempt to set impossible value on board");
struct board_element *elem = BOARD_ELEM(board, x, y);
elem->has_value = true;
elem->value = value;
} else
ERROR("Invalid parameters to function board_set()");
}
void board_mark(struct board *board, board_pos x, board_pos y,
element_value value) {
if (is_in_bounds(x, y) && is_valid_value(value)) {
ASSERT(!board_has_value(board, x, y), "Attempt to mark element with value");
struct board_element *elem = BOARD_ELEM(board, x, y);
if (!board_is_marked(board, x, y, value)) {
elem->potential |= 1 << value;
++(elem->complexity);
}
} else
ERROR("Invalid parameters to function board_mark()");
}
bool board_unmark(struct board *board, board_pos x, board_pos y,
element_value value) {
if (is_in_bounds(x, y) && is_valid_value(value)) {
ASSERT(!board_has_value(board, x, y), "Attempt to mark element with value");
return elem_unmark(BOARD_ELEM(board, x, y), value);
} else
ERROR("Invalid parameters to function board_unmark()");
}
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 */
elem->potential ^= (1 << value);
--(elem->complexity);
}
return elem->potential != 0;
}
bool board_has_value(const struct board *board, board_pos x, board_pos y) {
if (is_in_bounds(x, y)) {
return BOARD_ELEM(board, x, y)->has_value;
} else
ERROR("Invalid parameters to function board_has_value()");
}
element_value board_get_value(const struct board *board, board_pos x,
board_pos y) {
if (is_in_bounds(x, y)) {
return BOARD_ELEM(board, x, y)->value;
} else
ERROR("Invalid parameters to function board_get_value()");
}
bool board_is_marked(const struct board *board, board_pos x, board_pos y,
element_value value) {
if (is_in_bounds(x, y) && is_valid_value(value)) {
return elem_is_marked(BOARD_ELEM(board, x, y), value);
} 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 board_is_valid(struct board *board) {
for (board_pos y = 0; y < 9; ++y)
for (board_pos x = 0; x < 9; ++x)
if (!board_has_value(board, x, y) &&
BOARD_ELEM(board, x, y)->potential == 0)
return false;
return true;
}
void board_update_marks(struct board *board, board_pos x, board_pos y) {
if (is_in_bounds(x, y)) {
struct board_element *elem = BOARD_ELEM(board, x, y);
/* Mark all values as impossible */
elem->potential = 0;
elem->complexity = 0;
/* Check x-axis */
elem->potential |= BOARD_QUAD(board, x, y)->marked;
elem->potential |= BOARD_ROW(board, y)->marked;
elem->potential |= BOARD_COL(board, x)->marked;
/* Invert matches */
elem->potential ^= 0x1FF;
/* Count marked bits */
unsigned short potential = elem->potential;
while (potential != 0) {
if ((potential & 1) == 1)
++(elem->complexity);
potential >>= 1;
}
} else
ERROR("Invalid parameters to function board_update_marks()");
}
bool board_can_quad_set_value(struct board *board, board_pos x, board_pos y,
element_value value) {
if (is_in_bounds(x, y) && is_valid_value(value)) {
/* Compute quadrant bases */
board_pos quad_x = TO_QUAD(x);
board_pos quad_y = TO_QUAD(y);
/* Compute sub-quadrant positions */
board_pos simp_x = x % 3;
board_pos simp_y = y % 3;
bool next = false;
/* Check along x-axis */
for (board_pos base_x = 0; base_x < 9; base_x += 3) {
next = false;
if (base_x != quad_x) {
for (board_pos check_y = 0; check_y < 3 && !next; ++check_y)
if (check_y != simp_y)
for (board_pos check_x = 0; check_x < 3 && !next; ++check_x) {
board_pos target_x = base_x + check_x;
board_pos target_y = quad_y + check_y;
bool has_value = board_has_value(board, target_x, target_y);
/* Check if a quadrant can contain the given value */
if ((has_value &&
BOARD_ELEM(board, target_x, target_y)->value == value) ||
(!has_value &&
board_is_marked(board, target_x, target_y, value))) {
next = true;
break;
}
}
if (!next)
return false;
}
}
/* Check along y-axis */
for (board_pos base_y = 0; base_y < 9; base_y += 3) {
next = false;
if (base_y != quad_y) {
for (board_pos check_x = 0; check_x < 3 && !next; ++check_x)
if (check_x != simp_x)
for (board_pos check_y = 0; check_y < 3 && !next; ++check_y) {
board_pos target_x = quad_x + check_x;
board_pos target_y = base_y + check_y;
bool has_value = board_has_value(board, target_x, target_y);
/* Check if a quadrant can contain the given value */
if ((has_value &&
BOARD_ELEM(board, target_x, target_y)->value == value) ||
(!has_value &&
board_is_marked(board, target_x, target_y, value))) {
next = true;
break;
}
}
if (!next)
return false;
}
}
return true;
} else
ERROR("Invalid parameters to function board_can_quad_set_value()");
}
static inline bool field_is_potential(unsigned short field,
element_value value) {
return ((field >> (value * 2)) & 3) < 2;
}
static inline void field_invalidate(unsigned short *field,
element_value value) {
*field |= 2 << (value * 2);
}
static inline void field_increment(unsigned short *field, element_value value) {
if (field_is_potential(*field, value))
*field += (1 << (value * 2));
}
void board_update_all_marks(struct board *board) {
for (board_pos y = 0; y < 9; ++y)
for (board_pos x = 0; x < 9; ++x)
if (!board_has_value(board, x, y))
board_update_marks(board, x, y);
}
bool board_place(struct board *board, board_pos x, board_pos y,
element_value value) {
if (is_in_bounds(x, y) && is_valid_value(value)) {
if (board_meta_can_set(board, x, y, value)) {
unsigned pos = ELEM_POS(x, y);
/* Unmark all adjacent elements */
for (unsigned i = 0; i < 20; ++i)
if (!board->links[pos][i]->has_value &&
!elem_unmark(board->links[pos][i], value)) {
/* Unmarking potential caused element to have no potential */
return false;
}
/* Set value */
board_set(board, x, y, value);
/* Update metadata */
meta_set_value(BOARD_QUAD(board, x, y), value);
meta_set_value(BOARD_ROW(board, y), value);
meta_set_value(BOARD_COL(board, x), value);
return true;
} else
return false;
} else
ERROR("Invalid parameters to function board_place()");
}
struct board *board_place_speculative(const struct board *board,
struct board *board_duplicate,
board_pos x, board_pos y,
element_value value) {
if (is_in_bounds(x, y) && is_valid_value(value)) {
/* Ensure value can be placed*/
if (board_meta_can_set(board, x, y, value)) {
/* Create duplicate and place value */
board_copy(board, board_duplicate);
if (!board_place(board_duplicate, x, y, value))
return NULL;
board_refresh_complexity(board_duplicate);
return board_duplicate;
} else
return NULL;
} else
ERROR("Invalid parameters to function board_place_speculative()");
}
bool board_refresh_complexity(struct board *board) {
board->complexity = 10;
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 < board->complexity) {
/* If complexity is somehow 0, we have an invalid board state */
if (elem->complexity == 0)
return false;
board->complexity = elem->complexity;
/* Short-circuit function on comlpexity=1, since it can't go lower */
if (board->complexity == 1)
return true;
}
}
/* If there are no complex board elements, board is solved */
if (board->complexity == 10)
board->complexity = 0;
return true;
}
void board_copy(const struct board *board_from, struct board *board_to) {
/* Copy everything except the links */
memcpy(board_to, board_from,
sizeof(struct board) - sizeof(((struct board *)0)->links));
}