// Copyright 2022 Christian Ulrich // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . use std::io; use std::str::FromStr; #[derive(Clone, Copy)] enum Direction { Up, Right, Down, Left, } #[derive(Debug, Clone, Copy)] pub struct RopeState { knots: [(isize, isize); LEN], } pub struct RopeStates { direction: Direction, count: isize, state: RopeState, } impl FromStr for Direction { type Err = &'static str; fn from_str(s: &str) -> Result { match s { "U" => Ok(Self::Up), "R" => Ok(Self::Right), "D" => Ok(Self::Down), "L" => Ok(Self::Left), _ => Err("cannot parse direction"), } } } impl RopeState { pub fn new() -> Self { Self { knots: [(0, 0); LEN], } } pub fn head_pos(&self) -> (isize, isize) { self.knots[0] } pub fn tail_pos(&self) -> (isize, isize) { self.knots[LEN - 1] } fn adjust_knot(&mut self, index: usize) { let x_diff = self.knots[index - 1].0.abs_diff(self.knots[index].0); let y_diff = self.knots[index - 1].1.abs_diff(self.knots[index].1); let (move_x, move_y) = match (x_diff, y_diff) { (2, 0) => (true, false), (2, 1) => (true, true), (0, 2) => (false, true), (1, 2) => (true, true), (2, 2) => (true, true), _ => (false, false), }; if move_x { match (self.knots[index - 1].0, self.knots[index].0) { (h, t) if { h > t } => self.knots[index].0 += 1, (h, t) if { h < t } => self.knots[index].0 -= 1, _ => (), } } if move_y { match (self.knots[index - 1].1, self.knots[index].1) { (h, t) if { h > t } => self.knots[index].1 += 1, (h, t) if { h < t } => self.knots[index].1 -= 1, _ => (), } } } fn move_head(&mut self, direction: Direction) -> Result, &'static str> { const ERROR: &'static str = "invalid move"; match direction { Direction::Up => self.knots[0].1 = self.knots[0].1.checked_add(1).ok_or(ERROR)?, Direction::Right => self.knots[0].0 = self.knots[0].0.checked_add(1).ok_or(ERROR)?, Direction::Down => self.knots[0].1 = self.knots[0].1.checked_sub(1).ok_or(ERROR)?, Direction::Left => self.knots[0].0 = self.knots[0].0.checked_sub(1).ok_or(ERROR)?, } for i in 1..LEN { self.adjust_knot(i); } Ok(*self) } } impl Iterator for RopeStates { type Item = Result, &'static str>; fn next(&mut self) -> Option { if self.count == 0 { let mut line = String::new(); match io::stdin().read_line(&mut line) { Ok(_) if { line == "" || line == "\n" } => return None, Ok(_) => { if let Some((left, right)) = line.trim().split_once(' ') { if let (Ok(direction), Ok(count)) = (left.parse(), right.parse()) { self.direction = direction; self.count = count; } else { return Some(Err("input file contains invalid line")); } } else { return Some(Err("input file contains invalid line")); } } Err(_) => assert!(false), } } self.count -= 1; Some(self.state.move_head(self.direction)) } } pub fn rope_states() -> RopeStates { RopeStates { direction: Direction::Up, count: 0, state: RopeState::new(), } }