A bit of an overly-fussy implementation for Day 3.
This commit is contained in:
100
src/bin/tobaggan.rs
Normal file
100
src/bin/tobaggan.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use advent2020::errors::{MapParseError, TopLevelError};
|
||||
use advent2020::map::Map;
|
||||
use std::convert::TryFrom;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum Square {
|
||||
Empty,
|
||||
Tree,
|
||||
}
|
||||
|
||||
impl TryFrom<char> for Square {
|
||||
type Error = MapParseError;
|
||||
|
||||
fn try_from(c: char) -> Result<Self, Self::Error> {
|
||||
match c {
|
||||
'.' => Ok(Square::Empty),
|
||||
'#' => Ok(Square::Tree),
|
||||
_ => Err(MapParseError::UnexpectedCharacter(c)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Encounters {
|
||||
trees_encountered: usize,
|
||||
clear_spots_encountered: usize,
|
||||
}
|
||||
|
||||
fn trail_for_slope(map: &Map<Square>, run: usize, fall: usize) -> Encounters {
|
||||
let mut current_x = 0;
|
||||
let mut current_y = 0;
|
||||
let mut encounters = Encounters {
|
||||
trees_encountered: 0,
|
||||
clear_spots_encountered: 0,
|
||||
};
|
||||
|
||||
loop {
|
||||
match map.at(current_x, current_y) {
|
||||
None => break,
|
||||
Some(Square::Empty) => encounters.clear_spots_encountered += 1,
|
||||
Some(Square::Tree) => encounters.trees_encountered += 1,
|
||||
}
|
||||
|
||||
current_x += run;
|
||||
current_y += fall;
|
||||
}
|
||||
|
||||
encounters
|
||||
}
|
||||
|
||||
fn real_main() -> Result<(), TopLevelError> {
|
||||
let mut maybe_map = None;
|
||||
|
||||
for argument in env::args().skip(1) {
|
||||
let fname = argument.clone();
|
||||
let contents = fs::read_to_string(argument)?;
|
||||
match Map::<Square>::try_from(contents.as_str()) {
|
||||
Err(e) => eprintln!("Skipping file {}: Parse error: {}", fname, e),
|
||||
Ok(v) => {
|
||||
maybe_map = Some(v);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let map = match maybe_map {
|
||||
None => return Err(TopLevelError::NoInputFound),
|
||||
Some(x) => x,
|
||||
};
|
||||
|
||||
let initial = trail_for_slope(&map, 3, 1);
|
||||
println!(
|
||||
"For the initial slope, encountered {} trees and {} open spaces",
|
||||
initial.trees_encountered, initial.clear_spots_encountered
|
||||
);
|
||||
|
||||
let mut product = 1;
|
||||
let slopes = [(1, 1), (3, 1), (5, 1), (7, 1), (1, 2)];
|
||||
|
||||
for (run, fall) in slopes.iter() {
|
||||
let encounters = trail_for_slope(&map, *run, *fall);
|
||||
println!(
|
||||
"For slope ({},{}), encountered {} trees and {} open spaces",
|
||||
run, fall, encounters.trees_encountered, encounters.clear_spots_encountered
|
||||
);
|
||||
product *= encounters.trees_encountered;
|
||||
}
|
||||
|
||||
println!("The product of the trees encountered is {}", product);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match real_main() {
|
||||
Err(e) => eprintln!("ERROR: {}", e),
|
||||
Ok(_) => {}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ macro_rules! convert_error {
|
||||
|
||||
pub enum TopLevelError {
|
||||
IOError(io::Error),
|
||||
NoInputFound,
|
||||
NoSolutionFound,
|
||||
UnknownError,
|
||||
}
|
||||
@@ -23,6 +24,7 @@ impl fmt::Display for TopLevelError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
TopLevelError::IOError(e) => write!(f, "IO error: {}", e),
|
||||
TopLevelError::NoInputFound => write!(f, "No valid inputs found"),
|
||||
TopLevelError::NoSolutionFound => write!(f, "No solution found."),
|
||||
TopLevelError::UnknownError => {
|
||||
write!(f, "Unknown error occurred; this shouldn't be possible.")
|
||||
@@ -51,3 +53,19 @@ impl fmt::Display for PasswordParseError {
|
||||
|
||||
convert_error!(ParseIntError, PasswordParseError, StringToIntError);
|
||||
convert_error!(nom::Err<()>, PasswordParseError, NomError);
|
||||
|
||||
pub enum MapParseError {
|
||||
UnexpectedCharacter(char),
|
||||
UnevenLines(usize),
|
||||
}
|
||||
|
||||
impl fmt::Display for MapParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
MapParseError::UnevenLines(l) => write!(f, "Map has uneven width at line {}", l),
|
||||
MapParseError::UnexpectedCharacter(c) => {
|
||||
write!(f, "Unexpected character parsing map: {}", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod errors;
|
||||
pub mod map;
|
||||
|
||||
55
src/map.rs
Normal file
55
src/map.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use crate::errors::MapParseError;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub struct Map<A: Clone> {
|
||||
width: usize,
|
||||
_height: usize,
|
||||
data: Vec<Vec<A>>,
|
||||
}
|
||||
|
||||
impl<'a, E, X> TryFrom<&'a str> for Map<X>
|
||||
where
|
||||
X: Clone + TryFrom<char, Error = E>,
|
||||
E: From<MapParseError>,
|
||||
{
|
||||
type Error = E;
|
||||
|
||||
fn try_from(s: &str) -> Result<Map<X>, E> {
|
||||
let mut width = 0;
|
||||
let mut _height = 0;
|
||||
let mut data = Vec::new();
|
||||
|
||||
for line in s.lines() {
|
||||
let mut current_line = Vec::with_capacity(width);
|
||||
|
||||
for char in line.chars() {
|
||||
let item = X::try_from(char)?;
|
||||
current_line.push(item);
|
||||
}
|
||||
|
||||
_height += 1;
|
||||
if width == 0 {
|
||||
width = current_line.len();
|
||||
} else {
|
||||
if width != current_line.len() {
|
||||
return Err(E::from(MapParseError::UnevenLines(_height)));
|
||||
}
|
||||
}
|
||||
data.push(current_line);
|
||||
}
|
||||
|
||||
Ok(Map {
|
||||
width,
|
||||
_height,
|
||||
data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<X: Clone> Map<X> {
|
||||
pub fn at(&self, x: usize, y: usize) -> Option<X> {
|
||||
let row = self.data.get(y)?;
|
||||
let wrapped_x = x % self.width;
|
||||
Some(row[wrapped_x].clone())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user