164 lines
5.6 KiB
Rust
164 lines
5.6 KiB
Rust
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
use std::io;
|
|
use std::ops::Range;
|
|
|
|
pub struct CaveScan {
|
|
tiles: Vec<Vec<bool>>,
|
|
origin: (usize, usize),
|
|
x_window: Range<usize>,
|
|
y_window: Range<usize>,
|
|
}
|
|
|
|
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<CaveScan, &'static str> {
|
|
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<Vec<(usize, usize)>, _> = 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<Self::Item> {
|
|
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)
|
|
}
|
|
}
|