Skip to content

Commit aa52caf

Browse files
committed
Add solver for Nanameguri
1 parent b442cc8 commit aa52caf

4 files changed

Lines changed: 353 additions & 0 deletions

File tree

cspuz_rs_puzzles/src/puzzles/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ pub mod move_common;
8686
pub mod multiplication_link;
8787
pub mod n_cells;
8888
pub mod nagenawa;
89+
pub mod nanameguri;
8990
pub mod nanro;
9091
pub mod nikoji;
9192
pub mod nonogram;
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
use crate::util;
2+
use cspuz_rs::graph;
3+
use cspuz_rs::serializer::{
4+
problem_to_url_with_context, url_to_problem, Combinator, Context, ContextBasedGrid, MultiDigit,
5+
Rooms, Size, Tuple2,
6+
};
7+
use cspuz_rs::solver::{any, count_true, Solver};
8+
9+
pub const NANAMEGURI_EMPTY: i32 = 0;
10+
pub const NANAMEGURI_BACKSLASH: i32 = 1;
11+
pub const NANAMEGURI_SLASH: i32 = 2;
12+
13+
pub fn solve_nanameguri(
14+
borders: &graph::InnerGridEdges<Vec<Vec<bool>>>,
15+
cells: &[Vec<i32>],
16+
) -> Option<graph::BoolGridEdgesIrrefutableFacts> {
17+
let (h, w) = util::infer_shape(cells);
18+
19+
let mut solver = Solver::new();
20+
let is_line = &graph::BoolGridEdges::new(&mut solver, (h - 1, w - 1));
21+
solver.add_answer_key_bool(&is_line.horizontal);
22+
solver.add_answer_key_bool(&is_line.vertical);
23+
24+
graph::single_cycle_grid_edges(&mut solver, is_line);
25+
26+
for y in 0..h {
27+
for x in 0..w {
28+
if cells[y][x] == NANAMEGURI_SLASH {
29+
let mut cands = vec![];
30+
if y > 0 && x > 0 {
31+
cands.push(is_line.vertical.at((y - 1, x)) & is_line.horizontal.at((y, x - 1)));
32+
}
33+
if y < h - 1 && x < w - 1 {
34+
cands.push(is_line.vertical.at((y, x)) & is_line.horizontal.at((y, x)));
35+
}
36+
solver.add_expr(any(cands));
37+
} else if cells[y][x] == NANAMEGURI_BACKSLASH {
38+
let mut cands = vec![];
39+
if y > 0 && x < w - 1 {
40+
cands.push(is_line.vertical.at((y - 1, x)) & is_line.horizontal.at((y, x)));
41+
}
42+
if y < h - 1 && x > 0 {
43+
cands.push(is_line.vertical.at((y, x)) & is_line.horizontal.at((y, x - 1)));
44+
}
45+
solver.add_expr(any(cands));
46+
}
47+
}
48+
}
49+
50+
// each cell has 4 segments:
51+
// \ 0 /
52+
// \ /
53+
// 1 X 2
54+
// / \
55+
// / 3 \
56+
let mut room_id = vec![vec![[!0, !0, !0, !0]; w]; h];
57+
let mut idx = 0;
58+
59+
fn visit(
60+
room_id: &mut Vec<Vec<[usize; 4]>>,
61+
borders: &graph::InnerGridEdges<Vec<Vec<bool>>>,
62+
cells: &[Vec<i32>],
63+
y: usize,
64+
x: usize,
65+
seg: usize,
66+
idx: usize,
67+
) {
68+
if room_id[y][x][seg] != !0 {
69+
return;
70+
}
71+
room_id[y][x][seg] = idx;
72+
73+
match seg {
74+
0 => {
75+
if y > 0 && !borders.horizontal[y - 1][x] {
76+
visit(room_id, borders, cells, y - 1, x, 3, idx);
77+
}
78+
if cells[y][x] != NANAMEGURI_BACKSLASH {
79+
visit(room_id, borders, cells, y, x, 1, idx);
80+
}
81+
if cells[y][x] != NANAMEGURI_SLASH {
82+
visit(room_id, borders, cells, y, x, 2, idx);
83+
}
84+
}
85+
1 => {
86+
if x > 0 && !borders.vertical[y][x - 1] {
87+
visit(room_id, borders, cells, y, x - 1, 2, idx);
88+
}
89+
if cells[y][x] != NANAMEGURI_BACKSLASH {
90+
visit(room_id, borders, cells, y, x, 0, idx);
91+
}
92+
if cells[y][x] != NANAMEGURI_SLASH {
93+
visit(room_id, borders, cells, y, x, 3, idx);
94+
}
95+
}
96+
2 => {
97+
if x < cells[0].len() - 1 && !borders.vertical[y][x] {
98+
visit(room_id, borders, cells, y, x + 1, 1, idx);
99+
}
100+
if cells[y][x] != NANAMEGURI_BACKSLASH {
101+
visit(room_id, borders, cells, y, x, 3, idx);
102+
}
103+
if cells[y][x] != NANAMEGURI_SLASH {
104+
visit(room_id, borders, cells, y, x, 0, idx);
105+
}
106+
}
107+
3 => {
108+
if y < cells.len() - 1 && !borders.horizontal[y][x] {
109+
visit(room_id, borders, cells, y + 1, x, 0, idx);
110+
}
111+
if cells[y][x] != NANAMEGURI_BACKSLASH {
112+
visit(room_id, borders, cells, y, x, 2, idx);
113+
}
114+
if cells[y][x] != NANAMEGURI_SLASH {
115+
visit(room_id, borders, cells, y, x, 1, idx);
116+
}
117+
}
118+
_ => unreachable!(),
119+
}
120+
}
121+
122+
for y in 0..h {
123+
for x in 0..w {
124+
for seg in 0..4 {
125+
if room_id[y][x][seg] == !0 {
126+
visit(&mut room_id, borders, cells, y, x, seg, idx);
127+
idx += 1;
128+
}
129+
}
130+
}
131+
}
132+
133+
let mut borders_by_room = vec![vec![]; idx];
134+
for y in 0..h {
135+
for x in 0..w {
136+
if y > 0 && borders.horizontal[y - 1][x] {
137+
borders_by_room[room_id[y][x][0]].push(is_line.vertical.at((y - 1, x)));
138+
}
139+
if x > 0 && borders.vertical[y][x - 1] {
140+
borders_by_room[room_id[y][x][1]].push(is_line.horizontal.at((y, x - 1)));
141+
}
142+
if x < w - 1 && borders.vertical[y][x] {
143+
borders_by_room[room_id[y][x][2]].push(is_line.horizontal.at((y, x)));
144+
}
145+
if y < h - 1 && borders.horizontal[y][x] {
146+
borders_by_room[room_id[y][x][3]].push(is_line.vertical.at((y, x)));
147+
}
148+
}
149+
}
150+
151+
if idx == 1 {
152+
solver.add_expr(!any(&borders_by_room[0]));
153+
} else {
154+
for room in &borders_by_room {
155+
solver.add_expr(count_true(room).eq(2));
156+
}
157+
}
158+
159+
solver.irrefutable_facts().map(|f| f.get(is_line))
160+
}
161+
162+
pub type Problem = (graph::InnerGridEdges<Vec<Vec<bool>>>, Vec<Vec<i32>>);
163+
164+
fn combinator() -> impl Combinator<Problem> {
165+
Size::new(Tuple2::new(
166+
Rooms,
167+
ContextBasedGrid::new(MultiDigit::new(3, 3)),
168+
))
169+
}
170+
171+
pub fn serialize_problem(problem: &Problem) -> Option<String> {
172+
let height = problem.0.vertical.len();
173+
let width = problem.0.vertical[0].len() + 1;
174+
problem_to_url_with_context(
175+
combinator(),
176+
"nanameguri",
177+
problem.clone(),
178+
&Context::sized(height, width),
179+
)
180+
}
181+
182+
pub fn deserialize_problem(url: &str) -> Option<Problem> {
183+
url_to_problem(combinator(), &["nanameguri"], url)
184+
}
185+
186+
#[cfg(test)]
187+
mod tests {
188+
use super::*;
189+
190+
fn problem_for_tests() -> Problem {
191+
let borders = graph::InnerGridEdges {
192+
horizontal: crate::util::tests::to_bool_2d([
193+
[1, 1, 0, 1, 0],
194+
[0, 0, 1, 1, 1],
195+
[0, 0, 0, 0, 0],
196+
]),
197+
vertical: crate::util::tests::to_bool_2d([
198+
[0, 0, 0, 0],
199+
[1, 0, 1, 0],
200+
[0, 1, 0, 1],
201+
[0, 0, 0, 0],
202+
]),
203+
};
204+
205+
let cells = vec![
206+
vec![0, 1, 0, 1, 0],
207+
vec![0, 0, 0, 0, 0],
208+
vec![0, 1, 0, 0, 0],
209+
vec![0, 0, 0, 2, 0],
210+
];
211+
212+
(borders, cells)
213+
}
214+
215+
#[test]
216+
#[rustfmt::skip]
217+
fn test_nanameguri_problem() {
218+
let (borders, cells) = problem_for_tests();
219+
let ans = solve_nanameguri(&borders, &cells);
220+
assert!(ans.is_some());
221+
let ans = ans.unwrap();
222+
223+
let expected = graph::BoolGridEdgesIrrefutableFacts {
224+
horizontal: util::tests::to_option_bool_2d([
225+
[1, 0, 1, 0],
226+
[0, 1, 0, 1],
227+
[1, 0, 0, 1],
228+
[0, 1, 1, 0],
229+
]),
230+
vertical: util::tests::to_option_bool_2d([
231+
[1, 1, 1, 1, 0],
232+
[1, 0, 0, 0, 1],
233+
[0, 1, 0, 1, 0],
234+
]),
235+
};
236+
assert_eq!(ans, expected);
237+
}
238+
239+
#[test]
240+
fn test_nanameguri_serializer() {
241+
{
242+
let problem = problem_for_tests();
243+
let url = "https://puzz.link/p?nanameguri/5/4/1980q70390100i";
244+
util::tests::serializer_test(problem, url, serialize_problem, deserialize_problem);
245+
}
246+
}
247+
}

cspuz_solver_backend/src/puzzle/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ puzzle_list!(puzz_link,
160160
(moonsun, ["moonsun"], "Moon or Sun", "月か太陽"),
161161
(morningwalk, ["morningwalk"], "Morning Walk", "Morning Walk"),
162162
(nagenawa, ["nagenawa"], "Nagenawa", "なげなわ"),
163+
(nanameguri, ["nanameguri"], "Nanameguri", "ななめぐり"),
163164
(nanro, ["nanro"], "Nanro", "ナンロー"),
164165
(nikoji, ["nikoji"], "NIKOJI", "NIKOJI"),
165166
(nonogram, ["nonogram"], "Nonogram", "お絵かきロジック"),
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use crate::board::{Board, BoardKind, Item, ItemKind};
2+
use crate::uniqueness::check_uniqueness;
3+
use cspuz_rs_puzzles::puzzles::nanameguri;
4+
5+
pub fn solve(url: &str) -> Result<Board, &'static str> {
6+
let (borders, cells) = nanameguri::deserialize_problem(url).ok_or("invalid url")?;
7+
let ans = nanameguri::solve_nanameguri(&borders, &cells);
8+
9+
let height = cells.len();
10+
let width = cells[0].len();
11+
let mut board = Board::new(BoardKind::Grid, height, width, check_uniqueness(&ans));
12+
13+
board.add_borders(&borders, "black");
14+
15+
for y in 0..height {
16+
for x in 0..width {
17+
match cells[y][x] {
18+
nanameguri::NANAMEGURI_EMPTY => (),
19+
nanameguri::NANAMEGURI_BACKSLASH => {
20+
board.push(Item::cell(y, x, "black", ItemKind::Backslash))
21+
}
22+
nanameguri::NANAMEGURI_SLASH => {
23+
board.push(Item::cell(y, x, "black", ItemKind::Slash))
24+
}
25+
_ => unreachable!(),
26+
}
27+
}
28+
}
29+
30+
if let Some(is_line) = &ans {
31+
board.add_lines_irrefutable_facts(is_line, "green", None);
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://puzz.link/p?nanameguri/5/4/1980q70390100i"),
49+
Board {
50+
kind: BoardKind::Grid,
51+
height: 4,
52+
width: 5,
53+
data: vec![
54+
Item { y: 2, x: 1, color: "black", kind: ItemKind::BoldWall },
55+
Item { y: 2, x: 3, color: "black", kind: ItemKind::BoldWall },
56+
Item { y: 2, x: 7, color: "black", kind: ItemKind::BoldWall },
57+
Item { y: 3, x: 2, color: "black", kind: ItemKind::BoldWall },
58+
Item { y: 4, x: 5, color: "black", kind: ItemKind::BoldWall },
59+
Item { y: 3, x: 6, color: "black", kind: ItemKind::BoldWall },
60+
Item { y: 4, x: 7, color: "black", kind: ItemKind::BoldWall },
61+
Item { y: 4, x: 9, color: "black", kind: ItemKind::BoldWall },
62+
Item { y: 5, x: 4, color: "black", kind: ItemKind::BoldWall },
63+
Item { y: 5, x: 8, color: "black", kind: ItemKind::BoldWall },
64+
Item { y: 1, x: 3, color: "black", kind: ItemKind::Backslash },
65+
Item { y: 1, x: 7, color: "black", kind: ItemKind::Backslash },
66+
Item { y: 5, x: 3, color: "black", kind: ItemKind::Backslash },
67+
Item { y: 7, x: 7, color: "black", kind: ItemKind::Slash },
68+
Item { y: 2, x: 1, color: "green", kind: ItemKind::Line },
69+
Item { y: 2, x: 3, color: "green", kind: ItemKind::Line },
70+
Item { y: 2, x: 5, color: "green", kind: ItemKind::Line },
71+
Item { y: 2, x: 7, color: "green", kind: ItemKind::Line },
72+
Item { y: 2, x: 9, color: "green", kind: ItemKind::Cross },
73+
Item { y: 4, x: 1, color: "green", kind: ItemKind::Line },
74+
Item { y: 4, x: 3, color: "green", kind: ItemKind::Cross },
75+
Item { y: 4, x: 5, color: "green", kind: ItemKind::Cross },
76+
Item { y: 4, x: 7, color: "green", kind: ItemKind::Cross },
77+
Item { y: 4, x: 9, color: "green", kind: ItemKind::Line },
78+
Item { y: 6, x: 1, color: "green", kind: ItemKind::Cross },
79+
Item { y: 6, x: 3, color: "green", kind: ItemKind::Line },
80+
Item { y: 6, x: 5, color: "green", kind: ItemKind::Cross },
81+
Item { y: 6, x: 7, color: "green", kind: ItemKind::Line },
82+
Item { y: 6, x: 9, color: "green", kind: ItemKind::Cross },
83+
Item { y: 1, x: 2, color: "green", kind: ItemKind::Line },
84+
Item { y: 1, x: 4, color: "green", kind: ItemKind::Cross },
85+
Item { y: 1, x: 6, color: "green", kind: ItemKind::Line },
86+
Item { y: 1, x: 8, color: "green", kind: ItemKind::Cross },
87+
Item { y: 3, x: 2, color: "green", kind: ItemKind::Cross },
88+
Item { y: 3, x: 4, color: "green", kind: ItemKind::Line },
89+
Item { y: 3, x: 6, color: "green", kind: ItemKind::Cross },
90+
Item { y: 3, x: 8, color: "green", kind: ItemKind::Line },
91+
Item { y: 5, x: 2, color: "green", kind: ItemKind::Line },
92+
Item { y: 5, x: 4, color: "green", kind: ItemKind::Cross },
93+
Item { y: 5, x: 6, color: "green", kind: ItemKind::Cross },
94+
Item { y: 5, x: 8, color: "green", kind: ItemKind::Line },
95+
Item { y: 7, x: 2, color: "green", kind: ItemKind::Cross },
96+
Item { y: 7, x: 4, color: "green", kind: ItemKind::Line },
97+
Item { y: 7, x: 6, color: "green", kind: ItemKind::Line },
98+
Item { y: 7, x: 8, color: "green", kind: ItemKind::Cross },
99+
],
100+
uniqueness: Uniqueness::Unique,
101+
},
102+
);
103+
}
104+
}

0 commit comments

Comments
 (0)