2017-03-20 01:22:26 +04:00
|
|
|
//! Nonogram (A.K.A. Hanjie, Picross).
|
|
|
|
//!
|
|
|
|
//! https://en.wikipedia.org/wiki/Nonogram
|
|
|
|
|
|
|
|
extern crate puzzle_solver;
|
|
|
|
|
2021-12-07 20:56:04 +04:00
|
|
|
use puzzle_solver::*;
|
2017-03-20 01:22:26 +04:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::rc::Rc;
|
|
|
|
|
|
|
|
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>,
|
|
|
|
}
|
|
|
|
|
2021-12-07 20:56:04 +04:00
|
|
|
#[derive(Clone, Copy)]
|
2017-03-20 01:22:26 +04:00
|
|
|
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 {
|
2021-12-08 11:17:32 +04:00
|
|
|
fn new(vars: &[VarToken], rule: &[usize]) -> Self {
|
2017-03-20 01:22:26 +04:00
|
|
|
Nonogram {
|
2021-12-08 11:17:32 +04:00
|
|
|
vars: vars.to_vec(),
|
|
|
|
rule: rule.to_vec(),
|
2017-03-20 01:22:26 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-07 20:56:04 +04:00
|
|
|
fn autofill(
|
|
|
|
&self,
|
|
|
|
trial: &mut Vec<Val>,
|
|
|
|
pos: usize,
|
|
|
|
rule_idx: usize,
|
|
|
|
accum: &mut Vec<Val>,
|
|
|
|
cache: &mut HashMap<(usize, usize), AutoFillResult>,
|
|
|
|
) -> AutoFillResult {
|
2017-03-20 01:22:26 +04:00
|
|
|
assert!(pos <= trial.len() && rule_idx <= self.rule.len());
|
|
|
|
let key = (pos, rule_idx);
|
|
|
|
|
|
|
|
// Base case.
|
|
|
|
if pos == trial.len() || cache.contains_key(&key) {
|
2021-12-08 11:17:32 +04:00
|
|
|
#[allow(clippy::if_same_then_else)]
|
2017-03-20 01:22:26 +04:00
|
|
|
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) {
|
2021-12-08 11:17:32 +04:00
|
|
|
*a |= *t;
|
2017-03-20 01:22:26 +04:00
|
|
|
}
|
|
|
|
|
2021-12-07 20:56:04 +04:00
|
|
|
if accum
|
|
|
|
.iter()
|
|
|
|
.all(|&t| t < FLAG_UNKNOWN || t == FLAG_UNKNOWN | FLAG_ON | FLAG_OFF)
|
|
|
|
{
|
2017-03-20 01:22:26 +04:00
|
|
|
return AutoFillResult::SearchEnded;
|
|
|
|
}
|
|
|
|
|
|
|
|
return AutoFillResult::SolutionFound;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Not enough space.
|
|
|
|
if rule_idx < self.rule.len() {
|
2021-12-07 20:56:04 +04:00
|
|
|
let required_space =
|
|
|
|
self.rule[rule_idx..].iter().sum::<usize>() + (self.rule.len() - rule_idx - 1);
|
2017-03-20 01:22:26 +04:00
|
|
|
|
|
|
|
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) {
|
2021-12-07 20:56:04 +04:00
|
|
|
res @ AutoFillResult::SearchEnded => return res,
|
|
|
|
res @ AutoFillResult::SolutionFound => result = res,
|
2017-03-20 01:22:26 +04:00
|
|
|
AutoFillResult::Conflict => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try filled.
|
|
|
|
if trial[pos] != FLAG_OFF
|
2021-12-07 20:56:04 +04:00
|
|
|
&& rule_idx < self.rule.len()
|
|
|
|
&& pos + self.rule[rule_idx] <= trial.len()
|
|
|
|
{
|
2017-03-20 01:22:26 +04:00
|
|
|
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;
|
|
|
|
}
|
2021-12-08 11:17:32 +04:00
|
|
|
pos += 1;
|
2017-03-20 01:22:26 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if ok && pos < trial.len() {
|
|
|
|
if trial[pos] == FLAG_ON {
|
|
|
|
ok = false;
|
|
|
|
} else if trial[pos] >= FLAG_UNKNOWN {
|
|
|
|
trial[pos] = FLAG_UNKNOWN | FLAG_OFF;
|
2021-12-08 11:17:32 +04:00
|
|
|
pos += 1;
|
2017-03-20 01:22:26 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
match self.autofill(trial, pos, rule_idx + 1, accum, cache) {
|
2021-12-07 20:56:04 +04:00
|
|
|
res @ AutoFillResult::SearchEnded => return res,
|
|
|
|
res @ AutoFillResult::SolutionFound => result = res,
|
2017-03-20 01:22:26 +04:00
|
|
|
AutoFillResult::Conflict => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cache.insert(key, result);
|
|
|
|
result
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Constraint for Nonogram {
|
2021-12-07 20:56:04 +04:00
|
|
|
fn vars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VarToken> + 'a> {
|
2017-03-20 01:22:26 +04:00
|
|
|
Box::new(self.vars.iter())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn on_updated(&self, search: &mut PuzzleSearch) -> PsResult<()> {
|
|
|
|
let mut trial = vec![0; self.vars.len()];
|
2021-12-08 11:17:32 +04:00
|
|
|
for (pos, &var) in trial.iter_mut().zip(&self.vars) {
|
2017-03-20 01:22:26 +04:00
|
|
|
*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) {
|
2021-12-08 11:17:32 +04:00
|
|
|
AutoFillResult::Conflict => return Err(Error::Default),
|
2017-03-20 01:22:26 +04:00
|
|
|
AutoFillResult::SearchEnded => (),
|
2021-12-07 20:56:04 +04:00
|
|
|
AutoFillResult::SolutionFound => {
|
2021-12-08 11:17:32 +04:00
|
|
|
for (idx, var) in self.vars.iter().copied().enumerate() {
|
2017-03-20 01:22:26 +04:00
|
|
|
if accum[idx] == FLAG_UNKNOWN | FLAG_OFF {
|
2021-12-08 11:17:32 +04:00
|
|
|
search.set_candidate(var, 0)?;
|
2017-03-20 01:22:26 +04:00
|
|
|
} else if accum[idx] == FLAG_UNKNOWN | FLAG_ON {
|
2021-12-08 11:17:32 +04:00
|
|
|
search.set_candidate(var, 1)?;
|
2017-03-20 01:22:26 +04:00
|
|
|
}
|
2021-12-07 20:56:04 +04:00
|
|
|
}
|
|
|
|
}
|
2017-03-20 01:22:26 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-12-07 20:56:04 +04:00
|
|
|
fn substitute(&self, _search: VarToken, _replace: VarToken) -> PsResult<Rc<dyn Constraint>> {
|
2017-03-20 01:22:26 +04:00
|
|
|
unimplemented!();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*--------------------------------------------------------------*/
|
|
|
|
|
2021-12-07 20:56:04 +04:00
|
|
|
fn make_nonogram(rows: &[Vec<usize>], cols: &[Vec<usize>]) -> (Puzzle, Vec<Vec<VarToken>>) {
|
2017-03-20 01:22:26 +04:00
|
|
|
let mut sys = Puzzle::new();
|
|
|
|
let (w, h) = (cols.len(), rows.len());
|
2021-12-07 20:56:04 +04:00
|
|
|
let vars = sys.new_vars_with_candidates_2d(w, h, &[0, 1]);
|
2017-03-20 01:22:26 +04:00
|
|
|
|
|
|
|
for y in 0..h {
|
|
|
|
sys.add_constraint(Nonogram::new(&vars[y], &rows[y]));
|
|
|
|
}
|
|
|
|
|
|
|
|
for x in 0..w {
|
2021-12-08 11:17:32 +04:00
|
|
|
let tmp: Vec<_> = vars.iter().map(|row| row[x]).collect();
|
|
|
|
sys.add_constraint(Nonogram::new(&tmp, &cols[x]));
|
2017-03-20 01:22:26 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
(sys, vars)
|
|
|
|
}
|
|
|
|
|
2021-12-08 11:17:32 +04:00
|
|
|
fn print_nonogram(dict: &Solution, vars: &[Vec<VarToken>]) {
|
2017-03-20 01:22:26 +04:00
|
|
|
for row in vars.iter() {
|
|
|
|
for &var in row.iter() {
|
|
|
|
print!("{}", if dict[var] == 1 { "*" } else { "." });
|
|
|
|
}
|
|
|
|
println!();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-08 11:17:32 +04:00
|
|
|
fn verify_nonogram(dict: &Solution, vars: &[Vec<VarToken>], expected: &Board) {
|
2017-03-20 01:22:26 +04:00
|
|
|
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],
|
2021-12-07 20:56:04 +04:00
|
|
|
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],
|
2017-03-20 01:22:26 +04:00
|
|
|
vec![6],
|
2021-12-07 20:56:04 +04:00
|
|
|
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],
|
2017-03-20 01:22:26 +04:00
|
|
|
vec![3],
|
2021-12-07 20:56:04 +04:00
|
|
|
vec![2, 1],
|
2017-03-20 01:22:26 +04:00
|
|
|
];
|
|
|
|
|
|
|
|
let puzzle_cols = [
|
|
|
|
vec![2],
|
2021-12-07 20:56:04 +04:00
|
|
|
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],
|
2017-03-20 01:22:26 +04:00
|
|
|
vec![1],
|
|
|
|
vec![1],
|
|
|
|
];
|
|
|
|
|
|
|
|
let expected = [
|
2021-12-07 20:56:04 +04:00
|
|
|
[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],
|
|
|
|
];
|
2017-03-20 01:22:26 +04:00
|
|
|
|
|
|
|
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());
|
|
|
|
}
|