--- /dev/null
+use std::error::Error;
+use std::path::Path;
+
+const HEIGHT: i32 = 103;
+const WIDTH: i32 = 101;
+
+#[derive(Debug)]
+struct Robot {
+ pos: (i32, i32),
+ speed: (i32, i32),
+}
+
+impl Robot {
+ pub fn new(pos: (i32, i32), speed: (i32, i32)) -> Self {
+ Self { pos, speed }
+ }
+}
+
+struct Puzzle {
+ robots: Vec<Robot>,
+ height: i32,
+ width: i32,
+}
+
+impl Puzzle {
+ pub fn new(input: &str, height: i32, width: i32) -> Self {
+ let mut robots: Vec<Robot> = Vec::new();
+ input.lines()
+ .for_each(|l| {
+ let (start, speed) = l.split_once(" ").unwrap();
+
+ let start = start[2..start.len()].split_once(",").unwrap();
+ let start = (start.0.parse::<i32>().unwrap(), start.1.parse::<i32>().unwrap());
+
+ let speed = speed[2..speed.len()].split_once(",").unwrap();
+ let speed = (speed.0.parse::<i32>().unwrap(), speed.1.parse::<i32>().unwrap());
+
+ robots.push(Robot::new(start, speed));
+ });
+ Self { robots, height, width }
+ }
+
+ fn update_by(&mut self, seconds: i32) {
+ self.robots.iter_mut()
+ .for_each(|r| {
+ let (mut x, mut y) = r.pos;
+ let (vx, vy) = r.speed;
+ x = (x + vx*seconds) % self.width;
+ y = (y + vy*seconds) % self.height;
+ if x < 0 {
+ x += self.width;
+ }
+ if y < 0 {
+ y += self.height;
+ }
+ r.pos = (x, y);
+ });
+ }
+
+ fn safety_factor(&self) -> u32 {
+ let mut c = (0, 0, 0, 0);
+ self.robots.iter()
+ .for_each(|r| {
+ if r.pos.0 < self.width / 2 && r.pos.1 < self.height / 2 {
+ c.0 += 1;
+ }
+ else if r.pos.0 > self.width / 2 && r.pos.1 < self.height / 2 {
+ c.1 += 1;
+ }
+ else if r.pos.0 < self.width / 2 && r.pos.1 > self.height / 2 {
+ c.2 += 1;
+ }
+ else if r.pos.0 > self.width / 2 && r.pos.1 > self.height / 2 {
+ c.3 += 1;
+ }
+ });
+
+ c.0 * c.1 * c.2 * c.3
+ }
+}
+
+fn run_part1(input: &str) -> Result<u32, Box<dyn Error>> {
+ println!("Running {} - part 1", get_day());
+
+ let mut puzzle = Puzzle::new(input, HEIGHT, WIDTH);
+ puzzle.update_by(100);
+
+ Ok(puzzle.safety_factor())
+}
+
+fn run_part2(input: &str) -> Result<u32, Box<dyn Error>> {
+ println!("Running {} - part 2", get_day());
+
+ Ok(0)
+}
+
+pub fn run(input: &str) -> Result<(), Box<dyn Error>> {
+ let res = run_part1(input)?;
+ println!("{res}");
+
+ let res = run_part2(input)?;
+ println!("{res}");
+
+ Ok(())
+}
+
+fn get_day() -> String {
+ let filename = file!();
+ Path::new(filename).file_stem().unwrap().to_str().unwrap().to_string()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ static TEXT_INPUT: &str = "\
+p=0,4 v=3,-3
+p=6,3 v=-1,-3
+p=10,3 v=-1,2
+p=2,0 v=2,-1
+p=0,0 v=1,3
+p=3,0 v=-2,-2
+p=7,6 v=-1,-3
+p=3,0 v=-1,-2
+p=9,3 v=2,3
+p=7,3 v=-1,2
+p=2,4 v=2,-3
+p=9,5 v=-3,-3";
+
+ #[test]
+ fn test_part1() {
+ let mut puzzle = Puzzle::new(TEXT_INPUT, 7, 11);
+ puzzle.update_by(100);
+ assert_eq!(12, puzzle.safety_factor());
+ }
+
+ #[test]
+ fn test_part2() {
+ assert_eq!(0, run_part2(TEXT_INPUT).unwrap());
+ }
+}