Skip to content

Commit e36145b

Browse files
committed
Added solver for canal view
1 parent 138c30f commit e36145b

4 files changed

Lines changed: 196 additions & 0 deletions

File tree

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use crate::util;
2+
use cspuz_rs::graph;
3+
use cspuz_rs::serializer::{
4+
problem_to_url, url_to_problem, Choice, Combinator, Dict, Grid, HexInt, Optionalize, Spaces,
5+
};
6+
use cspuz_rs::solver::Solver;
7+
8+
pub fn solve_canalview(clues: &[Vec<Option<i32>>]) -> Option<Vec<Vec<Option<bool>>>> {
9+
let (h, w) = util::infer_shape(clues);
10+
11+
let mut solver = Solver::new();
12+
let is_black = &solver.bool_var_2d((h, w));
13+
solver.add_answer_key_bool(is_black);
14+
15+
graph::active_vertices_connected_2d(&mut solver, is_black);
16+
solver.add_expr(!is_black.conv2d_and((2, 2)));
17+
18+
for y in 0..h {
19+
for x in 0..w {
20+
if let Some(n) = clues[y][x] {
21+
solver.add_expr(!is_black.at((y, x)));
22+
if n < 0 {
23+
continue;
24+
}
25+
let up = is_black.slice_fixed_x((..y, x)).reverse();
26+
let down = is_black.slice_fixed_x(((y + 1).., x));
27+
let left = is_black.slice_fixed_y((y, ..x)).reverse();
28+
let right = is_black.slice_fixed_y((y, (x + 1)..));
29+
solver.add_expr(
30+
(up.consecutive_prefix_true()
31+
+ down.consecutive_prefix_true()
32+
+ left.consecutive_prefix_true()
33+
+ right.consecutive_prefix_true())
34+
.eq(n),
35+
);
36+
}
37+
}
38+
}
39+
40+
solver.irrefutable_facts().map(|f| f.get(is_black))
41+
}
42+
43+
type Problem = Vec<Vec<Option<i32>>>;
44+
45+
fn combinator() -> impl Combinator<Problem> {
46+
Grid::new(Choice::new(vec![
47+
Box::new(Optionalize::new(HexInt)),
48+
Box::new(Spaces::new(None, 'g')),
49+
Box::new(Dict::new(Some(-1), ".")),
50+
]))
51+
}
52+
53+
pub fn serialize_problem(problem: &Problem) -> Option<String> {
54+
problem_to_url(combinator(), "canal", problem.clone())
55+
}
56+
57+
pub fn deserialize_problem(url: &str) -> Option<Problem> {
58+
url_to_problem(combinator(), &["canal"], url)
59+
}
60+
61+
#[cfg(test)]
62+
mod tests {
63+
use super::*;
64+
65+
#[rustfmt::skip]
66+
fn problem_for_tests() -> Problem {
67+
vec![
68+
vec![Some(3), None, None, None, Some(3), None],
69+
vec![None, Some(2), None, None, None, None],
70+
vec![None, None, None, None, None, None],
71+
vec![None, None, None, None, None, None],
72+
vec![None, None, None, None, Some(4), None],
73+
vec![None, Some(5), None, None, None, Some(2)],
74+
]
75+
}
76+
77+
#[test]
78+
fn test_canalview_problem() {
79+
let problem = problem_for_tests();
80+
let ans = solve_canalview(&problem);
81+
assert!(ans.is_some());
82+
let ans = ans.unwrap();
83+
let expected = crate::util::tests::to_option_bool_2d([
84+
[0, 1, 1, 1, 0, 0],
85+
[0, 0, 1, 0, 0, 0],
86+
[0, 0, 1, 0, 1, 0],
87+
[0, 1, 1, 1, 1, 1],
88+
[1, 1, 0, 1, 0, 1],
89+
[1, 0, 1, 1, 0, 0],
90+
]);
91+
assert_eq!(ans, expected);
92+
}
93+
94+
#[test]
95+
fn test_canalview_serializer() {
96+
let problem = problem_for_tests();
97+
let url = "https://puzz.link/p?canal/6/6/3i3h2z4h5i2";
98+
util::tests::serializer_test(problem, url, serialize_problem, deserialize_problem);
99+
}
100+
}

cspuz_rs_puzzles/src/puzzles/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub mod barns;
1313
pub mod battleship;
1414
pub mod bdwalk;
1515
pub mod bosanowa;
16+
pub mod canalview;
1617
pub mod castle_walker;
1718
pub mod castle_wall;
1819
pub mod cave;
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use crate::board::{Board, BoardKind, Item, ItemKind};
2+
use crate::uniqueness::check_uniqueness;
3+
use cspuz_rs_puzzles::puzzles::canalview;
4+
5+
pub fn solve(url: &str) -> Result<Board, &'static str> {
6+
let problem = canalview::deserialize_problem(url).ok_or("invalid url")?;
7+
let ans = canalview::solve_canalview(&problem);
8+
9+
let height = problem.len();
10+
let width = problem[0].len();
11+
let mut board = Board::new(BoardKind::Grid, height, width, check_uniqueness(&ans));
12+
for y in 0..height {
13+
for x in 0..width {
14+
if let Some(clue) = problem[y][x] {
15+
if clue > 0 {
16+
board.push(Item::cell(y, x, "black", ItemKind::Num(clue)));
17+
} else {
18+
board.push(Item::cell(y, x, "black", ItemKind::Text("?")));
19+
}
20+
} else if let Some(ans) = &ans {
21+
if let Some(a) = ans[y][x] {
22+
board.push(Item::cell(
23+
y,
24+
x,
25+
"green",
26+
if a { ItemKind::Block } else { ItemKind::Dot },
27+
));
28+
}
29+
}
30+
}
31+
}
32+
33+
Ok(board)
34+
}
35+
36+
#[cfg(test)]
37+
mod tests {
38+
use super::solve;
39+
use crate::board::*;
40+
use crate::compare_board_and_check_no_solution_case;
41+
use crate::uniqueness::Uniqueness;
42+
43+
#[test]
44+
#[rustfmt::skip]
45+
fn test_solve() {
46+
compare_board_and_check_no_solution_case!(
47+
solve("https://puzz.link/p?canal/6/6/3i3h2z4h5i2"),
48+
Board {
49+
kind: BoardKind::Grid,
50+
height: 6,
51+
width: 6,
52+
data: vec![
53+
Item { y: 1, x: 1, color: "black", kind: ItemKind::Num(3) },
54+
Item { y: 1, x: 3, color: "green", kind: ItemKind::Block },
55+
Item { y: 1, x: 5, color: "green", kind: ItemKind::Block },
56+
Item { y: 1, x: 7, color: "green", kind: ItemKind::Block },
57+
Item { y: 1, x: 9, color: "black", kind: ItemKind::Num(3) },
58+
Item { y: 1, x: 11, color: "green", kind: ItemKind::Dot },
59+
Item { y: 3, x: 1, color: "green", kind: ItemKind::Dot },
60+
Item { y: 3, x: 3, color: "black", kind: ItemKind::Num(2) },
61+
Item { y: 3, x: 5, color: "green", kind: ItemKind::Block },
62+
Item { y: 3, x: 7, color: "green", kind: ItemKind::Dot },
63+
Item { y: 3, x: 9, color: "green", kind: ItemKind::Dot },
64+
Item { y: 3, x: 11, color: "green", kind: ItemKind::Dot },
65+
Item { y: 5, x: 1, color: "green", kind: ItemKind::Dot },
66+
Item { y: 5, x: 3, color: "green", kind: ItemKind::Dot },
67+
Item { y: 5, x: 5, color: "green", kind: ItemKind::Block },
68+
Item { y: 5, x: 7, color: "green", kind: ItemKind::Dot },
69+
Item { y: 5, x: 9, color: "green", kind: ItemKind::Block },
70+
Item { y: 5, x: 11, color: "green", kind: ItemKind::Dot },
71+
Item { y: 7, x: 1, color: "green", kind: ItemKind::Dot },
72+
Item { y: 7, x: 3, color: "green", kind: ItemKind::Block },
73+
Item { y: 7, x: 5, color: "green", kind: ItemKind::Block },
74+
Item { y: 7, x: 7, color: "green", kind: ItemKind::Block },
75+
Item { y: 7, x: 9, color: "green", kind: ItemKind::Block },
76+
Item { y: 7, x: 11, color: "green", kind: ItemKind::Block },
77+
Item { y: 9, x: 1, color: "green", kind: ItemKind::Block },
78+
Item { y: 9, x: 3, color: "green", kind: ItemKind::Block },
79+
Item { y: 9, x: 5, color: "green", kind: ItemKind::Dot },
80+
Item { y: 9, x: 7, color: "green", kind: ItemKind::Block },
81+
Item { y: 9, x: 9, color: "black", kind: ItemKind::Num(4) },
82+
Item { y: 9, x: 11, color: "green", kind: ItemKind::Block },
83+
Item { y: 11, x: 1, color: "green", kind: ItemKind::Block },
84+
Item { y: 11, x: 3, color: "black", kind: ItemKind::Num(5) },
85+
Item { y: 11, x: 5, color: "green", kind: ItemKind::Block },
86+
Item { y: 11, x: 7, color: "green", kind: ItemKind::Block },
87+
Item { y: 11, x: 9, color: "green", kind: ItemKind::Dot },
88+
Item { y: 11, x: 11, color: "black", kind: ItemKind::Num(2) },
89+
],
90+
uniqueness: Uniqueness::Unique,
91+
},
92+
);
93+
}
94+
}

cspuz_solver_backend/src/puzzle/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ puzzle_list!(puzz_link,
9999
(battleship, ["battleship"], "Battleship", "Battleship"),
100100
(bdwalk, ["bdwalk"], "Building Walk", "ビルウォーク"),
101101
(bosanowa, ["bosanowa"], "Bosanowa", "ボサノワ"),
102+
(canalview, ["canal"], "Canal View", "Canal View"),
102103
(castle_wall, ["castle"], "Castle Wall", "Castle Wall"),
103104
(cave, ["cave"], "Cave", "バッグ"),
104105
(chainedb, ["chainedb"], "Chained Block", "チェンブロ"),

0 commit comments

Comments
 (0)