Add WIP 2022 day 17

This commit is contained in:
Kienan Stewart 2022-12-18 11:43:08 -05:00
parent d156de3e10
commit 1cc1da81e5
8 changed files with 913 additions and 10 deletions

14
2022/17/Cargo.lock generated Normal file
View File

@ -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",
]

9
2022/17/Cargo.toml Normal file
View File

@ -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" }

491
2022/17/flamegraph.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 36 KiB

1
2022/17/input Normal file

File diff suppressed because one or more lines are too long

BIN
2022/17/perf.data Normal file

Binary file not shown.

387
2022/17/src/main.rs Normal file
View File

@ -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(&current_piece.t) as i64 > self.width as i64 {
blown = false;
}
// There may be collision between pieces in the blow step.
blown = blown && !self.collides(&current_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(&current_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(&current_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;
}
}

1
2022/17/test_input Normal file
View File

@ -0,0 +1 @@
>>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>

View File

@ -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);