sudoku_bat/solver/puzzle.c

212 lines
6.7 KiB
C

#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_cell_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
// (which is the cell with the fewest options)
uint8_t best_cell_noptions = 255;
uint8_t best_cell = 255;
for (uint8_t cell = 0; cell < N_CELLS; cell++) {
if (puzzle_cell_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) {
// no valid starting cell: continue!
return true;
}
{
// try every option that's still available (those from 1-9) that appear
// in the options set that we have
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_propagate_constraints may have created a situation
// where there is no need to even try the next branch, as it is
// known to be unsolvable
puzzle_set_cell(puzzle, cell, tile);
if (puzzle_solve1(puzzle)) {
return true;
}
}
puzzle_undo_propagate_constraints(puzzle, tile, &result);
}
// we didn't explicitly clear this earlier, so clear this here
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];
// every cell must be populated
if (solved.value == '.') {
crash("invalid solution (empty cell)");
}
// new cells must have the same value as the old cell in their position
if (original.value != '.' && original.value != solved.value) {
crash("invalid solution (changed original cell)");
}
// there must be no cases where the interference graph sees
// two neighbors with the same tile value
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;
// find every row that cell interferes with
// for every incomplete cell in that row, remove this tile value
// from the options
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_cell_is_complete(puzzle, other)) {
continue;
}
puzzle_options_t *options = &puzzle->options[other];
if (puzzle_options_remove(options, tile)) {
cellset_add(&result->cells, other);
// if there is no possible value for a cell, then we picked wrong
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
) {
// we recorded every cell that was touched, so untouch those cells
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_cell_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;
}