#include #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; }