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}