Run cargo fmt
This commit is contained in:
parent
7d4e474731
commit
8dc7954f53
7 changed files with 642 additions and 413 deletions
|
@ -1,13 +1,13 @@
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use sudoku_solver::grid::{Grid, CellValue};
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use sudoku_solver::solver::{SolveController, SolveStatistics};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::{mpsc, Arc};
|
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::thread;
|
use std::str::FromStr;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{mpsc, Arc};
|
||||||
|
use std::thread;
|
||||||
|
use sudoku_solver::grid::{CellValue, Grid};
|
||||||
|
use sudoku_solver::solver::{SolveController, SolveStatistics};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We have to be very careful here because Grid contains lots of Rcs and RefCells which could enable mutability
|
We have to be very careful here because Grid contains lots of Rcs and RefCells which could enable mutability
|
||||||
|
@ -24,28 +24,28 @@ enum Difficulty {
|
||||||
Challenge,
|
Challenge,
|
||||||
Hard,
|
Hard,
|
||||||
Medium,
|
Medium,
|
||||||
Easy
|
Easy,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Difficulty {
|
impl Difficulty {
|
||||||
fn map_to_solve_controller(&self) -> SolveController {
|
fn map_to_solve_controller(&self) -> SolveController {
|
||||||
let mut controller = SolveController{
|
let mut controller = SolveController {
|
||||||
determine_uniqueness: true,
|
determine_uniqueness: true,
|
||||||
search_singles: true,
|
search_singles: true,
|
||||||
search_hidden_singles: true,
|
search_hidden_singles: true,
|
||||||
find_possibility_groups: true,
|
find_possibility_groups: true,
|
||||||
search_useful_constraint: true,
|
search_useful_constraint: true,
|
||||||
make_guesses: true
|
make_guesses: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Difficulty::Challenge => {}, // Do nothing, already hard
|
Difficulty::Challenge => {} // Do nothing, already hard
|
||||||
Difficulty::Hard => {
|
Difficulty::Hard => {
|
||||||
controller.make_guesses = false;
|
controller.make_guesses = false;
|
||||||
}
|
}
|
||||||
Difficulty::Medium => {
|
Difficulty::Medium => {
|
||||||
controller.make_guesses = false;
|
controller.make_guesses = false;
|
||||||
},
|
}
|
||||||
Difficulty::Easy => {
|
Difficulty::Easy => {
|
||||||
controller.make_guesses = false;
|
controller.make_guesses = false;
|
||||||
controller.search_useful_constraint = false;
|
controller.search_useful_constraint = false;
|
||||||
|
@ -59,31 +59,35 @@ impl Difficulty {
|
||||||
fn meets_minimum_requirements(&self, solve_statistics: &SolveStatistics) -> bool {
|
fn meets_minimum_requirements(&self, solve_statistics: &SolveStatistics) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Difficulty::Challenge => {
|
Difficulty::Challenge => {
|
||||||
(solve_statistics.guesses > 0) && (solve_statistics.possibility_groups > 20) && (solve_statistics.useful_constraints > 20)
|
(solve_statistics.guesses > 0)
|
||||||
|
&& (solve_statistics.possibility_groups > 20)
|
||||||
|
&& (solve_statistics.useful_constraints > 20)
|
||||||
}
|
}
|
||||||
Difficulty::Hard => {
|
Difficulty::Hard => {
|
||||||
(solve_statistics.possibility_groups > 20) && (solve_statistics.useful_constraints > 20)
|
(solve_statistics.possibility_groups > 20)
|
||||||
|
&& (solve_statistics.useful_constraints > 20)
|
||||||
}
|
}
|
||||||
Difficulty::Medium => {
|
Difficulty::Medium => {
|
||||||
(solve_statistics.possibility_groups > 10) && (solve_statistics.useful_constraints > 10)
|
(solve_statistics.possibility_groups > 10)
|
||||||
|
&& (solve_statistics.useful_constraints > 10)
|
||||||
}
|
}
|
||||||
Difficulty::Easy => {true} // easy has no minimum
|
Difficulty::Easy => true, // easy has no minimum
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Difficulty { // Needed for argparse
|
impl FromStr for Difficulty {
|
||||||
|
// Needed for argparse
|
||||||
type Err = String;
|
type Err = String;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s.eq_ignore_ascii_case("EASY") {
|
||||||
if s.eq_ignore_ascii_case("EASY"){
|
|
||||||
return Ok(Difficulty::Easy);
|
return Ok(Difficulty::Easy);
|
||||||
} else if s.eq_ignore_ascii_case("MEDIUM"){
|
} else if s.eq_ignore_ascii_case("MEDIUM") {
|
||||||
return Ok(Difficulty::Medium);
|
return Ok(Difficulty::Medium);
|
||||||
} else if s.eq_ignore_ascii_case("HARD"){
|
} else if s.eq_ignore_ascii_case("HARD") {
|
||||||
return Ok(Difficulty::Hard);
|
return Ok(Difficulty::Hard);
|
||||||
} else if s.eq_ignore_ascii_case("CHALLENGE"){
|
} else if s.eq_ignore_ascii_case("CHALLENGE") {
|
||||||
return Ok(Difficulty::Challenge);
|
return Ok(Difficulty::Challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,49 +96,74 @@ impl FromStr for Difficulty { // Needed for argparse
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
||||||
let mut debug = false;
|
let mut debug = false;
|
||||||
let mut max_hints = 81;
|
let mut max_hints = 81;
|
||||||
let mut max_attempts = 100;
|
let mut max_attempts = 100;
|
||||||
let mut filename : Option<String> = None;
|
let mut filename: Option<String> = None;
|
||||||
let mut difficulty = Difficulty::Challenge;
|
let mut difficulty = Difficulty::Challenge;
|
||||||
let mut threads = 1;
|
let mut threads = 1;
|
||||||
|
|
||||||
{ // this block limits scope of borrows by ap.refer() method
|
{
|
||||||
|
// this block limits scope of borrows by ap.refer() method
|
||||||
let mut ap = argparse::ArgumentParser::new();
|
let mut ap = argparse::ArgumentParser::new();
|
||||||
ap.set_description("Generate Sudoku puzzles");
|
ap.set_description("Generate Sudoku puzzles");
|
||||||
ap.refer(&mut debug)
|
ap.refer(&mut debug)
|
||||||
.add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode");
|
.add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode");
|
||||||
|
|
||||||
ap.refer(&mut max_hints)
|
ap.refer(&mut max_hints).add_option(
|
||||||
.add_option(&["--hints"], argparse::Store, "Only return a puzzle with less than or equal to this number of hints");
|
&["--hints"],
|
||||||
|
argparse::Store,
|
||||||
|
"Only return a puzzle with less than or equal to this number of hints",
|
||||||
|
);
|
||||||
|
|
||||||
ap.refer(&mut max_attempts)
|
ap.refer(&mut max_attempts)
|
||||||
.add_option(&["--attempts"], argparse::Store, "Number of puzzles each thread will generate to find an appropriate puzzle; default is 100");
|
.add_option(&["--attempts"], argparse::Store, "Number of puzzles each thread will generate to find an appropriate puzzle; default is 100");
|
||||||
|
|
||||||
ap.refer(&mut filename)
|
ap.refer(&mut filename).add_argument(
|
||||||
.add_argument("filename", argparse::StoreOption, "Optional filename to store puzzle in as a CSV");
|
"filename",
|
||||||
|
argparse::StoreOption,
|
||||||
|
"Optional filename to store puzzle in as a CSV",
|
||||||
|
);
|
||||||
|
|
||||||
ap.refer(&mut difficulty)
|
ap.refer(&mut difficulty).add_option(
|
||||||
.add_option(&["-d", "--difficulty"], argparse::Store, "Max difficulty setting; values are EASY, MEDIUM, HARD, or CHALLENGE");
|
&["-d", "--difficulty"],
|
||||||
|
argparse::Store,
|
||||||
|
"Max difficulty setting; values are EASY, MEDIUM, HARD, or CHALLENGE",
|
||||||
|
);
|
||||||
|
|
||||||
ap.refer(&mut threads)
|
ap.refer(&mut threads).add_option(
|
||||||
.add_option(&["--threads"], argparse::Store, "Number of threads to use when generating possible puzzles");
|
&["--threads"],
|
||||||
|
argparse::Store,
|
||||||
|
"Number of threads to use when generating possible puzzles",
|
||||||
|
);
|
||||||
|
|
||||||
ap.parse_args_or_exit();
|
ap.parse_args_or_exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
let solve_controller = difficulty.map_to_solve_controller();
|
let solve_controller = difficulty.map_to_solve_controller();
|
||||||
|
|
||||||
let (result, num_attempts) =
|
let (result, num_attempts) = if threads < 1 {
|
||||||
if threads < 1 {
|
|
||||||
eprintln!("--threads must be at least 1");
|
eprintln!("--threads must be at least 1");
|
||||||
exit(1);
|
exit(1);
|
||||||
} else if threads == 1 {
|
} else if threads == 1 {
|
||||||
let mut rng = SmallRng::from_entropy();
|
let mut rng = SmallRng::from_entropy();
|
||||||
get_puzzle_matching_conditions(&mut rng, &difficulty, &solve_controller, max_attempts, max_hints, &AtomicBool::new(false))
|
get_puzzle_matching_conditions(
|
||||||
|
&mut rng,
|
||||||
|
&difficulty,
|
||||||
|
&solve_controller,
|
||||||
|
max_attempts,
|
||||||
|
max_hints,
|
||||||
|
&AtomicBool::new(false),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
run_multi_threaded(max_attempts, max_hints, threads, debug, solve_controller, difficulty)
|
run_multi_threaded(
|
||||||
|
max_attempts,
|
||||||
|
max_hints,
|
||||||
|
threads,
|
||||||
|
debug,
|
||||||
|
solve_controller,
|
||||||
|
difficulty,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let (grid, solve_statistics, num_hints) = match result {
|
let (grid, solve_statistics, num_hints) = match result {
|
||||||
|
@ -145,16 +174,27 @@ fn main() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
println!("{}", grid);
|
println!("{}", grid);
|
||||||
println!("Puzzle has {} hints and was found in {} attempts.", num_hints, num_attempts);
|
println!(
|
||||||
|
"Puzzle has {} hints and was found in {} attempts.",
|
||||||
|
num_hints, num_attempts
|
||||||
|
);
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
println!("Solving this puzzle involves roughly:");
|
println!("Solving this puzzle involves roughly:");
|
||||||
println!("\t{} SINGLE actions", solve_statistics.singles);
|
println!("\t{} SINGLE actions", solve_statistics.singles);
|
||||||
println!("\t{} HIDDEN_SINGLE actions", solve_statistics.hidden_singles);
|
println!(
|
||||||
println!("\t{} USEFUL_CONSTRAINT actions", solve_statistics.useful_constraints);
|
"\t{} HIDDEN_SINGLE actions",
|
||||||
println!("\t{} POSSIBILITY_GROUP actions", solve_statistics.possibility_groups);
|
solve_statistics.hidden_singles
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"\t{} USEFUL_CONSTRAINT actions",
|
||||||
|
solve_statistics.useful_constraints
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"\t{} POSSIBILITY_GROUP actions",
|
||||||
|
solve_statistics.possibility_groups
|
||||||
|
);
|
||||||
println!("\t{} GUESS actions", solve_statistics.guesses);
|
println!("\t{} GUESS actions", solve_statistics.guesses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,17 +204,23 @@ fn main() {
|
||||||
if filename.ends_with(".pdf") {
|
if filename.ends_with(".pdf") {
|
||||||
sudoku_solver::pdf::draw_grid(&grid, &filename).unwrap();
|
sudoku_solver::pdf::draw_grid(&grid, &filename).unwrap();
|
||||||
println!("Grid saved as pdf to {}", filename);
|
println!("Grid saved as pdf to {}", filename);
|
||||||
} else{
|
} else {
|
||||||
save_grid_csv(&grid, &filename).unwrap();
|
save_grid_csv(&grid, &filename).unwrap();
|
||||||
println!("Grid saved as CSV to {}", filename);
|
println!("Grid saved as CSV to {}", filename);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bool, solve_controller: SolveController, difficulty: Difficulty) -> (Option<(Grid, SolveStatistics, i32)>, i32){
|
fn run_multi_threaded(
|
||||||
|
max_attempts: i32,
|
||||||
|
max_hints: i32,
|
||||||
|
threads: i32,
|
||||||
|
debug: bool,
|
||||||
|
solve_controller: SolveController,
|
||||||
|
difficulty: Difficulty,
|
||||||
|
) -> (Option<(Grid, SolveStatistics, i32)>, i32) {
|
||||||
let mut thread_rng = thread_rng();
|
let mut thread_rng = thread_rng();
|
||||||
let (transmitter, receiver) = mpsc::channel();
|
let (transmitter, receiver) = mpsc::channel();
|
||||||
let mut remaining_attempts = max_attempts;
|
let mut remaining_attempts = max_attempts;
|
||||||
|
@ -195,11 +241,18 @@ fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bo
|
||||||
}
|
}
|
||||||
|
|
||||||
let should_stop = &*should_stop;
|
let should_stop = &*should_stop;
|
||||||
let (result, num_attempts) = get_puzzle_matching_conditions(&mut rng, &difficulty, &solve_controller, thread_attempts, max_hints, should_stop);
|
let (result, num_attempts) = get_puzzle_matching_conditions(
|
||||||
|
&mut rng,
|
||||||
|
&difficulty,
|
||||||
|
&solve_controller,
|
||||||
|
thread_attempts,
|
||||||
|
max_hints,
|
||||||
|
should_stop,
|
||||||
|
);
|
||||||
|
|
||||||
let mut result_was_some = false;
|
let mut result_was_some = false;
|
||||||
let result = match result {
|
let result = match result {
|
||||||
None => {None}
|
None => None,
|
||||||
Some((grid, solve_statistics, num_hints)) => {
|
Some((grid, solve_statistics, num_hints)) => {
|
||||||
result_was_some = true;
|
result_was_some = true;
|
||||||
Some((SafeGridWrapper(grid), solve_statistics, num_hints))
|
Some((SafeGridWrapper(grid), solve_statistics, num_hints))
|
||||||
|
@ -209,7 +262,10 @@ fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bo
|
||||||
cloned_transmitter.send((result, num_attempts)).unwrap();
|
cloned_transmitter.send((result, num_attempts)).unwrap();
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
println!("Thread {}, terminated having run {} attempts; did send result: {}", i, num_attempts, result_was_some);
|
println!(
|
||||||
|
"Thread {}, terminated having run {} attempts; did send result: {}",
|
||||||
|
i, num_attempts, result_was_some
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -220,7 +276,7 @@ fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bo
|
||||||
|
|
||||||
while threads_running > 0 {
|
while threads_running > 0 {
|
||||||
let signal = receiver.recv().unwrap(); // Not sure what errors can result here but they are unexpected and deserve a panic
|
let signal = receiver.recv().unwrap(); // Not sure what errors can result here but they are unexpected and deserve a panic
|
||||||
threads_running-=1;
|
threads_running -= 1;
|
||||||
|
|
||||||
let (result, attempts) = signal;
|
let (result, attempts) = signal;
|
||||||
attempt_count += attempts;
|
attempt_count += attempts;
|
||||||
|
@ -237,12 +293,19 @@ fn run_multi_threaded(max_attempts: i32, max_hints: i32, threads: i32, debug: bo
|
||||||
return (result_to_return, attempt_count);
|
return (result_to_return, attempt_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_puzzle_matching_conditions(rng: &mut SmallRng, difficulty: &Difficulty, solve_controller: &SolveController, max_attempts: i32, max_hints: i32, should_stop: &AtomicBool) -> (Option<(Grid, SolveStatistics, i32)>, i32){
|
fn get_puzzle_matching_conditions(
|
||||||
|
rng: &mut SmallRng,
|
||||||
|
difficulty: &Difficulty,
|
||||||
|
solve_controller: &SolveController,
|
||||||
|
max_attempts: i32,
|
||||||
|
max_hints: i32,
|
||||||
|
should_stop: &AtomicBool,
|
||||||
|
) -> (Option<(Grid, SolveStatistics, i32)>, i32) {
|
||||||
let mut num_attempts = 0;
|
let mut num_attempts = 0;
|
||||||
|
|
||||||
while num_attempts < max_attempts && !should_stop.load(Ordering::Relaxed){
|
while num_attempts < max_attempts && !should_stop.load(Ordering::Relaxed) {
|
||||||
|
let (grid, num_hints, solve_statistics) =
|
||||||
let (grid, num_hints, solve_statistics) = sudoku_solver::generator::generate_grid(rng, &solve_controller);
|
sudoku_solver::generator::generate_grid(rng, &solve_controller);
|
||||||
num_attempts += 1;
|
num_attempts += 1;
|
||||||
|
|
||||||
if difficulty.meets_minimum_requirements(&solve_statistics) && num_hints <= max_hints {
|
if difficulty.meets_minimum_requirements(&solve_statistics) && num_hints <= max_hints {
|
||||||
|
@ -253,7 +316,7 @@ fn get_puzzle_matching_conditions(rng: &mut SmallRng, difficulty: &Difficulty, s
|
||||||
return (None, num_attempts);
|
return (None, num_attempts);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box<dyn Error>>{
|
fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box<dyn Error>> {
|
||||||
// Not using the csv crate for writing because it's being difficult and won't accept raw integers
|
// Not using the csv crate for writing because it's being difficult and won't accept raw integers
|
||||||
let mut file = std::fs::File::create(filename)?;
|
let mut file = std::fs::File::create(filename)?;
|
||||||
|
|
||||||
|
@ -261,10 +324,9 @@ fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box<dyn Error>>{
|
||||||
for y in 0..9 {
|
for y in 0..9 {
|
||||||
let cell = grid.get(x, y).unwrap();
|
let cell = grid.get(x, y).unwrap();
|
||||||
let value = &*cell.value.borrow();
|
let value = &*cell.value.borrow();
|
||||||
let digit =
|
let digit = match value {
|
||||||
match value {
|
CellValue::Fixed(digit) => *digit,
|
||||||
CellValue::Fixed(digit) => {*digit}
|
CellValue::Unknown(_) => 0,
|
||||||
CellValue::Unknown(_) => {0}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut text = digit.to_string();
|
let mut text = digit.to_string();
|
||||||
|
@ -272,7 +334,6 @@ fn save_grid_csv(grid: &Grid, filename: &str) -> Result<(), Box<dyn Error>>{
|
||||||
text.push(',');
|
text.push(',');
|
||||||
}
|
}
|
||||||
file.write(text.as_bytes())?;
|
file.write(text.as_bytes())?;
|
||||||
|
|
||||||
}
|
}
|
||||||
file.write(b"\n")?;
|
file.write(b"\n")?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,19 +3,21 @@ use std::str::FromStr;
|
||||||
use sudoku_solver::grid::Grid;
|
use sudoku_solver::grid::Grid;
|
||||||
use sudoku_solver::solver::solve_grid;
|
use sudoku_solver::solver::solve_grid;
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut debug = false;
|
let mut debug = false;
|
||||||
let mut filename = String::new();
|
let mut filename = String::new();
|
||||||
{ // this block limits scope of borrows by ap.refer() method
|
{
|
||||||
|
// this block limits scope of borrows by ap.refer() method
|
||||||
let mut ap = argparse::ArgumentParser::new();
|
let mut ap = argparse::ArgumentParser::new();
|
||||||
ap.set_description("Solve Sudoku puzzles");
|
ap.set_description("Solve Sudoku puzzles");
|
||||||
ap.refer(&mut debug)
|
ap.refer(&mut debug)
|
||||||
.add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode");
|
.add_option(&["--debug"], argparse::StoreTrue, "Run in debug mode");
|
||||||
|
|
||||||
ap.refer(&mut filename)
|
ap.refer(&mut filename).required().add_argument(
|
||||||
.required()
|
"filename",
|
||||||
.add_argument("filename", argparse::Store, "Path to puzzle CSV file");
|
argparse::Store,
|
||||||
|
"Path to puzzle CSV file",
|
||||||
|
);
|
||||||
|
|
||||||
ap.parse_args_or_exit();
|
ap.parse_args_or_exit();
|
||||||
}
|
}
|
||||||
|
@ -27,7 +29,6 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let mut grid = match read_grid(&filename) {
|
let mut grid = match read_grid(&filename) {
|
||||||
Ok(grid) => grid,
|
Ok(grid) => grid,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -42,8 +43,6 @@ fn main() {
|
||||||
solve_grid(&mut grid);
|
solve_grid(&mut grid);
|
||||||
|
|
||||||
println!("Solved grid:\n{}", grid);
|
println!("Solved grid:\n{}", grid);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_grid(filename: &str) -> Result<Grid, String> {
|
fn read_grid(filename: &str) -> Result<Grid, String> {
|
||||||
|
@ -78,18 +77,15 @@ fn read_grid(filename: &str) -> Result<Grid, String> {
|
||||||
if digit > 0 {
|
if digit > 0 {
|
||||||
grid.get(row, column).unwrap().set(digit);
|
grid.get(row, column).unwrap().set(digit);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
Err(_error) => return Err("Invalid cell value".to_string()),
|
||||||
Err(_error) => {return Err("Invalid cell value".to_string())}
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
},
|
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
row = row + 1;
|
row = row + 1;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(grid);
|
return Ok(grid);
|
||||||
|
|
149
src/generator.rs
149
src/generator.rs
|
@ -1,12 +1,14 @@
|
||||||
use crate::grid::{Cell, Grid, CellValue, Section};
|
use crate::grid::{Cell, CellValue, Grid, Section};
|
||||||
use crate::solver::{SolveStatus, SolveController, Uniqueness, evaluate_grid_with_solve_controller, SolveStatistics};
|
use crate::solver::{
|
||||||
use std::rc::Rc;
|
evaluate_grid_with_solve_controller, SolveController, SolveStatistics, SolveStatus, Uniqueness,
|
||||||
|
};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub static mut DEBUG : bool = false;
|
pub static mut DEBUG: bool = false;
|
||||||
|
|
||||||
impl Grid {
|
impl Grid {
|
||||||
fn get_random_empty_cell(&self, rng : &mut SmallRng) -> Result<Rc<Cell>, &str> {
|
fn get_random_empty_cell(&self, rng: &mut SmallRng) -> Result<Rc<Cell>, &str> {
|
||||||
// Idea - put all empty cells into a vector and choose one at random
|
// Idea - put all empty cells into a vector and choose one at random
|
||||||
// If vector is empty we return an error
|
// If vector is empty we return an error
|
||||||
|
|
||||||
|
@ -16,11 +18,10 @@ impl Grid {
|
||||||
let cell = self.get(x, y).unwrap();
|
let cell = self.get(x, y).unwrap();
|
||||||
let add_cell = {
|
let add_cell = {
|
||||||
let cell_value = &*cell.value.borrow();
|
let cell_value = &*cell.value.borrow();
|
||||||
match cell_value { // May cause issues with borrow rules
|
match cell_value {
|
||||||
CellValue::Fixed(_) => {false}
|
// May cause issues with borrow rules
|
||||||
CellValue::Unknown(_) => {
|
CellValue::Fixed(_) => false,
|
||||||
true
|
CellValue::Unknown(_) => true,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if add_cell {
|
if add_cell {
|
||||||
|
@ -31,13 +32,13 @@ impl Grid {
|
||||||
|
|
||||||
match empty_cells.iter().choose(rng) {
|
match empty_cells.iter().choose(rng) {
|
||||||
Some(cell) => Ok(Rc::clone(cell)),
|
Some(cell) => Ok(Rc::clone(cell)),
|
||||||
None => Err("Unable to find an empty cell")
|
None => Err("Unable to find an empty cell"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cell {
|
impl Cell {
|
||||||
fn delete_value(&self){
|
fn delete_value(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
if DEBUG {
|
if DEBUG {
|
||||||
println!("Cell {}, {} had its value deleted.", self.x, self.y);
|
println!("Cell {}, {} had its value deleted.", self.x, self.y);
|
||||||
|
@ -47,10 +48,21 @@ impl Cell {
|
||||||
self.set_value_exact(CellValue::Unknown(vec![])); // placeholder
|
self.set_value_exact(CellValue::Unknown(vec![])); // placeholder
|
||||||
|
|
||||||
// This will reset all the possibilities for this cell and the ones that might have been limited by this cell
|
// This will reset all the possibilities for this cell and the ones that might have been limited by this cell
|
||||||
self.section.upgrade().unwrap().borrow().recalculate_and_set_possibilities();
|
self.section
|
||||||
self.row.upgrade().unwrap().borrow().recalculate_and_set_possibilities();
|
.upgrade()
|
||||||
self.column.upgrade().unwrap().borrow().recalculate_and_set_possibilities();
|
.unwrap()
|
||||||
|
.borrow()
|
||||||
|
.recalculate_and_set_possibilities();
|
||||||
|
self.row
|
||||||
|
.upgrade()
|
||||||
|
.unwrap()
|
||||||
|
.borrow()
|
||||||
|
.recalculate_and_set_possibilities();
|
||||||
|
self.column
|
||||||
|
.upgrade()
|
||||||
|
.unwrap()
|
||||||
|
.borrow()
|
||||||
|
.recalculate_and_set_possibilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,8 +72,8 @@ impl Cell {
|
||||||
fn calculate_possibilities(&self) -> Vec<u8> {
|
fn calculate_possibilities(&self) -> Vec<u8> {
|
||||||
// Need to calculate possibilities for this cell
|
// Need to calculate possibilities for this cell
|
||||||
let mut possibilities = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
|
let mut possibilities = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||||
fn eliminate_possibilities(possibilities: &mut Vec<u8>, line: &Section, cell: &Cell){
|
fn eliminate_possibilities(possibilities: &mut Vec<u8>, line: &Section, cell: &Cell) {
|
||||||
for (_index, other) in line.vec.iter().enumerate(){
|
for (_index, other) in line.vec.iter().enumerate() {
|
||||||
if other.x != cell.x || other.y != cell.y {
|
if other.x != cell.x || other.y != cell.y {
|
||||||
let value = &*other.value.borrow();
|
let value = &*other.value.borrow();
|
||||||
match value {
|
match value {
|
||||||
|
@ -80,9 +92,21 @@ impl Cell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eliminate_possibilities(&mut possibilities, &self.section.upgrade().unwrap().borrow(), self);
|
eliminate_possibilities(
|
||||||
eliminate_possibilities(&mut possibilities, &self.row.upgrade().unwrap().borrow(), self);
|
&mut possibilities,
|
||||||
eliminate_possibilities(&mut possibilities, &self.column.upgrade().unwrap().borrow(), self);
|
&self.section.upgrade().unwrap().borrow(),
|
||||||
|
self,
|
||||||
|
);
|
||||||
|
eliminate_possibilities(
|
||||||
|
&mut possibilities,
|
||||||
|
&self.row.upgrade().unwrap().borrow(),
|
||||||
|
self,
|
||||||
|
);
|
||||||
|
eliminate_possibilities(
|
||||||
|
&mut possibilities,
|
||||||
|
&self.column.upgrade().unwrap().borrow(),
|
||||||
|
self,
|
||||||
|
);
|
||||||
|
|
||||||
return possibilities;
|
return possibilities;
|
||||||
}
|
}
|
||||||
|
@ -95,10 +119,10 @@ impl Section {
|
||||||
let new_possibilities = {
|
let new_possibilities = {
|
||||||
let cell_value = &*cell.value.borrow();
|
let cell_value = &*cell.value.borrow();
|
||||||
match cell_value {
|
match cell_value {
|
||||||
CellValue::Fixed(_) => { continue; }
|
CellValue::Fixed(_) => {
|
||||||
CellValue::Unknown(_) => {
|
continue;
|
||||||
cell.calculate_possibilities()
|
|
||||||
}
|
}
|
||||||
|
CellValue::Unknown(_) => cell.calculate_possibilities(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -107,8 +131,10 @@ impl Section {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_grid(rng: &mut SmallRng, solve_controller: &SolveController) -> (Grid, i32, SolveStatistics) {
|
pub fn generate_grid(
|
||||||
|
rng: &mut SmallRng,
|
||||||
|
solve_controller: &SolveController,
|
||||||
|
) -> (Grid, i32, SolveStatistics) {
|
||||||
let mut grid = generate_completed_grid(rng);
|
let mut grid = generate_completed_grid(rng);
|
||||||
let mut num_hints = 81;
|
let mut num_hints = 81;
|
||||||
|
|
||||||
|
@ -132,8 +158,8 @@ pub fn generate_grid(rng: &mut SmallRng, solve_controller: &SolveController) ->
|
||||||
|
|
||||||
cell_clone.delete_value();
|
cell_clone.delete_value();
|
||||||
|
|
||||||
|
let (status, statistics) =
|
||||||
let (status, statistics) = evaluate_grid_with_solve_controller(&mut grid_clone, solve_controller);
|
evaluate_grid_with_solve_controller(&mut grid_clone, solve_controller);
|
||||||
match status {
|
match status {
|
||||||
SolveStatus::Complete(uniqueness) => {
|
SolveStatus::Complete(uniqueness) => {
|
||||||
let uniqueness = uniqueness.unwrap();
|
let uniqueness = uniqueness.unwrap();
|
||||||
|
@ -142,31 +168,34 @@ pub fn generate_grid(rng: &mut SmallRng, solve_controller: &SolveController) ->
|
||||||
num_hints = num_hints - 1;
|
num_hints = num_hints - 1;
|
||||||
grid = grid_clone;
|
grid = grid_clone;
|
||||||
}
|
}
|
||||||
Uniqueness::NotUnique => continue // We can't remove this cell; continue onto the next one (note that grid hasn't been modified because of solve_controller)
|
Uniqueness::NotUnique => continue, // We can't remove this cell; continue onto the next one (note that grid hasn't been modified because of solve_controller)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SolveStatus::Unfinished => panic!("evaluate_grid_with_solve_controller should never return UNFINISHED"),
|
SolveStatus::Unfinished => {
|
||||||
SolveStatus::Invalid => panic!("Removing constraints should not have set the # of solutions to zero")
|
panic!("evaluate_grid_with_solve_controller should never return UNFINISHED")
|
||||||
|
}
|
||||||
|
SolveStatus::Invalid => {
|
||||||
|
panic!("Removing constraints should not have set the # of solutions to zero")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
statistics_option = Some(statistics);
|
statistics_option = Some(statistics);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (grid, num_hints, statistics_option.unwrap());
|
return (grid, num_hints, statistics_option.unwrap());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We generate a completed grid with no mind for difficulty; afterward generate_puzzle will take out as many fields as it can with regards to the difficulty
|
// We generate a completed grid with no mind for difficulty; afterward generate_puzzle will take out as many fields as it can with regards to the difficulty
|
||||||
fn generate_completed_grid(rng: &mut SmallRng) -> Grid {
|
fn generate_completed_grid(rng: &mut SmallRng) -> Grid {
|
||||||
let solve_controller = SolveController{
|
let solve_controller = SolveController {
|
||||||
determine_uniqueness: true,
|
determine_uniqueness: true,
|
||||||
search_singles: true,
|
search_singles: true,
|
||||||
search_hidden_singles: true,
|
search_hidden_singles: true,
|
||||||
find_possibility_groups: true,
|
find_possibility_groups: true,
|
||||||
search_useful_constraint: true,
|
search_useful_constraint: true,
|
||||||
make_guesses: true
|
make_guesses: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut grid : Grid = loop {
|
let mut grid: Grid = loop {
|
||||||
// First step; randomly assign 8 different digits to different empty cells and see if there's a possible solution
|
// First step; randomly assign 8 different digits to different empty cells and see if there's a possible solution
|
||||||
// We have to ensure that 8 of the digits appear at least once, otherwise the solution can't be unique because you could interchange the two missing digits throughout the puzzle
|
// We have to ensure that 8 of the digits appear at least once, otherwise the solution can't be unique because you could interchange the two missing digits throughout the puzzle
|
||||||
// We do this in a loop so that if we are really unlucky and our guesses stop there from being any solution, we can easily re-run it
|
// We do this in a loop so that if we are really unlucky and our guesses stop there from being any solution, we can easily re-run it
|
||||||
|
@ -199,23 +228,24 @@ fn generate_completed_grid(rng: &mut SmallRng) -> Grid {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Alright, we now have a grid that we can start adding more guesses onto until we find a unique solution
|
// Alright, we now have a grid that we can start adding more guesses onto until we find a unique solution
|
||||||
grid =
|
grid = 'outer: loop {
|
||||||
'outer: loop {
|
|
||||||
let cell = grid.get_random_empty_cell(rng).unwrap(); // We unwrap because if somehow we're filled each cell without finding a solution, that's reason for a panic
|
let cell = grid.get_random_empty_cell(rng).unwrap(); // We unwrap because if somehow we're filled each cell without finding a solution, that's reason for a panic
|
||||||
let cell = &*cell;
|
let cell = &*cell;
|
||||||
let mut cell_possibilities = cell.get_value_possibilities().expect("An empty cell has no possibilities");
|
let mut cell_possibilities = cell
|
||||||
|
.get_value_possibilities()
|
||||||
|
.expect("An empty cell has no possibilities");
|
||||||
|
|
||||||
// Let's scramble the order
|
// Let's scramble the order
|
||||||
cell_possibilities.shuffle(rng);
|
cell_possibilities.shuffle(rng);
|
||||||
|
|
||||||
for (_index, digit) in cell_possibilities.iter().enumerate() {
|
for (_index, digit) in cell_possibilities.iter().enumerate() {
|
||||||
|
|
||||||
let mut grid_clone = grid.clone();
|
let mut grid_clone = grid.clone();
|
||||||
let cell = &*grid_clone.get(cell.x, cell.y).unwrap();
|
let cell = &*grid_clone.get(cell.x, cell.y).unwrap();
|
||||||
|
|
||||||
cell.set(*digit);
|
cell.set(*digit);
|
||||||
|
|
||||||
let (status, _statistics) = evaluate_grid_with_solve_controller(&mut grid_clone, &solve_controller);
|
let (status, _statistics) =
|
||||||
|
evaluate_grid_with_solve_controller(&mut grid_clone, &solve_controller);
|
||||||
match status {
|
match status {
|
||||||
SolveStatus::Complete(uniqueness) => {
|
SolveStatus::Complete(uniqueness) => {
|
||||||
let uniqueness = uniqueness.unwrap();
|
let uniqueness = uniqueness.unwrap();
|
||||||
|
@ -230,14 +260,15 @@ fn generate_completed_grid(rng: &mut SmallRng) -> Grid {
|
||||||
SolveStatus::Unfinished => panic!("evaluate_grid_with_solve_controller should never return UNFINISHED if making guesses"),
|
SolveStatus::Unfinished => panic!("evaluate_grid_with_solve_controller should never return UNFINISHED if making guesses"),
|
||||||
SolveStatus::Invalid => continue // Try another guess
|
SolveStatus::Invalid => continue // Try another guess
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// If we reach this point in the loop, then none of the possibilities for cell provided any solution
|
// If we reach this point in the loop, then none of the possibilities for cell provided any solution
|
||||||
// Which means something serious happened before in the solving process - reason for panic
|
// Which means something serious happened before in the solving process - reason for panic
|
||||||
eprint!("No valid hints were found for puzzle\n{} at cell ({}, {})", grid, cell.x, cell.y);
|
eprint!(
|
||||||
|
"No valid hints were found for puzzle\n{} at cell ({}, {})",
|
||||||
|
grid, cell.x, cell.y
|
||||||
|
);
|
||||||
panic!("Unable to continue as puzzle is invalid");
|
panic!("Unable to continue as puzzle is invalid");
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
crate::solver::solve_grid(&mut grid);
|
crate::solver::solve_grid(&mut grid);
|
||||||
|
@ -245,12 +276,13 @@ fn generate_completed_grid(rng: &mut SmallRng) -> Grid {
|
||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::grid::*;
|
|
||||||
use crate::solver::{solve_grid_with_solve_controller, SolveController, Uniqueness, SolveStatus, SolveStatistics};
|
|
||||||
use crate::generator::generate_grid;
|
use crate::generator::generate_grid;
|
||||||
|
use crate::grid::*;
|
||||||
|
use crate::solver::{
|
||||||
|
solve_grid_with_solve_controller, SolveController, SolveStatistics, SolveStatus, Uniqueness,
|
||||||
|
};
|
||||||
use rand::prelude::SmallRng;
|
use rand::prelude::SmallRng;
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
|
|
||||||
|
@ -292,36 +324,40 @@ mod tests {
|
||||||
|
|
||||||
grid.get(8, 2).unwrap().set(6);
|
grid.get(8, 2).unwrap().set(6);
|
||||||
|
|
||||||
let status = solve_grid_with_solve_controller(&mut grid, &SolveController{
|
let status = solve_grid_with_solve_controller(
|
||||||
|
&mut grid,
|
||||||
|
&SolveController {
|
||||||
determine_uniqueness: true,
|
determine_uniqueness: true,
|
||||||
search_singles: true,
|
search_singles: true,
|
||||||
search_hidden_singles: true,
|
search_hidden_singles: true,
|
||||||
find_possibility_groups: true,
|
find_possibility_groups: true,
|
||||||
search_useful_constraint: true,
|
search_useful_constraint: true,
|
||||||
make_guesses: true
|
make_guesses: true,
|
||||||
}, &mut SolveStatistics::new());
|
},
|
||||||
|
&mut SolveStatistics::new(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(status, SolveStatus::Complete(Some(Uniqueness::NotUnique)));
|
assert_eq!(status, SolveStatus::Complete(Some(Uniqueness::NotUnique)));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// There was a bug where even though mutate_grid was set to false, the end result was still solved
|
// There was a bug where even though mutate_grid was set to false, the end result was still solved
|
||||||
#[test]
|
#[test]
|
||||||
fn ensure_grid_not_complete(){
|
fn ensure_grid_not_complete() {
|
||||||
let solve_controller = SolveController{
|
let solve_controller = SolveController {
|
||||||
determine_uniqueness: true,
|
determine_uniqueness: true,
|
||||||
search_singles: true,
|
search_singles: true,
|
||||||
search_hidden_singles: true,
|
search_hidden_singles: true,
|
||||||
find_possibility_groups: true,
|
find_possibility_groups: true,
|
||||||
search_useful_constraint: true,
|
search_useful_constraint: true,
|
||||||
make_guesses: true
|
make_guesses: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Note that the puzzle itself doesn't matter
|
// Note that the puzzle itself doesn't matter
|
||||||
let (grid, _num_hints, _statistics) = generate_grid(&mut SmallRng::seed_from_u64(123), &solve_controller);
|
let (grid, _num_hints, _statistics) =
|
||||||
|
generate_grid(&mut SmallRng::seed_from_u64(123), &solve_controller);
|
||||||
|
|
||||||
let mut observed_empty_cell = false;
|
let mut observed_empty_cell = false;
|
||||||
'outer : for x in 0..9 {
|
'outer: for x in 0..9 {
|
||||||
for y in 0..9 {
|
for y in 0..9 {
|
||||||
let cell = grid.get(x, y).unwrap();
|
let cell = grid.get(x, y).unwrap();
|
||||||
let value = cell.get_value_copy();
|
let value = cell.get_value_copy();
|
||||||
|
@ -337,6 +373,5 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(observed_empty_cell);
|
assert!(observed_empty_cell);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
101
src/grid.rs
101
src/grid.rs
|
@ -1,13 +1,13 @@
|
||||||
use std::rc::{Rc, Weak};
|
use std::cell::RefCell;
|
||||||
use std::cell::{RefCell};
|
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
|
use std::rc::{Rc, Weak};
|
||||||
|
|
||||||
pub static mut DEBUG: bool = false;
|
pub static mut DEBUG: bool = false;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum CellValue {
|
pub enum CellValue {
|
||||||
Fixed(u8),
|
Fixed(u8),
|
||||||
Unknown(Vec<u8>)
|
Unknown(Vec<u8>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A representation of a single cell in a Sudoku grid. Don't make this directly; make a Grid.
|
/// A representation of a single cell in a Sudoku grid. Don't make this directly; make a Grid.
|
||||||
|
@ -42,7 +42,7 @@ impl Cell {
|
||||||
/// assert_eq!(cell2.get_value_copy(), CellValue::Unknown(vec![2,3,4,5,6,7,8,9]));
|
/// assert_eq!(cell2.get_value_copy(), CellValue::Unknown(vec![2,3,4,5,6,7,8,9]));
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set(&self, digit: u8){
|
pub fn set(&self, digit: u8) {
|
||||||
unsafe {
|
unsafe {
|
||||||
if DEBUG {
|
if DEBUG {
|
||||||
println!("Cell {}, {} was set with digit {}", self.x, self.y, digit);
|
println!("Cell {}, {} was set with digit {}", self.x, self.y, digit);
|
||||||
|
@ -76,12 +76,12 @@ impl Cell {
|
||||||
|
|
||||||
/// Set the cell value with a provided `CellValue`; if `value` is Fixed then the related cell's
|
/// Set the cell value with a provided `CellValue`; if `value` is Fixed then the related cell's
|
||||||
/// possibilities are adjusted like in `set`.
|
/// possibilities are adjusted like in `set`.
|
||||||
pub fn set_value(&self, value: CellValue){
|
pub fn set_value(&self, value: CellValue) {
|
||||||
match value {
|
match value {
|
||||||
CellValue::Fixed(digit) => {
|
CellValue::Fixed(digit) => {
|
||||||
self.set(digit);
|
self.set(digit);
|
||||||
return;
|
return;
|
||||||
},
|
}
|
||||||
CellValue::Unknown(_) => {
|
CellValue::Unknown(_) => {
|
||||||
self.set_value_exact(value);
|
self.set_value_exact(value);
|
||||||
} // continue on
|
} // continue on
|
||||||
|
@ -108,10 +108,13 @@ impl Cell {
|
||||||
/// assert_eq!(cell2.get_value_copy(), CellValue::Unknown(vec![1,2,3,4,5,6,7,8,9])); // still contains 1
|
/// assert_eq!(cell2.get_value_copy(), CellValue::Unknown(vec![1,2,3,4,5,6,7,8,9])); // still contains 1
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
pub fn set_value_exact(&self, value: CellValue){
|
pub fn set_value_exact(&self, value: CellValue) {
|
||||||
unsafe {
|
unsafe {
|
||||||
if DEBUG {
|
if DEBUG {
|
||||||
println!("Cell {}, {} was set with CellValue exact {:?}", self.x, self.y, value);
|
println!(
|
||||||
|
"Cell {}, {} was set with CellValue exact {:?}",
|
||||||
|
self.x, self.y, value
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,13 +127,13 @@ impl Cell {
|
||||||
let value = &*self.value.borrow();
|
let value = &*self.value.borrow();
|
||||||
match value {
|
match value {
|
||||||
CellValue::Fixed(_) => None,
|
CellValue::Fixed(_) => None,
|
||||||
CellValue::Unknown(x) => Some(x.clone())
|
CellValue::Unknown(x) => Some(x.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal function - mark all the Sections the cell belongs to as having had a change
|
// Internal function - mark all the Sections the cell belongs to as having had a change
|
||||||
// so that the solver will look at it later
|
// so that the solver will look at it later
|
||||||
fn mark_updates(&self){
|
fn mark_updates(&self) {
|
||||||
{
|
{
|
||||||
let row = &*self.row.upgrade().unwrap();
|
let row = &*self.row.upgrade().unwrap();
|
||||||
let row = &*row.borrow();
|
let row = &*row.borrow();
|
||||||
|
@ -149,12 +152,12 @@ impl Cell {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go through and remove digit from the Section's Cells' possibilities
|
// Go through and remove digit from the Section's Cells' possibilities
|
||||||
fn process_possibilities(line: &Section, digit: u8){
|
fn process_possibilities(line: &Section, digit: u8) {
|
||||||
for (_index, cell) in line.vec.iter().enumerate() {
|
for (_index, cell) in line.vec.iter().enumerate() {
|
||||||
let cell = &**cell;
|
let cell = &**cell;
|
||||||
|
|
||||||
// Find the new CellValue to set; may be None if the cell was already fixed or had no possibilities remaining
|
// Find the new CellValue to set; may be None if the cell was already fixed or had no possibilities remaining
|
||||||
let new_value_option : Option<CellValue> = {
|
let new_value_option: Option<CellValue> = {
|
||||||
let value = &*cell.value.borrow();
|
let value = &*cell.value.borrow();
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
|
@ -162,7 +165,9 @@ impl Cell {
|
||||||
let mut new_possibilities = possibilities.clone();
|
let mut new_possibilities = possibilities.clone();
|
||||||
|
|
||||||
match new_possibilities.binary_search(&digit) {
|
match new_possibilities.binary_search(&digit) {
|
||||||
Ok(index_remove) => {new_possibilities.remove(index_remove);},
|
Ok(index_remove) => {
|
||||||
|
new_possibilities.remove(index_remove);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -176,18 +181,17 @@ impl Cell {
|
||||||
} else {
|
} else {
|
||||||
Some(CellValue::UNKNOWN(new_possibilities))
|
Some(CellValue::UNKNOWN(new_possibilities))
|
||||||
}*/
|
}*/
|
||||||
},
|
}
|
||||||
CellValue::Fixed(_) => {None}
|
CellValue::Fixed(_) => None,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match new_value_option {
|
match new_value_option {
|
||||||
Some(new_value) => {
|
Some(new_value) => {
|
||||||
cell.set_value(new_value);
|
cell.set_value(new_value);
|
||||||
},
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,23 +204,23 @@ pub struct Section {
|
||||||
pub vec: Vec<Rc<Cell>>,
|
pub vec: Vec<Rc<Cell>>,
|
||||||
pub do_update: RefCell<bool>,
|
pub do_update: RefCell<bool>,
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
pub section_type: SectionType
|
pub section_type: SectionType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SectionType {
|
pub enum SectionType {
|
||||||
Row,
|
Row,
|
||||||
Column,
|
Column,
|
||||||
Square
|
Square,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Section {
|
impl Section {
|
||||||
fn push(&mut self, x: Rc<Cell>){
|
fn push(&mut self, x: Rc<Cell>) {
|
||||||
self.vec.push(x);
|
self.vec.push(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Short-hand for accessing `vec` and calling it's `get` method.
|
/// Short-hand for accessing `vec` and calling it's `get` method.
|
||||||
pub fn get(&self, index: usize) -> Option<&Rc<Cell>>{
|
pub fn get(&self, index: usize) -> Option<&Rc<Cell>> {
|
||||||
self.vec.get(index)
|
self.vec.get(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +229,7 @@ impl Section {
|
||||||
vec: Vec::new(),
|
vec: Vec::new(),
|
||||||
do_update: RefCell::new(false),
|
do_update: RefCell::new(false),
|
||||||
index,
|
index,
|
||||||
section_type: line_type
|
section_type: line_type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +254,6 @@ pub struct Grid {
|
||||||
impl Grid {
|
impl Grid {
|
||||||
/// Generate a new empty `Grid` with full empty possibilities for each `Cell`
|
/// Generate a new empty `Grid` with full empty possibilities for each `Cell`
|
||||||
pub fn new() -> Grid {
|
pub fn new() -> Grid {
|
||||||
|
|
||||||
let mut rows: Vec<MultiMut<Section>> = Vec::new();
|
let mut rows: Vec<MultiMut<Section>> = Vec::new();
|
||||||
let mut columns: Vec<MultiMut<Section>> = Vec::new();
|
let mut columns: Vec<MultiMut<Section>> = Vec::new();
|
||||||
let mut sections: Vec<MultiMut<Section>> = Vec::new();
|
let mut sections: Vec<MultiMut<Section>> = Vec::new();
|
||||||
|
@ -262,17 +265,17 @@ impl Grid {
|
||||||
}
|
}
|
||||||
|
|
||||||
for row_index in 0..9 {
|
for row_index in 0..9 {
|
||||||
let row_rc = unsafe {
|
let row_rc = unsafe { rows.get_unchecked(row_index) };
|
||||||
rows.get_unchecked(row_index)
|
|
||||||
};
|
|
||||||
|
|
||||||
let row_ref = &mut *row_rc.borrow_mut();
|
let row_ref = &mut *row_rc.borrow_mut();
|
||||||
|
|
||||||
for column_index in 0..9 {
|
for column_index in 0..9 {
|
||||||
let section_index = (row_index / 3) * 3 + column_index / 3;
|
let section_index = (row_index / 3) * 3 + column_index / 3;
|
||||||
let (column_rc, section_rc) = unsafe {
|
let (column_rc, section_rc) = unsafe {
|
||||||
(columns.get_unchecked_mut(column_index),
|
(
|
||||||
sections.get_unchecked_mut(section_index))
|
columns.get_unchecked_mut(column_index),
|
||||||
|
sections.get_unchecked_mut(section_index),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let column_weak = Rc::downgrade(column_rc);
|
let column_weak = Rc::downgrade(column_rc);
|
||||||
|
@ -289,7 +292,7 @@ impl Grid {
|
||||||
value: RefCell::new(CellValue::Unknown(vec![1, 2, 3, 4, 5, 6, 7, 8, 9])),
|
value: RefCell::new(CellValue::Unknown(vec![1, 2, 3, 4, 5, 6, 7, 8, 9])),
|
||||||
row: row_weak,
|
row: row_weak,
|
||||||
column: column_weak,
|
column: column_weak,
|
||||||
section: section_weak
|
section: section_weak,
|
||||||
};
|
};
|
||||||
|
|
||||||
let ref1 = Rc::new(cell);
|
let ref1 = Rc::new(cell);
|
||||||
|
@ -302,7 +305,11 @@ impl Grid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Grid { rows, columns, sections };
|
return Grid {
|
||||||
|
rows,
|
||||||
|
columns,
|
||||||
|
sections,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `Cell` (in an `Rc`) at the specified coordinates.
|
/// Returns the `Cell` (in an `Rc`) at the specified coordinates.
|
||||||
|
@ -311,34 +318,33 @@ impl Grid {
|
||||||
///
|
///
|
||||||
/// Returns None if the coordinates are out of bounds.
|
/// Returns None if the coordinates are out of bounds.
|
||||||
pub fn get(&self, r: usize, c: usize) -> Option<Rc<Cell>> {
|
pub fn get(&self, r: usize, c: usize) -> Option<Rc<Cell>> {
|
||||||
|
|
||||||
let row = match self.rows.get(r) {
|
let row = match self.rows.get(r) {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => return None
|
None => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let row = &*(&**row).borrow();
|
let row = &*(&**row).borrow();
|
||||||
|
|
||||||
let cell = match row.get(c) {
|
let cell = match row.get(c) {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => return None
|
None => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
return Some(Rc::clone(cell));
|
return Some(Rc::clone(cell));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_unknown(x: &Vec<u8>, digit: u8, row: &mut String){
|
fn process_unknown(x: &Vec<u8>, digit: u8, row: &mut String) {
|
||||||
if x.contains(&digit) {
|
if x.contains(&digit) {
|
||||||
row.push('*');
|
row.push('*');
|
||||||
} else{
|
} else {
|
||||||
row.push(' ');
|
row.push(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the smallest empty `Cell` in terms of possibilities; returns `None` if all Cells have
|
/// Find the smallest empty `Cell` in terms of possibilities; returns `None` if all Cells have
|
||||||
/// `Fixed` `CellValue`s.
|
/// `Fixed` `CellValue`s.
|
||||||
pub fn find_smallest_cell(&self) -> Option<Rc<Cell>>{
|
pub fn find_smallest_cell(&self) -> Option<Rc<Cell>> {
|
||||||
let mut smallest_cell : Option<Rc<Cell>> = None;
|
let mut smallest_cell: Option<Rc<Cell>> = None;
|
||||||
let mut smallest_size = usize::MAX;
|
let mut smallest_size = usize::MAX;
|
||||||
|
|
||||||
for x in 0..9 {
|
for x in 0..9 {
|
||||||
|
@ -349,11 +355,11 @@ impl Grid {
|
||||||
|
|
||||||
match cell_value {
|
match cell_value {
|
||||||
CellValue::Unknown(possibilities) => {
|
CellValue::Unknown(possibilities) => {
|
||||||
if (possibilities.len() < smallest_size) && (possibilities.len() > 0){
|
if (possibilities.len() < smallest_size) && (possibilities.len() > 0) {
|
||||||
smallest_size = possibilities.len();
|
smallest_size = possibilities.len();
|
||||||
smallest_cell = Some(cell_rc);
|
smallest_cell = Some(cell_rc);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -397,24 +403,23 @@ impl Clone for Grid {
|
||||||
impl std::fmt::Display for Grid {
|
impl std::fmt::Display for Grid {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
for r in 0..9 {
|
for r in 0..9 {
|
||||||
|
|
||||||
// Each row corresponds to 3 rows since we leave room for guesses
|
// Each row corresponds to 3 rows since we leave room for guesses
|
||||||
let mut row1 = String::new();
|
let mut row1 = String::new();
|
||||||
let mut row2 = String::new();
|
let mut row2 = String::new();
|
||||||
let mut row3 = String::new();
|
let mut row3 = String::new();
|
||||||
|
|
||||||
for c in 0..9 {
|
for c in 0..9 {
|
||||||
|
|
||||||
let cell = &*self.get(r, c).unwrap();
|
let cell = &*self.get(r, c).unwrap();
|
||||||
let value = &*cell.value.borrow();
|
let value = &*cell.value.borrow();
|
||||||
|
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
CellValue::Fixed(x) => {
|
CellValue::Fixed(x) => {
|
||||||
row1.push_str(" ");
|
row1.push_str(" ");
|
||||||
row2.push(' '); row2.push_str(&x.to_string()); row2.push(' ');
|
row2.push(' ');
|
||||||
|
row2.push_str(&x.to_string());
|
||||||
|
row2.push(' ');
|
||||||
row3.push_str(" ");
|
row3.push_str(" ");
|
||||||
},
|
}
|
||||||
CellValue::Unknown(x) => {
|
CellValue::Unknown(x) => {
|
||||||
Grid::process_unknown(&x, 1, &mut row1);
|
Grid::process_unknown(&x, 1, &mut row1);
|
||||||
Grid::process_unknown(&x, 2, &mut row1);
|
Grid::process_unknown(&x, 2, &mut row1);
|
||||||
|
@ -430,17 +435,15 @@ impl std::fmt::Display for Grid {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (c % 3 == 2) && (c < 8){
|
if (c % 3 == 2) && (c < 8) {
|
||||||
row1.push('\u{2503}');
|
row1.push('\u{2503}');
|
||||||
row2.push('\u{2503}');
|
row2.push('\u{2503}');
|
||||||
row3.push('\u{2503}');
|
row3.push('\u{2503}');
|
||||||
} else if c < 8{
|
} else if c < 8 {
|
||||||
row1.push('┆');
|
row1.push('┆');
|
||||||
row2.push('┆');
|
row2.push('┆');
|
||||||
row3.push('┆');
|
row3.push('┆');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
write!(f, "{}", row1)?;
|
write!(f, "{}", row1)?;
|
||||||
|
@ -452,7 +455,7 @@ impl std::fmt::Display for Grid {
|
||||||
|
|
||||||
if (r % 3 == 2) && (r < 8) {
|
if (r % 3 == 2) && (r < 8) {
|
||||||
write!(f, "━━━┿━━━┿━━━╋━━━┿━━━┿━━━╋━━━┿━━━┿━━━\n")?;
|
write!(f, "━━━┿━━━┿━━━╋━━━┿━━━┿━━━╋━━━┿━━━┿━━━\n")?;
|
||||||
} else if r < 8{
|
} else if r < 8 {
|
||||||
write!(f, "┄┄┄┼┄┄┄┼┄┄┄╂┄┄┄┼┄┄┄┼┄┄┄╂┄┄┄┼┄┄┄┼┄┄┄\n")?;
|
write!(f, "┄┄┄┼┄┄┄┼┄┄┄╂┄┄┄┼┄┄┄┼┄┄┄╂┄┄┄┼┄┄┄┼┄┄┄\n")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
pub mod grid;
|
|
||||||
pub mod solver;
|
|
||||||
pub mod generator;
|
pub mod generator;
|
||||||
|
pub mod grid;
|
||||||
pub mod pdf;
|
pub mod pdf;
|
||||||
|
pub mod solver;
|
||||||
|
|
36
src/pdf.rs
36
src/pdf.rs
|
@ -1,15 +1,15 @@
|
||||||
|
use crate::grid::{CellValue, Grid};
|
||||||
use printpdf::*;
|
use printpdf::*;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::BufWriter;
|
use std::io::BufWriter;
|
||||||
use crate::grid::{Grid, CellValue};
|
|
||||||
|
|
||||||
const BOTTOM_LEFT_X : f64 = 10.0;
|
const BOTTOM_LEFT_X: f64 = 10.0;
|
||||||
const BOTTOM_LEFT_Y : f64 = 279.0 - 200.0 - 10.0;
|
const BOTTOM_LEFT_Y: f64 = 279.0 - 200.0 - 10.0;
|
||||||
const GRID_DIMENSION : f64 = 190.0;
|
const GRID_DIMENSION: f64 = 190.0;
|
||||||
|
|
||||||
const A4 : (Mm, Mm) = (Mm(215.0), Mm(279.0));
|
const A4: (Mm, Mm) = (Mm(215.0), Mm(279.0));
|
||||||
|
|
||||||
pub fn draw_grid(grid: &Grid, filename: &str) -> Result<(), Box<dyn std::error::Error>>{
|
pub fn draw_grid(grid: &Grid, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let (doc, page1, layer1) = PdfDocument::new("Sudoku Puzzle", A4.0, A4.1, "Layer 1");
|
let (doc, page1, layer1) = PdfDocument::new("Sudoku Puzzle", A4.0, A4.1, "Layer 1");
|
||||||
let layer = doc.get_page(page1).get_layer(layer1);
|
let layer = doc.get_page(page1).get_layer(layer1);
|
||||||
|
|
||||||
|
@ -28,7 +28,6 @@ pub fn draw_grid(grid: &Grid, filename: &str) -> Result<(), Box<dyn std::error::
|
||||||
let x_offset = 6.1;
|
let x_offset = 6.1;
|
||||||
let y_offset = -16.5;
|
let y_offset = -16.5;
|
||||||
|
|
||||||
|
|
||||||
for r in 0..9 {
|
for r in 0..9 {
|
||||||
let y = Mm(BOTTOM_LEFT_Y + (GRID_DIMENSION / 9.0) * (9.0 - r as f64) + y_offset);
|
let y = Mm(BOTTOM_LEFT_Y + (GRID_DIMENSION / 9.0) * (9.0 - r as f64) + y_offset);
|
||||||
|
|
||||||
|
@ -41,7 +40,7 @@ pub fn draw_grid(grid: &Grid, filename: &str) -> Result<(), Box<dyn std::error::
|
||||||
CellValue::Fixed(digit) => {
|
CellValue::Fixed(digit) => {
|
||||||
let text = digit.to_string();
|
let text = digit.to_string();
|
||||||
layer.use_text(text, font_size, x, y, &font);
|
layer.use_text(text, font_size, x, y, &font);
|
||||||
},
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,11 +49,9 @@ pub fn draw_grid(grid: &Grid, filename: &str) -> Result<(), Box<dyn std::error::
|
||||||
doc.save(&mut BufWriter::new(File::create(filename)?))?;
|
doc.save(&mut BufWriter::new(File::create(filename)?))?;
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_empty_grid(layer: &PdfLayerReference){
|
fn draw_empty_grid(layer: &PdfLayerReference) {
|
||||||
|
|
||||||
// x represents position on left-right scale
|
// x represents position on left-right scale
|
||||||
// y represents position on up-down scale
|
// y represents position on up-down scale
|
||||||
|
|
||||||
|
@ -65,7 +62,7 @@ fn draw_empty_grid(layer: &PdfLayerReference){
|
||||||
{
|
{
|
||||||
let starting_x = Mm(BOTTOM_LEFT_X);
|
let starting_x = Mm(BOTTOM_LEFT_X);
|
||||||
let ending_x = Mm(BOTTOM_LEFT_X + GRID_DIMENSION);
|
let ending_x = Mm(BOTTOM_LEFT_X + GRID_DIMENSION);
|
||||||
let y_increment = GRID_DIMENSION/3.0;
|
let y_increment = GRID_DIMENSION / 3.0;
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
let y = Mm(BOTTOM_LEFT_Y + (i as f64) * y_increment);
|
let y = Mm(BOTTOM_LEFT_Y + (i as f64) * y_increment);
|
||||||
draw_line(layer, Point::new(starting_x, y), Point::new(ending_x, y));
|
draw_line(layer, Point::new(starting_x, y), Point::new(ending_x, y));
|
||||||
|
@ -76,7 +73,7 @@ fn draw_empty_grid(layer: &PdfLayerReference){
|
||||||
{
|
{
|
||||||
let starting_y = Mm(BOTTOM_LEFT_Y);
|
let starting_y = Mm(BOTTOM_LEFT_Y);
|
||||||
let ending_y = Mm(BOTTOM_LEFT_Y + GRID_DIMENSION);
|
let ending_y = Mm(BOTTOM_LEFT_Y + GRID_DIMENSION);
|
||||||
let x_increment = GRID_DIMENSION/3.0;
|
let x_increment = GRID_DIMENSION / 3.0;
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
let x = Mm(BOTTOM_LEFT_X + (i as f64) * x_increment);
|
let x = Mm(BOTTOM_LEFT_X + (i as f64) * x_increment);
|
||||||
draw_line(layer, Point::new(x, starting_y), Point::new(x, ending_y));
|
draw_line(layer, Point::new(x, starting_y), Point::new(x, ending_y));
|
||||||
|
@ -90,13 +87,12 @@ fn draw_empty_grid(layer: &PdfLayerReference){
|
||||||
{
|
{
|
||||||
let starting_x = Mm(BOTTOM_LEFT_X);
|
let starting_x = Mm(BOTTOM_LEFT_X);
|
||||||
let ending_x = Mm(BOTTOM_LEFT_X + GRID_DIMENSION);
|
let ending_x = Mm(BOTTOM_LEFT_X + GRID_DIMENSION);
|
||||||
let y_increment = GRID_DIMENSION/9.0;
|
let y_increment = GRID_DIMENSION / 9.0;
|
||||||
for i in 1..9 {
|
for i in 1..9 {
|
||||||
if i % 3 != 0{
|
if i % 3 != 0 {
|
||||||
let y = Mm(BOTTOM_LEFT_Y + (i as f64) * y_increment);
|
let y = Mm(BOTTOM_LEFT_Y + (i as f64) * y_increment);
|
||||||
draw_line(layer, Point::new(starting_x, y), Point::new(ending_x, y));
|
draw_line(layer, Point::new(starting_x, y), Point::new(ending_x, y));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +100,7 @@ fn draw_empty_grid(layer: &PdfLayerReference){
|
||||||
{
|
{
|
||||||
let starting_y = Mm(BOTTOM_LEFT_Y);
|
let starting_y = Mm(BOTTOM_LEFT_Y);
|
||||||
let ending_y = Mm(BOTTOM_LEFT_Y + GRID_DIMENSION);
|
let ending_y = Mm(BOTTOM_LEFT_Y + GRID_DIMENSION);
|
||||||
let x_increment = GRID_DIMENSION/9.0;
|
let x_increment = GRID_DIMENSION / 9.0;
|
||||||
for i in 1..9 {
|
for i in 1..9 {
|
||||||
if i % 3 != 0 {
|
if i % 3 != 0 {
|
||||||
let x = Mm(BOTTOM_LEFT_X + (i as f64) * x_increment);
|
let x = Mm(BOTTOM_LEFT_X + (i as f64) * x_increment);
|
||||||
|
@ -114,9 +110,7 @@ fn draw_empty_grid(layer: &PdfLayerReference){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_line(layer: &PdfLayerReference, point1: Point, point2: Point) {
|
||||||
|
|
||||||
fn draw_line(layer: &PdfLayerReference, point1: Point, point2: Point){
|
|
||||||
let points = vec![(point1, false), (point2, false)];
|
let points = vec![(point1, false), (point2, false)];
|
||||||
|
|
||||||
let line = Line {
|
let line = Line {
|
||||||
|
@ -124,7 +118,7 @@ fn draw_line(layer: &PdfLayerReference, point1: Point, point2: Point){
|
||||||
is_closed: false,
|
is_closed: false,
|
||||||
has_fill: false,
|
has_fill: false,
|
||||||
has_stroke: true,
|
has_stroke: true,
|
||||||
is_clipping_path: false
|
is_clipping_path: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
layer.add_shape(line);
|
layer.add_shape(line);
|
||||||
|
|
488
src/solver.rs
488
src/solver.rs
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue