Add minesweeper solver
This commit is contained in:
parent
e5f408aed2
commit
d130fd57d9
1 changed files with 159 additions and 0 deletions
159
src/bin/minesweeper.rs
Normal file
159
src/bin/minesweeper.rs
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use simple_sat_solver::{
|
||||||
|
dpll::Index, expr::Expr, normal_form::NormalForm, solver::SolutionIterator,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn solve_minesweeper<F>(f: F, input: &[Vec<u8>], mines: u64) -> (u64, Vec<Vec<u64>>, f64)
|
||||||
|
where
|
||||||
|
F: Fn(&NormalForm) -> Option<Vec<Index>>,
|
||||||
|
{
|
||||||
|
let mut cnf = Vec::new();
|
||||||
|
|
||||||
|
let mut total_flags = 0;
|
||||||
|
let mut unknown_fields = 0;
|
||||||
|
|
||||||
|
for y in 0..input.len() {
|
||||||
|
for x in 0..input[y].len() {
|
||||||
|
match input[y][x] {
|
||||||
|
0 => (),
|
||||||
|
1..=9 => {
|
||||||
|
let mut neighbors = Vec::new();
|
||||||
|
let mut flags = 0;
|
||||||
|
for (nx, ny) in (-1..=1)
|
||||||
|
.flat_map(|dx| (-1..=1).map(move |dy| (dx, dy)))
|
||||||
|
.filter_map(|(dx, dy)| {
|
||||||
|
x.checked_add_signed(dx)
|
||||||
|
.filter(|&nx| nx < input[y].len())
|
||||||
|
.zip(y.checked_add_signed(dy).filter(|&ny| ny < input.len()))
|
||||||
|
})
|
||||||
|
{
|
||||||
|
match input[ny][nx] {
|
||||||
|
0..10 => (),
|
||||||
|
10 => neighbors.push((nx, ny)),
|
||||||
|
11 => flags += 1,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let bombs = (input[y][x] - flags) as usize;
|
||||||
|
if bombs > 0 {
|
||||||
|
assert!(!neighbors.is_empty());
|
||||||
|
cnf.push(Expr::cnf_from_truth_function(
|
||||||
|
|bits| bits.iter().filter(|&&b| b).count() == bombs,
|
||||||
|
&neighbors,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
10 => unknown_fields += 1,
|
||||||
|
11 => total_flags += 1,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let expr = Expr::And(cnf);
|
||||||
|
|
||||||
|
let mut result = vec![vec![u64::MAX; input[0].len()]; input.len()];
|
||||||
|
let mut total = 0;
|
||||||
|
|
||||||
|
let mut interesting_fields = 0;
|
||||||
|
|
||||||
|
let mut bomb_distribution = HashMap::new();
|
||||||
|
|
||||||
|
for i in SolutionIterator::new(f, expr) {
|
||||||
|
total += 1;
|
||||||
|
interesting_fields = i.len();
|
||||||
|
let mut bombs = 0;
|
||||||
|
for ((x, y), b) in i {
|
||||||
|
if result[y][x] == u64::MAX {
|
||||||
|
result[y][x] = 0;
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
result[y][x] += 1;
|
||||||
|
bombs += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match bomb_distribution.entry(bombs) {
|
||||||
|
std::collections::hash_map::Entry::Occupied(mut occupied_entry) => {
|
||||||
|
*occupied_entry.get_mut() += 1;
|
||||||
|
}
|
||||||
|
std::collections::hash_map::Entry::Vacant(vacant_entry) => {
|
||||||
|
vacant_entry.insert(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected_bombs = bomb_distribution
|
||||||
|
.iter()
|
||||||
|
.map(|(&k, &v)| k as f64 * v as f64)
|
||||||
|
.sum::<f64>()
|
||||||
|
/ total as f64;
|
||||||
|
|
||||||
|
(
|
||||||
|
total,
|
||||||
|
result,
|
||||||
|
(mines as f64 - expected_bombs) / (unknown_fields - interesting_fields) as f64,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_input(input: &[Vec<u8>]) {
|
||||||
|
for row in input.iter() {
|
||||||
|
for elem in row.iter() {
|
||||||
|
match elem {
|
||||||
|
0 => print!(" . "),
|
||||||
|
1..=9 => print!(" {elem} "),
|
||||||
|
10 => print!(" # "),
|
||||||
|
11 => print!(" F "),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_output(total: u64, input: &[Vec<u8>], map: &[Vec<u64>], default: f64) {
|
||||||
|
for (input_row, map_row) in input.iter().zip(map.iter()) {
|
||||||
|
for (input_elem, map_elem) in input_row.iter().zip(map_row.iter()) {
|
||||||
|
match input_elem {
|
||||||
|
0 => print!(" . "),
|
||||||
|
1..=9 => print!(" {input_elem} "),
|
||||||
|
10 => {
|
||||||
|
if *map_elem == u64::MAX {
|
||||||
|
print!("{:3}", (default * 100.0) as u8);
|
||||||
|
} else {
|
||||||
|
print!("{:3}", map_elem * 100 / total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
11 => print!(" F "),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// let input = vec![
|
||||||
|
// vec![0, 0, 1, 10, 10, 10],
|
||||||
|
// vec![1, 1, 2, 10, 10, 10],
|
||||||
|
// vec![1, 11, 2, 2, 10, 10],
|
||||||
|
// vec![1, 1, 2, 10, 10, 10],
|
||||||
|
// vec![1, 1, 2, 10, 10, 10],
|
||||||
|
// vec![10, 10, 2, 10, 10, 10],
|
||||||
|
// vec![10; 6],
|
||||||
|
// ];
|
||||||
|
//
|
||||||
|
let input = vec![
|
||||||
|
vec![2, 2, 10],
|
||||||
|
vec![10, 10, 10],
|
||||||
|
vec![10, 10, 10],
|
||||||
|
vec![10, 10, 10],
|
||||||
|
];
|
||||||
|
|
||||||
|
print_input(&input);
|
||||||
|
|
||||||
|
let (total, result, default) = solve_minesweeper(simple_sat_solver::cdcl::cdcl, &input, 5);
|
||||||
|
|
||||||
|
print_output(total, &input, &result, default);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue