//use std::{thread::{sleep}, time::Duration}; use std::{collections::VecDeque, thread::sleep, time::Duration}; const ROCK: char = '#'; const AIR: char = '.'; //type Grid = Vec>; type GridRow = (usize,Vec); struct Grid { rows: VecDeque } impl Grid { const WIDTH:usize = 7; fn new() -> Grid { let mut rows = VecDeque::new(); rows.push_back((0,(0..Grid::WIDTH).map(|_| { AIR }).collect())); Grid { rows } } fn iy(&mut self, p: &Pos) -> usize { let lb = self.rows[0].0; let y = p.y as usize; let iy:usize = y - lb; // will fail if we try to access a trimmed blcok // Grow on demand if iy >= self.rows.len() { let lr = self.rows.back().unwrap().0; for y in (lr+1)..=y { self.rows.push_back((y, (0..Grid::WIDTH).map(|_| { AIR }).collect())) } } iy } fn get(&mut self, p: &Pos)->char { let iy = self.iy(p); self.rows[iy].1[p.x as usize] } fn set(&mut self, p: &Pos, x: char) { let iy:usize = self.iy(p); self.rows[iy].1[p.x as usize] = x; } fn trim(&mut self) { // cheat, jjust keep the top 100 rows // really we should check for impassable points // I don't really like this solution either tbh, I want an rtruncate method... let tt = self.rows.len() as i32 - 30; if tt > 0 { for _ in 0..tt { let _ = self.rows.pop_front(); } } } fn rock_height(&self) -> usize { self.rows.iter() .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(&self, rock: &Rock, rock_count: usize) { print!("{}[2J", 27 as char); // clear terminal! println!("+++++++"); let pts = rock.points(); for (y ,row) in self.rows.iter().rev() { print!("{:012} ", y); 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: {}", self.rock_height(), rock_count); } } #[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: &mut Grid) -> Result { 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 >= Grid::WIDTH as i32 || // Intersecting another rock? grid.get(p) != AIR }) { Err(self) } else { let mut nr = self.clone(); nr.pos = self.pos.translate(&du); Ok(nr) } } pub fn solidify(self, grid: &mut Grid) { for pt in self.points() { grid.set(&pt,ROCK); } } } pub fn run(input: String) { 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 = Grid::new(); let mut rock = Rock{ rtype: rocks.next().unwrap(), pos: Pos{x: 2, y: 3} }; let mut rock_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, &mut grid).unwrap_or_else(|e| {e}); // apply gravity rock = match rock.mv(Direction::Down, &mut grid) { Ok(rr) => rr, // Complete the move! Err(rr) => { // We didn't move, solidify! rr.solidify(&mut grid); if rock_count == 1_000_000_000_000 { break 'lp; } // Measure height... let max_rock = grid.rock_height(); // remember how many rocks we did rock_count += 1; if rock_count % 1_000_000 == 0 { println!("Done {} rocks...", rock_count); } // Spawn new rock Rock { rtype: rocks.next().unwrap(), pos: Pos{x: 2, y: max_rock as i32 +3 } } } }; grid.trim(); // grid.print(&rock, rock_count); //print(&grid, &rock, rocks_count); // sleep(Duration::from_millis(50)); // It just isn't enough.. we need to detect the repeating pattern!!!!! or find a fast enough computer :D } println!("Day 17: {}", grid.rock_height()); }