Skip to content

Commit 1f26538

Browse files
committed
Day 14 web visualization
1 parent c4f2e63 commit 1f26538

File tree

6 files changed

+162
-10
lines changed

6 files changed

+162
-10
lines changed

aoc-solver/src/y2024/day14.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ type Coords = (i64, i64);
88
const DIRECTIONS: [Coords; 4] = [(0, -1), (1, 0), (0, 1), (-1, 0)];
99

1010
#[derive(Debug, PartialEq)]
11-
struct Robot {
11+
pub struct Robot {
1212
v: Coords,
1313
pos: Coords,
1414
}
1515

16-
fn parse(input: &str) -> Result<Vec<Robot>, AocError> {
16+
pub fn parse(input: &str) -> Result<Vec<Robot>, AocError> {
1717
let machines = input
1818
.trim()
1919
.lines()
@@ -37,7 +37,7 @@ fn parse(input: &str) -> Result<Vec<Robot>, AocError> {
3737
Ok(machines)
3838
}
3939

40-
fn predict(robot: &Robot, width: u64, height: u64, seconds: i64) -> Coords {
40+
pub fn predict(robot: &Robot, width: u64, height: u64, seconds: i64) -> Coords {
4141
let x = (robot.pos.0 + seconds * robot.v.0).rem_euclid(width as i64);
4242
let y = (robot.pos.1 + seconds * robot.v.1).rem_euclid(height as i64);
4343
(x, y)
@@ -148,11 +148,11 @@ impl Solution for Day14 {
148148
let width = 101;
149149
let height = 103;
150150

151-
// Turns out this logic was more or less useless - all the robot velocities
152-
// are less than the (prime) width & height, so the repeated positions are just
153-
// determined by lcm(101, 103). But this logic would find the repeating patterns
154-
// even if some robots were moving faster than the size of the area and
155-
// possibly teleporting more than once per second.
151+
// Turns out this logic was more or less useless - the width and the height are
152+
// both primes, and also all the robot velocities less than the area dimensions,
153+
// so the repeated positions are just determined by lcm(101, 103).
154+
// But this logic would find the repeating patterns even if the dimensions were not
155+
// prime or if robots were teleporting many times per second.
156156
let loops_after = repeats_every(&robots, width, height);
157157

158158
// Assume that the christmas tree probably has a large continuous area,

aoc-web/src/header.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub fn header(props: &HeaderProps) -> Html {
3333
<NavLink route={Route::Solution { year: 2024, day: 12 }} current={props.route.clone()} text={"12"}/>
3434
<NavLink route={Route::Solution { year: 2024, day: 13 }} current={props.route.clone()} text={"13"}/>
3535
<NavLink route={Route::Solution { year: 2024, day: 14 }} current={props.route.clone()} text={"14"}/>
36+
<NavLink route={Route::EasterEgg} current={props.route.clone()} text={"14+"}/>
3637
</>
3738
}
3839
},

aoc-web/src/router.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use yew_router::prelude::*;
44

55
use crate::{
66
header::Header, home::Home, runner::SolutionTask, solution::Solution,
7-
syntax::SyntaxHighlightTask, y2022,
7+
syntax::SyntaxHighlightTask, y2022, y2024,
88
};
99

1010
#[derive(Clone, Routable, PartialEq)]
@@ -21,6 +21,8 @@ pub enum Route {
2121
Lava,
2222
#[at("/2022/22/cube")]
2323
Cube,
24+
#[at("/2024/14/visual")]
25+
EasterEgg,
2426
#[not_found]
2527
#[at("/404")]
2628
NotFound,
@@ -31,6 +33,7 @@ pub fn switch(route: Route) -> Html {
3133
Route::Index | Route::NotFound => 2024,
3234
Route::Solution { year, day: _ } | Route::Home { year } => year,
3335
Route::Lava | Route::Rope | Route::Cube => 2022,
36+
Route::EasterEgg => 2024,
3437
};
3538

3639
let main = match route {
@@ -48,6 +51,9 @@ pub fn switch(route: Route) -> Html {
4851
Route::Cube => {
4952
html! { <y2022::Cube/> }
5053
}
54+
Route::EasterEgg => {
55+
html! { <y2024::EasterEgg/> }
56+
}
5157
Route::NotFound => html! {<h1>{ "Not Found :(" }</h1>},
5258
};
5359

aoc-web/src/y2024/easter_egg.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use std::collections::HashSet;
2+
3+
use yew::prelude::*;
4+
5+
use aoc_solver::solution::Solution;
6+
use aoc_solver::y2024::day14::{parse, predict, Day14, Robot};
7+
8+
pub struct EasterEgg {
9+
seconds: i64,
10+
width: u64,
11+
height: u64,
12+
robots: Vec<Robot>,
13+
predicted: HashSet<(i64, i64)>,
14+
}
15+
16+
pub enum Msg {
17+
TimeChanged(i64),
18+
}
19+
20+
impl Component for EasterEgg {
21+
type Message = Msg;
22+
type Properties = ();
23+
24+
fn create(_ctx: &Context<Self>) -> Self {
25+
let robots = parse(Day14.default_input()).unwrap();
26+
let width = 101;
27+
let height = 103;
28+
let seconds = 7892;
29+
30+
let predicted: HashSet<(i64, i64)> = robots
31+
.iter()
32+
.map(|robot| predict(robot, width, height, seconds))
33+
.collect();
34+
35+
Self {
36+
seconds,
37+
width,
38+
height,
39+
robots,
40+
predicted,
41+
}
42+
}
43+
44+
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {}
45+
46+
fn destroy(&mut self, _: &Context<Self>) {}
47+
48+
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
49+
match msg {
50+
Msg::TimeChanged(new_time) => {
51+
self.seconds = new_time;
52+
self.predicted = self
53+
.robots
54+
.iter()
55+
.map(|robot| predict(robot, self.width, self.height, self.seconds))
56+
.collect();
57+
58+
true
59+
}
60+
}
61+
}
62+
63+
fn view(&self, ctx: &Context<Self>) -> Html {
64+
let output = (0..self.height)
65+
.map(|y| {
66+
(0..self.width)
67+
.map(|x| {
68+
if self.predicted.contains(&(x as i64, y as i64)) {
69+
'#'
70+
} else {
71+
'.'
72+
}
73+
})
74+
.collect::<String>()
75+
})
76+
.collect::<Vec<_>>()
77+
.join("\n");
78+
79+
html! {
80+
<>
81+
<h2>{"-- Day 14 --"}</h2>
82+
83+
<input
84+
class="full-width"
85+
type="range"
86+
min="0"
87+
max="10403"
88+
value={self.seconds.to_string()}
89+
step="1"
90+
oninput={ctx.link().callback(|event: InputEvent| {
91+
let input = event.target_unchecked_into::<web_sys::HtmlInputElement>();
92+
let seconds = input.value().parse::<i64>().unwrap_or_default();
93+
Msg::TimeChanged(seconds)
94+
})}
95+
/>
96+
<span class="success">
97+
{format!("t={}", self.seconds)}
98+
</span>
99+
<pre class="small">
100+
<code>{ output }</code>
101+
</pre>
102+
</>
103+
}
104+
}
105+
}

aoc-web/src/y2024/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
mod easter_egg;
12

3+
pub use self::easter_egg::EasterEgg;

aoc-web/static/styles.css

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,4 +222,42 @@ code:before {
222222
opacity: 0;
223223
animation: fadeInAnimation ease-in 400ms;
224224
animation-fill-mode: forwards;
225-
}
225+
}
226+
227+
.full-width {
228+
width: 100%;
229+
max-width: 900px;
230+
}
231+
232+
.small {
233+
font-size: 0.75rem;
234+
}
235+
236+
input[type="range" i] {
237+
padding-top: 1em;
238+
padding-bottom: 1em;
239+
margin: 1em 0 0 0;
240+
height: 10px;
241+
border: none;
242+
-webkit-appearance: none;
243+
appearance: none;
244+
background-color: transparent;
245+
background-size: 16px 100%;
246+
background-position: 0% 0%;
247+
background-repeat: repeat;
248+
}
249+
250+
input[type="range" i]::-webkit-slider-runnable-track {
251+
height: 5px;
252+
background-color: #009900;
253+
}
254+
255+
input[type="range" i]::-webkit-slider-thumb {
256+
width: 10px;
257+
height: 30px;
258+
background-color: #ffff66;
259+
box-shadow: 0 0 5px #ffff66;
260+
-webkit-appearance: none;
261+
margin-top: -1em;
262+
}
263+

0 commit comments

Comments
 (0)