212 lines
6.7 KiB
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;
|
|
}
|