//use std::{thread::{sleep}, time::Duration}; const ROCK: char = '#'; const AIR: char = '.'; type Grid = Vec>; #[derive(Debug,Clone,PartialEq)] struct Pos { x: i32, y: i32, } impl Pos { fn new(x:i32,y:i32) -> Pos { Pos{x, y} } fn translate(&self, other: &Pos) -> Pos { Pos{x: self.x+other.x, y: self.y+other.y} } } /* #### .#. ### .#. ..# ..# ### # # # # ## ## */ #[derive(Debug,Clone,PartialEq)] enum RockType { Bar,Cross,Ell,VBar,Square } impl RockType { fn points(&self) -> Vec { match self { Self::Bar => vec![Pos::new(0,0), Pos::new(1,0), Pos::new(2,0), Pos::new(3,0)], Self::Cross => vec![Pos::new(1,0), Pos::new(0,1), Pos::new(1,1), Pos::new(2,1), Pos::new(1,2)], Self::Ell => vec![Pos::new(0,0), Pos::new(1,0), Pos::new(2,0), Pos::new(2,1), Pos::new(2,2)], Self::VBar => vec![Pos::new(0,0), Pos::new(0,1), Pos::new(0,2), Pos::new(0,3)], Self::Square => vec![Pos::new(0,0), Pos::new(0,1), Pos::new(1,0), Pos::new(1,1)], } } } #[derive(Debug,Clone,PartialEq)] enum Direction { Down,Left,Right } impl Direction { fn unit(&self)->Pos { match self { Self::Down => Pos{x:0, y:-1}, Self::Left => Pos{x:-1, y:0}, Self::Right => Pos{x:1, y:0}, } } } #[derive(Debug,Clone,PartialEq)] struct Rock { rtype: RockType, pos: Pos, // Position of lower left point in formation } impl Rock { fn points(&self) -> Vec { self.rtype.points() .into_iter().map(|p| {p.translate(&self.pos)}).collect() } pub fn mv(self, dir: Direction, grid: &Grid) -> Result { let width: i32 = grid[0].len() as i32; let du = dir.unit(); let np: Vec = self.points().into_iter().map(|p| {p.translate(&du)}).collect(); if np.iter().any(|p| { // Bounds check p.y < 0 || p.x < 0 || p.x >= width || // Intersecting another rock? grid[p.y as usize][p.x as usize] != AIR }) { Err(self) } else { let mut nr = self.clone(); nr.pos = self.pos.translate(&du); Ok(nr) } } pub fn solidify(self, grid: &mut Vec>) { for pt in self.points() { grid[pt.y as usize][pt.x as usize] = ROCK; } } } fn rock_height(grid: &Grid) -> usize { grid.iter().enumerate() .rfind(|row| { row.1.iter().any(|cell| {*cell == ROCK}) }) .map(|row| { row.0+1 // height is one more than the index.. }) .unwrap_or(0) } // fn print(grid: &Grid, rock: &Rock, rock_count: usize) { // print!("{}[2J", 27 as char); // clear terminal! // println!("+++++++"); // let pts = rock.points(); // for (y ,row) in grid.iter().enumerate().rev() { // for (x, cell) in row.iter().enumerate() { // if pts.contains(&Pos{x:(x as i32),y:(y as i32)}) { // print!("@"); // } else { // print!("{}", cell); // } // } // print!("\n"); // } // println!("+++++++ height: {} rocks: {}", rock_height(grid), rock_count); // } pub fn run(input: String) { let initial_height = 6; let width = 7; let mut rocks = vec![RockType::Bar,RockType::Cross,RockType::Ell,RockType::VBar,RockType::Square].into_iter().cycle(); let mut gas_jets = input.chars().cycle(); let mut grid: Grid = (0..initial_height).map(|_| { (0..width).map(|_| {AIR}).collect() }).collect(); let mut rock = Rock{ rtype: rocks.next().unwrap(), pos: Pos{x: 2, y: 3} }; let mut rocks_count: usize = 1; 'lp: loop { let push = match gas_jets.next().unwrap() { '>' => Direction::Right, '<' => Direction::Left, dir => panic!("Unexpected jet direction! {}", dir) }; // apply jet, ignore any error. rock = rock.mv(push, &grid).unwrap_or_else(|e| {e}); // apply gravity rock = match rock.mv(Direction::Down, &grid) { Ok(rr) => rr, // Complete the move! Err(rr) => { // We didn't move, solidify! rr.solidify(&mut grid); if rocks_count == 2022 { break 'lp; } // Measure height... let max_rock = rock_height(&grid); // Extend grid to max_rock + 7 (enough room for any new rocks at a height of at least 3 over the old ones) let ext = (max_rock+7)-grid.len(); if ext > 0 { grid.extend((0..ext).map(|_| { (0..width).map(|_| { AIR }).collect() })); } // remember how many rocks we did rocks_count += 1; // Spawn new rock Rock { rtype: rocks.next().unwrap(), pos: Pos{x: 2, y: max_rock as i32 +3 } } } }; //print(&grid, &rock, rocks_count); //sleep(Duration::from_millis(100)); } println!("Day 17: {}", rock_height(&grid)); }