Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2a5ba0c
Added support for the border toggle for aqre
ReverM Feb 15, 2026
a0807ec
Added full support for slither, and ran format
ReverM Feb 15, 2026
0c4a7fc
Added full support for masyu
ReverM Feb 16, 2026
679035c
Added support for empty country road rooms
ReverM Feb 16, 2026
d2efeb2
Forgot to credit rook
ReverM Feb 16, 2026
84b4f13
Put the full loop constraint in its own file for less repetition
ReverM Feb 16, 2026
2a8d221
Added the full variants for the walk puzzles
ReverM Feb 16, 2026
4fa1a5e
Added full support for geradeweg
ReverM Feb 16, 2026
01c3ccf
Add max 3 constraint to fillomino
ReverM Feb 16, 2026
d45040d
Added all shaded outside for yajilin and regional yajilin
ReverM Feb 16, 2026
35e0929
Added helper function
ReverM Feb 16, 2026
e3f1654
Finalized the yajilin variants
ReverM Feb 16, 2026
1abdf14
Added reduced support for akichiwake
ReverM Feb 16, 2026
68668ff
Added no loop back variant for balloon box
ReverM Feb 16, 2026
2439cdf
Removed extra 1
ReverM Feb 16, 2026
dc274f7
Fixed typo in helper function
ReverM Feb 17, 2026
186decd
Fixed issue with non loop full genre
ReverM Feb 19, 2026
ecc2ebf
avoid hardcoding pzprxs URLs in cspuz_rs_puzzles (#199)
semiexp Feb 22, 2026
3d00537
Changed url of puzzles that do not have puzz.link variant support to …
ReverM Feb 22, 2026
43a8140
Merge branch 'main' into various-variants
ReverM Feb 22, 2026
390aee3
Changed URLs for genre with no puzz.link support for variants
ReverM Feb 22, 2026
8a9272a
Merge branch 'various-variants' of https://github.com/ReverM/cspuz_co…
ReverM Feb 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions cspuz_rs/src/solver/ndarray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,37 @@ impl<T: Clone> NdArray<(usize, usize), T> {
}
ret
}

pub fn eight_neighbor_indices(&self, idx: (usize, usize)) -> Vec<(usize, usize)> {
let (h, w) = self.shape();
let (y, x) = idx;
let mut ret = vec![];
if y > 0 {
ret.push((y - 1, x));
}
if x > 0 {
ret.push((y, x - 1));
}
if y < h - 1 {
ret.push((y + 1, x));
}
if x < w - 1 {
ret.push((y, x + 1));
}
if y > 0 && x > 0 {
ret.push((y - 1, x - 1));
}
if x > 0 && y < h - 1 {
ret.push((y + 1, x - 1));
}
if x < w - 1 && y < h - 1 {
ret.push((y + 1, x + 1));
}
if x < w - 1 && y > 0 {
ret.push((y - 1, x + 1));
}
ret
}
}

impl<T: Clone> NdArray<(usize, usize), T> {
Expand Down Expand Up @@ -387,6 +418,10 @@ impl<T: Clone> NdArray<(usize, usize), T> {
self.select(self.four_neighbor_indices(idx))
}

pub fn eight_neighbors(&self, idx: (usize, usize)) -> NdArray<(usize,), T> {
self.select(self.eight_neighbor_indices(idx))
}

pub fn pointing_cells(
&self,
cell: (usize, usize),
Expand Down
125 changes: 91 additions & 34 deletions cspuz_rs_puzzles/src/puzzles/akichiwake.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use cspuz_rs::graph;
use cspuz_rs::serializer::{
problem_to_url_with_context, url_to_problem, Choice, Combinator, Context, HexInt, Optionalize,
RoomsWithValues, Size, Spaces,
problem_to_url_with_context, url_to_problem, Choice, Combinator, Context, Dict, HexInt,
Optionalize, RoomsWithValues, Size, Spaces, Tuple2,
};
use cspuz_rs::solver::Solver;

pub fn solve_akichiwake(
reduced: bool,
borders: &graph::InnerGridEdges<Vec<Vec<bool>>>,
clues: &[Option<i32>],
) -> Option<Vec<Vec<Option<bool>>>> {
Expand Down Expand Up @@ -67,15 +68,17 @@ pub fn solve_akichiwake(
let p = rooms[i][0];
if n == 0 {
solver.add_expr(is_black.at(p));
} else if n == 1 {
} else if n == 1 && !reduced {
solver.add_expr(!is_black.at(p));
} else {
return None;
}
continue;
}
let sizes = &solver.int_var_1d(rooms[i].len(), 1, n);
solver.add_expr(sizes.ge(n).any());
if !reduced {
solver.add_expr(sizes.ge(n).any());
}

let mut edges = vec![];
let mut has_edge = vec![];
Expand All @@ -101,18 +104,27 @@ pub fn solve_akichiwake(
solver.irrefutable_facts().map(|f| f.get(is_black))
}

pub(super) type Problem = (graph::InnerGridEdges<Vec<Vec<bool>>>, Vec<Option<i32>>);
pub(super) type Problem = (
bool,
(graph::InnerGridEdges<Vec<Vec<bool>>>, Vec<Option<i32>>),
);

pub(super) fn combinator() -> impl Combinator<Problem> {
Size::new(RoomsWithValues::new(Choice::new(vec![
Box::new(Optionalize::new(HexInt)),
Box::new(Spaces::new(None, 'g')),
])))
Tuple2::new(
Choice::new(vec![
Box::new(Dict::new(true, "x/")),
Box::new(Dict::new(false, "")),
]),
Size::new(RoomsWithValues::new(Choice::new(vec![
Box::new(Optionalize::new(HexInt)),
Box::new(Spaces::new(None, 'g')),
]))),
)
}

pub fn serialize_problem(problem: &Problem) -> Option<String> {
let height = problem.0.vertical.len();
let width = problem.0.vertical[0].len() + 1;
let height = problem.1 .0.vertical.len();
let width = problem.1 .0.vertical[0].len() + 1;
problem_to_url_with_context(
combinator(),
"akichi",
Expand All @@ -129,31 +141,47 @@ pub fn deserialize_problem(url: &str) -> Option<Problem> {
mod tests {
use super::*;

fn problem_for_tests() -> Problem {
fn problem_for_tests1() -> Problem {
(
false,
(
graph::InnerGridEdges {
horizontal: crate::util::tests::to_bool_2d([
[0, 0, 0, 1, 1, 1],
[0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0],
]),
vertical: crate::util::tests::to_bool_2d([
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 1],
[0, 0, 1, 0, 1],
[0, 1, 0, 0, 1],
[0, 1, 0, 0, 1],
]),
},
vec![Some(3), Some(2), Some(1), Some(3), None, Some(5)],
),
)
}

fn problem_for_tests2() -> Problem {
(
graph::InnerGridEdges {
horizontal: crate::util::tests::to_bool_2d([
[0, 0, 0, 1, 1, 1],
[0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0],
]),
vertical: crate::util::tests::to_bool_2d([
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 1],
[0, 0, 1, 0, 1],
[0, 1, 0, 0, 1],
[0, 1, 0, 0, 1],
]),
},
vec![Some(3), Some(2), Some(1), Some(3), None, Some(5)],
true,
(
graph::InnerGridEdges {
horizontal: crate::util::tests::to_bool_2d([[1, 1, 1], [1, 1, 1]]),
vertical: crate::util::tests::to_bool_2d([[0, 0], [0, 0], [1, 0]]),
},
vec![Some(2), None, Some(0), Some(1)],
),
)
}

#[test]
fn test_akichiwake_problem() {
let (borders, clues) = problem_for_tests();
let ans = solve_akichiwake(&borders, &clues);
fn test_akichiwake_problem1() {
let (reduced, (borders, clues)) = problem_for_tests1();
let ans = solve_akichiwake(reduced, &borders, &clues);
assert!(ans.is_some());
let ans = ans.unwrap();

Expand All @@ -167,10 +195,39 @@ mod tests {
assert_eq!(ans, expected);
}

#[test]
fn test_akichiwake_problem2() {
let (reduced, (borders, clues)) = problem_for_tests2();
let ans = solve_akichiwake(reduced, &borders, &clues);
assert!(ans.is_some());
let ans = ans.unwrap();

let expected = crate::util::tests::to_option_bool_2d([[0, 1, 0], [0, 0, 0], [1, 0, 1]]);
assert_eq!(ans, expected);
}

#[test]
fn test_akichiwake_serializer() {
let problem = problem_for_tests();
let url = "https://puzz.link/p?akichi/6/5/455993g7o03213g5";
crate::util::tests::serializer_test(problem, url, serialize_problem, deserialize_problem);
{
let problem = problem_for_tests1();
let url = "https://puzz.link/p?akichi/6/5/455993g7o03213g5";
crate::util::tests::serializer_test(
problem,
url,
serialize_problem,
deserialize_problem,
);
}

{
let problem = problem_for_tests2();
let url = "https://puzz.link/p?akichi/x/3/3/10vg2g01";
crate::util::tests::serializer_test(
problem,
url,
serialize_problem,
deserialize_problem,
);
}
}
}
111 changes: 88 additions & 23 deletions cspuz_rs_puzzles/src/puzzles/aqre.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use cspuz_rs::graph;
use cspuz_rs::serializer::{
problem_to_url_with_context, url_to_problem, Choice, Combinator, Context, Dict, HexInt,
Optionalize, RoomsWithValues, Size, Spaces,
problem_to_url_with_context_pzprxs, url_to_problem, Choice, Combinator, Context, Dict, HexInt,
Optionalize, RoomsWithValues, Size, Spaces, Tuple2,
};
use cspuz_rs::solver::{count_true, Solver};

pub fn solve_aqre(
border_exactly_once: bool,
borders: &graph::InnerGridEdges<Vec<Vec<bool>>>,
clues: &[Option<i32>],
) -> Option<Vec<Vec<Option<bool>>>> {
Expand All @@ -19,14 +20,31 @@ pub fn solve_aqre(

graph::active_vertices_connected_2d(&mut solver, is_black);

solver.add_expr(!is_black.conv2d_and((1, 4)));
solver.add_expr(is_black.conv2d_or((1, 4)));
solver.add_expr(!is_black.conv2d_and((4, 1)));
solver.add_expr(is_black.conv2d_or((4, 1)));
if w > 3 {
solver.add_expr(!is_black.conv2d_and((1, 4)));
solver.add_expr(is_black.conv2d_or((1, 4)));
}
if h > 3 {
solver.add_expr(!is_black.conv2d_and((4, 1)));
solver.add_expr(is_black.conv2d_or((4, 1)));
}

let rooms = graph::borders_to_rooms(borders);
assert_eq!(rooms.len(), clues.len());

if border_exactly_once {
for y in 0..h {
for x in 0..w {
if y < h - 1 && borders.horizontal[y][x] {
solver.add_expr(is_black.at((y, x)) ^ is_black.at((y + 1, x)));
}
if x < w - 1 && borders.vertical[y][x] {
solver.add_expr(is_black.at((y, x)) ^ is_black.at((y, x + 1)));
}
}
}
}

for i in 0..rooms.len() {
if let Some(n) = clues[i] {
let mut cells = vec![];
Expand All @@ -42,20 +60,29 @@ pub fn solve_aqre(
solver.irrefutable_facts().map(|f| f.get(is_black))
}

type Problem = (graph::InnerGridEdges<Vec<Vec<bool>>>, Vec<Option<i32>>);
type Problem = (
bool,
(graph::InnerGridEdges<Vec<Vec<bool>>>, Vec<Option<i32>>),
);

fn combinator() -> impl Combinator<Problem> {
Size::new(RoomsWithValues::new(Choice::new(vec![
Box::new(Optionalize::new(HexInt)),
Box::new(Spaces::new(None, 'g')),
Box::new(Dict::new(Some(-1), ".")),
])))
Tuple2::new(
Choice::new(vec![
Box::new(Dict::new(true, "b/")),
Box::new(Dict::new(false, "")),
]),
Size::new(RoomsWithValues::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<String> {
let height = problem.0.vertical.len();
let width = problem.0.vertical[0].len() + 1;
problem_to_url_with_context(
let height = problem.1 .0.vertical.len();
let width = problem.1 .0.vertical[0].len() + 1;
problem_to_url_with_context_pzprxs(
combinator(),
"aqre",
problem.clone(),
Expand All @@ -71,7 +98,7 @@ pub fn deserialize_problem(url: &str) -> Option<Problem> {
mod tests {
use super::*;

fn problem_for_tests() -> Problem {
fn problem_for_tests1() -> Problem {
let borders = graph::InnerGridEdges {
horizontal: crate::util::tests::to_bool_2d([
[0, 0, 0, 0, 1, 1],
Expand All @@ -90,13 +117,22 @@ mod tests {
]),
};
let clues = vec![Some(0), None, Some(3), Some(0), Some(0), None];
(borders, clues)
(false, (borders, clues))
}

fn problem_for_tests2() -> Problem {
let borders = graph::InnerGridEdges {
horizontal: crate::util::tests::to_bool_2d([[0, 0, 0], [0, 0, 0]]),
vertical: crate::util::tests::to_bool_2d([[1, 1], [1, 1], [1, 1]]),
};
let clues = vec![None, None, None];
(true, (borders, clues))
}

#[test]
fn test_aqre_problem() {
let (borders, clues) = problem_for_tests();
let ans = solve_aqre(&borders, &clues);
fn test_aqre_problem1() {
let (border_exactly_once, (borders, clues)) = problem_for_tests1();
let ans = solve_aqre(border_exactly_once, &borders, &clues);
assert!(ans.is_some());
let ans = ans.unwrap();

Expand All @@ -111,10 +147,39 @@ mod tests {
assert_eq!(ans, expected);
}

#[test]
fn test_aqre_problem2() {
let (border_exactly_once, (borders, clues)) = problem_for_tests2();
let ans = solve_aqre(border_exactly_once, &borders, &clues);
assert!(ans.is_some());
let ans = ans.unwrap();

let expected = crate::util::tests::to_option_bool_2d([[0, 1, 0], [0, 1, 0], [0, 1, 0]]);
assert_eq!(ans, expected);
}

#[test]
fn test_aqre_serializer() {
let problem = problem_for_tests();
let url = "https://puzz.link/p?aqre/6/6/8a41dd1t0re00g300g";
crate::util::tests::serializer_test(problem, url, serialize_problem, deserialize_problem);
{
let problem = problem_for_tests1();
let url = "https://pzprxs.vercel.app/p?aqre/6/6/8a41dd1t0re00g300g";
crate::util::tests::serializer_test(
problem,
url,
serialize_problem,
deserialize_problem,
);
}

{
let problem = problem_for_tests2();
let url = "https://pzprxs.vercel.app/p?aqre/b/3/3/vg00i";
crate::util::tests::serializer_test(
problem,
url,
serialize_problem,
deserialize_problem,
);
}
}
}
Loading