Dump my code into a repo!

This commit is contained in:
2024-05-25 16:08:47 -07:00
commit 7e84d09250
24 changed files with 2387 additions and 0 deletions

4
solver/BUILD Normal file
View File

@ -0,0 +1,4 @@
cc_binary(
name = "solver",
srcs = glob(["*.c", "*.h"])
)

31
solver/cellset.c Normal file
View File

@ -0,0 +1,31 @@
#include "cellset.h"
void cellset_init(cellset_t* row) {
row->count = 0;
}
void cellset_add(
cellset_t* row,
uint8_t other
) {
for (uint8_t i = 0; i < row->count; i++) {
if (row->cells[i] == other) {
return;
}
}
if (row->count >= N_CELLS) {
crash("too many interference items");
}
row->cells[row->count++] = other;
}
void cellset_add_exclude(
cellset_t* row,
uint8_t exclude,
uint8_t other
) {
if (other == exclude) { return; }
cellset_add(row, other);
}

39
solver/cellset.h Normal file
View File

@ -0,0 +1,39 @@
// A cellset_t is a list of cells. (without duplicates)
//
// This is a very thin wrapper over an array of cells.
//
// You're encouraged to use the underlying representation directly by accessing
// the cells field.
#ifndef CELLSET_H
#define CELLSET_H
#include "shared.h"
#include <stdint.h>
typedef struct {
uint8_t count;
uint8_t cells[N_CELLS];
} cellset_t;
// Create an empty cellset_t.
void cellset_init(cellset_t* cells);
// Add a cell to the cellset_t.
void cellset_add(
cellset_t* row,
uint8_t cell
);
// Add a cell to the cellset_t, unless the new value equals exclude, in which
// case nothing is done.
//
// (This operation is specifically useful in building the interference map.
// See interference.h.)
void cellset_add_exclude(
cellset_t* row,
uint8_t exclude,
uint8_t cell
);
#endif

36
solver/interference.c Normal file
View File

@ -0,0 +1,36 @@
#include "interference.h"
// Convert a two-dimensional coordinate into a one-dimensional cell index.
#define PACK(x, y) ((y * SZ) + x)
interference_t interference;
void interference_init() {
for (uint8_t y = 0; y < SZ; y++) {
for (uint8_t x = 0; x < SZ; x++) {
uint8_t cell = PACK(x, y);
cellset_t *row = &interference.rows[cell];
cellset_init(row);
// rows
for (uint8_t ox = 0; ox < SZ; ox++) {
cellset_add_exclude(row, cell, PACK(ox, y));
}
// columns
for (uint8_t oy = 0; oy < SZ; oy++) {
cellset_add_exclude(row, cell, PACK(x, oy));
}
// boxes
uint8_t box_x = (x / 3) * 3;
uint8_t box_y = (y / 3) * 3;
for (uint8_t oy = box_y; oy < box_y + 3; oy++) {
for (uint8_t ox = box_x; ox < box_x + 3; ox++) {
cellset_add_exclude(row, cell, PACK(ox, oy));
}
}
}
}
}

24
solver/interference.h Normal file
View File

@ -0,0 +1,24 @@
// The interference table is a list of cellsets.
//
// The interference set for cell X is the set of cells that can't have the same
// value as X.
//
// Again, you're encouraged to reach in and use this directly: it's just a
// jagged array of arrays!
#ifndef INTERFERENCE_H
#define INTERFERENCE_H
#include "cellset.h"
#include "shared.h"
typedef struct {
cellset_t rows[N_CELLS];
} interference_t;
extern interference_t interference;
// Initialize the global interference table.
void interference_init();
#endif

54
solver/main.c Normal file
View File

@ -0,0 +1,54 @@
#include <stdio.h>
#include "interference.h"
#include "puzzle.h"
#include "puzzle_io.h"
// run_puzzle solves a single puzzle and prints the output
void run_puzzle(char* puzzle_text);
int main(int argc, char* argv[]) {
char *filename;
if (argc < 2) {
filename = "sudoku_puzzles.txt";
} else {
filename = argv[1];
}
FILE *file = fopen(filename, "r");
if (file == NULL) {
perror("Failed to open file");
return -1;
}
char line[N_CELLS + 2];
// we expect a line of exactly N_CELLS+1 chars, where the last one is \n
// we replace the \n with a \0 for compatibility with puzzle_init_string
while (fgets(line, sizeof(line), file)) {
bool too_short = strlen(line) != N_CELLS + 1;
bool too_long = line[N_CELLS] != '\n';
if (too_short || too_long) {
crash("wrong number of characters in a puzzle");
}
line[N_CELLS] = '\0';
run_puzzle(line);
}
return 0;
}
void run_puzzle(char* puzzle_text) {
// this only needs to be done once
// but it's harmless to do it more than once
interference_init();
// load and display info about the puzzle before and after solving
puzzle_t puzzle;
puzzle_init_string(&puzzle, puzzle_text);
printf("Initial state:\n\n");
puzzle_display(&puzzle);
printf("\nSolving...\n\n");
puzzle_solve(&puzzle);
puzzle_display(&puzzle);
printf("\n");
}

193
solver/puzzle.c Normal file
View File

@ -0,0 +1,193 @@
#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;
}

31
solver/puzzle.h Normal file
View File

@ -0,0 +1,31 @@
// The puzzle represents a Sudoku board where some cells may still be empty.
//
// All solve failures and inconsistencies are handled by crashing the program
// -- if puzzle_solve completes, then solved_board contains a copy of
// input_board where every blank tile is now set to some digit value.
#ifndef PUZZLE_H
#define PUZZLE_H
#include <stdint.h>
#include "shared.h"
#include "puzzle_options.h"
typedef struct {
tile_t input_board[N_CELLS];
tile_t solved_board[N_CELLS];
puzzle_options_t options[N_CELLS];
} puzzle_t;
// Create a puzzle whose input board is as given.
//
// You probably want puzzle_init_string from puzzle_io.h instead..
void puzzle_init(puzzle_t* puzzle, const tile_t board[N_CELLS]);
// Solve the puzzle.
//
// If this returns, the puzzle is solved. (Otherwise, we crash the process.)
void puzzle_solve(puzzle_t* puzzle);
#endif

25
solver/puzzle_io.c Normal file
View File

@ -0,0 +1,25 @@
#include <stdio.h>
#include <string.h>
#include "puzzle_io.h"
void puzzle_init_string(puzzle_t* puzzle, const char* text) {
if (strlen(text) != N_CELLS) {
crash("puzzles should be N_CELLS characters long");
}
tile_t tiles[N_CELLS];
for (uint8_t cell = 0; cell < N_CELLS; cell++) {
tiles[cell] = tile_new(text[cell]);
}
puzzle_init(puzzle, tiles);
}
void puzzle_display(puzzle_t* puzzle) {
for (uint8_t y = 0; y < SZ; y++) {
for (uint8_t x = 0; x < SZ; x++) {
putchar(puzzle->solved_board[y * SZ + x].value);
}
putchar('\n');
}
}

19
solver/puzzle_io.h Normal file
View File

@ -0,0 +1,19 @@
// It's possible to convert the puzzle to a terse string, and to display the
// puzzle with newlines between its rows for the person viewing.
//
// This functionality depends on stdio.h, which is gross, so I broke it out.
#ifndef PUZZLE_IO_H
#define PUZZLE_IO_H
#include "puzzle.h"
// Initialize a puzzle from an 81-character string containing its contents.
//
// (The characters are read from left to right, top to bottom.)
void puzzle_init_string(puzzle_t* puzzle, const char* text);
// Display a puzzle, writing it to stdout.
void puzzle_display(puzzle_t* puzzle);
#endif

47
solver/puzzle_options.c Normal file
View File

@ -0,0 +1,47 @@
#include "puzzle_options.h"
// this macro just does the validation that is always the same
#define TILE_TO_IX(bail) \
if (tile.value < '1' || tile.value > '9') { bail; } \
uint8_t ix = tile.value - '1'
// all of these are set operations for a bitfield with 9 bits!
// (done in the most obvious way possible)
void puzzle_options_init(puzzle_options_t* puzzle_options) {
puzzle_options->bitfield = 0x1FF; // 9 set bits
}
void puzzle_options_add(puzzle_options_t* puzzle_options, tile_t tile) {
TILE_TO_IX(return);
puzzle_options->bitfield |= 1 << ix;
}
bool puzzle_options_remove(puzzle_options_t* puzzle_options, tile_t tile) {
TILE_TO_IX(return false);
uint16_t old = puzzle_options->bitfield;
puzzle_options->bitfield &= ~(1 << ix);
uint16_t new = puzzle_options->bitfield;
return old != new;
}
bool puzzle_options_contains(puzzle_options_t* puzzle_options, tile_t tile) {
TILE_TO_IX(return false);
return (puzzle_options->bitfield & (1 << ix)) != 0;
}
uint8_t puzzle_options_count(puzzle_options_t* puzzle_options) {
uint8_t count = 0;
for (uint8_t ix = 0; ix < 9; ix++) {
if ((puzzle_options->bitfield & (1 << ix)) != 0) {
count++;
}
}
return count;
}
bool puzzle_options_is_empty(puzzle_options_t* puzzle_options) {
return puzzle_options->bitfield == 0;
}

40
solver/puzzle_options.h Normal file
View File

@ -0,0 +1,40 @@
// A puzzle_options_t is a set of possible tile values that can live somewhere
// in the puzzle.
//
// It has the interface of Set<tile_t>.
//
// Only values '1' through '9' are representable. Trying to add any other value
// to the set will silently fail.
#ifndef PUZZLE_OPTIONS_H
#define PUZZLE_OPTIONS_H
#include <stdint.h>
#include <stdbool.h>
#include "tile.h"
typedef struct {
uint16_t bitfield;
} puzzle_options_t;
// Create a blank puzzle options set.
void puzzle_options_init(puzzle_options_t* puzzle_options);
// Add a tile to a puzzle options set.
void puzzle_options_add(puzzle_options_t* puzzle_options, tile_t tile);
// Remove a tile from a puzzle options set.
bool puzzle_options_remove(puzzle_options_t* puzzle_options, tile_t tile);
// Return true if a puzzle options set contains this tile
bool puzzle_options_contains(puzzle_options_t* puzzle_options, tile_t tile);
// Count the tiles in this puzzle options set.
uint8_t puzzle_options_count(puzzle_options_t* puzzle_options);
// Check if a puzzle options set is empty.
//
// Equivalent to puzzle_options_count(...) == 0.
bool puzzle_options_is_empty(puzzle_options_t* puzzle_options);
#endif

View File

@ -0,0 +1,19 @@
// A constraint propagation operation generates a propagation result.
//
// Such a result is a list of cells that were touched and a "viable" flag.
//
// If "viable" is false, then at least one cell became impossible to fill
// during the propagation operation. (That is, the propagate operation found
// a contradiction.)
#ifndef PUZZLE_PROPAGATE_RESULT_H
#define PUZZLE_PROPAGATE_RESULT_H
#include <stdbool.h>
#include "cellset.h"
typedef struct {
bool viable;
cellset_t cells;
} puzzle_propagate_result_t;
#endif

8
solver/shared.c Normal file
View File

@ -0,0 +1,8 @@
#include <stdio.h>
#include <stdlib.h>
#include "shared.h"
void crash(const char* msg) {
fprintf(stderr, "%s\n", msg);
exit(-1);
}

12
solver/shared.h Normal file
View File

@ -0,0 +1,12 @@
// These shared values apply to the whole program.
#ifndef SHARED_H
#define SHARED_H
#define SZ 9
#define N_CELLS (SZ * SZ)
// Unceremoniously crash the process. RIP!
void crash(const char* msg);
#endif

16
solver/tile.c Normal file
View File

@ -0,0 +1,16 @@
#include <stdio.h>
#include "tile.h"
#include "shared.h"
tile_t tile_new(char c) {
tile_t tile;
tile.value = 0;
if (c == '.' || (c >= '1' && c <= '9')) {
tile.value = c;
return tile;
}
printf("tile value: %d", c);
crash("invalid tile value");
return tile;
}

18
solver/tile.h Normal file
View File

@ -0,0 +1,18 @@
// A tile_t is a char in "123456789." (including the dot)
//
// tile_new checks this.
//
// The digits represent the values of filled cells, while the dot represents an
// empty cell.
#ifndef TILE_H
#define TILE_H
typedef struct { char value; } tile_t;
// Create a tile from a character, validating that the character is in
// "123456789."
tile_t tile_new(char c);
#endif