advent-of-code 2025.5.0

Solutions to Advent of Code
Documentation
use crate::year2022::day24::Valley;
use svgplot::{
    SvgCircle, SvgColor, SvgGroup, SvgImage, SvgPath, SvgScript, SvgShape, SvgStyle, SvgTransform,
    SvgUse,
};
pub struct Renderer {
    pub reachable_per_step: Vec<(Vec<u64>, /*going down: */ bool)>,
    pub svg: SvgImage,
}
impl Renderer {
    pub fn new(reachable: &[u64], valley: &Valley) -> Self {
        let mut svg = SvgImage::new().style("background:black");
        let reachable_per_step = vec![(reachable.to_vec(), true)];
        let blizzard_def_id = svg.define(
            SvgPath::default()
                .shape(
                    SvgShape::new()
                        .move_to_absolute(0.5, 0.25)
                        .line_to_relative(0.25, 0.5)
                        .line_to_relative(-0.5, 0.0)
                        .close(),
                )
                .fill(SvgColor::Rgb(0x00, 0xB1, 0xD2)),
        );
        for (blizzard, a, d, dy, dir) in [
            (&valley.blizzards_up, 1., 1., 0., "up"),
            (&valley.blizzards_down, 1., -1., 1., "down"),
        ] {
            for y in 0..valley.height {
                let mut group = SvgGroup::new().class(format!("blizzard blizzard-{dir}"));
                for (x, col) in blizzard.iter().enumerate() {
                    if (col & (1 << y)) == 0 {
                        group.add(
                            SvgUse::new(blizzard_def_id).transform(SvgTransform::Matrix {
                                a,
                                b: 0.,
                                c: 0.,
                                d,
                                dx: x as f64,
                                dy,
                            }),
                        );
                    }
                }
                svg.add(group);
            }
        }
        for (blizzard, b, c, dx, dir) in [
            (&valley.blizzards_right, -1., -1., 1., "right"),
            (&valley.blizzards_left, 1., 1., 0., "left"),
        ] {
            for col in blizzard.iter() {
                let mut group = SvgGroup::new().class(format!("blizzard blizzard-{dir}"));
                for y in 0..valley.height {
                    if (col & (1 << y)) == 0 {
                        group.add(
                            SvgUse::new(blizzard_def_id).transform(SvgTransform::Matrix {
                                a: 0.,
                                b,
                                c,
                                d: 0.,
                                dx,
                                dy: y as f64 + dx,
                            }),
                        );
                    }
                }
                svg.add(group);
            }
        }
        Self {
            svg,
            reachable_per_step,
        }
    }
    pub fn final_svg(mut self, valley: &Valley, minute: usize) -> String {
        let step_duration = 1000;
        let animation_duration = step_duration - 200;
        self.svg = self
            .svg
            .data_attribute("steps".to_string(), format!("{}", minute + 1))
            .data_attribute("step-duration".to_string(), format!("{step_duration}"))
            .view_box((-1, -1, valley.width as i64 + 2, valley.height as i64 + 2));
        self.svg.add(SvgStyle::new(format!(".blizzard {{ transition: transform {animation_duration}ms; }} .elf {{ transition: fill-opacity {animation_duration}ms ease-in-out; }}")));
        let mut reachable_array = Vec::new();
        for (reachable, heading_down) in self.reachable_per_step.iter() {
            let mut this_reachable = Vec::new();
            this_reachable.push(if *heading_down {
                (0_i32, -1_i32)
            } else {
                ((valley.width - 1) as i32, valley.height as i32)
            });
            for (x, col) in reachable.iter().enumerate() {
                for y in 0..valley.height {
                    if col & (1 << y) > 0 {
                        this_reachable.push((x as i32, y as i32));
                    }
                }
            }
            reachable_array.push(this_reachable);
        }
        let mut reachable_array_js = String::from("const reachablePerStep = [");
        for (arr_idx, arr) in reachable_array.iter().enumerate() {
            if arr_idx > 0 {
                reachable_array_js.push(',');
            }
            reachable_array_js.push('[');
            for (idx, (x, y)) in arr.iter().enumerate() {
                if idx > 0 {
                    reachable_array_js.push(',');
                }
                reachable_array_js.push_str(&format!("[{x},{y}]"));
            }
            reachable_array_js.push(']');
        }
        reachable_array_js.push(']');
        let reachable_circle_id = self.svg.define(SvgCircle {
            cx: 0.5,
            cy: 0.5,
            r: 0.25,
            fill: Some(SvgColor::Rgb(0xfd, 0xdb, 0x27)),
        });
        let reachable_even_path_id = self.svg.add_with_id(SvgGroup::new().class("elf"));
        let reachable_odd_path_id = self.svg.add_with_id(SvgGroup::new().class("elf"));
        self.svg.add(SvgScript::new(format!(
            "{};\n\
                        const leftBlizzards = document.querySelectorAll('.blizzard-left');
                        const rightBlizzards = document.querySelectorAll('.blizzard-right');
                        const upBlizzards = document.querySelectorAll('.blizzard-up');
                        const downBlizzards = document.querySelectorAll('.blizzard-down');
                        const evenPath = document.getElementById('{}');
                        const oddPath = document.getElementById('{}');
                        const width = {};
                        const height = {};
                        const mod = (n, m) => (n % m + m) % m;
                        window.onNewStep = (step) => {{
                            const newCircles = reachablePerStep[step].map(a => {{
                                const c = document.createElementNS('http://www.w3.org/2000/svg', 'use');
                                c.setAttribute('href', '#{}');
                                c.setAttribute('x', a[0]);
                                c.setAttribute('y', a[1]);
                                return c;
                            }});\n\
                            const [oldPath, newPath] = (step % 2 == 0) ? [oddPath, evenPath] : [evenPath, oddPath];
                            oldPath.setAttribute('fill-opacity', 0);
                            newPath.setAttribute('fill-opacity', 1);
                            newPath.replaceChildren(...newCircles);
                            for (let [idx, el] of leftBlizzards.entries()) {{
                                let amount = mod((idx - step), width);
                                if (amount === width -1) {{ el.style.transition = 'none'; }} else {{ el.style.transition = ''; }}
                                el.style.transform = `translate(${{amount}}px,0px)`;
                            }}
                            for (let [idx, el] of rightBlizzards.entries()) {{
                                let amount = mod((idx + step), width);
                                if (amount === 0) {{ el.style.transition = 'none'; }} else {{ el.style.transition = ''; }}
                                el.style.transform = `translate(${{amount}}px,0px)`;
                            }}\n\
                            for (let [idx, el] of upBlizzards.entries()) {{
                                let amount = mod((idx - step), height);
                                if (amount === height - 1) {{ el.style.transition = 'none'; }} else {{ el.style.transition = ''; }}
                                el.style.transform = `translate(0px,${{amount}}px)`;
                            }}
                            for (let [idx, el] of downBlizzards.entries()) {{
                                let amount = mod((idx + step), height);
                                if (amount === 0) {{ el.style.transition = 'none'; }} else {{ el.style.transition = ''; }}
                                el.style.transform = `translate(0px,${{amount}}px)`;
                            }}
                        }};",
            reachable_array_js, reachable_even_path_id, reachable_odd_path_id, valley.width, valley.height, reachable_circle_id
        )));
        self.svg.add(
            SvgPath::default()
                .fill(SvgColor::Rgb(0xff, 0xff, 0xff))
                .shape(
                    SvgShape::at(-1, -1)
                        .line_to_relative(1, 0)
                        .line_to_relative(0, valley.height as i32 + 2)
                        .line_to_relative(-1, 0)
                        .close()
                        .move_to_absolute(-1, valley.height as i32)
                        .line_to_relative(valley.width as i32, 0)
                        .line_to_relative(0, 1)
                        .line_to_relative(-(valley.width as i32), 0)
                        .close()
                        .move_to_absolute(1, -1)
                        .line_to_relative(valley.width as i32, 0)
                        .line_to_relative(0, 1)
                        .line_to_relative(-(valley.width as i32), 0)
                        .close()
                        .move_to_absolute(valley.width as i32, -1)
                        .line_to_relative(1, 0)
                        .line_to_relative(0, valley.height as i32 + 2)
                        .line_to_relative(-1, 0)
                        .close(),
                ),
        );
        self.svg.to_svg_string()
    }
}