Skip to content

Commit 0b3a90c

Browse files
init partidge puzzle
1 parent 4ab9d75 commit 0b3a90c

File tree

4 files changed

+345
-0
lines changed

4 files changed

+345
-0
lines changed

PartridgePuzzle/Cargo.lock

Lines changed: 151 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

PartridgePuzzle/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "PartridgePuzzle"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
colored = "3.0.0"
8+
hsv = "0.1.1"
9+
rayon = "1.11.0"

PartridgePuzzle/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Partridge Puzzle Solver
2+
3+
Inspired by https://www.youtube.com/watch?v=eqyuQZHfNPQ and https://www.mathpuzzle.com/partridge.html

PartridgePuzzle/src/main.rs

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
use colored::{ColoredString, Colorize};
2+
use hsv::hsv_to_rgb;
3+
use rayon::prelude::*;
4+
5+
const ORDER: u8 = 8;
6+
const BOARD_SIZE: usize = ORDER as usize * (ORDER as usize + 1) / 2;
7+
8+
fn main() {
9+
if ORDER < 8 {
10+
println!(
11+
"There are no solutions for this puzzle with an order less than 8. But fine, I'll check anyway. Just for you <3"
12+
);
13+
}
14+
15+
println!("Finding puzzle solutions...");
16+
println!("{} solutions found.", find_puzzle_solutions(Board::new()));
17+
}
18+
19+
fn find_puzzle_solutions(board: Board) -> usize {
20+
if let Some((x, y)) = board.iter_coords().find(|(x, y)| board.is_empty(*x, *y)) {
21+
//println!("Filling position ({}, {})", x, y);
22+
//board.display();
23+
24+
let mut solutions: usize = 0;
25+
26+
solutions += (2..=ORDER)
27+
.into_par_iter()
28+
.rev()
29+
.map(|id| Piece::from_id(id))
30+
.filter(|p| board.has(*p) && board.fits(x, y, *p))
31+
.map(|p| find_puzzle_solutions(board.place(x, y, p)))
32+
.sum::<usize>();
33+
34+
if board.has(Piece::from_id(1)) {
35+
if x.min(y) != 0 && x.max(y) != BOARD_SIZE - 1 {
36+
// The 1x1 piece cannot go on the edges
37+
if !(board.is_empty(x + 1, y) && !board.is_empty(x - 1, y + 1)) {
38+
// The 1x1 piece cannot go into a corner created by two larger pieces
39+
// (<x,y) (x,<y) must be filled and (x,>y) must be empty
40+
solutions += find_puzzle_solutions(board.place(x, y, Piece::from_id(1)));
41+
}
42+
}
43+
}
44+
45+
return solutions;
46+
} else {
47+
println!("Found a solution!");
48+
//board.display();
49+
return 1;
50+
}
51+
}
52+
53+
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
54+
struct Board {
55+
board: [Piece; BOARD_SIZE * BOARD_SIZE],
56+
pieces: [u8; ORDER as usize],
57+
}
58+
59+
impl Board {
60+
fn new() -> Self {
61+
Self {
62+
board: [Piece { id: 0 }; BOARD_SIZE * BOARD_SIZE],
63+
pieces: std::array::from_fn(|i| (i + 1) as u8),
64+
}
65+
}
66+
67+
fn has(&self, piece: Piece) -> bool {
68+
let id = piece.id as usize;
69+
self.pieces[id - 1] > 0
70+
}
71+
72+
fn spend(&mut self, piece: Piece) {
73+
let id = piece.id as usize;
74+
self.pieces[id - 1] -= 1;
75+
}
76+
77+
fn is_empty(&self, x: usize, y: usize) -> bool {
78+
self.get(x, y).is_empty()
79+
}
80+
81+
fn get(&self, x: usize, y: usize) -> Piece {
82+
self.board[y * BOARD_SIZE + x]
83+
}
84+
85+
fn set(&mut self, x: usize, y: usize, value: Piece) {
86+
self.board[y * BOARD_SIZE + x] = value;
87+
}
88+
89+
fn fits(&self, x: usize, y: usize, piece: Piece) -> bool {
90+
let size = piece.size();
91+
if x + size as usize > BOARD_SIZE || y + size as usize > BOARD_SIZE {
92+
return false;
93+
}
94+
for dy in 0..size {
95+
for dx in 0..size {
96+
if !self.get(x + dx as usize, y + dy as usize).is_empty() {
97+
return false;
98+
}
99+
}
100+
}
101+
true
102+
}
103+
104+
fn place(&self, x: usize, y: usize, piece: Piece) -> Board {
105+
let mut new_board = self.clone();
106+
new_board.spend(piece);
107+
108+
let size = piece.size();
109+
for dy in 0..size {
110+
for dx in 0..size {
111+
new_board.set(x + dx as usize, y + dy as usize, piece);
112+
}
113+
}
114+
new_board
115+
}
116+
117+
fn iter_coords(&self) -> impl Iterator<Item = (usize, usize)> {
118+
(0..BOARD_SIZE * BOARD_SIZE).map(|i| (i % BOARD_SIZE, i / BOARD_SIZE))
119+
}
120+
121+
fn display(&self) {
122+
if self.pieces.iter().sum::<u8>() != 0 {
123+
println!(
124+
"Pieces left: {}",
125+
self.pieces
126+
.iter()
127+
.enumerate()
128+
.map(|(i, count)| format!("{}x{}: {}", i + 1, i + 1, count))
129+
.filter(|s| !s.ends_with("0"))
130+
.collect::<Vec<_>>()
131+
.join(", ")
132+
);
133+
} else {
134+
println!("A solution!");
135+
}
136+
137+
println!("┌{}─┐", "──".repeat(BOARD_SIZE));
138+
for y in 0..BOARD_SIZE {
139+
print!("│");
140+
for x in 0..BOARD_SIZE {
141+
let t = self.get(x, y);
142+
let colorized = if t.is_empty() {
143+
" -".normal()
144+
} else {
145+
t.colorize()
146+
};
147+
print!("{}", colorized);
148+
}
149+
println!(" │");
150+
}
151+
println!("└{}─┘", "──".repeat(BOARD_SIZE));
152+
}
153+
}
154+
155+
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
156+
struct Piece {
157+
id: u8,
158+
}
159+
160+
impl Piece {
161+
fn from_id(id: u8) -> Self {
162+
Self { id }
163+
}
164+
165+
fn size(&self) -> u8 {
166+
self.id
167+
}
168+
169+
fn is_empty(&self) -> bool {
170+
self.id == 0
171+
}
172+
173+
fn colorize(&self) -> ColoredString {
174+
let fill = format!(" {}", self.id);
175+
176+
let fraction = (self.id - 1) as f64 / ORDER as f64;
177+
let hue = fraction * 360.0;
178+
let (r, g, b) = hsv_to_rgb(hue, 0.5, 1.0);
179+
180+
fill.truecolor(r, g, b)
181+
}
182+
}

0 commit comments

Comments
 (0)