advent2021/src/day23.rs

246 lines
6.9 KiB
Rust
Raw Normal View History

2021-12-24 19:42:18 +00:00
use std::cmp::Ordering;
use std::collections::{BinaryHeap, HashMap};
use std::fmt;
use std::hash::{Hash, Hasher};
#[derive(Copy, Clone, Eq)]
enum Space {
Empty = 1,
Amber,
Bronze,
Copper,
Desert,
}
impl Space {
fn cost(&self) -> usize {
match self {
Self::Amber => 1,
Self::Bronze => 10,
Self::Copper => 100,
Self::Desert => 1000,
_ => panic!("empty spaces don't move"),
}
}
fn glyph(&self) -> char {
match self {
Self::Empty => '.',
Self::Amber => 'A',
Self::Bronze => 'B',
Self::Copper => 'C',
Self::Desert => 'D',
}
}
}
impl Hash for Space {
fn hash<H: Hasher>(&self, state: &mut H) {
(*self as u32).hash(state);
}
}
impl PartialEq for Space {
fn eq(&self, other: &Self) -> bool {
*self as u32 == *other as u32
}
}
impl Ord for Space {
fn cmp(&self, other: &Self) -> Ordering {
(*other as u32).cmp(&(*self as u32))
}
}
impl PartialOrd for Space {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Debug for Space {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.glyph())
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
struct Step {
cost: usize,
position: [Space; 19],
}
impl Ord for Step {
fn cmp(&self, other: &Self) -> Ordering {
other
.cost
.cmp(&self.cost)
.then_with(|| self.position.cmp(&other.position))
}
}
impl PartialOrd for Step {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Debug for Step {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "#############")?;
writeln!(
f,
"#{}#",
self.position[0..11]
.iter()
.map(|space| space.glyph())
.collect::<String>()
)?;
writeln!(
f,
"###{}#{}#{}#{}###",
self.position[11].glyph(),
self.position[13].glyph(),
self.position[15].glyph(),
self.position[17].glyph()
)?;
writeln!(
f,
" #{}#{}#{}#{}#",
self.position[12].glyph(),
self.position[14].glyph(),
self.position[16].glyph(),
self.position[18].glyph()
)?;
writeln!(f, " #########")
}
}
fn swap(position: [Space; 19], a: usize, b: usize) -> [Space; 19] {
let mut new_position = position;
new_position[a] = position[b];
new_position[b] = position[a];
new_position
}
struct Burrow {
goal: [Space; 19],
}
impl Burrow {
fn valid_moves(&self, position: [Space; 19]) -> Vec<([Space; 19], usize)> {
let mut moves = Vec::new();
for door in [2, 4, 6, 8] {
if position[door] != Space::Empty {
if position[door - 1] == Space::Empty {
moves.push((swap(position, door - 1, door), position[door].cost()));
}
if position[door + 1] == Space::Empty {
moves.push((swap(position, door, door + 1), position[door].cost()));
}
if !moves.is_empty() {
return moves;
}
}
}
for room in [11, 13, 15, 17] {
if position[room] != Space::Empty && position[room + 1] == Space::Empty {
moves.push((swap(position, room, room + 1), position[room].cost()));
}
if position[room + 1] != Space::Empty && position[room] == Space::Empty {
moves.push((swap(position, room, room + 1), position[room + 1].cost()));
}
if position[room] != Space::Empty && position[room - 9] == Space::Empty {
moves.push((swap(position, room, room - 9), position[room].cost()));
}
if (position[room - 9] != Space::Empty && position[room] == Space::Empty)
&& ((position[room - 9] == self.goal[room + 1]
&& position[room + 1] == Space::Empty)
|| (position[room - 9] == self.goal[room]
&& position[room + 1] == self.goal[room + 1]))
{
moves.push((swap(position, room - 9, room), position[room - 9].cost()));
}
}
for hallway in [1, 3, 5, 7, 9] {
if position[hallway] != Space::Empty {
if position[hallway - 1] == Space::Empty {
moves.push((
swap(position, hallway, hallway - 1),
position[hallway].cost(),
));
}
if position[hallway + 1] == Space::Empty {
moves.push((
swap(position, hallway, hallway + 1),
position[hallway].cost(),
));
}
}
}
if position[0] != Space::Empty && position[1] == Space::Empty {
moves.push((swap(position, 0, 1), position[0].cost()));
}
if position[10] != Space::Empty && position[9] == Space::Empty {
moves.push((swap(position, 9, 10), position[10].cost()));
}
moves
}
}
fn least_energy(start: &[Space; 19], goal: &[Space; 19]) -> Option<usize> {
let burrow = Burrow { goal: *goal };
let mut dist: HashMap<[Space; 19], usize> = HashMap::new();
let mut heap = BinaryHeap::new();
dist.insert(*start, 0);
heap.push(Step {
cost: 0,
position: *start,
});
while let Some(Step { cost, position }) = heap.pop() {
if &position == goal {
return Some(cost);
}
let d = dist.entry(position).or_insert(usize::MAX);
if &cost > d {
continue;
}
println!("{:?}", Step { cost, position });
let moves = burrow.valid_moves(position);
for (next_position, next_cost) in moves {
let next_cost = next_cost + cost;
let next = Step {
cost: next_cost,
position: next_position,
};
if &next_cost < dist.get(&next_position).unwrap_or(&usize::MAX) {
heap.push(next);
dist.insert(next_position, next_cost);
}
}
}
None
}
fn main() {
use Space::*;
let start = [
Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Amber, Amber,
Bronze, Bronze, Copper, Copper, Desert, Desert,
];
let goal = [
Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Empty, Desert,
Bronze, Desert, Amber, Copper, Amber, Bronze, Copper,
];
println!("solution {}", least_energy(&start, &goal).unwrap());
}