Add WIP 2022 day 17
This commit is contained in:
parent
d156de3e10
commit
1cc1da81e5
|
@ -0,0 +1,14 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "common"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "day17"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"common",
|
||||
]
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "day17"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
common = { path = "../common" }
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 36 KiB |
File diff suppressed because one or more lines are too long
Binary file not shown.
|
@ -0,0 +1,387 @@
|
|||
use common::Point;
|
||||
|
||||
fn main() {
|
||||
let input_file = common::parse_args_input_file(&mut std::env::args());
|
||||
let contents = std::fs::read_to_string(input_file).expect("Failed to read input file");
|
||||
|
||||
let mut game = Game::new();
|
||||
let mut goal = 2022;
|
||||
game.jets = JetDirection::get_jets_from_string(&contents);
|
||||
loop {
|
||||
game.do_round();
|
||||
|
||||
// If the 2023rd piece spawned, it's because 2022 have come to a rest
|
||||
if game.stopped_rocks >= goal {
|
||||
break;
|
||||
}
|
||||
}
|
||||
println!("[PART 1] After {} rounds, {} pieces came to a rest, towering up {}",
|
||||
game.rounds, game.pieces.len() - 1, game.rock_height);
|
||||
|
||||
game = Game::new();
|
||||
game.jets = JetDirection::get_jets_from_string(&contents);
|
||||
goal = 1_000_000_000_000;
|
||||
let mut time = std::time::Instant::now();
|
||||
loop {
|
||||
game.do_round();
|
||||
if time.elapsed().as_secs() > 1 {
|
||||
time = std::time::Instant::now();
|
||||
println!("{} of {}", game.pieces.len(), goal);
|
||||
game.print();
|
||||
break;
|
||||
}
|
||||
if game.stopped_rocks >= goal {
|
||||
break;
|
||||
}
|
||||
}
|
||||
println!("[PART 2] After {} rounds, {} pieces came to a rest, towering up {}",
|
||||
game.rounds, game.pieces.len() - 1, game.rock_height);
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy)]
|
||||
enum JetDirection {
|
||||
Left = -1,
|
||||
Right = 1,
|
||||
}
|
||||
|
||||
impl JetDirection {
|
||||
fn get_jets_from_string(contents: &String) -> std::vec::Vec<JetDirection> {
|
||||
let mut jets = std::vec::Vec::<JetDirection>::new();
|
||||
for line in contents.lines() {
|
||||
if line.eq("") {
|
||||
continue;
|
||||
}
|
||||
for c in line.chars() {
|
||||
if c.eq(&'>') {
|
||||
jets.push(JetDirection::Right);
|
||||
}
|
||||
else if c.eq(&'<') {
|
||||
jets.push(JetDirection::Left);
|
||||
}
|
||||
else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return jets;
|
||||
}
|
||||
}
|
||||
|
||||
struct Game {
|
||||
current_piece: Option<usize>,
|
||||
pieces: std::vec::Vec<Piece>,
|
||||
jets: std::vec::Vec<JetDirection>,
|
||||
next_jet: usize,
|
||||
width: u32,
|
||||
next_piece: u32,
|
||||
rounds: u64,
|
||||
rock_height: u64,
|
||||
collision_stop: usize,
|
||||
stopped_rocks: u64,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
fn new() -> Game {
|
||||
return Game {
|
||||
current_piece: None,
|
||||
pieces: std::vec::Vec::<Piece>::new(),
|
||||
jets: std::vec::Vec::<JetDirection>::new(),
|
||||
next_jet: 0,
|
||||
width: 7,
|
||||
next_piece: 0,
|
||||
rounds: 0,
|
||||
rock_height: 0,
|
||||
collision_stop: 0,
|
||||
stopped_rocks: 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn print(&self) {
|
||||
let mut max_y = 0;
|
||||
let mut points = std::collections::HashSet::<Point>::new();
|
||||
for piece in self.pieces.iter() {
|
||||
for p in piece.points().iter() {
|
||||
max_y = std::cmp::max(max_y, p.y);
|
||||
points.insert(*p);
|
||||
}
|
||||
}
|
||||
|
||||
let mut y = max_y;
|
||||
while y >= 0 {
|
||||
let mut line = String::new();
|
||||
line.push('|');
|
||||
for x in 0..self.width {
|
||||
if points.contains(&Point{x: x as i64, y: y}) {
|
||||
line.push('#');
|
||||
}
|
||||
else {
|
||||
line.push('.');
|
||||
}
|
||||
}
|
||||
line.push('|');
|
||||
println!("{}", line);
|
||||
y -= 1;
|
||||
}
|
||||
println!("+-------+");
|
||||
}
|
||||
|
||||
fn collides(&self, piece: &Piece, offset: Point) -> bool {
|
||||
let mut collides = false;
|
||||
let mut hypothetical_piece = piece.clone();
|
||||
hypothetical_piece.position.y += offset.y;
|
||||
hypothetical_piece.position.x += offset.x;
|
||||
let points = hypothetical_piece.points();
|
||||
let mut all_points = std::collections::HashSet::<Point>::new();
|
||||
|
||||
// Start with the the more recent indices, since they might be nearer.
|
||||
if self.pieces.len() < 2 {
|
||||
// If we're the only piece, or there are no pieces we can leave.
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut index = self.pieces.len() - 1;
|
||||
while index > 0 {
|
||||
let piece = &self.pieces[index];
|
||||
if piece.falling {
|
||||
index -= 1;
|
||||
continue;
|
||||
}
|
||||
if piece.position.y + PieceType::height(&piece.t) as i64 >= hypothetical_piece.position.y {
|
||||
if !piece.points().is_disjoint(&points) {
|
||||
// println!("[Round {}] Current piece will collide with piece at index {}",
|
||||
// self.rounds, index);
|
||||
collides = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
index -= 1;
|
||||
}
|
||||
return collides;
|
||||
}
|
||||
|
||||
fn do_round(&mut self) {
|
||||
if self.current_piece.is_none() {
|
||||
// Create a new piece
|
||||
self.current_piece = Some(self.pieces.len());
|
||||
let mut piece = Piece::new(PieceType::from(self.next_piece).unwrap());
|
||||
self.next_piece = (self.next_piece + 1) % PieceType::count();
|
||||
|
||||
// Determine where it's origin should be.
|
||||
// Since we're using a bottom left origin, the point is easy to set.
|
||||
piece.position.x = 2;
|
||||
// Since we're tracking the rock height for the part 1 score, we can
|
||||
// re-use that. Otherwise we would have to iterate over the pieces
|
||||
// at rest and get the highest y coordinate + the piece height + 3
|
||||
piece.position.y = self.rock_height as i64 + 3;
|
||||
// println!("[Round {}] Spawned new piece of type {:?} at {:?}",
|
||||
// self.rounds, piece.t, piece.position);
|
||||
self.pieces.push(piece);
|
||||
|
||||
// self.print();
|
||||
// println!("^ Spawned on round {} ^", self.rounds);
|
||||
}
|
||||
let current_piece_index = self.current_piece.unwrap();
|
||||
let mut current_piece = self.pieces[current_piece_index].clone();
|
||||
|
||||
// In each round, the piece is blown to the left or right based on the
|
||||
// the next jet. Then, it falls one spot if possible.
|
||||
let jet_offset = self.jets[self.next_jet] as i64;
|
||||
self.next_jet = (self.next_jet+1) % self.jets.len();
|
||||
|
||||
let mut blown = true;
|
||||
if current_piece.position.x + jet_offset < 0 {
|
||||
blown = false;
|
||||
}
|
||||
if current_piece.position.x + jet_offset + PieceType::width(¤t_piece.t) as i64 > self.width as i64 {
|
||||
blown = false;
|
||||
}
|
||||
// There may be collision between pieces in the blow step.
|
||||
blown = blown && !self.collides(¤t_piece, Point {x: jet_offset, y: 0});
|
||||
if blown {
|
||||
current_piece.position.x += jet_offset;
|
||||
// println!("[Round {}] Piece blown {} from {:?} to {:?}",
|
||||
// self.rounds, jet_offset, self.pieces[current_piece_index].position,
|
||||
// current_piece.position);
|
||||
self.pieces[current_piece_index].position = current_piece.position;
|
||||
}
|
||||
|
||||
// Check if falling would collide with anything.
|
||||
let mut fall = true;
|
||||
if current_piece.position.y - 1 < 0 {
|
||||
fall = false;
|
||||
}
|
||||
// Find pieces that are vertically near the current piece, and check collisions
|
||||
// between the current piece down one square and those pieces. However, we don't
|
||||
// want to do the check if we've already stopped falling.
|
||||
fall = fall && !self.collides(¤t_piece, Point {x: 0, y: -1});
|
||||
|
||||
if fall {
|
||||
current_piece.position.y -= 1;
|
||||
// println!("[Round {}] Piece fell from {:?} to {:?}",
|
||||
// self.rounds, self.pieces[current_piece_index].position,
|
||||
// current_piece.position);
|
||||
self.pieces[current_piece_index].position = current_piece.position;
|
||||
}
|
||||
else {
|
||||
// Coming to a rest.
|
||||
self.pieces[current_piece_index].falling = false;
|
||||
// println!("[Round {}] Piece came to a rest", self.rounds);
|
||||
// Update max height
|
||||
self.rock_height = std::cmp::max(
|
||||
self.rock_height,
|
||||
(current_piece.position.y as u64) + (PieceType::height(¤t_piece.t) as u64)
|
||||
);
|
||||
self.stopped_rocks += 1;
|
||||
self.current_piece = None;
|
||||
|
||||
// When a piece comes to rest, we check the nearby pieces to see if a line is formed,
|
||||
// keeping track of the oldest piece nearby. If a line is formed, nothing can fall below.
|
||||
// let mut max_y = 0;
|
||||
// let mut points = std::collections::HashMap::<Point, usize>::new();
|
||||
// for (index, piece) in self.pieces.iter().enumerate() {
|
||||
// for p in piece.points().iter() {
|
||||
// max_y = std::cmp::max(max_y, p.y);
|
||||
// points.insert(*p, index);
|
||||
// }
|
||||
// }
|
||||
|
||||
// let mut lowest_piece_index = usize::MAX;
|
||||
// let mut blocked = 0;
|
||||
// let mut all_blocked = 0;
|
||||
// for x in 0..self.width {
|
||||
// all_blocked |= 1 << x;
|
||||
// }
|
||||
// let y = max_y;
|
||||
// while y >= 0 {
|
||||
// for x in 0..self.width {
|
||||
// if (blocked >> x) & 1 == 1 {
|
||||
// continue;
|
||||
// }
|
||||
// let point = Point{x: x as i64, y: y as i64};
|
||||
// if points.contains_key(&point) {
|
||||
// lowest_piece_index = std::cmp::min(lowest_piece_index, *points.get(&point).unwrap());
|
||||
// blocked |= 1 << x;
|
||||
// }
|
||||
// }
|
||||
// if blocked == all_blocked {
|
||||
// self.collision_stop = std::cmp::max(lowest_piece_index, self.collision_stop);
|
||||
// }
|
||||
// y -= 1;
|
||||
// }
|
||||
}
|
||||
self.rounds += 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
enum PieceType {
|
||||
HorizontalLine = 0,
|
||||
Plus = 1,
|
||||
Ell = 2,
|
||||
VerticalLine = 3,
|
||||
Square = 4,
|
||||
}
|
||||
|
||||
impl PieceType {
|
||||
fn from(v: u32) -> Option<PieceType> {
|
||||
let t = match v {
|
||||
0 => { Some(PieceType::HorizontalLine) },
|
||||
1 => { Some(PieceType::Plus) },
|
||||
2 => { Some(PieceType::Ell) },
|
||||
3 => { Some(PieceType::VerticalLine) },
|
||||
4 => { Some(PieceType::Square) },
|
||||
_ => { None },
|
||||
};
|
||||
return t;
|
||||
}
|
||||
|
||||
fn count() -> u32 {
|
||||
return 5;
|
||||
}
|
||||
|
||||
fn width(&self) -> u32 {
|
||||
return match self {
|
||||
PieceType::HorizontalLine => { 4 },
|
||||
PieceType::Plus => { 3 },
|
||||
PieceType::Ell => { 3 },
|
||||
PieceType::VerticalLine => { 1 },
|
||||
PieceType::Square => { 2 },
|
||||
};
|
||||
}
|
||||
|
||||
fn height(&self) -> u32 {
|
||||
return match self {
|
||||
PieceType::HorizontalLine => { 1 },
|
||||
PieceType::Plus => { 3 },
|
||||
PieceType::Ell => { 3 },
|
||||
PieceType::VerticalLine => { 4 },
|
||||
PieceType::Square => { 2 },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Piece {
|
||||
// Bottom left of the piece
|
||||
position: Point,
|
||||
falling: bool,
|
||||
t: PieceType,
|
||||
}
|
||||
|
||||
impl Piece {
|
||||
fn new(t: PieceType) -> Piece {
|
||||
return Piece {
|
||||
position: Point { x: 0, y: i64::MAX },
|
||||
falling: true,
|
||||
t: t,
|
||||
};
|
||||
}
|
||||
|
||||
fn points(&self) -> std::collections::HashSet<Point> {
|
||||
let mut p = std::collections::HashSet::<Point>::new();
|
||||
match self.t {
|
||||
PieceType::HorizontalLine => {
|
||||
for x in 0..PieceType::width(&self.t) {
|
||||
p.insert(Point { x: self.position.x + x as i64, y: self.position.y });
|
||||
}
|
||||
},
|
||||
PieceType::VerticalLine => {
|
||||
for y in 0..PieceType::height(&self.t) {
|
||||
p.insert(Point { x: self.position.x, y: self.position.y + y as i64});
|
||||
}
|
||||
},
|
||||
PieceType::Square => {
|
||||
for x in 0..PieceType::width(&self.t) {
|
||||
for y in 0..PieceType::height(&self.t) {
|
||||
p.insert(Point { x: self.position.x + x as i64, y: self.position.y + y as i64 });
|
||||
}
|
||||
}
|
||||
},
|
||||
PieceType::Ell => {
|
||||
for x in 0..PieceType::width(&self.t) {
|
||||
p.insert(Point { x: self.position.x + x as i64, y: self.position.y as i64 });
|
||||
}
|
||||
for y in 0..PieceType::height(&self.t) {
|
||||
let x = PieceType::width(&self.t) as i64 - 1;
|
||||
p.insert(Point { x: self.position.x + x, y: self.position.y + y as i64});
|
||||
}
|
||||
},
|
||||
PieceType::Plus => {
|
||||
let max_x = PieceType::width(&self.t);
|
||||
let max_y = PieceType::height(&self.t);
|
||||
for x in 0..max_x {
|
||||
for y in 0..max_y {
|
||||
if (x == 0 && y == 0) || (x == 0 && y == max_y - 1) ||
|
||||
(x == max_x - 1 && y == 0) || (x == max_x -1 && y == max_y -1) {
|
||||
continue;
|
||||
}
|
||||
p.insert(Point{ x: self.position.x + x as i64, y: self.position.y + y as i64});
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
return p;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
>>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>
|
|
@ -19,12 +19,12 @@ pub fn parse_args_input_file(args: &mut std::env::Args) -> String {
|
|||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Point {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub x: i64,
|
||||
pub y: i64,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub fn displacement(direction: char, magnitude: i32) -> Point {
|
||||
pub fn displacement(direction: char, magnitude: i64) -> Point {
|
||||
let delta = match direction {
|
||||
'U' => Point { x: 0, y: magnitude },
|
||||
'D' => Point { x: 0, y: -magnitude},
|
||||
|
@ -84,13 +84,13 @@ impl Point {
|
|||
pub fn unit(&mut self) {
|
||||
let larger = std::cmp::max(self.x.abs(), self.y.abs()) as f32;
|
||||
assert!(larger != 0.0);
|
||||
self.x = (self.x.abs() as f32 / larger).ceil() as i32;
|
||||
self.y = (self.y.abs() as f32 / larger).ceil() as i32;
|
||||
self.x = (self.x.abs() as f32 / larger).ceil() as i64;
|
||||
self.y = (self.y.abs() as f32 / larger).ceil() as i64;
|
||||
}
|
||||
|
||||
pub fn manhattan_distance(&self, other: &Point) -> u32 {
|
||||
let x = (self.x - other.x).abs() as u32;
|
||||
let y = (self.y - other.y).abs() as u32;
|
||||
pub fn manhattan_distance(&self, other: &Point) -> u64 {
|
||||
let x = (self.x - other.x).abs() as u64;
|
||||
let y = (self.y - other.y).abs() as u64;
|
||||
return x + y;
|
||||
}
|
||||
}
|
||||
|
@ -113,8 +113,8 @@ mod tests {
|
|||
|
||||
let h = Point { x: 0, y: 0 };
|
||||
|
||||
for x in -1..2 as i32 {
|
||||
for y in -1..2 as i32 {
|
||||
for x in -1..2 as i64 {
|
||||
for y in -1..2 as i64 {
|
||||
let p = Point { x: x, y: y };
|
||||
println!("{},{}", x, y);
|
||||
assert_eq!(p.towards(&h), no_movement);
|
||||
|
|
Loading…
Reference in New Issue