From 2f20aa4086430518a5e3d3e5e5383387c64dcd96 Mon Sep 17 00:00:00 2001 From: David Wang Date: Tue, 21 Mar 2017 08:10:14 +1100 Subject: [PATCH] Add test: Takuzu (A.K.A. Binairo). takuzu_grid1: 12 guesses. takuzu_grid2: 0 guesses. takuzu_grid3: 0 guesses. takuzu_grid4: 20724 guesses (all solutions). --- tests/takuzu.rs | 266 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 tests/takuzu.rs diff --git a/tests/takuzu.rs b/tests/takuzu.rs new file mode 100644 index 0000000..5a342e0 --- /dev/null +++ b/tests/takuzu.rs @@ -0,0 +1,266 @@ +//! Takuzu (A.K.A. Binairo). +//! +//! https://en.wikipedia.org/wiki/Takuzu + +extern crate puzzle_solver; + +use std::iter; +use std::rc::Rc; +use puzzle_solver::*; + +const X: Val = -1; + +/*--------------------------------------------------------------*/ + +struct BinaryRepr { + // value = sum_i 2^i bits[i] + value: VarToken, + bits: Vec, +} + +impl Constraint for BinaryRepr { + fn vars<'a>(&'a self) -> Box + 'a> { + Box::new(iter::once(&self.value).chain(&self.bits)) + } + + fn on_assigned(&self, search: &mut PuzzleSearch, var: VarToken, val: Val) + -> PsResult<()> { + if var == self.value { + let mut val = val; + for &var in self.bits.iter() { + try!(search.set_candidate(var, val & 1)); + val = val >> 1; + } + } else if let Some(bitpos) = self.bits.iter().position(|&v| v == var) { + let bit = 1 << bitpos; + let discard = search.get_unassigned(self.value) + .filter(|c| c & bit != val * bit) + .collect::>(); + + for c in discard.into_iter() { + try!(search.remove_candidate(self.value, c)); + } + } + + Ok(()) + } + + fn substitute(&self, _from: VarToken, _to: VarToken) + -> PsResult> { + unimplemented!(); + } +} + +/*--------------------------------------------------------------*/ + +fn make_sums(size: usize) -> Vec { + let mut vec = Vec::new(); + + for val in 0..(1 << size) { + let mut count = 0; + let mut v = val as usize; + + while v > 0 { + count = count + (v & 1); + v = v >> 1; + } + + if count == size / 2 { + vec.push(val); + } + } + + vec +} + +fn make_takuzu(puzzle: &Vec>) -> (Puzzle, Vec>) { + let height = puzzle.len(); + assert!(height > 0 && height % 2 == 0); + let width = puzzle[0].len(); + assert!(width > 0 && width % 2 == 0); + + let row_candidates = make_sums(height); + let col_candidates = make_sums(width); + + let mut sys = Puzzle::new(); + let vars = sys.new_vars_with_candidates_2d(width, height, &[0,1]); + let row_values = sys.new_vars_with_candidates_1d(height, &row_candidates); + let col_values = sys.new_vars_with_candidates_1d(width, &col_candidates); + + for y in 0..height { + let total = (height as Val) / 2; + sys.equals(total, vars[y].iter().fold(LinExpr::from(0), |sum, &x| sum + x)); + sys.add_constraint(BinaryRepr { + value: row_values[y], + bits: vars[y].clone(), + }); + } + + for x in 0..width { + let total = (width as Val) / 2; + sys.equals(total, vars.iter().fold(LinExpr::from(0), |sum, row| sum + row[x])); + sys.add_constraint(BinaryRepr { + value: col_values[x], + bits: (0..height).map(|y| vars[y][x]).collect(), + }); + } + + // No three in a row, i.e. not: 000, 111. + for y in 0..height { + for window in vars[y].windows(3) { + let disjunction = sys.new_var_with_candidates(&[1,2]); + sys.equals(window[0] + window[1] + window[2], disjunction); + } + } + + for x in 0..width { + for y in 0..(height - 2) { + let disjunction = sys.new_var_with_candidates(&[1,2]); + sys.equals(vars[y + 0][x] + vars[y + 1][x] + vars[y + 2][x], disjunction); + } + } + + sys.all_different(&row_values); + sys.all_different(&col_values); + + for y in 0..height { + for x in 0..width { + if puzzle[y][x] != X { + sys.set_value(vars[y][x], puzzle[y][x]); + } + } + } + + (sys, vars) +} + +fn print_takuzu(dict: &Solution, vars: &Vec>) { + for row in vars.iter() { + let sum = row.iter().fold(0, |sum, &var| 2 * sum + dict[var]); + for &var in row.iter() { + print!("{}", dict[var]); + } + println!(" = {}", sum); + } +} + +fn verify_takuzu(dict: &Solution, vars: &Vec>, expected: &[Val]) { + for (row, &expected) in vars.iter().zip(expected) { + let sum = row.iter().fold(0, |sum, &var| 2 * sum + dict[var]); + assert_eq!(sum, expected); + } +} + +#[test] +fn takuzu_grid1() { + let puzzle = vec![ + vec![ X,1,0,X,X,X ], + vec![ 1,X,X,X,0,X ], + vec![ X,X,0,X,X,X ], + vec![ 1,1,X,X,1,0 ], + vec![ X,X,X,X,0,X ], + vec![ X,X,X,X,X,X ] ]; + + let (mut sys, vars) = make_takuzu(&puzzle); + let solutions = sys.solve_all(); + assert_eq!(solutions.len(), 6); + + print_takuzu(&solutions[0], &vars); + println!("takuzu_grid1: {} guesses", sys.num_guesses()); +} + +#[test] +fn takuzu_grid2() { + let puzzle = vec![ + vec![ 0,X,X,X,X,1,1,X,X,0,X,X ], + vec![ X,X,X,1,X,X,X,0,X,X,X,X ], + vec![ X,0,X,X,X,X,1,X,X,X,0,0 ], + vec![ 1,X,X,1,X,X,1,1,X,X,X,1 ], + vec![ X,X,X,X,X,X,X,X,X,1,X,X ], + vec![ 0,X,0,X,X,X,1,X,X,X,X,X ], + vec![ X,X,X,X,0,X,X,X,X,X,X,X ], + vec![ X,X,X,X,0,1,X,0,X,X,X,X ], + vec![ X,X,0,0,X,X,0,X,0,X,X,0 ], + vec![ X,X,X,X,X,1,X,X,X,X,1,X ], + vec![ 1,0,X,0,X,X,X,X,X,X,X,X ], + vec![ X,X,1,X,X,X,X,1,X,X,0,0 ] ]; + + let expected = [ + 0b_010101101001, + 0b_010101001011, + 0b_101010110100, + 0b_100100110011, + 0b_011011001100, + 0b_010010110011, + 0b_101100101010, + 0b_001101001101, + 0b_110010010110, + 0b_010101101010, + 0b_101010010101, + 0b_101011010100 ]; + + let (mut sys, vars) = make_takuzu(&puzzle); + let dict = sys.solve_unique().expect("solution"); + print_takuzu(&dict, &vars); + verify_takuzu(&dict, &vars, &expected); + println!("takuzu_grid2: {} guesses", sys.num_guesses()); +} + +#[test] +fn takuzu_grid3() { + let puzzle = vec![ + vec![ X,X,X,0,X,0,X,X,X,X,0,X ], + vec![ 1,X,X,X,X,X,X,1,X,X,X,1 ], + vec![ X,X,1,1,X,X,X,X,X,X,0,X ], + vec![ X,0,X,X,X,X,X,X,X,X,X,0 ], + vec![ X,X,X,0,X,X,1,1,0,X,X,X ], + vec![ 0,X,0,0,X,0,X,1,X,X,0,X ], + vec![ X,X,X,X,X,X,0,X,X,X,0,X ], + vec![ 1,X,1,X,0,X,X,X,X,X,X,X ], + vec![ X,X,X,X,X,X,1,0,1,X,0,X ], + vec![ X,1,X,X,0,X,X,X,X,0,0,X ], + vec![ X,X,X,1,X,X,X,0,X,X,X,X ], + vec![ X,X,X,X,X,1,1,X,X,1,X,X ] ]; + + let expected = [ + 0b_101010011001, + 0b_110010010011, + 0b_001101101100, + 0b_101101001010, + 0b_010010110011, + 0b_010010110101, + 0b_101101001100, + 0b_101101010010, + 0b_010010101101, + 0b_011001011001, + 0b_100110100110, + 0b_010101100110 ]; + + let (mut sys, vars) = make_takuzu(&puzzle); + let dict = sys.solve_unique().expect("solution"); + print_takuzu(&dict, &vars); + verify_takuzu(&dict, &vars, &expected); + println!("takuzu_grid3: {} guesses", sys.num_guesses()); +} + +#[test] +fn takuzu_grid4() { + let puzzle = vec![ + vec![ X,X,X,X,X,1,1,X,X,0,X,X ], + vec![ X,X,X,1,X,X,X,0,X,X,X,X ], + vec![ X,0,X,X,X,X,1,X,X,X,0,0 ], + vec![ X,X,X,1,X,X,1,1,X,X,X,1 ], + vec![ X,X,X,X,X,X,X,X,X,1,X,X ], + vec![ X,X,0,X,X,X,1,X,X,X,X,X ], + vec![ X,X,X,X,0,X,X,X,X,X,X,X ], + vec![ X,X,X,X,0,1,X,0,X,X,X,X ], + vec![ X,X,0,0,X,X,0,X,0,X,X,0 ], + vec![ X,X,X,X,X,1,X,X,X,X,1,X ], + vec![ X,X,X,0,X,X,X,X,X,X,X,X ], + vec![ X,X,1,X,X,X,X,1,X,X,0,0 ] ]; + + let (mut sys, vars) = make_takuzu(&puzzle); + let dict = &sys.solve_any().expect("solution"); + print_takuzu(&dict, &vars); + println!("takuzu_grid4: {} guesses", sys.num_guesses()); +}