Add test: Nonogram (A.K.A. Hanjie, Picross).
nonogram_wikipedia: 10 guesses.
This commit is contained in:
parent
c7c0bfa128
commit
b22c6466ca
297
tests/nonogram.rs
Normal file
297
tests/nonogram.rs
Normal file
@ -0,0 +1,297 @@
|
||||
//! Nonogram (A.K.A. Hanjie, Picross).
|
||||
//!
|
||||
//! https://en.wikipedia.org/wiki/Nonogram
|
||||
|
||||
extern crate puzzle_solver;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use puzzle_solver::*;
|
||||
|
||||
const WIDTH: usize = 20;
|
||||
const HEIGHT: usize = 20;
|
||||
type Board = [[Val; WIDTH]; HEIGHT];
|
||||
|
||||
/*--------------------------------------------------------------*/
|
||||
|
||||
const FLAG_OFF: Val = 1;
|
||||
const FLAG_ON: Val = 2;
|
||||
const FLAG_UNKNOWN: Val = 4;
|
||||
|
||||
struct Nonogram {
|
||||
vars: Vec<VarToken>,
|
||||
rule: Vec<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy)]
|
||||
enum AutoFillResult {
|
||||
// Too many solutions, no need to find any more
|
||||
SearchEnded,
|
||||
|
||||
// Descendant found a solution.
|
||||
// Backtrack and look for other solutions.
|
||||
SolutionFound,
|
||||
|
||||
// Some earlier choices lead to a conflict.
|
||||
// Backtrack and look for other solutions.
|
||||
Conflict,
|
||||
}
|
||||
|
||||
impl Nonogram {
|
||||
fn new(vars: &Vec<VarToken>, rule: &Vec<usize>) -> Self {
|
||||
Nonogram {
|
||||
vars: vars.clone(),
|
||||
rule: rule.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn autofill(&self,
|
||||
trial: &mut Vec<Val>, pos: usize, rule_idx: usize,
|
||||
accum: &mut Vec<Val>,
|
||||
cache: &mut HashMap<(usize, usize), AutoFillResult>)
|
||||
-> AutoFillResult {
|
||||
assert!(pos <= trial.len() && rule_idx <= self.rule.len());
|
||||
let key = (pos, rule_idx);
|
||||
|
||||
// Base case.
|
||||
if pos == trial.len() || cache.contains_key(&key) {
|
||||
if pos == trial.len() && rule_idx != self.rule.len() {
|
||||
return AutoFillResult::Conflict;
|
||||
} else if let Some(&AutoFillResult::Conflict) = cache.get(&key) {
|
||||
return AutoFillResult::Conflict;
|
||||
}
|
||||
|
||||
for (a, t) in accum[0..pos].iter_mut().zip(trial) {
|
||||
*a = *a | *t;
|
||||
}
|
||||
|
||||
if accum.iter().all(|&t|
|
||||
t < FLAG_UNKNOWN || t == FLAG_UNKNOWN | FLAG_ON | FLAG_OFF) {
|
||||
return AutoFillResult::SearchEnded;
|
||||
}
|
||||
|
||||
return AutoFillResult::SolutionFound;
|
||||
}
|
||||
|
||||
// Not enough space.
|
||||
if rule_idx < self.rule.len() {
|
||||
let required_space
|
||||
= self.rule[rule_idx..].iter().sum::<usize>()
|
||||
+ (self.rule.len() - rule_idx - 1);
|
||||
|
||||
if pos + required_space > trial.len() {
|
||||
return AutoFillResult::Conflict;
|
||||
}
|
||||
}
|
||||
|
||||
let mut result = AutoFillResult::Conflict;
|
||||
|
||||
// Try empty.
|
||||
if trial[pos] != FLAG_ON {
|
||||
if trial[pos] >= FLAG_UNKNOWN {
|
||||
trial[pos] = FLAG_UNKNOWN | FLAG_OFF;
|
||||
}
|
||||
|
||||
match self.autofill(trial, pos + 1, rule_idx, accum, cache) {
|
||||
res@AutoFillResult::SearchEnded => return res,
|
||||
res@AutoFillResult::SolutionFound => result = res,
|
||||
AutoFillResult::Conflict => (),
|
||||
}
|
||||
}
|
||||
|
||||
// Try filled.
|
||||
if trial[pos] != FLAG_OFF
|
||||
&& rule_idx < self.rule.len()
|
||||
&& pos + self.rule[rule_idx] <= trial.len() {
|
||||
let mut ok = true;
|
||||
let mut pos = pos;
|
||||
|
||||
for _ in 0..self.rule[rule_idx] {
|
||||
if trial[pos] == FLAG_OFF {
|
||||
ok = false;
|
||||
break;
|
||||
} else if trial[pos] >= FLAG_UNKNOWN {
|
||||
trial[pos] = FLAG_UNKNOWN | FLAG_ON;
|
||||
}
|
||||
pos = pos + 1;
|
||||
}
|
||||
|
||||
if ok && pos < trial.len() {
|
||||
if trial[pos] == FLAG_ON {
|
||||
ok = false;
|
||||
} else if trial[pos] >= FLAG_UNKNOWN {
|
||||
trial[pos] = FLAG_UNKNOWN | FLAG_OFF;
|
||||
pos = pos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ok {
|
||||
match self.autofill(trial, pos, rule_idx + 1, accum, cache) {
|
||||
res@AutoFillResult::SearchEnded => return res,
|
||||
res@AutoFillResult::SolutionFound => result = res,
|
||||
AutoFillResult::Conflict => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache.insert(key, result);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Constraint for Nonogram {
|
||||
fn vars<'a>(&'a self) -> Box<Iterator<Item=&'a VarToken> + 'a> {
|
||||
Box::new(self.vars.iter())
|
||||
}
|
||||
|
||||
fn on_updated(&self, search: &mut PuzzleSearch) -> PsResult<()> {
|
||||
let mut trial = vec![0; self.vars.len()];
|
||||
for (mut pos, &var) in trial.iter_mut().zip(&self.vars) {
|
||||
*pos = match search.get_assigned(var) {
|
||||
Some(0) => FLAG_OFF,
|
||||
Some(1) => FLAG_ON,
|
||||
_ => FLAG_UNKNOWN,
|
||||
};
|
||||
}
|
||||
|
||||
let mut accum = trial.clone();
|
||||
let mut cache = HashMap::new();
|
||||
match self.autofill(&mut trial, 0, 0, &mut accum, &mut cache) {
|
||||
AutoFillResult::Conflict => return Err(()),
|
||||
AutoFillResult::SearchEnded => (),
|
||||
AutoFillResult::SolutionFound =>
|
||||
for idx in 0..self.vars.len() {
|
||||
if accum[idx] == FLAG_UNKNOWN | FLAG_OFF {
|
||||
try!(search.set_candidate(self.vars[idx], 0));
|
||||
} else if accum[idx] == FLAG_UNKNOWN | FLAG_ON {
|
||||
try!(search.set_candidate(self.vars[idx], 1));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn substitute(&self, _search: VarToken, _replace: VarToken)
|
||||
-> PsResult<Rc<Constraint>> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------*/
|
||||
|
||||
fn make_nonogram(rows: &[Vec<usize>], cols: &[Vec<usize>])
|
||||
-> (Puzzle, Vec<Vec<VarToken>>) {
|
||||
let mut sys = Puzzle::new();
|
||||
let (w, h) = (cols.len(), rows.len());
|
||||
let vars = sys.new_vars_with_candidates_2d(w, h, &[0,1]);
|
||||
|
||||
for y in 0..h {
|
||||
sys.add_constraint(Nonogram::new(&vars[y], &rows[y]));
|
||||
}
|
||||
|
||||
for x in 0..w {
|
||||
sys.add_constraint(Nonogram::new(
|
||||
&vars.iter().map(|row| row[x]).collect(),
|
||||
&cols[x]));
|
||||
}
|
||||
|
||||
(sys, vars)
|
||||
}
|
||||
|
||||
fn print_nonogram(dict: &Solution, vars: &Vec<Vec<VarToken>>) {
|
||||
for row in vars.iter() {
|
||||
for &var in row.iter() {
|
||||
print!("{}", if dict[var] == 1 { "*" } else { "." });
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_nonogram(dict: &Solution, vars: &Vec<Vec<VarToken>>, expected: &Board) {
|
||||
for y in 0..HEIGHT {
|
||||
for x in 0..WIDTH {
|
||||
assert_eq!(dict[vars[y][x]], expected[y][x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nonogram_wikipedia() {
|
||||
let puzzle_rows = [
|
||||
vec![3],
|
||||
vec![5],
|
||||
vec![3,1],
|
||||
vec![2,1],
|
||||
vec![3,3,4],
|
||||
vec![2,2,7],
|
||||
vec![6,1,1],
|
||||
vec![4,2,2],
|
||||
vec![1,1],
|
||||
vec![3,1],
|
||||
vec![6],
|
||||
vec![2,7],
|
||||
vec![6,3,1],
|
||||
vec![1,2,2,1,1],
|
||||
vec![4,1,1,3],
|
||||
vec![4,2,2],
|
||||
vec![3,3,1],
|
||||
vec![3,3],
|
||||
vec![3],
|
||||
vec![2,1],
|
||||
];
|
||||
|
||||
let puzzle_cols = [
|
||||
vec![2],
|
||||
vec![1,2],
|
||||
vec![2,3],
|
||||
vec![2,3],
|
||||
vec![3,1,1],
|
||||
vec![2,1,1],
|
||||
vec![1,1,1,2,2],
|
||||
vec![1,1,3,1,3],
|
||||
vec![2,6,4],
|
||||
vec![3,3,9,1],
|
||||
vec![5,3,2],
|
||||
vec![3,1,2,2],
|
||||
vec![2,1,7],
|
||||
vec![3,3,2],
|
||||
vec![2,4],
|
||||
vec![2,1,2],
|
||||
vec![2,2,1],
|
||||
vec![2,2],
|
||||
vec![1],
|
||||
vec![1],
|
||||
];
|
||||
|
||||
let expected = [
|
||||
[ 0,0,0,0,0, 0,0,0,0,0, 1,1,1,0,0, 0,0,0,0,0 ],
|
||||
[ 0,0,0,0,0, 0,0,0,0,1, 1,1,1,1,0, 0,0,0,0,0 ],
|
||||
[ 0,0,0,0,0, 0,0,0,0,1, 1,1,0,1,0, 0,0,0,0,0 ],
|
||||
[ 0,0,0,0,0, 0,0,0,0,1, 1,0,0,1,0, 0,0,0,0,0 ],
|
||||
[ 0,0,0,0,0, 0,1,1,1,0, 1,1,1,0,1, 1,1,1,0,0 ],
|
||||
|
||||
[ 0,0,0,0,1, 1,0,0,1,1, 0,0,0,1,1, 1,1,1,1,1 ],
|
||||
[ 0,0,1,1,1, 1,1,1,0,1, 0,0,0,1,0, 0,0,0,0,0 ],
|
||||
[ 0,1,1,1,1, 0,0,0,1,1, 0,0,1,1,0, 0,0,0,0,0 ],
|
||||
[ 0,0,0,0,0, 0,0,0,1,0, 0,0,1,0,0, 0,0,0,0,0 ],
|
||||
[ 0,0,0,0,0, 0,0,1,1,1, 0,0,1,0,0, 0,0,0,0,0 ],
|
||||
|
||||
[ 0,0,0,0,0, 0,0,1,1,1, 1,1,1,0,0, 0,0,0,0,0 ],
|
||||
[ 0,1,1,0,0, 0,1,1,1,1, 1,1,1,0,0, 0,0,0,0,0 ],
|
||||
[ 1,1,1,1,1, 1,0,0,1,1, 1,0,1,0,0, 0,0,0,0,0 ],
|
||||
[ 1,0,1,1,0, 0,1,1,0,1, 0,0,1,0,0, 0,0,0,0,0 ],
|
||||
[ 0,0,0,1,1, 1,1,0,0,1, 0,1,0,0,1, 1,1,0,0,0 ],
|
||||
|
||||
[ 0,0,0,0,0, 0,0,0,1,1, 1,1,0,1,1, 0,1,1,0,0 ],
|
||||
[ 0,0,0,0,0, 0,0,0,1,1, 1,0,0,1,1, 1,0,1,0,0 ],
|
||||
[ 0,0,0,0,0, 0,0,1,1,1, 0,0,0,0,1, 1,1,0,0,0 ],
|
||||
[ 0,0,0,0,0, 0,1,1,1,0, 0,0,0,0,0, 0,0,0,0,0 ],
|
||||
[ 0,0,0,0,0, 0,1,1,0,1, 0,0,0,0,0, 0,0,0,0,0 ] ];
|
||||
|
||||
let (mut sys, vars) = make_nonogram(&puzzle_rows, &puzzle_cols);
|
||||
let dict = sys.solve_unique().expect("solution");
|
||||
print_nonogram(&dict, &vars);
|
||||
verify_nonogram(&dict, &vars, &expected);
|
||||
println!("nonogram_wikipedia: {} guesses.", sys.num_guesses());
|
||||
}
|
Loading…
Reference in New Issue
Block a user