sudoku_bat/solver/puzzle.c

194 lines
5.7 KiB
C
Raw Normal View History

2024-05-25 23:08:47 +00:00
#include <stdio.h>
#include "puzzle.h"
#include "puzzle_propagate_result.h"
#include "interference.h"
// Crash the program if the solution is bad
// (read: if any assertion is violated)
//
// assumes that the interference graph is correct
void puzzle_check_solution(puzzle_t* puzzle);
// Solve the puzzle, returning false if a backtrack is needed.
//
// (unlike puzzle_solve, which crashes the program)
bool puzzle_solve1(puzzle_t* puzzle);
// Propagate the constraints corresponding to cell=tile.
//
// Anything connected to cell on the interference graph won't be set to tile.
void puzzle_propagate_constraints(
puzzle_t* puzzle,
uint8_t cell,
tile_t tile,
puzzle_propagate_result_t* result
);
// Undo propagating the constraints corresponding to cell=tile.
void puzzle_undo_propagate_constraints(
puzzle_t* puzzle,
tile_t tile,
puzzle_propagate_result_t* result
);
// Return true if cell is bound to something other than '.'.
bool puzzle_is_complete(puzzle_t* puzzle, uint8_t cell);
// Bind cell to some value, including '.'
void puzzle_set_cell(puzzle_t* puzzle, uint8_t cell, tile_t tile);
void puzzle_init(puzzle_t* puzzle, const tile_t board[N_CELLS]) {
// save the original board
for (uint8_t cell = 0; cell < N_CELLS; cell++) {
puzzle->input_board[cell] = board[cell];
}
// create a blank board
for (uint8_t cell = 0; cell < N_CELLS; cell++) {
puzzle->solved_board[cell] = tile_new('.');
puzzle_options_init(&puzzle->options[cell]);
}
// add every item from the input board to the new board
for (uint8_t cell = 0; cell < N_CELLS; cell++) {
tile_t tile = puzzle->input_board[cell];
if (tile.value != '.') {
puzzle_propagate_result_t propagate_result;
puzzle_propagate_constraints(puzzle, cell, tile, &propagate_result);
if (!propagate_result.viable) {
crash("puzzle contains a contradiction");
}
puzzle_set_cell(puzzle, cell, tile);
}
}
}
void puzzle_solve(puzzle_t* puzzle) {
bool success = puzzle_solve1(puzzle);
if (!success) {
crash("couldn't solve! horrible.");
}
puzzle_check_solution(puzzle);
}
bool puzzle_solve1(puzzle_t* puzzle) {
// find best cell to start from
uint8_t best_cell_noptions = 255;
uint8_t best_cell = 255;
for (uint8_t cell = 0; cell < N_CELLS; cell++) {
if (puzzle_is_complete(puzzle, cell)) {
continue;
}
puzzle_options_t *options = &puzzle->options[cell];
uint8_t cell_noptions = puzzle_options_count(options);
if (cell_noptions < best_cell_noptions) {
best_cell_noptions = cell_noptions;
best_cell = cell;
}
}
if (best_cell == 255) {
return true; // solved!
}
{
uint8_t cell = best_cell;
puzzle_options_t *options = &puzzle->options[cell];
for (char digit = '1'; digit <= (char) '9'; digit++) {
tile_t tile = tile_new(digit);
if (!puzzle_options_contains(options, tile)) {
continue;
}
puzzle_propagate_result_t result;
puzzle_propagate_constraints(puzzle, cell, tile, &result);
if (result.viable) {
puzzle_set_cell(puzzle, cell, tile);
if (puzzle_solve1(puzzle)) {
return true;
}
}
puzzle_undo_propagate_constraints(puzzle, tile, &result);
}
puzzle_set_cell(puzzle, cell, tile_new('.'));
}
return false;
}
void puzzle_check_solution(puzzle_t* puzzle) {
for (uint8_t cell = 0; cell < N_CELLS; cell++) {
tile_t original = puzzle->input_board[cell];
tile_t solved = puzzle->solved_board[cell];
if (solved.value == '.') {
crash("invalid solution (empty cell)");
}
if (original.value != '.' && original.value != solved.value) {
crash("invalid solution (changed original cell)");
}
cellset_t *interference_row = &interference.rows[cell];
for (uint8_t other_i = 0; other_i < interference_row->count; other_i++) {
uint8_t other = interference_row->cells[other_i];
if (puzzle->solved_board[other].value == solved.value) {
crash("invalid solution (sudoku constraints violated)");
}
}
}
}
void puzzle_propagate_constraints(
puzzle_t* puzzle,
uint8_t cell,
tile_t tile,
puzzle_propagate_result_t* result
) {
cellset_init(&result->cells);
result->viable = true;
cellset_t *interference_row = &interference.rows[cell];
for (uint8_t other_i = 0; other_i < interference_row->count; other_i++) {
uint8_t other = interference_row->cells[other_i];
if (puzzle_is_complete(puzzle, other)) {
continue;
}
puzzle_options_t *options = &puzzle->options[other];
if (puzzle_options_remove(options, tile)) {
cellset_add(&result->cells, other);
if (puzzle_options_is_empty(options)) {
result->viable = false;
break;
}
}
}
}
void puzzle_undo_propagate_constraints(
puzzle_t* puzzle,
tile_t tile,
puzzle_propagate_result_t* result
) {
cellset_t *cells = &result->cells;
for (uint8_t cell_i = 0; cell_i < cells->count; cell_i++) {
uint8_t cell = cells->cells[cell_i];
puzzle_options_add(&puzzle->options[cell], tile);
}
}
bool puzzle_is_complete(puzzle_t* puzzle, uint8_t cell) {
return puzzle->solved_board[cell].value != '.';
}
void puzzle_set_cell(puzzle_t* puzzle, uint8_t cell, tile_t tile) {
puzzle->solved_board[cell] = tile;
}