Improve Pos
This commit is contained in:
parent
651ccb9cba
commit
88be9a39a0
6 changed files with 649 additions and 45 deletions
357
src/days/day22/mod.rs
Normal file
357
src/days/day22/mod.rs
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
use super::template::{DayTrait, ResultType};
|
||||
use crate::common::{
|
||||
direction::Direction,
|
||||
parser::{eol_terminated, extract_result, ignore, usize},
|
||||
pos::Pos2D,
|
||||
};
|
||||
use nom::{
|
||||
branch::alt,
|
||||
character::complete::{char, multispace0},
|
||||
combinator::value,
|
||||
error::Error,
|
||||
multi::many1,
|
||||
Err, IResult, Parser,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
const DAY_NUMBER: usize = 22;
|
||||
|
||||
pub struct Day;
|
||||
|
||||
impl DayTrait for Day {
|
||||
fn get_day_number(&self) -> usize {
|
||||
DAY_NUMBER
|
||||
}
|
||||
|
||||
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let (world_map, instructions) = Day::parse(lines)?;
|
||||
let world = WrappingWorld::new(world_map);
|
||||
let walker = Walker::walk_all(&world, instructions)?;
|
||||
Ok(ResultType::Integer(walker.value()))
|
||||
}
|
||||
|
||||
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
||||
let (world_map, instructions) = Day::parse(lines)?;
|
||||
let world = CubeWorld::new(world_map);
|
||||
let walker = Walker::walk_all(&world, instructions)?;
|
||||
Ok(ResultType::Integer(walker.value()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Day {
|
||||
fn parse(input: &str) -> Result<(WorldMap, Vec<Instruction>), WorldError> {
|
||||
let (input, world) = WorldMap::parse(input)?;
|
||||
let input = ignore(multispace0)(input)?;
|
||||
let instructions = extract_result(Instruction::parse)(input)?;
|
||||
Ok((world, instructions))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum WorldError {
|
||||
#[error("Not a valid description: {0}")]
|
||||
ParsingError(String),
|
||||
#[error("No Starting Point found")]
|
||||
NoStartingPoint,
|
||||
}
|
||||
|
||||
impl From<Err<Error<&str>>> for WorldError {
|
||||
fn from(error: Err<Error<&str>>) -> Self {
|
||||
WorldError::ParsingError(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
struct WorldMap {
|
||||
tiles: Vec<Vec<Option<bool>>>,
|
||||
}
|
||||
|
||||
impl FromStr for WorldMap {
|
||||
type Err = WorldError;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
Ok(extract_result(WorldMap::parse)(input)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl WorldMap {
|
||||
pub fn new(tiles: Vec<Vec<Option<bool>>>) -> Self {
|
||||
Self { tiles }
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> IResult<&str, WorldMap> {
|
||||
let tile = alt((
|
||||
value(Some(false), char('#')),
|
||||
value(Some(true), char('.')),
|
||||
value(None, char(' ')),
|
||||
));
|
||||
let line = eol_terminated(many1(tile));
|
||||
let mut lines = many1(line).map(|tiles| WorldMap::new(tiles));
|
||||
lines.parse(input)
|
||||
}
|
||||
|
||||
fn get_first(&self, pos: Pos2D<usize>, facing: Direction) -> Option<Pos2D<usize>> {
|
||||
match facing {
|
||||
Direction::East => self.tiles.get(pos.y()).and_then(|row| {
|
||||
row.iter()
|
||||
.position(|t| t.is_some())
|
||||
.map(|x| Pos2D::new(x, pos.y()))
|
||||
}),
|
||||
Direction::West => self.tiles.get(pos.y()).and_then(|row| {
|
||||
row.iter()
|
||||
.rposition(|t| t.is_some())
|
||||
.map(|x| Pos2D::new(x, pos.y()))
|
||||
}),
|
||||
Direction::South => self
|
||||
.tiles
|
||||
.iter()
|
||||
.position(|row| pos.x() < row.len() && row[pos.x()].is_some())
|
||||
.map(|y| Pos2D::new(pos.x(), y)),
|
||||
Direction::North => self
|
||||
.tiles
|
||||
.iter()
|
||||
.rposition(|row| pos.x() < row.len() && row[pos.x()].is_some())
|
||||
.map(|y| Pos2D::new(pos.x(), y)),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_tile(&self, pos: Pos2D<usize>) -> Option<bool> {
|
||||
self.tiles
|
||||
.get(pos.y())
|
||||
.and_then(|row| row.get(pos.x()))
|
||||
.copied()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
trait World {
|
||||
fn get_map(&self) -> &WorldMap;
|
||||
|
||||
fn wrap(&self, pos: Pos2D<usize>, facing: Direction) -> Option<(Pos2D<usize>, Direction)>;
|
||||
|
||||
fn get_start(&self, pos: Pos2D<usize>, facing: Direction) -> Option<Pos2D<usize>> {
|
||||
self.get_map().get_first(pos, facing)
|
||||
}
|
||||
|
||||
fn step(&self, pos: Pos2D<usize>, facing: Direction) -> Option<(Pos2D<usize>, Direction)> {
|
||||
let Some(pos) = pos.check_add(facing) else {
|
||||
return self.wrap(pos, facing);
|
||||
};
|
||||
|
||||
match self.get_map().get_tile(pos) {
|
||||
Some(true) => Some((pos, facing)),
|
||||
Some(false) => None,
|
||||
None => self.wrap(pos, facing),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WrappingWorld {
|
||||
map: WorldMap,
|
||||
}
|
||||
impl WrappingWorld {
|
||||
fn new(world_map: WorldMap) -> Self {
|
||||
WrappingWorld { map: world_map }
|
||||
}
|
||||
}
|
||||
|
||||
impl World for WrappingWorld {
|
||||
fn get_map(&self) -> &WorldMap {
|
||||
&self.map
|
||||
}
|
||||
|
||||
fn wrap(&self, pos: Pos2D<usize>, facing: Direction) -> Option<(Pos2D<usize>, Direction)> {
|
||||
if let Some(pos) = self.map.get_first(pos, facing) {
|
||||
match self.map.get_tile(pos) {
|
||||
Some(true) => Some((pos, facing)),
|
||||
Some(false) => None,
|
||||
None => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CubeWorld {
|
||||
map: WorldMap,
|
||||
}
|
||||
impl CubeWorld {
|
||||
fn new(world_map: WorldMap) -> Self {
|
||||
CubeWorld { map: world_map }
|
||||
}
|
||||
}
|
||||
|
||||
impl World for CubeWorld {
|
||||
fn get_map(&self) -> &WorldMap {
|
||||
&self.map
|
||||
}
|
||||
|
||||
fn wrap(&self, pos: Pos2D<usize>, facing: Direction) -> Option<(Pos2D<usize>, Direction)> {
|
||||
if let Some(pos) = self.map.get_first(pos, facing) {
|
||||
match self.map.get_tile(pos) {
|
||||
Some(true) => Some((pos, facing)),
|
||||
Some(false) => None,
|
||||
None => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Instruction {
|
||||
Walk(usize),
|
||||
Right,
|
||||
Left,
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
fn parse(input: &str) -> IResult<&str, Vec<Instruction>> {
|
||||
many1(alt((
|
||||
usize.map(|v| Instruction::Walk(v)),
|
||||
value(Instruction::Right, char('R')),
|
||||
value(Instruction::Left, char('L')),
|
||||
)))
|
||||
.parse(input)
|
||||
}
|
||||
}
|
||||
|
||||
struct Walker {
|
||||
pos: Pos2D<usize>,
|
||||
facing: Direction,
|
||||
}
|
||||
|
||||
impl Walker {
|
||||
fn new<W: World>(world: &W) -> Result<Self, WorldError> {
|
||||
let facing = Direction::East;
|
||||
if let Some(pos) = world.get_start(Pos2D::new(0, 0), facing) {
|
||||
Ok(Walker { pos, facing })
|
||||
} else {
|
||||
Err(WorldError::NoStartingPoint)
|
||||
}
|
||||
}
|
||||
|
||||
fn value(&self) -> i64 {
|
||||
(self.pos.y() + 1) as i64 * 1000
|
||||
+ (self.pos.x() + 1) as i64 * 4
|
||||
+ Walker::face_value(self.facing)
|
||||
}
|
||||
|
||||
fn face_value(facing: Direction) -> i64 {
|
||||
match facing {
|
||||
Direction::East => 0,
|
||||
Direction::North => 3,
|
||||
Direction::West => 2,
|
||||
Direction::South => 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn act<W: World>(mut self, world: &W, instruction: Instruction) -> Self {
|
||||
match instruction {
|
||||
Instruction::Right => self.facing = self.facing.turn_right(),
|
||||
Instruction::Left => self.facing = self.facing.turn_left(),
|
||||
Instruction::Walk(steps) => {
|
||||
for _ in 0..steps {
|
||||
let Some((next_pos, next_facing)) = world.step(self.pos, self.facing) else {
|
||||
break;
|
||||
};
|
||||
self.pos = next_pos;
|
||||
self.facing = next_facing;
|
||||
}
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn walk_all<W: World>(
|
||||
world: &W,
|
||||
instructions: Vec<Instruction>,
|
||||
) -> Result<Self, WorldError> {
|
||||
let mut walker = Walker::new(world)?;
|
||||
|
||||
for instruction in instructions {
|
||||
walker = walker.act(world, instruction);
|
||||
}
|
||||
Ok(walker)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::common::file::read_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_part1() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Integer(6032);
|
||||
let result = day.part1(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part2() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let expected = ResultType::Nothing;
|
||||
let result = day.part2(&lines)?;
|
||||
assert_eq!(result, expected);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let (world, instructions) = Day::parse(&lines)?;
|
||||
|
||||
assert_eq!(world.tiles.len(), 12);
|
||||
assert_eq!(instructions.len(), 13);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walk() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let (world_map, _) = Day::parse(&lines)?;
|
||||
|
||||
let world = WrappingWorld::new(world_map);
|
||||
|
||||
let mut walker = Walker::new(&world)?;
|
||||
assert_eq!(walker.pos, Pos2D::new(8, 0));
|
||||
|
||||
walker = walker.act(&world, Instruction::Walk(10));
|
||||
assert_eq!(walker.pos, Pos2D::new(10, 0));
|
||||
|
||||
walker = walker.act(&world, Instruction::Left);
|
||||
|
||||
walker = walker.act(&world, Instruction::Walk(2));
|
||||
assert_eq!(walker.pos, Pos2D::new(10, 10));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn walk_all() -> Result<()> {
|
||||
let day = Day {};
|
||||
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
||||
let (world_map, instructions) = Day::parse(&lines)?;
|
||||
let world = WrappingWorld::new(world_map);
|
||||
|
||||
let walker = Walker::walk_all(&world, instructions)?;
|
||||
assert_eq!(walker.pos, Pos2D::new(7, 5));
|
||||
assert_eq!(walker.facing, Direction::East);
|
||||
assert_eq!(walker.value(), 6032);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue