Skip to content

Commit d2bd6e9

Browse files
committed
Add solver for Slant
1 parent c12a85b commit d2bd6e9

4 files changed

Lines changed: 315 additions & 0 deletions

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
use crate::util;
2+
use cspuz_rs::graph;
3+
use cspuz_rs::serializer::{
4+
problem_to_url_with_context, url_to_problem, Choice, Combinator, Context, ContextBasedGrid,
5+
Dict, NumSpaces, Size, Spaces,
6+
};
7+
use cspuz_rs::solver::{count_true, Solver, TRUE};
8+
9+
pub const GOKIGEN_SLASH: i32 = 0;
10+
pub const GOKIGEN_BACKSLASH: i32 = 1;
11+
12+
pub fn solve_gokigen(clues: &[Vec<Option<i32>>]) -> Option<Vec<Vec<Option<i32>>>> {
13+
let h = clues.len() - 1;
14+
let w = clues[0].len() - 1;
15+
16+
let mut solver = Solver::new();
17+
let ans = &solver.int_var_2d((h, w), 0, 1);
18+
solver.add_answer_key_int(ans);
19+
20+
for y in 0..=h {
21+
for x in 0..=w {
22+
if let Some(n) = clues[y][x] {
23+
let mut adj = vec![];
24+
25+
if y > 0 && x > 0 {
26+
adj.push(ans.at((y - 1, x - 1)).eq(GOKIGEN_BACKSLASH));
27+
}
28+
if y > 0 && x < w {
29+
adj.push(ans.at((y - 1, x)).eq(GOKIGEN_SLASH));
30+
}
31+
if y < h && x > 0 {
32+
adj.push(ans.at((y, x - 1)).eq(GOKIGEN_SLASH));
33+
}
34+
if y < h && x < w {
35+
adj.push(ans.at((y, x)).eq(GOKIGEN_BACKSLASH));
36+
}
37+
solver.add_expr(count_true(adj).eq(n));
38+
}
39+
}
40+
}
41+
42+
/*
43+
Each cell contains 4 segments:
44+
0 /\ 1
45+
2 \/ 3
46+
*/
47+
let mut g = graph::Graph::new(h * w * 4 + 1);
48+
let mut is_active = vec![];
49+
for y in 0..(h * 2) {
50+
for x in 0..(w * 2) {
51+
if y == 0 || y == h * 2 - 1 || x == 0 || x == w * 2 - 1 {
52+
g.add_edge(h * w * 4, y * w * 2 + x);
53+
}
54+
if y % 2 == x % 2 {
55+
is_active.push(ans.at((y / 2, x / 2)).eq(GOKIGEN_SLASH));
56+
} else {
57+
is_active.push(ans.at((y / 2, x / 2)).eq(GOKIGEN_BACKSLASH));
58+
}
59+
60+
if y % 2 == x % 2 {
61+
if y < h * 2 - 1 {
62+
if x > 0 {
63+
g.add_edge(y * w * 2 + x, (y + 1) * w * 2 + x - 1);
64+
}
65+
g.add_edge(y * w * 2 + x, (y + 1) * w * 2 + x);
66+
}
67+
if x < w * 2 - 1 {
68+
g.add_edge(y * w * 2 + x, y * w * 2 + x + 1);
69+
}
70+
} else {
71+
if y < h * 2 - 1 {
72+
g.add_edge(y * w * 2 + x, (y + 1) * w * 2 + x);
73+
}
74+
if x < w * 2 - 1 {
75+
g.add_edge(y * w * 2 + x, y * w * 2 + x + 1);
76+
}
77+
if y < h * 2 - 1 && x < w * 2 - 1 {
78+
g.add_edge(y * w * 2 + x, (y + 1) * w * 2 + x + 1);
79+
}
80+
}
81+
}
82+
}
83+
is_active.push(TRUE);
84+
graph::active_vertices_connected(&mut solver, &is_active, &g);
85+
86+
solver.irrefutable_facts().map(|f| f.get(ans))
87+
}
88+
89+
type Problem = Vec<Vec<Option<i32>>>;
90+
91+
fn combinator() -> impl Combinator<Problem> {
92+
Size::with_offset(
93+
ContextBasedGrid::new(Choice::new(vec![
94+
Box::new(NumSpaces::new(4, 2)),
95+
Box::new(Spaces::new(None, 'g')),
96+
Box::new(Dict::new(Some(-1), ".")),
97+
])),
98+
1,
99+
)
100+
}
101+
102+
pub fn serialize_problem(problem: &Problem) -> Option<String> {
103+
let (h, w) = util::infer_shape(problem);
104+
problem_to_url_with_context(
105+
combinator(),
106+
"gokigen",
107+
problem.clone(),
108+
&Context::sized(h, w),
109+
)
110+
}
111+
112+
pub fn deserialize_problem(url: &str) -> Option<Problem> {
113+
url_to_problem(combinator(), &["gokigen"], url)
114+
}
115+
116+
#[cfg(test)]
117+
mod tests {
118+
use super::*;
119+
120+
#[rustfmt::skip]
121+
fn problem_for_tests() -> Problem {
122+
vec![
123+
vec![None, None, None, None, None],
124+
vec![None, Some(3), Some(2), None, Some(2)],
125+
vec![Some(1), None, Some(1), None, Some(1)],
126+
vec![None, Some(1), None, None, None],
127+
]
128+
}
129+
130+
#[test]
131+
fn test_gokigen_problem() {
132+
let problem = problem_for_tests();
133+
let ans = solve_gokigen(&problem);
134+
assert!(ans.is_some());
135+
let ans = ans.unwrap();
136+
137+
let expected = crate::util::tests::to_option_2d([[1, 0, 1, 1], [0, 0, 1, 0], [0, 0, 0, 0]]);
138+
assert_eq!(ans, expected);
139+
}
140+
141+
#[test]
142+
fn test_gokigen_serializer() {
143+
let problem = problem_for_tests();
144+
let url = "https://puzz.link/p?gokigen/4/3/l372666bg";
145+
util::tests::serializer_test(problem, url, serialize_problem, deserialize_problem);
146+
}
147+
}

cspuz_rs_puzzles/src/puzzles/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub mod firefly;
4646
pub mod firewalk;
4747
pub mod forestwalk;
4848
pub mod geradeweg;
49+
pub mod gokigen;
4950
pub mod guidearrow;
5051
pub mod hashi;
5152
pub mod hebiichigo;
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
use crate::board::{Board, BoardKind, Item, ItemKind};
2+
use crate::uniqueness::check_uniqueness;
3+
use cspuz_rs_puzzles::puzzles::gokigen::{self, GOKIGEN_SLASH};
4+
5+
pub fn solve(url: &str) -> Result<Board, &'static str> {
6+
let problem = gokigen::deserialize_problem(url).ok_or("invalid url")?;
7+
let ans = gokigen::solve_gokigen(&problem);
8+
9+
let height = problem.len() - 1;
10+
let width = problem[0].len() - 1;
11+
let mut board = Board::new(BoardKind::Empty, height, width, check_uniqueness(&ans));
12+
if let Some(ans) = ans {
13+
for y in 0..height {
14+
for x in 0..width {
15+
if let Some(a) = ans[y][x] {
16+
board.push(Item::cell(
17+
y,
18+
x,
19+
"green",
20+
if a == GOKIGEN_SLASH {
21+
ItemKind::Slash
22+
} else {
23+
ItemKind::Backslash
24+
},
25+
));
26+
}
27+
}
28+
}
29+
}
30+
for y in 0..=height {
31+
for x in 0..=width {
32+
if y < height {
33+
board.push(Item {
34+
y: y * 2 + 1,
35+
x: x * 2,
36+
color: "black",
37+
kind: ItemKind::DottedWall,
38+
});
39+
}
40+
if x < width {
41+
board.push(Item {
42+
y: y * 2,
43+
x: x * 2 + 1,
44+
color: "black",
45+
kind: ItemKind::DottedWall,
46+
});
47+
}
48+
}
49+
}
50+
for y in 0..=height {
51+
for x in 0..=width {
52+
if let Some(n) = problem[y][x] {
53+
board.push(Item {
54+
y: y * 2,
55+
x: x * 2,
56+
color: "white",
57+
kind: ItemKind::FilledCircle,
58+
});
59+
board.push(Item {
60+
y: y * 2,
61+
x: x * 2,
62+
color: "black",
63+
kind: ItemKind::Circle,
64+
});
65+
if n >= 0 {
66+
board.push(Item {
67+
y: y * 2,
68+
x: x * 2,
69+
color: "black",
70+
kind: ItemKind::Num(n),
71+
});
72+
}
73+
}
74+
}
75+
}
76+
77+
Ok(board)
78+
}
79+
80+
#[cfg(test)]
81+
mod tests {
82+
use super::solve;
83+
use crate::board::*;
84+
use crate::compare_board_and_check_no_solution_case;
85+
use crate::uniqueness::Uniqueness;
86+
87+
#[test]
88+
#[rustfmt::skip]
89+
fn test_solve() {
90+
compare_board_and_check_no_solution_case!(
91+
solve("https://puzz.link/p?gokigen/4/3/l372666bg"),
92+
Board {
93+
kind: BoardKind::Empty,
94+
height: 3,
95+
width: 4,
96+
data: vec![
97+
Item { y: 1, x: 1, color: "green", kind: ItemKind::Backslash },
98+
Item { y: 1, x: 3, color: "green", kind: ItemKind::Slash },
99+
Item { y: 1, x: 5, color: "green", kind: ItemKind::Backslash },
100+
Item { y: 1, x: 7, color: "green", kind: ItemKind::Backslash },
101+
Item { y: 3, x: 1, color: "green", kind: ItemKind::Slash },
102+
Item { y: 3, x: 3, color: "green", kind: ItemKind::Slash },
103+
Item { y: 3, x: 5, color: "green", kind: ItemKind::Backslash },
104+
Item { y: 3, x: 7, color: "green", kind: ItemKind::Slash },
105+
Item { y: 5, x: 1, color: "green", kind: ItemKind::Slash },
106+
Item { y: 5, x: 3, color: "green", kind: ItemKind::Slash },
107+
Item { y: 5, x: 5, color: "green", kind: ItemKind::Slash },
108+
Item { y: 5, x: 7, color: "green", kind: ItemKind::Slash },
109+
Item { y: 1, x: 0, color: "black", kind: ItemKind::DottedWall },
110+
Item { y: 0, x: 1, color: "black", kind: ItemKind::DottedWall },
111+
Item { y: 1, x: 2, color: "black", kind: ItemKind::DottedWall },
112+
Item { y: 0, x: 3, color: "black", kind: ItemKind::DottedWall },
113+
Item { y: 1, x: 4, color: "black", kind: ItemKind::DottedWall },
114+
Item { y: 0, x: 5, color: "black", kind: ItemKind::DottedWall },
115+
Item { y: 1, x: 6, color: "black", kind: ItemKind::DottedWall },
116+
Item { y: 0, x: 7, color: "black", kind: ItemKind::DottedWall },
117+
Item { y: 1, x: 8, color: "black", kind: ItemKind::DottedWall },
118+
Item { y: 3, x: 0, color: "black", kind: ItemKind::DottedWall },
119+
Item { y: 2, x: 1, color: "black", kind: ItemKind::DottedWall },
120+
Item { y: 3, x: 2, color: "black", kind: ItemKind::DottedWall },
121+
Item { y: 2, x: 3, color: "black", kind: ItemKind::DottedWall },
122+
Item { y: 3, x: 4, color: "black", kind: ItemKind::DottedWall },
123+
Item { y: 2, x: 5, color: "black", kind: ItemKind::DottedWall },
124+
Item { y: 3, x: 6, color: "black", kind: ItemKind::DottedWall },
125+
Item { y: 2, x: 7, color: "black", kind: ItemKind::DottedWall },
126+
Item { y: 3, x: 8, color: "black", kind: ItemKind::DottedWall },
127+
Item { y: 5, x: 0, color: "black", kind: ItemKind::DottedWall },
128+
Item { y: 4, x: 1, color: "black", kind: ItemKind::DottedWall },
129+
Item { y: 5, x: 2, color: "black", kind: ItemKind::DottedWall },
130+
Item { y: 4, x: 3, color: "black", kind: ItemKind::DottedWall },
131+
Item { y: 5, x: 4, color: "black", kind: ItemKind::DottedWall },
132+
Item { y: 4, x: 5, color: "black", kind: ItemKind::DottedWall },
133+
Item { y: 5, x: 6, color: "black", kind: ItemKind::DottedWall },
134+
Item { y: 4, x: 7, color: "black", kind: ItemKind::DottedWall },
135+
Item { y: 5, x: 8, color: "black", kind: ItemKind::DottedWall },
136+
Item { y: 6, x: 1, color: "black", kind: ItemKind::DottedWall },
137+
Item { y: 6, x: 3, color: "black", kind: ItemKind::DottedWall },
138+
Item { y: 6, x: 5, color: "black", kind: ItemKind::DottedWall },
139+
Item { y: 6, x: 7, color: "black", kind: ItemKind::DottedWall },
140+
Item { y: 2, x: 2, color: "white", kind: ItemKind::FilledCircle },
141+
Item { y: 2, x: 2, color: "black", kind: ItemKind::Circle },
142+
Item { y: 2, x: 2, color: "black", kind: ItemKind::Num(3) },
143+
Item { y: 2, x: 4, color: "white", kind: ItemKind::FilledCircle },
144+
Item { y: 2, x: 4, color: "black", kind: ItemKind::Circle },
145+
Item { y: 2, x: 4, color: "black", kind: ItemKind::Num(2) },
146+
Item { y: 2, x: 8, color: "white", kind: ItemKind::FilledCircle },
147+
Item { y: 2, x: 8, color: "black", kind: ItemKind::Circle },
148+
Item { y: 2, x: 8, color: "black", kind: ItemKind::Num(2) },
149+
Item { y: 4, x: 0, color: "white", kind: ItemKind::FilledCircle },
150+
Item { y: 4, x: 0, color: "black", kind: ItemKind::Circle },
151+
Item { y: 4, x: 0, color: "black", kind: ItemKind::Num(1) },
152+
Item { y: 4, x: 4, color: "white", kind: ItemKind::FilledCircle },
153+
Item { y: 4, x: 4, color: "black", kind: ItemKind::Circle },
154+
Item { y: 4, x: 4, color: "black", kind: ItemKind::Num(1) },
155+
Item { y: 4, x: 8, color: "white", kind: ItemKind::FilledCircle },
156+
Item { y: 4, x: 8, color: "black", kind: ItemKind::Circle },
157+
Item { y: 4, x: 8, color: "black", kind: ItemKind::Num(1) },
158+
Item { y: 6, x: 2, color: "white", kind: ItemKind::FilledCircle },
159+
Item { y: 6, x: 2, color: "black", kind: ItemKind::Circle },
160+
Item { y: 6, x: 2, color: "black", kind: ItemKind::Num(1) },
161+
],
162+
uniqueness: Uniqueness::Unique,
163+
},
164+
);
165+
}
166+
}

cspuz_solver_backend/src/puzzle/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ puzzle_list!(puzz_link,
129129
(forestwalk, ["forestwalk"], "Forest Walk", "フォレストウォーク"),
130130
(fourcells, ["fourcells"], "Fourcells", "フォーセルズ"),
131131
(geradeweg, ["geradeweg"], "Geradeweg", "グラーデヴェグ"),
132+
(gokigen, ["gokigen"], "Slant", "ごきげんななめ"),
132133
(guidearrow, ["guidearrow"], "Guide Arrow", "ガイドアロー"),
133134
(hashi, ["hashi"], "Hashiwokakero", "橋をかけろ"),
134135
(hebiichigo, ["hebi"], "Hebi-Ichigo", "へびいちご"),

0 commit comments

Comments
 (0)