diff --git a/.aoc_tiles/tiles/2025/10.png b/.aoc_tiles/tiles/2025/10.png
new file mode 100644
index 0000000..60bcc90
Binary files /dev/null and b/.aoc_tiles/tiles/2025/10.png differ
diff --git a/README.md b/README.md
index b97cb02..8897295 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
- 2025 - 17 ⭐ - Rust
+ 2025 - 18 ⭐ - Rust
@@ -29,4 +29,7 @@
+
+
+
diff --git a/src/day10.rs b/src/day10.rs
new file mode 100644
index 0000000..0d33b39
--- /dev/null
+++ b/src/day10.rs
@@ -0,0 +1,185 @@
+use std::{collections::BinaryHeap, iter::repeat_n};
+
+use aoc_runner_derive::{aoc, aoc_generator};
+use itertools::Itertools;
+use regex::Regex;
+
+#[derive(Clone, Debug, Default)]
+struct MachineDefinition {
+ desired: Vec,
+ buttons: Vec>,
+ joltages: Vec,
+}
+
+impl MachineDefinition {
+ fn create<'a>(&'a self) -> Machine<'a> {
+ Machine {
+ d: self,
+ lights: Vec::from_iter(repeat_n(false, self.desired.len())),
+ }
+ }
+}
+
+impl From<&str> for MachineDefinition {
+ fn from(value: &str) -> Self {
+ let parse_re = Regex::new(
+ r##"\[(?[.#]+)\] (?(\([0-9,]+\) ?)+) \{(?[0-9,]+)\}"##,
+ )
+ .unwrap();
+
+ let parts = parse_re.captures(value).unwrap();
+
+ MachineDefinition {
+ desired: parts["desired"].chars().map(|c| c == '#').collect(),
+ buttons: parts["buttons"]
+ .split_ascii_whitespace()
+ .map(|s| {
+ s[1..s.len() - 1]
+ .split(',')
+ .map(|n| n.parse().unwrap())
+ .collect()
+ })
+ .collect(),
+ joltages: parts["joltages"]
+ .split(',')
+ .map(|n| n.parse().unwrap())
+ .collect(),
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+struct Machine<'a> {
+ d: &'a MachineDefinition,
+ lights: Vec,
+}
+
+impl<'a> Machine<'a> {
+ /// Get the state after pressing `button`, returns None if the state is as desired
+ fn press(&self, button: usize) -> Option {
+ let mut new_state = self.lights.clone();
+ for light in &self.d.buttons[button] {
+ new_state[*light] = !new_state[*light]
+ }
+ if new_state == self.d.desired {
+ None
+ } else {
+ Some(Machine {
+ d: self.d,
+ lights: new_state,
+ })
+ }
+ }
+
+ /// Get the possible states from the current position
+ fn next_states(&self) -> Vec<(usize, Option>)> {
+ self.d
+ .buttons
+ .iter()
+ .enumerate()
+ .map(|(i, _but)| (i, self.press(i)))
+ .collect()
+ }
+}
+
+#[derive(Debug, Clone)]
+struct PressSet<'a> {
+ machine: Machine<'a>,
+ presses: Vec,
+}
+
+// NOTE: All compares are reversed so our max heap becomes a min heap
+impl<'a> Eq for PressSet<'a> {}
+
+impl<'a> PartialEq for PressSet<'a> {
+ fn eq(&self, other: &Self) -> bool {
+ other.presses.len().eq(&self.presses.len())
+ }
+}
+
+impl<'a> PartialOrd for PressSet<'a> {
+ fn partial_cmp(&self, other: &Self) -> Option {
+ other.presses.len().partial_cmp(&self.presses.len())
+ }
+}
+
+impl<'a> Ord for PressSet<'a> {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ other.presses.len().cmp(&self.presses.len())
+ }
+}
+
+fn find_best<'a>(md: &'a MachineDefinition) -> Vec {
+ let m = md.create();
+ let mut to_check = BinaryHeap::new();
+ for next in m.next_states() {
+ if let Some(new_m) = next.1 {
+ to_check.push(PressSet {
+ presses: vec![next.0],
+ machine: new_m.clone(),
+ })
+ } else {
+ // what we found a solution on the first move?
+ return vec![next.0];
+ }
+ }
+ while let Some(candidate) = to_check.pop() {
+ let cm = candidate.machine.clone();
+ for next in cm.next_states() {
+ let mut presses = candidate.presses.clone();
+ presses.push(next.0);
+ if let Some(new_m) = next.1 {
+ to_check.push(PressSet {
+ presses,
+ machine: new_m.clone(),
+ })
+ } else {
+ return presses;
+ }
+ }
+ }
+ panic!()
+}
+
+#[aoc_generator(day10)]
+fn parse(input: &str) -> Vec {
+ input.lines().map(|l| l.into()).collect()
+}
+
+#[aoc(day10, part1)]
+fn part1(input: &Vec) -> u64 {
+ input
+ .into_iter()
+ .map(|md| find_best(md))
+ // .inspect(|sol| println!(" [{sol:?}]"))
+ .map(|sol| sol.len() as u64)
+ .sum()
+}
+
+#[aoc(day10, part2)]
+fn part2(input: &Vec) -> u64 {
+ 0
+}
+
+#[cfg(test)]
+mod tests {
+ use rstest::rstest;
+
+ use super::*;
+
+ const EXAMPLE: &str = "[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7}
+[...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2}
+[.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5}";
+
+ #[rstest]
+ #[case(EXAMPLE, 7)]
+ fn part1_example(#[case] input: &str, #[case] expected: u64) {
+ assert_eq!(part1(&parse(input)), expected);
+ }
+
+ #[rstest]
+ #[case(EXAMPLE, 0)]
+ fn part2_example(#[case] input: &str, #[case] expected: u64) {
+ assert_eq!(part2(&parse(input)), expected);
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index b38dc0b..4831a4d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
mod day1;
+mod day10;
mod day2;
mod day3;
mod day4;