Created
December 13, 2022 22:00
-
-
Save Arkham/e500d3d76dfdb04b5ae98049a7157320 to your computer and use it in GitHub Desktop.
aoc day 12 wasm fun
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pub fn part_one(input: &str) -> Option<(Vec<Coords>, usize)> { | |
let board = Board::from(input); | |
run_dijkstra( | |
&[board.start], | |
|node| board.successors(node), | |
|node| node == &board.end, | |
) | |
} | |
pub fn part_two(input: &str) -> Option<usize> { | |
let board = Board::from(input); | |
let mut starting_cells: Vec<Coords> = vec![]; | |
for (y, row) in board.cells.iter().enumerate() { | |
for (x, cell) in row.iter().enumerate() { | |
if let Cell::Value('a') = cell { | |
starting_cells.push(Coords { x, y }) | |
} | |
} | |
} | |
run_dijkstra( | |
&starting_cells, | |
|node| board.successors(node), | |
|node| node == &board.end, | |
) | |
.map(|(_, count)| count) | |
} | |
use eframe::egui; | |
use egui::{Color32, Sense, Stroke}; | |
use std::collections::HashSet; | |
use std::time::Duration; | |
#[cfg(target_arch = "wasm32")] | |
fn main() { | |
// Make sure panics are logged using `console.error`. | |
console_error_panic_hook::set_once(); | |
// Redirect tracing to console.log and friends: | |
tracing_wasm::set_as_global_default(); | |
let web_options = eframe::WebOptions::default(); | |
wasm_bindgen_futures::spawn_local(async { | |
eframe::start_web( | |
// this is the id of the `<canvas>` element we have | |
// in our `index.html` | |
"canvas", | |
web_options, | |
Box::new(|_cc| Box::new(MyApp::new())), | |
) | |
.await | |
.expect("failed to start eframe"); | |
}); | |
} | |
#[cfg(not(target_arch = "wasm32"))] | |
fn main() { | |
let options = eframe::NativeOptions { | |
initial_window_size: Some(egui::vec2(1280.0, 720.0)), | |
..Default::default() | |
}; | |
eframe::run_native( | |
"AoC 2022 - Day 12", | |
options, | |
Box::new(|_cc| Box::new(MyApp::new())), | |
) | |
} | |
// egui stuff | |
struct MyApp { | |
coords: Vec<Coords>, | |
current: usize, | |
visited: HashSet<Coords>, | |
} | |
impl MyApp { | |
fn new() -> Self { | |
let input = include_str!("../inputs/12.txt"); | |
let (coords, _) = part_one(input).unwrap(); | |
Self { | |
coords, | |
current: 0, | |
visited: HashSet::new(), | |
} | |
} | |
fn update_state(&mut self) { | |
match self.coords.get(self.current) { | |
Some(v) => { | |
self.visited.insert(*v); | |
self.current += 1; | |
} | |
None => return, | |
} | |
} | |
} | |
impl eframe::App for MyApp { | |
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { | |
self.update_state(); | |
egui::CentralPanel::default().show(ctx, |ui| { | |
const CANVAS_WIDTH: f32 = 900.0; | |
const CANVAS_HEIGHT: f32 = 900.0; | |
const SIDE: f32 = 6.0; | |
let painter_size = egui::vec2(CANVAS_WIDTH, CANVAS_HEIGHT); | |
let (_res, painter) = ui.allocate_painter(painter_size, Sense::hover()); | |
let to_panel_pos = | |
|pos: Coords| egui::vec2(pos.x as f32 * SIDE, pos.y as f32 * SIDE).to_pos2(); | |
let width = (CANVAS_WIDTH / SIDE).floor() as i32; | |
let height = (CANVAS_HEIGHT / SIDE).floor() as i32; | |
for x in 0..width { | |
for y in 0..height { | |
let dot = Coords { | |
x: x as usize, | |
y: y as usize, | |
}; | |
if !self.visited.contains(&dot) { | |
continue; | |
} | |
let color = if self.coords.last() == Some(&dot) { | |
Color32::GOLD | |
} else { | |
Color32::DARK_RED | |
}; | |
let dot_pos = to_panel_pos(dot); | |
painter.circle_stroke(dot_pos, 1.0, Stroke::new(2.0, color)); | |
} | |
} | |
if let (Some(prev), Some(curr)) = ( | |
self.coords.get(self.current - 1), | |
self.coords.get(self.current), | |
) { | |
// paint the head | |
let head_pos = to_panel_pos(*prev); | |
painter.circle_stroke(head_pos, 2.0, Stroke::new(2.0, Color32::GREEN)); | |
// paint the tail | |
let tail_pos = to_panel_pos(*curr); | |
painter.circle_stroke(tail_pos, 2.0, Stroke::new(2.0, Color32::YELLOW)); | |
// paint an arrow from head to tail | |
painter.arrow( | |
tail_pos, | |
head_pos - tail_pos, | |
Stroke::new(1.0, Color32::YELLOW), | |
) | |
} | |
}); | |
ctx.request_repaint_after(Duration::from_millis(25)); | |
} | |
} | |
// actually solving the problem | |
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] | |
pub struct Coords { | |
x: usize, | |
y: usize, | |
} | |
#[derive(Debug, PartialEq)] | |
enum Cell { | |
Start, | |
End, | |
Value(char), | |
} | |
impl Cell { | |
fn score(&self) -> i32 { | |
match self { | |
Cell::Start => 0, | |
Cell::Value(c) => *c as i32 - 96, // from 1 to 26 | |
Cell::End => 27, | |
} | |
} | |
} | |
impl ToString for Cell { | |
fn to_string(&self) -> String { | |
match self { | |
Cell::Start => "S".to_string(), | |
Cell::End => "E".to_string(), | |
Cell::Value(c) => format!("{}", c), | |
} | |
} | |
} | |
#[derive(Debug, PartialEq)] | |
struct Board { | |
num_rows: i32, | |
num_cols: i32, | |
start: Coords, | |
end: Coords, | |
cells: Vec<Vec<Cell>>, | |
} | |
impl Board { | |
fn successors(&self, coords: &Coords) -> Vec<(Coords, usize)> { | |
let &Coords { x, y } = coords; | |
let offsets: Vec<(i32, i32)> = vec![(0, 1), (0, -1), (1, 0), (-1, 0)]; | |
offsets | |
.iter() | |
.filter_map(|offset| { | |
let (new_x, new_y) = (x as i32 + offset.0, y as i32 + offset.1); | |
if (0..self.num_rows).contains(&new_y) && (0..self.num_cols).contains(&new_x) { | |
let new_score = self.cells[new_y as usize][new_x as usize].score(); | |
let old_score = self.cells[y][x].score(); | |
if new_score - old_score <= 1 { | |
Some(( | |
Coords { | |
x: new_x as usize, | |
y: new_y as usize, | |
}, | |
1, | |
)) | |
} else { | |
None | |
} | |
} else { | |
None | |
} | |
}) | |
.collect() | |
} | |
} | |
impl ToString for Board { | |
fn to_string(&self) -> String { | |
self.cells | |
.iter() | |
.map(|row| { | |
row.iter() | |
.map(|c| c.to_string()) | |
.collect::<Vec<_>>() | |
.join("") | |
}) | |
.collect::<Vec<_>>() | |
.join("\n") | |
} | |
} | |
impl From<&str> for Board { | |
fn from(input: &str) -> Self { | |
let mut start: Coords = Coords { x: 0, y: 0 }; | |
let mut end: Coords = Coords { x: 0, y: 0 }; | |
let cells: Vec<Vec<Cell>> = input | |
.lines() | |
.enumerate() | |
.map(|(y, line)| { | |
line.chars() | |
.enumerate() | |
.map(|(x, elem)| match elem { | |
'S' => { | |
start = Coords { x, y }; | |
Cell::Start | |
} | |
'E' => { | |
end = Coords { x, y }; | |
Cell::End | |
} | |
other => Cell::Value(other), | |
}) | |
.collect() | |
}) | |
.collect(); | |
Board { | |
num_rows: cells.len() as i32, | |
num_cols: cells[0].len() as i32, | |
start, | |
end, | |
cells, | |
} | |
} | |
} | |
use std::cmp::Ordering; | |
use std::collections::{BinaryHeap, HashMap}; | |
#[derive(Clone, Eq, PartialEq, Copy, Debug)] | |
struct HeapState<T> { | |
node: T, | |
cost: usize, | |
} | |
// Manually implement Ord so we get a min-heap instead of a max-heap | |
impl<T: Eq> Ord for HeapState<T> { | |
fn cmp(&self, other: &Self) -> Ordering { | |
other.cost.cmp(&self.cost) | |
} | |
} | |
impl<T: Eq> PartialOrd for HeapState<T> { | |
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { | |
Some(self.cmp(other)) | |
} | |
} | |
use core::hash::Hash; | |
use std::marker::Copy; | |
fn run_dijkstra<T: Eq + Hash + Copy, S: FnMut(&T) -> Vec<(T, usize)>, E: FnMut(&T) -> bool>( | |
starting: &[T], | |
mut successors: S, | |
mut is_end: E, | |
) -> Option<(Vec<T>, usize)> { | |
let mut best: HashMap<T, usize> = HashMap::new(); | |
let mut parent: HashMap<T, T> = HashMap::new(); | |
let mut heap = BinaryHeap::new(); | |
for start in starting { | |
best.insert(*start, 0); | |
heap.push(HeapState { | |
node: *start, | |
cost: 0, | |
}); | |
} | |
while let Some(HeapState { node, cost }) = heap.pop() { | |
if is_end(&node) { | |
let mut path = vec![]; | |
let mut current = Some(&node); | |
path.push(node); | |
while let Some(prev) = current { | |
path.push(*prev); | |
current = parent.get(prev) | |
} | |
path.reverse(); | |
return Some((path, cost)); | |
} | |
for (next, next_cost) in successors(&node) { | |
let new_cost = cost + next_cost; | |
let next_state = HeapState { | |
node: next, | |
cost: new_cost, | |
}; | |
if new_cost < *best.get(&next).unwrap_or(&std::usize::MAX) { | |
best.insert(next, new_cost); | |
parent.insert(next, node); | |
heap.push(next_state); | |
} | |
} | |
} | |
None | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment