sat_solver/nonogram/
solver.rs

1use crate::sat::clause_storage::LiteralStorage;
2use crate::sat::cnf::Cnf;
3use crate::sat::literal::Literal;
4use crate::sat::solver::Solutions;
5use std::collections::HashMap;
6use std::fmt::Display;
7use std::num::NonZeroI32;
8use std::path::PathBuf;
9
10/// Represents the state of a cell in a Nonogram puzzle.
11#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub enum Cell {
13    /// Cell is unknown, not yet filled or empty.
14    Unknown = 0,
15    /// Cell is filled with a block.
16    Filled = 1,
17    /// Cell is empty, indicating no block is present.
18    Empty = 2,
19}
20
21impl Display for Cell {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        match self {
24            Cell::Empty => write!(f, "."),
25            Cell::Filled => write!(f, "#"),
26            Cell::Unknown => write!(f, " "),
27        }
28    }
29}
30
31type Constraint = Vec<u32>;
32type Size = usize;
33type Pattern = Vec<Cell>;
34
35/// Represents a Nonogram puzzle, including its constraints and solution.
36#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct Nonogram {
38    rows: Vec<Constraint>,
39    cols: Vec<Constraint>,
40    solution: Vec<Vec<Cell>>,
41}
42
43impl Display for Nonogram {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        writeln!(f, "Nonogram:")?;
46        writeln!(f, "Rows: {:?}", self.rows)?;
47        writeln!(f, "Cols: {:?}", self.cols)?;
48
49        for row in &self.solution {
50            for cell in row {
51                write!(f, "{}", cell)?;
52            }
53            writeln!(f)?;
54        }
55        Ok(())
56    }
57}
58
59impl Nonogram {
60    /// Creates a new Nonogram instance with the given row and column constraints.
61    pub fn new(rows: Vec<Constraint>, cols: Vec<Constraint>) -> Self {
62        let row_size = rows.len();
63        let col_size = cols.len();
64        let solution = vec![vec![Cell::Unknown; col_size]; row_size];
65        Nonogram {
66            rows,
67            cols,
68            solution,
69        }
70    }
71
72    /// returns the number of rows in the Nonogram.
73    pub fn num_rows(&self) -> usize {
74        self.rows.len()
75    }
76
77    /// returns the number of columns in the Nonogram.
78    pub fn num_cols(&self) -> usize {
79        self.cols.len()
80    }
81
82    /// decodes a given assignment of variables into a solution grid.
83    pub fn decode(&self, assignment: &Solutions) -> Vec<Vec<Cell>> {
84        let mut solution = vec![vec![Cell::Unknown; self.num_cols()]; self.num_rows()];
85        let assignment_set: std::collections::HashSet<NonZeroI32> =
86            assignment.iter().cloned().collect();
87
88        for r in 0..self.num_rows() {
89            for c in 0..self.num_cols() {
90                let fill_var = Variable::new(r, c, Cell::Filled).encode(self);
91                let empty_var = Variable::new(r, c, Cell::Empty).encode(self);
92
93                let fill_var = NonZeroI32::new(fill_var as i32).unwrap();
94                let empty_var = NonZeroI32::new(empty_var as i32).unwrap();
95
96                if assignment_set.contains(&(fill_var)) {
97                    solution[r][c] = Cell::Filled;
98                } else if assignment_set.contains(&(empty_var)) {
99                    solution[r][c] = Cell::Empty;
100                }
101            }
102        }
103
104        solution
105    }
106
107    /// Converts the Nonogram into a CNF (Conjunctive Normal Form) representation suitable for SAT solving.
108    pub fn to_cnf<L: Literal, S: LiteralStorage<L>>(&self) -> Cnf<L, S> {
109        let num_vars_cell = self.num_rows() * self.num_cols() * 2;
110        let mut next_aux_var = (num_vars_cell + 1) as u32;
111
112        println!("Generating cell clauses...");
113        let cell_clauses = generate_cell_clauses(self);
114        println!("Generating cell unique clauses...");
115        let cell_unique_clauses = generate_cell_unique_clauses(self);
116
117        println!("Generating row clauses...");
118        let (row_clauses, next_aux_var_after_rows) =
119            generate_line_clauses(self, true, next_aux_var);
120        next_aux_var = next_aux_var_after_rows;
121        println!("Generating column clauses...");
122        let (col_clauses, _) = generate_line_clauses(self, false, next_aux_var);
123
124        println!("Combining clauses...");
125        let all_clauses: Vec<Vec<i32>> = cell_clauses
126            .into_iter()
127            .chain(cell_unique_clauses)
128            .chain(row_clauses)
129            .chain(col_clauses)
130            .collect();
131
132        println!(
133            "Generated {} clauses with {} variables.",
134            all_clauses.len(),
135            next_aux_var - 1
136        );
137        Cnf::new(all_clauses)
138    }
139}
140
141fn generate_cell_clauses(nonogram: &Nonogram) -> Vec<Vec<i32>> {
142    let mut clauses = Vec::new();
143    for r in 0..nonogram.num_rows() {
144        for c in 0..nonogram.num_cols() {
145            let fill_var = Variable::new(r, c, Cell::Filled).encode(nonogram);
146            let empty_var = Variable::new(r, c, Cell::Empty).encode(nonogram);
147
148            clauses.push(vec![-(fill_var as i32), -(empty_var as i32)]);
149        }
150    }
151    clauses
152}
153
154fn generate_cell_unique_clauses(nonogram: &Nonogram) -> Vec<Vec<i32>> {
155    let mut clauses = Vec::new();
156    for r in 0..nonogram.num_rows() {
157        for c in 0..nonogram.num_cols() {
158            let fill_var = Variable::new(r, c, Cell::Filled).encode(nonogram);
159            let empty_var = Variable::new(r, c, Cell::Empty).encode(nonogram);
160
161            clauses.push(vec![fill_var as i32, empty_var as i32]);
162        }
163    }
164    clauses
165}
166
167fn generate_line_clauses(
168    nonogram: &Nonogram,
169    is_row: bool,
170    mut next_aux_var: u32,
171) -> (Vec<Vec<i32>>, u32) {
172    let mut clauses = Vec::new();
173    let outer_loop_size = if is_row {
174        nonogram.num_rows()
175    } else {
176        nonogram.num_cols()
177    };
178    let line_size = if is_row {
179        nonogram.num_cols()
180    } else {
181        nonogram.num_rows()
182    };
183    let constraints_vec = if is_row {
184        &nonogram.rows
185    } else {
186        &nonogram.cols
187    };
188
189    let mut memo = HashMap::new();
190
191    for i in 0..outer_loop_size {
192        let constraint = &constraints_vec[i];
193        println!(
194            "  Processing {} {}: Constraint {:?}, Size {}",
195            if is_row { "Row" } else { "Col" },
196            i,
197            constraint,
198            line_size
199        );
200
201        let possible_patterns = generate_possible_solutions_memo(line_size, constraint, &mut memo);
202        println!(
203            "    Found {} possible patterns for {} {}",
204            possible_patterns.len(),
205            if is_row { "Row" } else { "Col" },
206            i
207        );
208
209        if possible_patterns.is_empty() {
210            println!(
211                "Warning: No possible patterns found for {} {} with constraints {:?}. Puzzle might be unsatisfiable.",
212                if is_row { "Row" } else { "Col" },
213                i,
214                constraint
215            );
216            clauses.push(vec![]);
217            continue;
218        }
219
220        let aux_vars: Vec<u32> = (0..possible_patterns.len())
221            .map(|_| {
222                let var = next_aux_var;
223                next_aux_var += 1;
224                var
225            })
226            .collect();
227
228        clauses.push(aux_vars.iter().map(|&v| v as i32).collect());
229
230        for j in 0..aux_vars.len() {
231            for k in (j + 1)..aux_vars.len() {
232                clauses.push(vec![-(aux_vars[j] as i32), -(aux_vars[k] as i32)]);
233            }
234        }
235
236        for (pattern_idx, pattern) in possible_patterns.iter().enumerate() {
237            let aux_var = aux_vars[pattern_idx];
238            for k in 0..line_size {
239                let cell_state = pattern[k];
240                let (r, c) = if is_row { (i, k) } else { (k, i) };
241                let cell_var = Variable::new(r, c, cell_state).encode(nonogram);
242
243                clauses.push(vec![-(aux_var as i32), cell_var as i32]);
244            }
245        }
246    }
247
248    (clauses, next_aux_var)
249}
250
251fn generate_possible_solutions_memo(
252    size: Size,
253    constraint: &Constraint,
254    memo: &mut HashMap<(Size, Constraint), Vec<Pattern>>,
255) -> Vec<Pattern> {
256    let key = (size, constraint.clone());
257    if let Some(cached) = memo.get(&key) {
258        return cached.clone();
259    }
260
261    let mut solutions = Vec::new();
262    generate_recursive(
263        size,
264        constraint,
265        0,
266        0,
267        &mut vec![Cell::Unknown; size],
268        &mut solutions,
269    );
270
271    memo.insert(key, solutions.clone());
272    solutions
273}
274
275fn generate_recursive(
276    size: Size,
277    constraints: &Constraint,
278    constraint_idx: usize,
279    start_pos: usize,
280    current_pattern: &mut Pattern,
281    solutions: &mut Vec<Pattern>,
282) {
283    if constraint_idx == constraints.len() {
284        for i in start_pos..size {
285            if current_pattern[i] == Cell::Unknown {
286                current_pattern[i] = Cell::Empty;
287            }
288        }
289        solutions.push(current_pattern.clone());
290        return;
291    }
292
293    let block_len = constraints[constraint_idx] as usize;
294    let remaining_constraints = &constraints[(constraint_idx + 1)..];
295    let min_len_for_remaining =
296        remaining_constraints.iter().sum::<u32>() as usize + remaining_constraints.len();
297
298    for pos in start_pos..=size {
299        let end_pos = pos + block_len;
300
301        if end_pos > size {
302            break;
303        }
304
305        let space_needed_after = if constraint_idx < constraints.len() - 1 {
306            min_len_for_remaining + 1
307        } else {
308            0
309        };
310        if end_pos + space_needed_after > size {
311            continue;
312        }
313
314        let mut possible = true;
315        for i in pos..end_pos {
316            if current_pattern[i] == Cell::Empty {
317                possible = false;
318                break;
319            }
320        }
321        if !possible {
322            continue;
323        }
324
325        if end_pos < size && current_pattern[end_pos] == Cell::Filled {
326            possible = false;
327        }
328        if !possible {
329            continue;
330        }
331
332        let original_pattern_slice: Vec<Cell> = current_pattern[pos..].to_vec();
333
334        for i in start_pos..pos {
335            if current_pattern[i] == Cell::Unknown {
336                current_pattern[i] = Cell::Empty;
337            } else if current_pattern[i] == Cell::Filled {
338                possible = false;
339                break;
340            }
341        }
342        if !possible {
343            current_pattern[start_pos..pos]
344                .copy_from_slice(&original_pattern_slice[(start_pos - pos)..0]);
345            continue;
346        }
347
348        for i in pos..end_pos {
349            current_pattern[i] = Cell::Filled;
350        }
351
352        let next_start_pos;
353        if end_pos < size {
354            current_pattern[end_pos] = Cell::Empty;
355            next_start_pos = end_pos + 1;
356        } else {
357            next_start_pos = end_pos;
358        }
359
360        generate_recursive(
361            size,
362            constraints,
363            constraint_idx + 1,
364            next_start_pos,
365            current_pattern,
366            solutions,
367        );
368
369        for i in pos..current_pattern
370            .len()
371            .min(pos + original_pattern_slice.len())
372        {
373            current_pattern[i] = original_pattern_slice[i - pos];
374        }
375        if end_pos < size {
376            if end_pos - pos < original_pattern_slice.len() {
377                current_pattern[end_pos] = original_pattern_slice[end_pos - pos];
378            } else {
379                current_pattern[end_pos] = Cell::Unknown;
380            }
381        }
382    }
383}
384
385#[derive(Clone, Debug, PartialEq, Eq, Hash)]
386struct Variable {
387    /// 0-based row index
388    row: usize,
389    /// 0-based column index
390    col: usize,
391    /// State represented by this variable
392    fill: Cell,
393}
394
395impl Variable {
396    /// Creates a new variable representation.
397    /// Uses 0-based indexing internally for row/col.
398    fn new(row: usize, col: usize, fill: Cell) -> Self {
399        assert!(
400            fill == Cell::Filled || fill == Cell::Empty,
401            "Variable must represent Filled or Empty state"
402        );
403        Variable { row, col, fill }
404    }
405
406    /// Encodes the variable into a unique positive integer for the SAT solver.
407    /// 1-based result.
408    fn encode(&self, nonogram: &Nonogram) -> u32 {
409        let num_cols = nonogram.num_cols();
410        let base = (self.row * num_cols + self.col) * 2;
411        (base + self.fill as usize) as u32
412    }
413}
414
415/// Parses a Nonogram from a string description.
416/// Format Assumption:
417/// rows <num_rows>
418/// cols <num_cols>
419/// <row_1_constraint_1> <row_1_constraint_2> ...
420/// ... (num_rows lines)
421/// <col_1_constraint_1> <col_1_constraint_2> ...
422/// ... (num_cols lines)
423/// Constraints are space-separated positive integers. Empty lines for constraints mean [0].
424pub fn parse_nonogram(input: &str) -> Result<Nonogram, String> {
425    let mut lines = input.lines().map(str::trim).filter(|l| !l.is_empty());
426
427    let mut num_rows: Option<usize> = None;
428    let mut num_cols: Option<usize> = None;
429
430    if let Some(line) = lines.next() {
431        if line.starts_with("rows ") {
432            num_rows = line.split_whitespace().nth(1).and_then(|s| s.parse().ok());
433        } else {
434            return Err("Expected 'rows <num>' format".to_string());
435        }
436    } else {
437        return Err("Missing 'rows' line".to_string());
438    }
439
440    if let Some(line) = lines.next() {
441        if line.starts_with("cols ") {
442            num_cols = line.split_whitespace().nth(1).and_then(|s| s.parse().ok());
443        } else {
444            return Err("Expected 'cols <num>' format".to_string());
445        }
446    } else {
447        return Err("Missing 'cols' line".to_string());
448    }
449
450    let num_rows = num_rows.ok_or("Invalid number for rows")?;
451    let num_cols = num_cols.ok_or("Invalid number for cols")?;
452
453    if num_rows == 0 || num_cols == 0 {
454        return Err("Rows and columns must be positive".to_string());
455    }
456
457    let mut rows = Vec::with_capacity(num_rows);
458    for i in 0..num_rows {
459        let line = lines
460            .next()
461            .ok_or(format!("Missing row constraint line {}", i + 1))?;
462        let constraint: Constraint = line
463            .split_whitespace()
464            .map(|s| s.parse::<u32>())
465            .collect::<Result<_, _>>()
466            .map_err(|e| format!("Invalid number in row constraint {}: {}", i + 1, e))?;
467        if constraint.is_empty() || (constraint.len() == 1 && constraint[0] == 0) {
468            rows.push(vec![]);
469        } else {
470            rows.push(constraint);
471        }
472    }
473
474    let mut cols = Vec::with_capacity(num_cols);
475    for i in 0..num_cols {
476        let line = lines
477            .next()
478            .ok_or(format!("Missing column constraint line {}", i + 1))?;
479        let constraint: Constraint = line
480            .split_whitespace()
481            .map(|s| s.parse::<u32>())
482            .collect::<Result<_, _>>()
483            .map_err(|e| format!("Invalid number in column constraint {}: {}", i + 1, e))?;
484        if constraint.is_empty() || (constraint.len() == 1 && constraint[0] == 0) {
485            cols.push(vec![]);
486        } else {
487            cols.push(constraint);
488        }
489    }
490
491    if lines.next().is_some() {
492        return Err("Extra lines found after column constraints".to_string());
493    }
494
495    Ok(Nonogram::new(rows, cols))
496}
497
498/// Parses a Nonogram from a file.
499pub fn parse_nonogram_file(file_path: &PathBuf) -> Result<Nonogram, String> {
500    let content = std::fs::read_to_string(file_path)
501        .map_err(|e| format!("Failed to read file '{}': {}", file_path.display(), e))?;
502    parse_nonogram(&content)
503}