sudoku_variants/lib.rs
1// Code lints
2
3#![warn(trivial_casts)]
4#![warn(trivial_numeric_casts)]
5#![warn(unreachable_pub)]
6#![warn(unused_import_braces)]
7#![warn(unused_lifetimes)]
8#![warn(unused_qualifications)]
9
10// Doc lints
11
12#![warn(broken_intra_doc_links)]
13#![warn(missing_docs)]
14#![warn(missing_crate_level_docs)]
15#![warn(invalid_codeblock_attributes)]
16
17//! This crate implements an easy-to-understand and flexible Sudoku engine. It
18//! supports the following key features:
19//!
20//! * Parsing and printing Sudoku
21//! * Checking validity of Sudoku and solutions according to standard rules as
22//! well as some common variants
23//! * Injection of custom constraints
24//! * Solving Sudoku using a perfect backtracking algorithm
25//! * Generating Sudoku with a possibility to specify a custom solver that has
26//! to be able to solve the result, thus controlling the difficulty
27//!
28//! Note in this introduction we will mostly be using 4x4 Sudoku due to their
29//! simpler nature. These are divided in 4 2x2 blocks, each with the digits 1
30//! to 4, just like each row and column.
31//!
32//! # Parsing and printing Sudoku
33//!
34//! See [SudokuGrid::parse] for the exact format of a Sudoku code.
35//!
36//! Codes can be used to exchange Sudoku, while pretty prints can be used to
37//! display a Sudoku in a clearer manner. An example of how to parse and
38//! display a Sudoku grid is provided below.
39//!
40//! ```
41//! use sudoku_variants::SudokuGrid;
42//!
43//! let grid =
44//! SudokuGrid::parse("2x2;2, ,3, , ,1, , ,1, , ,4, ,2, ,3").unwrap();
45//! println!("{}", grid);
46//! ```
47//!
48//! # Checking validity of Sudoku
49//!
50//! To check validity, an instance of [Sudoku] not only contains the numbers
51//! (stored in a [SudokuGrid]), but also some constraint which specifies the
52//! rules. For classic Sudoku rules,
53//! [DefaultConstraint](constraint::DefaultConstraint) can be used.
54//!
55//! It is possible to check an entire Sudoku, individual cells, or potential
56//! changes to individual cells that do not require changing the Sudoku's
57//! state. An example of the former is provided below.
58//!
59//! ```
60//! use sudoku_variants::Sudoku;
61//! use sudoku_variants::constraint::DefaultConstraint;
62//!
63//! // Some Sudoku for which it is totally unclear whether it is valid.
64//! let sudoku = Sudoku::parse("2x2;1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1",
65//! DefaultConstraint).unwrap();
66//! assert!(!sudoku.is_valid());
67//! ```
68//!
69//! If you are developing an app that gives feedback to the user, it may be
70//! desirable to specify where they made an error. Also, sometimes checking the
71//! entire Sudoku is redundant, since only one cell has changed. To do this, it
72//! is possible to check the validity of just one cell in the grid.
73//!
74//! ```
75//! use sudoku_variants::Sudoku;
76//! use sudoku_variants::constraint::DefaultConstraint;
77//!
78//! // A riddle posed by our app:
79//! // ╔═══╤═══╦═══╤═══╗
80//! // ║ │ ║ │ 4 ║
81//! // ╟───┼───╫───┼───╢
82//! // ║ │ 4 ║ 3 │ ║
83//! // ╠═══╪═══╬═══╪═══╣
84//! // ║ │ 3 ║ │ ║
85//! // ╟───┼───╫───┼───╢
86//! // ║ │ ║ 1 │ ║
87//! // ╚═══╧═══╩═══╧═══╝
88//! let mut sudoku = Sudoku::parse("2x2; , , ,4, ,4,3, , ,3, , , , ,1, ",
89//! DefaultConstraint).unwrap();
90//!
91//! // Some (unfortunatly wrong) user input to the top-left cell
92//! sudoku.grid_mut().set_cell(0, 0, 4);
93//! assert!(!sudoku.is_valid_cell(0, 0).unwrap());
94//! ```
95//!
96//! Similarly, it is also possible to check a singular cell with a potntial new
97//! entry, before changing the Sudoku, using [Sudoku::is_valid_number]. Since
98//! it otherwise behaves just like the example above, we will not provide
99//! another example.
100//!
101//! All examples above have been using the
102//! [DefaultConstraint](constraint::DefaultConstraint), which is actually a
103//! composition of [RowConstraint](constraint::RowConstraint),
104//! [ColumnConstraint](constraint::ColumnConstraint), and
105//! [BlockConstraint](constraint::BlockConstraint). Additionally to
106//! those three primitives, a few more common Sudoku variants' rules are
107//! provided, which can be combined into more exciting rule sets. Check out the
108//! [constraint] module for more details and instructions on how to write your
109//! own rules.
110//!
111//! # Solving Sudoku
112//!
113//! This crate offers a [Solver](solver::Solver) trait for structs that can
114//! totally or partially solve Sudoku (that is, able to solve every Sudoku with
115//! a unique solution or not). As a default implementation,
116//! [BacktrackingSolver](solver::BacktrackingSolver) is provided, which can
117//! solve every uniquely solveable Sudoku.
118//!
119//! To use it, first instantiate a Sudoku an then call
120//! [Solver.solve](solver::Solver::solve) on a backtracking
121//! solver (as it is a zero-sized struct, no instantiation is required).
122//!
123//! ```
124//! use sudoku_variants::{Sudoku, SudokuGrid};
125//! use sudoku_variants::constraint::DefaultConstraint;
126//! use sudoku_variants::solver::{BacktrackingSolver, Solution, Solver};
127//!
128//! // The same Sudoku as in our previous example.
129//! let sudoku = Sudoku::parse("2x2; , , ,4, ,4,3, , ,3, , , , ,1, ",
130//! DefaultConstraint).unwrap();
131//! let solution = BacktrackingSolver.solve(&sudoku);
132//!
133//! // The solution we expect:
134//! // ╔═══╤═══╦═══╤═══╗
135//! // ║ 3 │ 1 ║ 2 │ 4 ║
136//! // ╟───┼───╫───┼───╢
137//! // ║ 2 │ 4 ║ 3 │ 1 ║
138//! // ╠═══╪═══╬═══╪═══╣
139//! // ║ 1 │ 3 ║ 4 │ 2 ║
140//! // ╟───┼───╫───┼───╢
141//! // ║ 4 │ 2 ║ 1 │ 3 ║
142//! // ╚═══╧═══╩═══╧═══╝
143//! let expected_solution_grid =
144//! SudokuGrid::parse("2x2;3,1,2,4,2,4,3,1,1,3,4,2,4,2,1,3").unwrap();
145//!
146//! assert_eq!(Solution::Unique(expected_solution_grid), solution);
147//! ```
148//!
149//! The backtracking solver can deal with any (correctly implemented)
150//! constraint and type of Sudoku. If there is no solution, it will return
151//! `Solution::Impossible` and if there are multiple solutions, it will reutrn
152//! `Solution::Ambiguous`.
153//!
154//! ## Performance improvements
155//!
156//! Using pure backtracking can be an issue regarding performance. It is
157//! possible to use heuristics, so-called
158//! [Strategies][crate::solver::strategy::Strategy] that use logic to fill some
159//! cells or remove some optinos. This can be much faster than pure
160//! backtracking, but some experimentation is required. See the
161//! [strategy](crate::solver::strategy) module for further information.
162//!
163//! # Generating Sudoku
164//!
165//! Probably the most interesting feature of this crate is the generation of
166//! random Sudoku. This is done in two steps: generating a full grid using a
167//! [Generator](generator::Generator) and then removing as many clues as
168//! possible using a [Reducer](generator::Reducer).
169//!
170//! The generator needs a solver, which helps to reduce the search space for
171//! valid grids, and a random number generator, for which we use the `Rng`
172//! trait from the [rand](https://rust-random.github.io/rand/rand/index.html)
173//! crate. The reducer needs the same, however its solver is used to define the
174//! difficulty. Essentially, the reducer will generate a puzzle that is just
175//! not too hard for the given solver, that is, if one more clue were removed,
176//! the solver would be unable to solve it. An example of generating a minimal
177//! puzzle (where no clues can be removed without losing uniqueness) is
178//! provided below.
179//!
180//! ```
181//! use sudoku_variants::constraint::DefaultConstraint;
182//! use sudoku_variants::generator::{Generator, Reducer};
183//! use sudoku_variants::solver::{BacktrackingSolver, Solution, Solver};
184//!
185//! // new_default yields a generator/reducer with a backtracking solver and
186//! // rand::thread_rng()
187//! let mut generator = Generator::new_default();
188//! let mut reducer = Reducer::new_default();
189//!
190//! // Generate a full, 3x3 block Sudoku board with default rules.
191//! let mut sudoku = generator.generate(3, 3, DefaultConstraint).unwrap();
192//! assert!(sudoku.is_valid());
193//!
194//! // Remove as many clues as possible
195//! reducer.reduce(&mut sudoku);
196//!
197//! let unique = match BacktrackingSolver.solve(&sudoku) {
198//! Solution::Unique(_) => true,
199//! _ => false
200//! };
201//! assert!(unique);
202//! ```
203//!
204//! ## Defining a difficulty
205//!
206//! The above example removes digits as long as the resulting Sudoku is still
207//! uniquely solveable. This may result in some very difficult Sudoku, since it
208//! provides no guarantees as to *how* the Sudoku can be solved. To generate
209//! less difficult Sudoku, you can provide a less powerful
210//! [Solver](crate::solver::Solver), which has to be able to solve the reduced
211//! Sudoku. See the [strategy](crate::solver::strategy) module for further
212//! information.
213//!
214//! # Note regarding performance
215//!
216//! While generating ordinary, minimal Sudoku with this crate is doable within
217//! a few seconds, more complicated rule sets which result in less necessary
218//! clues or larger boards may result in performance issues. In any case, it is
219//! strongly recommended to use at least `opt-level = 2` for approximately a
220//! 28-fold performance improvement, even in tests that use Sudoku generation.
221
222pub mod constraint;
223pub mod error;
224pub mod generator;
225pub mod solver;
226pub mod util;
227
228#[cfg(test)]
229mod fix_tests;
230
231use constraint::Constraint;
232use error::{SudokuError, SudokuParseError, SudokuParseResult, SudokuResult};
233
234use std::fmt::{self, Display, Error, Formatter};
235
236/// A Sudoku grid is composed of cells that are organized into blocks of a
237/// given width and height in a way that makes the entire grid a square.
238/// Consequently, the number of blocks in a row is equal to the block height
239/// and vice versa. Each cell may or may not be occupied by a number.
240///
241/// In ordinary Sudoku, the block width and height are both 3. Here, however,
242/// more exotic variants are permitted, for example 4x2 blocks, which would
243/// result in a grid like this:
244///
245/// ```text
246/// ╔═══╤═══╤═══╤═══╦═══╤═══╤═══╤═══╗
247/// ║ │ │ │ ║ │ │ │ ║
248/// ╟───┼───┼───┼───╫───┼───┼───┼───╢
249/// ║ │ │ │ ║ │ │ │ ║
250/// ╠═══╪═══╪═══╪═══╬═══╪═══╪═══╪═══╣
251/// ║ │ │ │ ║ │ │ │ ║
252/// ╟───┼───┼───┼───╫───┼───┼───┼───╢
253/// ║ │ │ │ ║ │ │ │ ║
254/// ╠═══╪═══╪═══╪═══╬═══╪═══╪═══╪═══╣
255/// ║ │ │ │ ║ │ │ │ ║
256/// ╟───┼───┼───┼───╫───┼───┼───┼───╢
257/// ║ │ │ │ ║ │ │ │ ║
258/// ╠═══╪═══╪═══╪═══╬═══╪═══╪═══╪═══╣
259/// ║ │ │ │ ║ │ │ │ ║
260/// ╟───┼───┼───┼───╫───┼───┼───┼───╢
261/// ║ │ │ │ ║ │ │ │ ║
262/// ╚═══╧═══╧═══╧═══╩═══╧═══╧═══╧═══╝
263/// ```
264///
265/// `SudokuGrid` implements `Display`, but only grids with a size (that is,
266/// width or height) of less than or equal to 9 can be displayed with digits
267/// 1 to 9. Sudoku of all other sizes will raise an error.
268#[derive(Clone, Debug, Eq, PartialEq)]
269pub struct SudokuGrid {
270 block_width: usize,
271 block_height: usize,
272 size: usize,
273 cells: Vec<Option<usize>>
274}
275
276fn to_char(cell: Option<usize>) -> char {
277 if let Some(n) = cell {
278 (b'0' + n as u8) as char
279 }
280 else {
281 ' '
282 }
283}
284
285#[allow(clippy::too_many_arguments)]
286fn line(grid: &SudokuGrid, start: char, thick_sep: char, thin_sep: char,
287 segment: impl Fn(usize) -> char, pad: char, end: char, newline: bool) -> String {
288 let size = grid.size();
289 let mut result = String::new();
290
291 for x in 0..size {
292 if x == 0 {
293 result.push(start);
294 }
295 else if x % grid.block_width == 0 {
296 result.push(thick_sep);
297 }
298 else {
299 result.push(thin_sep);
300 }
301
302 result.push(pad);
303 result.push(segment(x));
304 result.push(pad);
305 }
306
307 result.push(end);
308
309 if newline {
310 result.push('\n');
311 }
312
313 result
314}
315
316fn top_row(grid: &SudokuGrid) -> String {
317 line(grid, '╔', '╦', '╤', |_| '═', '═', '╗', true)
318}
319
320fn thin_separator_line(grid: &SudokuGrid) -> String {
321 line(grid, '╟', '╫', '┼', |_| '─', '─', '╢', true)
322}
323
324fn thick_separator_line(grid: &SudokuGrid) -> String {
325 line(grid, '╠', '╬', '╪', |_| '═', '═', '╣', true)
326}
327
328fn bottom_row(grid: &SudokuGrid) -> String {
329 line(grid, '╚', '╩', '╧', |_| '═', '═', '╝', false)
330}
331
332fn content_row(grid: &SudokuGrid, y: usize) -> String {
333 line(grid, '║', '║', '│', |x| to_char(grid.get_cell(x, y).unwrap()), ' ',
334 '║', true)
335}
336
337impl Display for SudokuGrid {
338 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
339 let size = self.size();
340
341 if size > 9 {
342 return Err(Error::default());
343 }
344
345 let top_row = top_row(self);
346 let thin_separator_line = thin_separator_line(self);
347 let thick_separator_line = thick_separator_line(self);
348 let bottom_row = bottom_row(self);
349
350 for y in 0..size {
351 if y == 0 {
352 f.write_str(top_row.as_str())?;
353 }
354 else if y % self.block_height == 0 {
355 f.write_str(thick_separator_line.as_str())?;
356 }
357 else {
358 f.write_str(thin_separator_line.as_str())?;
359 }
360
361 f.write_str(content_row(self, y).as_str())?;
362 }
363
364 f.write_str(bottom_row.as_str())?;
365 Ok(())
366 }
367}
368
369fn to_string(cell: &Option<usize>) -> String {
370 if let Some(number) = cell {
371 number.to_string()
372 }
373 else {
374 String::from("")
375 }
376}
377
378pub(crate) fn index(column: usize, row: usize, size: usize) -> usize {
379 row * size + column
380}
381
382fn parse_dimensions(code: &str) -> Result<(usize, usize), SudokuParseError> {
383 let parts: Vec<&str> = code.split('x').collect();
384
385 if parts.len() != 2 {
386 return Err(SudokuParseError::MalformedDimensions);
387 }
388
389 Ok((parts[0].parse()?, parts[1].parse()?))
390}
391
392impl SudokuGrid {
393
394 /// Creates a new, empty Sudoku grid where the blocks have the given
395 /// dimensions. The total width and height of the grid will be equal to the
396 /// product of `block_width` and `block_height`.
397 ///
398 /// # Arguments
399 ///
400 /// * `block_width`: The horizontal dimension of one sub-block of the grid.
401 /// To ensure a square grid, this is also the number of blocks that compose
402 /// the grid vertically. For an ordinary Sudoku grid, this is 3. Must be
403 /// greater than 0.
404 /// * `block_height`: The vertical dimension of one sub-block of the grid.
405 /// To ensure a square grid, this is also the number of blocks that compose
406 /// the grid horizontally. For an ordinary Sudoku grid, this is 3. Must be
407 /// greater than 0.
408 ///
409 /// # Errors
410 ///
411 /// If `block_width` or `block_height` is invalid (zero).
412 pub fn new(block_width: usize, block_height: usize)
413 -> SudokuResult<SudokuGrid> {
414 if block_width == 0 || block_height == 0 {
415 return Err(SudokuError::InvalidDimensions);
416 }
417
418 let size = block_width * block_height;
419 let cells = vec![None; size * size];
420
421 Ok(SudokuGrid {
422 block_width,
423 block_height,
424 size,
425 cells
426 })
427 }
428
429 /// Parses a code encoding a Sudoku grid. The code has to be of the format
430 /// `<block_width>x<block_height>;<cells>` where `<cells>` is a
431 /// comma-separated list of entries, which are either empty or a number.
432 /// The entries are assigned left-to-right, top-to-bottom, where each row
433 /// is completed before the next one is started. Whitespace in the entries
434 /// is ignored to allow for more intuitive formatting. The number of
435 /// entries must match the amount of cells in a grid with the given
436 /// dimensions, i.e. it must be `(block_width · block_height)²`.
437 ///
438 /// As an example, the code `2x2;1, ,2, , ,3, ,4, , , ,3, ,1, ,2` will
439 /// parse to the following grid:
440 ///
441 /// ```text
442 /// ╔═══╤═══╦═══╤═══╗
443 /// ║ 1 │ ║ 2 │ ║
444 /// ╟───┼───╫───┼───╢
445 /// ║ │ 3 ║ │ 4 ║
446 /// ╠═══╪═══╬═══╪═══╣
447 /// ║ │ ║ 3 │ ║
448 /// ╟───┼───╫───┼───╢
449 /// ║ │ 1 ║ │ 2 ║
450 /// ╚═══╧═══╩═══╧═══╝
451 /// ```
452 ///
453 /// # Errors
454 ///
455 /// Any specialization of `SudokuParseError` (see that documentation).
456 pub fn parse(code: &str) -> SudokuParseResult<SudokuGrid> {
457 let parts: Vec<&str> = code.split(';').collect();
458
459 if parts.len() != 2 {
460 return Err(SudokuParseError::WrongNumberOfParts);
461 }
462
463 let (block_width, block_height) = parse_dimensions(parts[0])?;
464
465 if let Ok(mut grid) = SudokuGrid::new(block_width, block_height) {
466 let size = grid.size();
467 let numbers: Vec<&str> = parts[1].split(',').collect();
468
469 if numbers.len() != size * size {
470 return Err(SudokuParseError::WrongNumberOfCells);
471 }
472
473 for (i, number_str) in numbers.iter().enumerate() {
474 let number_str = number_str.trim();
475
476 if number_str.is_empty() {
477 continue;
478 }
479
480 let number = number_str.parse::<usize>()?;
481
482 if number == 0 || number > size {
483 return Err(SudokuParseError::InvalidNumber);
484 }
485
486 grid.cells[i] = Some(number);
487 }
488
489 Ok(grid)
490 }
491 else {
492 Err(SudokuParseError::InvalidDimensions)
493 }
494 }
495
496 /// Converts the grid into a `String` in a way that is consistent with
497 /// [SudokuGrid::parse](#method.parse). That is, a grid that is converted
498 /// to a string and parsed again will not change, as is illustrated below.
499 ///
500 /// ```
501 /// use sudoku_variants::SudokuGrid;
502 ///
503 /// let mut grid = SudokuGrid::new(3, 2).unwrap();
504 ///
505 /// // Just some arbitrary changes to create some content.
506 /// grid.set_cell(1, 1, 4).unwrap();
507 /// grid.set_cell(1, 2, 5).unwrap();
508 ///
509 /// let grid_str = grid.to_parseable_string();
510 /// let grid_parsed = SudokuGrid::parse(grid_str.as_str()).unwrap();
511 /// assert_eq!(grid, grid_parsed);
512 /// ```
513 pub fn to_parseable_string(&self) -> String {
514 let mut s = format!("{}x{};", self.block_width, self.block_height);
515 let cells = self.cells.iter()
516 .map(to_string)
517 .collect::<Vec<String>>()
518 .join(",");
519 s.push_str(cells.as_str());
520 s
521 }
522
523 /// Gets the width (number of columns) of one sub-block of the grid. To
524 /// ensure a square grid, this is also the number of blocks that compose
525 /// the grid vertically.
526 pub fn block_width(&self) -> usize {
527 self.block_width
528 }
529
530 /// Gets the height (number of rows) of one sub-block of the grid. To
531 /// ensure a square grid, this is also the number of blocks that compose
532 /// the grid horizontally.
533 pub fn block_height(&self) -> usize {
534 self.block_height
535 }
536
537 /// Gets the total size of the grid on one axis (horizontally or
538 /// vertically). Since a square grid is enforced at construction time, this
539 /// is guaranteed to be valid for both axes.
540 pub fn size(&self) -> usize {
541 self.size
542 }
543
544 /// Gets the content of the cell at the specified position.
545 ///
546 /// # Arguments
547 ///
548 /// * `column`: The column (x-coordinate) of the desired cell. Must be in
549 /// the range `[0, size[`.
550 /// * `row`: The row (y-coordinate) of the desired cell. Must be in the
551 /// range `[0, size[`.
552 ///
553 /// # Errors
554 ///
555 /// If either `column` or `row` are not in the specified range. In that
556 /// case, `SudokuError::OutOfBounds` is returned.
557 pub fn get_cell(&self, column: usize, row: usize)
558 -> SudokuResult<Option<usize>> {
559 let size = self.size();
560
561 if column >= size || row >= size {
562 Err(SudokuError::OutOfBounds)
563 }
564 else {
565 let index = index(column, row, size);
566 Ok(self.cells[index])
567 }
568 }
569
570 /// Indicates whether the cell at the specified position has the given
571 /// number. This will return `false` if there is a different number in that
572 /// cell or it is empty.
573 ///
574 /// # Arguments
575 ///
576 /// * `column`: The column (x-coordinate) of the checked cell. Must be in
577 /// the range `[0, size[`.
578 /// * `row`: The row (y-coordinate) of the checked cell. Must be in the
579 /// range `[0, size[`.
580 /// * `number`: The number to check whether it is in the specified cell. If
581 /// it is *not* in the range `[1, size]`, `false` will always be returned.
582 ///
583 /// # Errors
584 ///
585 /// If either `column` or `row` are not in the specified range. In that
586 /// case, `SudokuError::OutOfBounds` is returned.
587 pub fn has_number(&self, column: usize, row: usize, number: usize)
588 -> SudokuResult<bool> {
589 if let Some(content) = self.get_cell(column, row)? {
590 Ok(number == content)
591 }
592 else {
593 Ok(false)
594 }
595 }
596
597 /// Sets the content of the cell at the specified position to the given
598 /// number. If the cell was not empty, the old number will be overwritten.
599 ///
600 /// # Arguments
601 ///
602 /// * `column`: The column (x-coordinate) of the assigned cell. Must be in
603 /// the range `[0, size[`.
604 /// * `row`: The row (y-coordinate) of the assigned cell. Must be in the
605 /// range `[0, size[`.
606 /// * `number`: The number to assign to the specified cell. Must be in the
607 /// range `[1, size]`.
608 ///
609 /// # Errors
610 ///
611 /// * `SudokuError::OutOfBounds` If either `column` or `row` are not in the
612 /// specified range.
613 /// * `SudokuError::InvalidNumber` If `number` is not in the specified
614 /// range.
615 pub fn set_cell(&mut self, column: usize, row: usize, number: usize)
616 -> SudokuResult<()> {
617 let size = self.size();
618
619 if column >= size || row >= size {
620 return Err(SudokuError::OutOfBounds);
621 }
622
623 if number == 0 || number > size {
624 return Err(SudokuError::InvalidNumber);
625 }
626
627 let index = index(column, row, size);
628 self.cells[index] = Some(number);
629 Ok(())
630 }
631
632 /// Clears the content of the cell at the specified position, that is, if
633 /// contains a number, that number is removed. If the cell is already
634 /// empty, it will be left that way.
635 ///
636 /// # Arguments
637 ///
638 /// * `column`: The column (x-coordinate) of the cleared cell. Must be in
639 /// the range `[0, size[`.
640 /// * `row`: The row (y-coordinate) of the cleared cell. Must be in the
641 /// range `[0, size[`.
642 ///
643 /// # Errors
644 ///
645 /// If either `column` or `row` are not in the specified range. In that
646 /// case, `SudokuError::OutOfBounds` is returned.
647 pub fn clear_cell(&mut self, column: usize, row: usize)
648 -> SudokuResult<()> {
649 let size = self.size();
650
651 if column >= size || row >= size {
652 return Err(SudokuError::OutOfBounds);
653 }
654
655 let index = index(column, row, size);
656 self.cells[index] = None;
657 Ok(())
658 }
659
660 fn verify_dimensions(&self, other: &SudokuGrid) -> SudokuResult<()> {
661 if self.block_width != other.block_width ||
662 self.block_height != other.block_height {
663 Err(SudokuError::InvalidDimensions)
664 }
665 else {
666 Ok(())
667 }
668 }
669
670 /// Assigns the content of another grid to this one, i.e., changes the
671 /// cells in this grid to the state in `other`. The other grid must have
672 /// the same dimensions as this one.
673 ///
674 /// # Errors
675 ///
676 /// If the dimensions are not the same. In that case,
677 /// `SudokuError::InvalidDimensions` is returned.
678 pub fn assign(&mut self, other: &SudokuGrid) -> SudokuResult<()> {
679 self.verify_dimensions(other)?;
680 self.cells.copy_from_slice(&other.cells);
681 Ok(())
682 }
683
684 /// Counts the number of clues given by this grid. This is the number of
685 /// non-empty cells. While on average Sudoku with less clues are harder,
686 /// this is *not* a reliable measure of difficulty.
687 pub fn count_clues(&self) -> usize {
688 let size = self.size();
689 let mut clues = 0usize;
690
691 for row in 0..size {
692 for column in 0..size {
693 if self.get_cell(column, row).unwrap().is_some() {
694 clues += 1;
695 }
696 }
697 }
698
699 clues
700 }
701
702 /// Indicates whether this grid is full, i.e. every cell is filled with a
703 /// number. In this case, [SudokuGrid::count_clues] returns the square of
704 /// [SudokuGrid::size].
705 pub fn is_full(&self) -> bool {
706 !self.cells.iter().any(|c| c == &None)
707 }
708
709 /// Indicates whether this grid is empty, i.e. no cell is filled with a
710 /// number. In this case, [SudokuGrid::count_clues] returns 0.
711 pub fn is_empty(&self) -> bool {
712 self.cells.iter().all(|c| c == &None)
713 }
714
715 /// Indicates whether this grid configuration is a subset of another one.
716 /// That is, all cells filled in this grid with some number must be filled
717 /// in `other` with the same number. If this condition is met, `true` is
718 /// returned, and `false` otherwise.
719 ///
720 /// # Errors
721 ///
722 /// If the dimensions of this and the `other` grid are not the same. In
723 /// that case, `SudokuError::InvalidDimensions` is returned.
724 pub fn is_subset(&self, other: &SudokuGrid) -> SudokuResult<bool> {
725 self.verify_dimensions(other)?;
726 Ok(self.cells.iter()
727 .zip(other.cells.iter())
728 .all(|(self_cell, other_cell)| {
729 match self_cell {
730 Some(self_number) =>
731 match other_cell {
732 Some(other_number) => self_number == other_number,
733 None => false
734 },
735 None => true
736 }
737 }))
738 }
739
740 /// Indicates whether this grid configuration is a superset of another one.
741 /// That is, all cells filled in the `other `grid with some number must be
742 /// filled in this one with the same number. If this condition is met,
743 /// `true` is returned, and `false` otherwise.
744 ///
745 /// # Errors
746 ///
747 /// If the dimensions of this and the `other` grid are not the same. In
748 /// that case, `SudokuError::InvalidDimensions` is returned.
749 pub fn is_superset(&self, other: &SudokuGrid) -> SudokuResult<bool> {
750 other.is_subset(self)
751 }
752
753 /// Gets a reference to the vector which holds the cells. They are in
754 /// left-to-right, top-to-bottom order, where rows are together.
755 pub fn cells(&self) -> &Vec<Option<usize>> {
756 &self.cells
757 }
758
759 /// Gets a mutable reference to the vector which holds the cells. They are
760 /// in left-to-right, top-to-bottom order, where rows are together.
761 pub fn cells_mut(&mut self) -> &mut Vec<Option<usize>> {
762 &mut self.cells
763 }
764}
765
766/// A Sudoku represents a grid of numbers with an associated constraint. The
767/// numbers may or may not fulfill the constraint, but there is a method to
768/// check it.
769///
770/// There is no guarantee that the Sudoku is uniquely solveable or even
771/// solveable at all, however there are ways to check that (see the [solver]
772/// module).
773#[derive(Clone)]
774pub struct Sudoku<C: Constraint + Clone> {
775 grid: SudokuGrid,
776 constraint: C
777}
778
779impl<C: Constraint + Clone> Sudoku<C> {
780
781 /// Creates a new Sudoku with the provided constraint and an empty grid
782 /// of the given dimensions. The total width and height of the grid will be
783 /// equal to the product of `block_width` and `block_height`.
784 ///
785 /// # Arguments
786 ///
787 /// * `block_width`: The horizontal dimension of one sub-block of the grid.
788 /// To ensure a square grid, this is also the number of blocks that compose
789 /// the grid vertically. For an ordinary Sudoku grid, this is 3. Must be
790 /// greater than 0.
791 /// * `block_height`: The vertical dimension of one sub-block of the grid.
792 /// To ensure a square grid, this is also the number of blocks that compose
793 /// the grid horizontally. For an ordinary Sudoku grid, this is 3. Must be
794 /// greater than 0.
795 /// * `constraint`: The constraint which is checked by this Sudoku. Grid
796 /// configurations which violate this constraint will be seen as invalid by
797 /// [Sudoku::is_valid()].
798 ///
799 /// # Errors
800 ///
801 /// If `block_width` or `block_height` is invalid (zero).
802 pub fn new_empty(block_width: usize, block_height: usize, constraint: C)
803 -> SudokuResult<Sudoku<C>> {
804 Ok(Sudoku {
805 grid: SudokuGrid::new(block_width, block_height)?,
806 constraint
807 })
808 }
809
810 /// Creats a new Sudoku with the provided constraint and a given grid,
811 /// which may already contain some numbers. Note that it is *not* checked
812 /// whether the given grid fulfills the constraint - it is perfectly legal
813 /// to create an invalid Sudoku here.
814 ///
815 /// # Arguments
816 ///
817 /// * `grid`: The initial [SudokuGrid] which contains the numbers with
818 /// which the Sudoku is filled.
819 /// * `constraint`: The constraint which is checked by this Sudoku. Grid
820 /// configurations which violate this constraint will be seen as invalid by
821 /// [Sudoku::is_valid]).
822 pub fn new_with_grid(grid: SudokuGrid, constraint: C) -> Sudoku<C> {
823 Sudoku {
824 grid,
825 constraint
826 }
827 }
828
829 /// Parses the code into a [SudokuGrid] using [SudokuGrid::parse] and wraps
830 /// the result in a Sudoku with the given constraint. Note that it is not
831 /// required that the code matches the constraint. It is perfectly legal to
832 /// parse an invalid Sudoku.
833 ///
834 /// # Arguments
835 ///
836 /// * `code`: The code that specifies the grid. See [SudokuGrid::parse] for
837 /// a language specification.
838 /// * `constraint`: The constraint which is checked by this Sudoku. Grid
839 /// configurations which violate this constraint will be seen as invalid by
840 /// [Sudoku::is_valid].
841 ///
842 /// # Errors
843 ///
844 /// If the parsing fails. See [SudokuGrid::parse] for further information.
845 pub fn parse(code: &str, constraint: C) -> SudokuParseResult<Sudoku<C>> {
846 Ok(Sudoku::new_with_grid(SudokuGrid::parse(code)?, constraint))
847 }
848
849 /// Gets a reference to the `SudokuGrid` of this Sudoku.
850 pub fn grid(&self) -> &SudokuGrid {
851 &self.grid
852 }
853
854 /// Gets a mutable reference to the `SudokuGrid` of this Sudoku.
855 pub fn grid_mut(&mut self) -> &mut SudokuGrid {
856 &mut self.grid
857 }
858
859 /// Gets a reference to the `Constraint` of this Sudoku.
860 pub fn constraint(&self) -> &C {
861 &self.constraint
862 }
863
864 /// Indicates whether the entire grid matches the constraint.
865 pub fn is_valid(&self) -> bool {
866 self.constraint.check(&self.grid)
867 }
868
869 /// Indicates whether the cell at the given location matches the
870 /// constraint. That is, if the specified cell violates the constraint,
871 /// `false` is returned, and `true` otherwise.
872 ///
873 /// # Arguments
874 ///
875 /// * `column`: The column (x-coordinate) of the checked cell. Must be in
876 /// the range `[0, size[`.
877 /// * `row`: The row (y-coordinate) of the checked cell. Must be in the
878 /// range `[0, size[`.
879 pub fn is_valid_cell(&self, column: usize, row: usize)
880 -> SudokuResult<bool> {
881 let size = self.grid.size();
882
883 if column >= size || row >= size {
884 Err(SudokuError::OutOfBounds)
885 }
886 else {
887 Ok(self.constraint.check_cell(&self.grid, column, row))
888 }
889 }
890
891 /// Indicates whether the given number would be valid in the cell at the
892 /// given location. That is, if the number violated the constraint, `false`
893 /// is returned, and `true` otherwise.
894 ///
895 /// # Arguments
896 ///
897 /// * `column`: The column (x-coordinate) of the checked cell. Must be in
898 /// the range `[0, size[`.
899 /// * `row`: The row (y-coordinate) of the checked cell. Must be in the
900 /// range `[0, size[`.
901 /// * `number`: The number to check whether it is valid in the given cell.
902 ///
903 /// # Errors
904 ///
905 /// * `SudokuError::OutOfBounds` If either `column` or `row` are not in the
906 /// specified range.
907 /// * `SudokuError::InvalidNumber` If `number` is not in the specified
908 /// range.
909 pub fn is_valid_number(&self, column: usize, row: usize, number: usize)
910 -> SudokuResult<bool> {
911 let size = self.grid.size();
912
913 if column >= size || row >= size {
914 Err(SudokuError::OutOfBounds)
915 }
916 else if number == 0 || number > size {
917 Err(SudokuError::InvalidNumber)
918 }
919 else {
920 Ok(self.constraint.check_number(&self.grid, column, row, number))
921 }
922 }
923
924 /// Indicates whether the given [SudokuGrid] is a valid solution to this
925 /// puzzle. That is the case if all digits from this Sudoku can be found in
926 /// the `solution`, it matches the constraint of this Sudoku, and it is
927 /// full.
928 ///
929 /// # Errors
930 ///
931 /// If the dimensions of this Sudoku's grid and the `solution` grid are not
932 /// the same. In that case, `SudokuError::InvalidDimensions` is returned.
933 pub fn is_valid_solution(&self, solution: &SudokuGrid)
934 -> SudokuResult<bool> {
935 Ok(self.grid.is_subset(solution)? &&
936 self.constraint.check(solution) &&
937 solution.is_full())
938 }
939}
940
941#[cfg(test)]
942mod tests {
943
944 use super::*;
945
946 use crate::constraint::DefaultConstraint;
947
948 #[test]
949 fn parse_ok() {
950 let grid_res = SudokuGrid::parse("2x2; 1,,,2, ,3,,4, ,2,,, 3,,,");
951
952 if let Ok(grid) = grid_res {
953 assert_eq!(2, grid.block_width());
954 assert_eq!(2, grid.block_height());
955 assert_eq!(Some(1), grid.get_cell(0, 0).unwrap());
956 assert_eq!(None, grid.get_cell(1, 0).unwrap());
957 assert_eq!(None, grid.get_cell(2, 0).unwrap());
958 assert_eq!(Some(2), grid.get_cell(3, 0).unwrap());
959 assert_eq!(None, grid.get_cell(0, 1).unwrap());
960 assert_eq!(Some(3), grid.get_cell(1, 1).unwrap());
961 assert_eq!(None, grid.get_cell(2, 1).unwrap());
962 assert_eq!(Some(4), grid.get_cell(3, 1).unwrap());
963 assert_eq!(None, grid.get_cell(0, 2).unwrap());
964 assert_eq!(Some(2), grid.get_cell(1, 2).unwrap());
965 assert_eq!(None, grid.get_cell(2, 2).unwrap());
966 assert_eq!(None, grid.get_cell(3, 2).unwrap());
967 assert_eq!(Some(3), grid.get_cell(0, 3).unwrap());
968 assert_eq!(None, grid.get_cell(1, 3).unwrap());
969 assert_eq!(None, grid.get_cell(2, 3).unwrap());
970 assert_eq!(None, grid.get_cell(3, 3).unwrap());
971 }
972 else {
973 panic!("Parsing valid grid failed.");
974 }
975 }
976
977 #[test]
978 fn parse_malformed_dimensions() {
979 assert_eq!(Err(SudokuParseError::MalformedDimensions),
980 SudokuGrid::parse("2x2x2;,,,,,,,,,,,,,,,"));
981 }
982
983 #[test]
984 fn parse_invalid_dimensions() {
985 assert_eq!(Err(SudokuParseError::InvalidDimensions),
986 SudokuGrid::parse("2x0;,"));
987 }
988
989 #[test]
990 fn parse_wrong_number_of_parts() {
991 assert_eq!(Err(SudokuParseError::WrongNumberOfParts),
992 SudokuGrid::parse("2x2;,,,,,,,,,,,,,,,;whatever"));
993 }
994
995 #[test]
996 fn parse_number_format_error() {
997 assert_eq!(Err(SudokuParseError::NumberFormatError),
998 SudokuGrid::parse("2x#;,"));
999 }
1000
1001 #[test]
1002 fn parse_invalid_number() {
1003 assert_eq!(Err(SudokuParseError::InvalidNumber),
1004 SudokuGrid::parse("2x2;,,,4,,,5,,,,,,,,,"));
1005 }
1006
1007 #[test]
1008 fn parse_wrong_number_of_cells() {
1009 assert_eq!(Err(SudokuParseError::WrongNumberOfCells),
1010 SudokuGrid::parse("2x2;1,2,3,4,1,2,3,4,1,2,3,4,1,2,3"));
1011 assert_eq!(Err(SudokuParseError::WrongNumberOfCells),
1012 SudokuGrid::parse("2x2;1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1"));
1013 }
1014
1015 #[test]
1016 fn to_parseable_string() {
1017 let mut grid = SudokuGrid::new(2, 2).unwrap();
1018
1019 assert_eq!("2x2;,,,,,,,,,,,,,,,", grid.to_parseable_string().as_str());
1020
1021 grid.set_cell(0, 0, 1).unwrap();
1022 grid.set_cell(1, 1, 2).unwrap();
1023 grid.set_cell(2, 2, 3).unwrap();
1024 grid.set_cell(3, 3, 4).unwrap();
1025
1026 assert_eq!("2x2;1,,,,,2,,,,,3,,,,,4",
1027 grid.to_parseable_string().as_str());
1028
1029 let grid = SudokuGrid::new(4, 1).unwrap();
1030
1031 assert_eq!("4x1;,,,,,,,,,,,,,,,", grid.to_parseable_string().as_str());
1032 }
1033
1034 #[test]
1035 fn size() {
1036 let grid1x1 = SudokuGrid::new(1, 1).unwrap();
1037 let grid3x2 = SudokuGrid::new(3, 2).unwrap();
1038 let grid3x4 = SudokuGrid::new(3, 4).unwrap();
1039 assert_eq!(1, grid1x1.size());
1040 assert_eq!(6, grid3x2.size());
1041 assert_eq!(12, grid3x4.size());
1042 }
1043
1044 #[test]
1045 fn count_clues_and_empty_and_full() {
1046 let empty = SudokuGrid::parse("2x2;,,,,,,,,,,,,,,,").unwrap();
1047 let partial = SudokuGrid::parse("2x2;1,,3,2,4,,,,,,,,,,1,").unwrap();
1048 let full = SudokuGrid::parse("2x2;2,3,4,1,1,4,2,3,4,1,3,2,3,2,1,4")
1049 .unwrap();
1050
1051 assert_eq!(0, empty.count_clues());
1052 assert_eq!(5, partial.count_clues());
1053 assert_eq!(16, full.count_clues());
1054
1055 assert!(empty.is_empty());
1056 assert!(!partial.is_empty());
1057 assert!(!full.is_empty());
1058
1059 assert!(!empty.is_full());
1060 assert!(!partial.is_full());
1061 assert!(full.is_full());
1062 }
1063
1064 fn assert_subset_relation(a: &SudokuGrid, b: &SudokuGrid, a_subset_b: bool,
1065 b_subset_a: bool) {
1066 assert!(a.is_subset(b).unwrap() == a_subset_b);
1067 assert!(a.is_superset(b).unwrap() == b_subset_a);
1068 assert!(b.is_subset(a).unwrap() == b_subset_a);
1069 assert!(b.is_superset(a).unwrap() == a_subset_b);
1070 }
1071
1072 fn assert_true_subset(a: &SudokuGrid, b: &SudokuGrid) {
1073 assert_subset_relation(a, b, true, false)
1074 }
1075
1076 fn assert_equal_set(a: &SudokuGrid, b: &SudokuGrid) {
1077 assert_subset_relation(a, b, true, true)
1078 }
1079
1080 fn assert_unrelated_set(a: &SudokuGrid, b: &SudokuGrid) {
1081 assert_subset_relation(a, b, false, false)
1082 }
1083
1084 #[test]
1085 fn empty_is_subset() {
1086 let empty = SudokuGrid::new(2, 2).unwrap();
1087 let non_empty = SudokuGrid::parse("2x2;1,,,,,,,,,,,,,,,").unwrap();
1088 let full = SudokuGrid::parse("2x2;1,2,3,4,3,4,1,2,2,3,1,4,4,1,3,2")
1089 .unwrap();
1090
1091 assert_equal_set(&empty, &empty);
1092 assert_true_subset(&empty, &non_empty);
1093 assert_true_subset(&empty, &full);
1094 }
1095
1096 #[test]
1097 fn equal_grids_subsets() {
1098 let g = SudokuGrid::parse("2x2;1,,3,,2,,,,4,,4,3,,,,2").unwrap();
1099 assert_equal_set(&g, &g);
1100 }
1101
1102 #[test]
1103 fn true_subset() {
1104 let g1 = SudokuGrid::parse("2x2;1,,3,,2,,,,4,,4,3,,,,2").unwrap();
1105 let g2 = SudokuGrid::parse("2x2;1,2,3,,2,,3,,4,,4,3,,,1,2").unwrap();
1106 assert_true_subset(&g1, &g2);
1107 }
1108
1109 #[test]
1110 fn unrelated_grids_not_subsets() {
1111 // g1 and g2 differ in the third digit (3 in g1, 4 in g2)
1112 let g1 = SudokuGrid::parse("2x2;1,,3,,2,,,,4,,4,3,,,,2").unwrap();
1113 let g2 = SudokuGrid::parse("2x2;1,2,4,,2,,3,,4,,4,3,,,1,2").unwrap();
1114 assert_unrelated_set(&g1, &g2);
1115 }
1116
1117 fn solution_example_sudoku() -> Sudoku<DefaultConstraint> {
1118 Sudoku::parse("2x2;\
1119 2, , , ,\
1120 , ,3, ,\
1121 , , ,4,\
1122 ,2, , ", DefaultConstraint).unwrap()
1123 }
1124
1125 #[test]
1126 fn solution_not_full() {
1127 let sudoku = solution_example_sudoku();
1128 let solution = SudokuGrid::parse("2x2;\
1129 2,3,4,1,\
1130 1,4,3, ,\
1131 3,1,2,4,\
1132 4,2,1,3").unwrap();
1133 assert!(!sudoku.is_valid_solution(&solution).unwrap());
1134 }
1135
1136 #[test]
1137 fn solution_not_superset() {
1138 let sudoku = solution_example_sudoku();
1139 let solution = SudokuGrid::parse("2x2;\
1140 2,3,4,1,\
1141 1,4,3,2,\
1142 3,2,1,4,\
1143 4,1,2,3").unwrap();
1144 assert!(!sudoku.is_valid_solution(&solution).unwrap());
1145 }
1146
1147 #[test]
1148 fn solution_violates_constraint() {
1149 let sudoku = solution_example_sudoku();
1150 let solution = SudokuGrid::parse("2x2;\
1151 2,3,4,1,\
1152 1,3,3,2,\
1153 3,1,2,4,\
1154 4,2,1,3").unwrap();
1155 assert!(!sudoku.is_valid_solution(&solution).unwrap());
1156 }
1157
1158 #[test]
1159 fn solution_correct() {
1160 let sudoku = solution_example_sudoku();
1161 let solution = SudokuGrid::parse("2x2;\
1162 2,3,4,1,\
1163 1,4,3,2,\
1164 3,1,2,4,\
1165 4,2,1,3").unwrap();
1166 assert!(sudoku.is_valid_solution(&solution).unwrap());
1167 }
1168}