// 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::ops::Range; pub struct CaveScan { tiles: Vec>, origin: (usize, usize), x_window: Range, y_window: Range, } pub struct Falls<'a> { scan: &'a mut CaveScan, } impl CaveScan { fn insert_hline(&mut self, start: (usize, usize), end: (usize, usize)) { assert!(start.1 == end.1); for i in start.0..=end.0 { self.set_tile((i, start.1), true); } } fn insert_vline(&mut self, start: (usize, usize), end: (usize, usize)) { assert!(start.0 == end.0); for i in start.1..=end.1 { self.set_tile((start.0, i), true); } } pub fn from_stdin(origin: (usize, usize)) -> Result { const ERROR: &str = "input file contains invalid line"; let mut result = Self { tiles: vec![vec![false]], origin: origin, x_window: origin.0..origin.0, y_window: origin.1..origin.1, }; for line in io::stdin().lines() { let line = match line { Ok(line) if { line.len() > 0 } => line, _ => break, }; let points: Result, _> = line .split(" -> ") .map(|part| part.split_once(',').ok_or(ERROR)) .map(|split| { Ok(( split?.0.parse().map_err(|_| ERROR)?, split?.1.parse().map_err(|_| ERROR)?, )) }) .collect(); for points in points?.windows(2) { let start = points.get(0).ok_or(ERROR)?; let end = points.get(1).ok_or(ERROR)?; match (start, end) { (s, e) if { s.0 == e.0 && s.1 > e.1 } => result.insert_vline(*e, *s), (s, e) if { s.0 == e.0 } => result.insert_vline(*s, *e), (s, e) if { s.1 == e.1 && s.0 > e.0 } => result.insert_hline(*e, *s), (s, e) if { s.1 == e.1 } => result.insert_hline(*s, *e), _ => return Err(ERROR), } } } Ok(result) } fn bottom(&self) -> usize { self.tiles.len() } fn tile(&self, pos: (usize, usize)) -> bool { if pos.0 < self.x_window.start || pos.0 > self.x_window.end || pos.1 < self.y_window.start || pos.1 > self.y_window.end { return false; } self.tiles[pos.1 - self.y_window.start][pos.0 - self.x_window.start] } fn set_tile(&mut self, pos: (usize, usize), value: bool) { if !self.x_window.contains(&pos.0) { let new_x_window = usize::min(self.x_window.start, pos.0)..usize::max(self.x_window.end, pos.0); for line in self.tiles.iter_mut() { let old_line = line.clone(); *line = vec![false; new_x_window.len() + 1]; let x_offset = self.x_window.start - new_x_window.start; line[x_offset..old_line.len() + x_offset].copy_from_slice(&old_line[..]); } self.x_window = new_x_window; } if !self.y_window.contains(&pos.1) { let new_y_window = usize::min(self.y_window.start, pos.1)..usize::max(self.y_window.end, pos.1); let head = vec![ vec![false; self.x_window.len() + 1]; new_y_window.start - self.y_window.start ]; let tail = vec![vec![false; self.x_window.len() + 1]; new_y_window.end - self.y_window.end]; self.tiles = [&head[..], &self.tiles[..], &tail[..]].concat(); self.y_window = new_y_window; } self.tiles[pos.1 - self.y_window.start][pos.0 - self.x_window.start] = value; } fn step_down(&self, origin: (usize, usize)) -> Option<(usize, usize)> { let down_pos = (origin.0, origin.1.checked_add(1)?); let down_left_pos = (origin.0.checked_sub(1)?, down_pos.1); let down_right_pos = (origin.0.checked_add(1)?, down_pos.1); let result = match ( self.tile(down_left_pos), self.tile(down_pos), self.tile(down_right_pos), ) { (_, false, _) => Some(down_pos), (false, true, _) => Some(down_left_pos), (true, true, false) => Some(down_right_pos), (true, true, true) => None, }; result } pub fn falls(&mut self) -> Falls { Falls { scan: self } } } impl Iterator for Falls<'_> { type Item = (usize, usize); fn next(&mut self) -> Option { let mut result = self.scan.origin; while let Some(pos) = self.scan.step_down(result) { result = pos; if result.1 == self.scan.bottom() { return Some((result.0, usize::MAX)); } } self.scan.set_tile(result, true); Some(result) } }