Skip to content

Commit 0d76a01

Browse files
authored
Added disconnection (#229)
1 parent d8f3913 commit 0d76a01

4 files changed

Lines changed: 224 additions & 0 deletions

File tree

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use cspuz_rs::graph;
2+
use cspuz_rs::serializer::{
3+
problem_to_url_with_context_pzprxs, url_to_problem, Combinator, Context, Rooms, Size,
4+
};
5+
use cspuz_rs::solver::Solver;
6+
7+
pub fn solve_disco(
8+
borders: &graph::InnerGridEdges<Vec<Vec<bool>>>,
9+
) -> Option<Vec<Vec<Option<bool>>>> {
10+
let h = borders.vertical.len();
11+
assert!(h > 0);
12+
let w = borders.vertical[0].len() + 1;
13+
14+
let mut solver = Solver::new();
15+
let is_black = &solver.bool_var_2d((h, w));
16+
solver.add_answer_key_bool(is_black);
17+
graph::active_vertices_connected_2d(&mut solver, is_black);
18+
19+
let group_a = &solver.bool_var_2d((h, w));
20+
let group_b = &solver.bool_var_2d((h, w));
21+
solver.add_expr(!(group_a & group_b));
22+
solver.add_expr((group_a | group_b).iff(is_black));
23+
24+
solver.add_expr(!is_black.conv2d_and((2, 2)));
25+
26+
let rooms = graph::borders_to_rooms(borders);
27+
28+
for i in 0..rooms.len() {
29+
if (rooms[i].len() as i32) < 3 {
30+
return None;
31+
}
32+
33+
solver.add_expr(group_a.select(&rooms[i]).count_true().ge(1));
34+
solver.add_expr(group_b.select(&rooms[i]).count_true().ge(1));
35+
graph::active_vertices_connected_2d_region(&mut solver, group_a, &rooms[i]);
36+
graph::active_vertices_connected_2d_region(&mut solver, group_b, &rooms[i]);
37+
}
38+
39+
for y in 0..h {
40+
for x in 0..w {
41+
if y < h - 1 && !borders.horizontal[y][x] {
42+
solver.add_expr(!(group_a.at((y, x)) & group_b.at((y + 1, x))));
43+
solver.add_expr(!(group_b.at((y, x)) & group_a.at((y + 1, x))));
44+
}
45+
if x < w - 1 && !borders.vertical[y][x] {
46+
solver.add_expr(!(group_a.at((y, x)) & group_b.at((y, x + 1))));
47+
solver.add_expr(!(group_b.at((y, x)) & group_a.at((y, x + 1))));
48+
}
49+
}
50+
}
51+
solver.irrefutable_facts().map(|f| f.get(is_black))
52+
}
53+
54+
type Problem = graph::InnerGridEdges<Vec<Vec<bool>>>;
55+
56+
fn combinator() -> impl Combinator<Problem> {
57+
Size::new(Rooms)
58+
}
59+
pub fn serialize_problem(problem: &Problem) -> Option<String> {
60+
let height = problem.vertical.len();
61+
let width = problem.vertical[0].len() + 1;
62+
problem_to_url_with_context_pzprxs(
63+
combinator(),
64+
"disco",
65+
problem.clone(),
66+
&Context::sized(height, width),
67+
)
68+
}
69+
70+
pub fn deserialize_problem(url: &str) -> Option<Problem> {
71+
url_to_problem(combinator(), &["disco"], url)
72+
}
73+
74+
#[cfg(test)]
75+
mod tests {
76+
use super::*;
77+
78+
fn problem_for_tests() -> Problem {
79+
let ret = graph::InnerGridEdges {
80+
horizontal: crate::util::tests::to_bool_2d([
81+
[0, 1, 0, 1, 0],
82+
[1, 1, 0, 0, 1],
83+
[0, 1, 1, 1, 1],
84+
[1, 0, 0, 0, 0],
85+
]),
86+
vertical: crate::util::tests::to_bool_2d([
87+
[0, 1, 1, 0],
88+
[1, 0, 0, 1],
89+
[0, 1, 0, 0],
90+
[1, 0, 1, 0],
91+
[0, 0, 1, 0],
92+
]),
93+
};
94+
ret
95+
}
96+
97+
#[test]
98+
fn test_disco_problem() {
99+
let problem = problem_for_tests();
100+
let ans = solve_disco(&problem);
101+
assert!(ans.is_some());
102+
let ans = ans.unwrap();
103+
104+
let expected = crate::util::tests::to_option_bool_2d([
105+
[0, 1, 0, 1, 0],
106+
[1, 1, 1, 1, 1],
107+
[0, 1, 0, 0, 1],
108+
[1, 1, 1, 0, 1],
109+
[1, 0, 1, 1, 0],
110+
]);
111+
assert_eq!(ans, expected);
112+
}
113+
114+
#[test]
115+
fn test_disco_serializer() {
116+
let problem = problem_for_tests();
117+
let url = "https://pzprxs.vercel.app/p?disco/5/5/d552apfg";
118+
crate::util::tests::serializer_test(problem, url, serialize_problem, deserialize_problem);
119+
}
120+
}

cspuz_rs_puzzles/src/puzzles/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub mod cross_the_streams;
3636
pub mod crosswall;
3737
pub mod curvedata;
3838
pub mod dbchoco;
39+
pub mod disco;
3940
pub mod dominion;
4041
pub mod doppelblock;
4142
pub mod double_lits;
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use crate::board::{Board, BoardKind, Item, ItemKind};
2+
use crate::uniqueness::check_uniqueness;
3+
use cspuz_rs_puzzles::puzzles::disco;
4+
5+
pub fn solve(url: &str) -> Result<Board, &'static str> {
6+
let borders = disco::deserialize_problem(url).ok_or("invalid url")?;
7+
let ans = disco::solve_disco(&borders);
8+
9+
let height = borders.horizontal.len() + 1;
10+
let width = if borders.horizontal.is_empty() {
11+
0
12+
} else {
13+
borders.horizontal[0].len()
14+
};
15+
let mut board = Board::new(BoardKind::Grid, height, width, check_uniqueness(&ans));
16+
17+
board.add_borders(&borders, "black");
18+
19+
if let Some(is_black) = &ans {
20+
for y in 0..height {
21+
for x in 0..width {
22+
if let Some(b) = is_black[y][x] {
23+
board.push(Item::cell(
24+
y,
25+
x,
26+
"green",
27+
if b { ItemKind::Block } else { ItemKind::Dot },
28+
));
29+
}
30+
}
31+
}
32+
}
33+
34+
Ok(board)
35+
}
36+
37+
#[cfg(test)]
38+
mod tests {
39+
use super::solve;
40+
use crate::board::*;
41+
use crate::compare_board_and_check_no_solution_case;
42+
use crate::uniqueness::Uniqueness;
43+
44+
#[test]
45+
#[rustfmt::skip]
46+
fn test_solve() {
47+
compare_board_and_check_no_solution_case!(
48+
solve("https://pzprxs.vercel.app/p?disco/5/5/d552apfg"),
49+
Board {
50+
kind: BoardKind::Grid,
51+
height: 5,
52+
width: 5,
53+
data: vec![
54+
Item { y: 2, x: 3, color: "black", kind: ItemKind::BoldWall },
55+
Item { y: 1, x: 4, color: "black", kind: ItemKind::BoldWall },
56+
Item { y: 1, x: 6, color: "black", kind: ItemKind::BoldWall },
57+
Item { y: 2, x: 7, color: "black", kind: ItemKind::BoldWall },
58+
Item { y: 4, x: 1, color: "black", kind: ItemKind::BoldWall },
59+
Item { y: 3, x: 2, color: "black", kind: ItemKind::BoldWall },
60+
Item { y: 4, x: 3, color: "black", kind: ItemKind::BoldWall },
61+
Item { y: 3, x: 8, color: "black", kind: ItemKind::BoldWall },
62+
Item { y: 4, x: 9, color: "black", kind: ItemKind::BoldWall },
63+
Item { y: 6, x: 3, color: "black", kind: ItemKind::BoldWall },
64+
Item { y: 5, x: 4, color: "black", kind: ItemKind::BoldWall },
65+
Item { y: 6, x: 5, color: "black", kind: ItemKind::BoldWall },
66+
Item { y: 6, x: 7, color: "black", kind: ItemKind::BoldWall },
67+
Item { y: 6, x: 9, color: "black", kind: ItemKind::BoldWall },
68+
Item { y: 8, x: 1, color: "black", kind: ItemKind::BoldWall },
69+
Item { y: 7, x: 2, color: "black", kind: ItemKind::BoldWall },
70+
Item { y: 7, x: 6, color: "black", kind: ItemKind::BoldWall },
71+
Item { y: 9, x: 6, color: "black", kind: ItemKind::BoldWall },
72+
Item { y: 1, x: 1, color: "green", kind: ItemKind::Dot },
73+
Item { y: 1, x: 3, color: "green", kind: ItemKind::Block },
74+
Item { y: 1, x: 5, color: "green", kind: ItemKind::Dot },
75+
Item { y: 1, x: 7, color: "green", kind: ItemKind::Block },
76+
Item { y: 1, x: 9, color: "green", kind: ItemKind::Dot },
77+
Item { y: 3, x: 1, color: "green", kind: ItemKind::Block },
78+
Item { y: 3, x: 3, color: "green", kind: ItemKind::Block },
79+
Item { y: 3, x: 5, color: "green", kind: ItemKind::Block },
80+
Item { y: 3, x: 7, color: "green", kind: ItemKind::Block },
81+
Item { y: 3, x: 9, color: "green", kind: ItemKind::Block },
82+
Item { y: 5, x: 1, color: "green", kind: ItemKind::Dot },
83+
Item { y: 5, x: 3, color: "green", kind: ItemKind::Block },
84+
Item { y: 5, x: 5, color: "green", kind: ItemKind::Dot },
85+
Item { y: 5, x: 7, color: "green", kind: ItemKind::Dot },
86+
Item { y: 5, x: 9, color: "green", kind: ItemKind::Block },
87+
Item { y: 7, x: 1, color: "green", kind: ItemKind::Block },
88+
Item { y: 7, x: 3, color: "green", kind: ItemKind::Block },
89+
Item { y: 7, x: 5, color: "green", kind: ItemKind::Block },
90+
Item { y: 7, x: 7, color: "green", kind: ItemKind::Dot },
91+
Item { y: 7, x: 9, color: "green", kind: ItemKind::Block },
92+
Item { y: 9, x: 1, color: "green", kind: ItemKind::Block },
93+
Item { y: 9, x: 3, color: "green", kind: ItemKind::Dot },
94+
Item { y: 9, x: 5, color: "green", kind: ItemKind::Block },
95+
Item { y: 9, x: 7, color: "green", kind: ItemKind::Block },
96+
Item { y: 9, x: 9, color: "green", kind: ItemKind::Dot },
97+
],
98+
uniqueness: Uniqueness::Unique,
99+
},
100+
);
101+
}
102+
}

cspuz_solver_backend/src/puzzle/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ puzzle_list!(puzz_link,
119119
(cross_the_streams, ["cts"], "Cross the Streams", "Cross the Streams"),
120120
(curvedata, ["curvedata"], "Curve Data", "カーブデータ", enumerable),
121121
(dbchoco, ["dbchoco"], "Double Choco", "ダブルチョコ"),
122+
(disco, ["disco"], "Disconnection", "ディスコネクション"),
122123
(dominion, ["dominion"], "Dominion", "ドミニオン"),
123124
(doppelblock, ["doppelblock"], "Doppelblock", "ビトゥイーン・サム"),
124125
(doubleback, ["doubleback"], "Double Back", "Double Back"),

0 commit comments

Comments
 (0)