This commit is contained in:
mehbark 2025-07-09 16:21:00 -04:00
commit 9e74d12329
4 changed files with 209 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View file

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "nonogram"
version = "0.1.0"

6
Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[package]
name = "nonogram"
version = "0.1.0"
edition = "2024"
[dependencies]

195
src/main.rs Normal file
View file

@ -0,0 +1,195 @@
use std::{array, env, error::Error, fmt, str::FromStr};
// TODO: we need <4 bits
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
enum Clue {
C0,
C1,
C2,
C3,
C4,
C5,
C1_1,
C1_2,
C2_1,
C1_3,
C3_1,
C2_2,
C1_1_1,
}
impl From<Row> for Clue {
fn from(row: Row) -> Self {
match row.0 {
0b00000 => Clue::C0,
0b10000 | 0b01000 | 0b00100 | 0b00010 | 0b00001 => Clue::C1,
0b11000 | 0b01100 | 0b00110 | 0b00011 => Clue::C2,
0b11100 | 0b01110 | 0b00111 => Clue::C3,
0b11110 | 0b01111 => Clue::C4,
0b11111 => Clue::C5,
0b10100 | 0b10010 | 0b10001 | 0b01010 | 0b01001 | 0b00101 => Clue::C1_1,
0b10110 | 0b10011 | 0b01011 => Clue::C1_2,
0b11010 | 0b11001 | 0b01101 => Clue::C2_1,
0b10111 => Clue::C1_3,
0b11101 => Clue::C3_1,
0b11011 => Clue::C2_2,
0b10101 => Clue::C1_1_1,
_ => unreachable!(),
}
}
}
impl FromStr for Clue {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[allow(clippy::enum_glob_use)]
use Clue::*;
Ok(match s.trim() {
"0" => C0,
"1" => C1,
"2" => C2,
"3" => C3,
"4" => C4,
"5" => C5,
"1 1" => C1_1,
"1 2" => C1_2,
"2 1" => C2_1,
"1 3" => C1_3,
"3 1" => C3_1,
"2 2" => C2_2,
"1 1 1" => C1_1_1,
_ => return Err(()),
})
}
}
impl fmt::Display for Clue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:>5}",
match self {
Clue::C0 => "0",
Clue::C1 => "1",
Clue::C2 => "2",
Clue::C3 => "3",
Clue::C4 => "4",
Clue::C5 => "5",
Clue::C1_1 => "1 1",
Clue::C1_2 => "1 2",
Clue::C2_1 => "2 1",
Clue::C1_3 => "1 3",
Clue::C3_1 => "3 1",
Clue::C2_2 => "2 2",
Clue::C1_1_1 => "1 1 1",
}
)
}
}
#[derive(Clone, Copy)]
#[repr(transparent)]
struct Row(u8);
impl fmt::Debug for Row {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:05b}", self.0)
}
}
impl fmt::Display for Row {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for i in (0..5).rev() {
write!(f, "{}", if (self.0 >> i) & 1 == 1 { '#' } else { '.' })?;
}
Ok(())
}
}
#[derive(Clone, Copy)]
#[repr(transparent)]
struct Board(u32);
impl Board {
const fn cardinality() -> u32 {
1 << 25
}
fn universe() -> impl Iterator<Item = Self> {
(0..Self::cardinality()).map(Self)
}
fn rows(self) -> [Row; 5] {
array::from_fn(|i| Row((self.0 >> (i * 5) & 0b11111) as u8))
}
fn columns(self) -> [Row; 5] {
let [r1, r2, r3, r4, r5] = self.rows();
array::from_fn(|i| {
let i = 4 - i;
Row(((r1.0 >> i) & 1) << 4
| ((r2.0 >> i) & 1) << 3
| ((r3.0 >> i) & 1) << 2
| ((r4.0 >> i) & 1) << 1
| ((r5.0 >> i) & 1))
})
}
}
impl fmt::Debug for Board {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for row in self.rows() {
writeln!(f, "{row:?}")?;
}
Ok(())
}
}
impl fmt::Display for Board {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for row in self.rows() {
writeln!(f, "{}|{}", Clue::from(row), row)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Clues {
rows: [Clue; 5],
columns: [Clue; 5],
}
impl From<Board> for Clues {
fn from(board: Board) -> Self {
let rows = board.rows().map(Clue::from);
let columns = board.columns().map(Clue::from);
Clues { rows, columns }
}
}
fn parse_clues<'a>(i: impl Iterator<Item = &'a str>) -> Result<[Clue; 5], &'static str> {
i.map(str::parse::<Clue>)
.collect::<Result<Vec<_>, _>>()
.map_err(|()| "bad clue")?
.try_into()
.map_err(|_| "five clues please")
}
#[allow(clippy::cast_possible_truncation)]
fn main() -> Result<(), Box<dyn Error>> {
let rows = parse_clues(env::args().nth(1).ok_or("give me row clues")?.split(','))?;
let columns = parse_clues(env::args().nth(2).ok_or("give me column clues")?.split(','))?;
let clues = Clues { rows, columns };
println!("{clues:#?}");
for board in Board::universe() {
if Clues::from(board) == clues {
println!("{board}");
return Ok(());
}
}
Err("couldn't solve")?
}