This commit is contained in:
mehbark 2023-01-04 22:34:46 -05:00
commit 80e891268a
5 changed files with 173 additions and 0 deletions

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 = 3
[[package]]
name = "most-chips-needed-for-bingo-board"
version = "0.1.0"

8
Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "most-chips-needed-for-bingo-board"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

84
src/board.rs Normal file
View file

@ -0,0 +1,84 @@
use std::array;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Board {
checked: [bool; 25],
}
// a u32 would be much more efficient (only need 25 bits)
impl Board {
pub fn new() -> Self {
let checked = array::from_fn(|i| i == Self::idx(2, 2));
Self { checked }
}
pub fn new_without_center_dot() -> Self {
let checked = array::from_fn(|_| false);
Self { checked }
}
fn idx(x: usize, y: usize) -> usize {
x + y * 5
}
fn checked_at(&self, x: usize, y: usize) -> bool {
self.checked[Self::idx(x, y)]
}
fn checked_at_idx(&self, idx: usize) -> bool {
self.checked[idx]
}
pub fn num_chips(&self) -> usize {
self.checked.iter().filter(|b| **b).count()
}
pub fn is_done(&self) -> bool {
self.rows()
.chain(self.cols())
.chain(self.diags())
.any(and_all)
}
fn rows(&self) -> impl Iterator<Item = [bool; 5]> + '_ {
(0..5).map(|y| array::from_fn(|x| self.checked_at(x, y)))
}
fn cols(&self) -> impl Iterator<Item = [bool; 5]> + '_ {
(0..5).map(|x| array::from_fn(|y| self.checked_at(x, y)))
}
fn diags(&self) -> impl Iterator<Item = [bool; 5]> + '_ {
[
array::from_fn(|i| self.checked_at(i, i)),
array::from_fn(|i| self.checked_at(i, 4 - i)),
]
.into_iter()
}
fn with_checked(&self, at: usize) -> Self {
let mut new = *self;
new.checked[at] = true;
new
}
pub fn successors(&self) -> impl Iterator<Item = Self> + '_ {
self.checked
.into_iter()
.enumerate()
.filter_map(|(i, checked)| {
if checked {
None
} else {
Some(self.with_checked(i))
}
})
}
}
fn and_all<const N: usize>(bs: [bool; N]) -> bool {
bs.iter().all(|b| *b)
}

11
src/main.rs Normal file
View file

@ -0,0 +1,11 @@
mod board;
mod solver;
use crate::solver::solution;
fn main() {
let solution = solution();
eprintln!("The maximum number of chips needed for a 5x5 bingo board that starts with the center filled is:");
println!("{solution}");
eprintln!("(This assumes that people want to fill their winning square, which does make sense for showing that you won I suppose)");
}

63
src/solver.rs Normal file
View file

@ -0,0 +1,63 @@
use crate::board::Board;
use std::collections::HashSet;
pub fn solution() -> usize {
let mut solver = Solver::new();
solver.step_until_pointless();
solver.most_checked()
}
// there is definitely reflectional/rotational symmetry that could be utilized
#[derive(Debug, Clone)]
struct Solver {
end_states: HashSet<Board>,
in_progress_states: HashSet<Board>,
handled_states: HashSet<Board>,
}
impl Solver {
fn new() -> Self {
Self {
end_states: HashSet::new(),
in_progress_states: HashSet::from([Board::new()]),
handled_states: HashSet::new(),
}
}
fn most_checked(&self) -> usize {
self.end_states.iter().map(Board::num_chips).max().unwrap()
}
fn step_until_pointless(&mut self) {
loop {
let prev_end_state_count = self.end_states.len();
dbg!(prev_end_state_count);
self.step();
if !self.end_states.is_empty() && self.end_states.len() == prev_end_state_count {
break;
}
}
}
fn step(&mut self) {
let mut new_end_states = HashSet::new();
let mut new_ip_states = HashSet::new();
for state in &self.in_progress_states {
for succ in state.successors() {
if succ.is_done() {
new_end_states.insert(succ);
} else if !self.handled_states.contains(&succ) {
new_ip_states.insert(succ);
}
}
}
self.end_states.extend(new_end_states);
self.handled_states.extend(self.in_progress_states.iter());
self.in_progress_states = new_ip_states;
}
}