Run cargo fmt

This commit is contained in:
Joel Therrien 2020-09-29 13:45:48 -07:00
parent 7d4e474731
commit 8dc7954f53
7 changed files with 642 additions and 413 deletions

View file

@ -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")?;
} }

View file

@ -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);

View file

@ -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);
} }
} }

View file

@ -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")?;
} }
} }

View file

@ -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;

View file

@ -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);

File diff suppressed because it is too large Load diff