From 3353f2f33d828afc9937c9f032ae3150166dad84 Mon Sep 17 00:00:00 2001 From: ReverM Date: Tue, 3 Mar 2026 11:04:14 -0500 Subject: [PATCH] Added solver for simple gako --- cspuz_rs_puzzles/src/puzzles/mod.rs | 1 + cspuz_rs_puzzles/src/puzzles/simplegako.rs | 85 +++++++++++++++++++ cspuz_solver_backend/src/puzzle/mod.rs | 1 + cspuz_solver_backend/src/puzzle/simplegako.rs | 70 +++++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 cspuz_rs_puzzles/src/puzzles/simplegako.rs create mode 100644 cspuz_solver_backend/src/puzzle/simplegako.rs diff --git a/cspuz_rs_puzzles/src/puzzles/mod.rs b/cspuz_rs_puzzles/src/puzzles/mod.rs index daf2fc27..3678d29c 100644 --- a/cspuz_rs_puzzles/src/puzzles/mod.rs +++ b/cspuz_rs_puzzles/src/puzzles/mod.rs @@ -110,6 +110,7 @@ pub mod sendai; pub mod shakashaka; pub mod shikaku; pub mod shimaguni; +pub mod simplegako; pub mod simpleloop; pub mod slalom; pub mod slashpack; diff --git a/cspuz_rs_puzzles/src/puzzles/simplegako.rs b/cspuz_rs_puzzles/src/puzzles/simplegako.rs new file mode 100644 index 00000000..a899916b --- /dev/null +++ b/cspuz_rs_puzzles/src/puzzles/simplegako.rs @@ -0,0 +1,85 @@ +use crate::util; +use cspuz_rs::serializer::{ + problem_to_url, url_to_problem, Choice, Combinator, Dict, Grid, HexInt, Optionalize, Spaces, +}; +use cspuz_rs::solver::Solver; + +pub fn solve_simplegako(clues: &[Vec>]) -> Option>>> { + let (h, w) = util::infer_shape(clues); + + let mut solver = Solver::new(); + let num = &solver.int_var_2d((h, w), 1, (h + w) as i32); + solver.add_answer_key_int(num); + + for y in 0..h { + for x in 0..w { + if let Some(c) = clues[y][x] { + if c > 0 { + solver.add_expr(num.at((y, x)).eq(c)); + } + } + solver.add_expr( + (num.slice_fixed_x((.., x)).eq(num.at((y, x))).count_true() + + num.slice_fixed_y((y, ..)).eq(num.at((y, x))).count_true()) + .eq(num.at((y, x)) + 1), + ); + } + } + + solver.irrefutable_facts().map(|f| f.get(num)) +} + +type Problem = Vec>>; + +fn combinator() -> impl Combinator { + Grid::new(Choice::new(vec![ + Box::new(Optionalize::new(HexInt)), + Box::new(Spaces::new(None, 'g')), + Box::new(Dict::new(Some(-1), ".")), + ])) +} + +pub fn serialize_problem(problem: &Problem) -> Option { + problem_to_url(combinator(), "simplegako", problem.clone()) +} + +pub fn deserialize_problem(url: &str) -> Option { + url_to_problem(combinator(), &["simplegako"], url) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn problem_for_tests() -> Problem { + vec![ + vec![None, None, None, None], + vec![None, Some(5), None, Some(1)], + vec![Some(2), None, Some(2), None], + vec![None, None, None, None], + ] + } + + #[test] + fn test_simplegako_problem() { + let problem = problem_for_tests(); + let ans = solve_simplegako(&problem); + assert!(ans.is_some()); + let ans = ans.unwrap(); + + let expected = crate::util::tests::to_option_2d([ + [5, 5, 5, 3], + [5, 5, 5, 1], + [2, 1, 2, 3], + [5, 5, 5, 3], + ]); + assert_eq!(ans, expected); + } + + #[test] + fn test_simplegako_serializer() { + let problem = problem_for_tests(); + let url = "https://puzz.link/p?simplegako/4/4/k5g12g2k"; + crate::util::tests::serializer_test(problem, url, serialize_problem, deserialize_problem); + } +} diff --git a/cspuz_solver_backend/src/puzzle/mod.rs b/cspuz_solver_backend/src/puzzle/mod.rs index 9762348a..a5671107 100644 --- a/cspuz_solver_backend/src/puzzle/mod.rs +++ b/cspuz_solver_backend/src/puzzle/mod.rs @@ -180,6 +180,7 @@ puzzle_list!(puzz_link, (shakashaka, ["shakashaka"], "Shakashaka", "シャカシャカ"), (shikaku, ["shikaku"], "Shikaku", "四角に切れ"), (shimaguni, ["shimaguni"], "Shimaguni", "島国"), + (simplegako, ["simplegako"], "Simple Gako", "シンプルガコ"), (simpleloop, ["simpleloop"], "Simple Loop", "シンプルループ"), (slalom, ["slalom"], "Slalom", "スラローム"), (slashpack, ["slashpack"], "Slash Pack", "Slash Pack"), diff --git a/cspuz_solver_backend/src/puzzle/simplegako.rs b/cspuz_solver_backend/src/puzzle/simplegako.rs new file mode 100644 index 00000000..c4550d64 --- /dev/null +++ b/cspuz_solver_backend/src/puzzle/simplegako.rs @@ -0,0 +1,70 @@ +use crate::board::{Board, BoardKind, Item, ItemKind}; +use crate::uniqueness::check_uniqueness; +use cspuz_rs_puzzles::puzzles::simplegako; + +pub fn solve(url: &str) -> Result { + let problem = simplegako::deserialize_problem(url).ok_or("invalid url")?; + let ans = simplegako::solve_simplegako(&problem); + + let height = problem.len(); + let width = problem[0].len(); + let mut board = Board::new(BoardKind::Grid, height, width, check_uniqueness(&ans)); + + for y in 0..height { + for x in 0..width { + if let Some(n) = problem[y][x] { + if n >= 0 { + board.push(Item::cell(y, x, "black", ItemKind::Num(n))); + } else { + board.push(Item::cell(y, x, "black", ItemKind::Text("?"))); + } + } else if let Some(ans) = &ans { + if let Some(n) = ans[y][x] { + board.push(Item::cell(y, x, "green", ItemKind::Num(n))); + } + } + } + } + + Ok(board) +} + +#[cfg(test)] +mod tests { + use super::solve; + use crate::board::*; + use crate::compare_board_and_check_no_solution_case; + use crate::uniqueness::Uniqueness; + + #[test] + #[rustfmt::skip] + fn test_solve() { + compare_board_and_check_no_solution_case!( + solve("https://puzz.link/p?simplegako/4/4/k5g12g2k"), + Board { + kind: BoardKind::Grid, + height: 4, + width: 4, + data: vec![ + Item { y: 1, x: 1, color: "green", kind: ItemKind::Num(5) }, + Item { y: 1, x: 3, color: "green", kind: ItemKind::Num(5) }, + Item { y: 1, x: 5, color: "green", kind: ItemKind::Num(5) }, + Item { y: 1, x: 7, color: "green", kind: ItemKind::Num(3) }, + Item { y: 3, x: 1, color: "green", kind: ItemKind::Num(5) }, + Item { y: 3, x: 3, color: "black", kind: ItemKind::Num(5) }, + Item { y: 3, x: 5, color: "green", kind: ItemKind::Num(5) }, + Item { y: 3, x: 7, color: "black", kind: ItemKind::Num(1) }, + Item { y: 5, x: 1, color: "black", kind: ItemKind::Num(2) }, + Item { y: 5, x: 3, color: "green", kind: ItemKind::Num(1) }, + Item { y: 5, x: 5, color: "black", kind: ItemKind::Num(2) }, + Item { y: 5, x: 7, color: "green", kind: ItemKind::Num(3) }, + Item { y: 7, x: 1, color: "green", kind: ItemKind::Num(5) }, + Item { y: 7, x: 3, color: "green", kind: ItemKind::Num(5) }, + Item { y: 7, x: 5, color: "green", kind: ItemKind::Num(5) }, + Item { y: 7, x: 7, color: "green", kind: ItemKind::Num(3) }, + ], + uniqueness: Uniqueness::Unique, + }, + ); + } +}