This commit is contained in:
Malte Tammena 2022-12-18 07:33:34 +01:00
parent be5ff660dd
commit 23ac043d8e
11 changed files with 1074 additions and 219 deletions

340
Cargo.lock generated
View file

@ -2,12 +2,55 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "ahash"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "arrays"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dbdbb2877d26e0647c6b8125802b2ecf2dc2a28d864dde41e6f9b9a54da08fe"
[[package]]
name = "async-trait"
version = "0.1.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "async_once"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ce4f10ea3abcd6617873bae9f91d1c5332b4a778bd9ce34d0cd517474c1de82"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "beef"
version = "0.5.2"
@ -20,6 +63,58 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitvec_simd"
version = "0.20.5"
source = "git+https://github.com/Imberflur/bitvec_simd?branch=master#b2c1df36d8fc015b0c2494305f3be70451e4f763"
dependencies = [
"smallvec",
"wide",
]
[[package]]
name = "bytemuck"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f"
[[package]]
name = "cached"
version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b4147cd94d5fbdc2ab71b11d50a2f45493625576b3bb70257f59eedea69f3d"
dependencies = [
"async-trait",
"async_once",
"cached_proc_macro",
"cached_proc_macro_types",
"futures",
"hashbrown 0.12.3",
"instant",
"lazy_static",
"once_cell",
"thiserror",
"tokio",
]
[[package]]
name = "cached_proc_macro"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "751f7f4e7a091545e7f6c65bacc404eaee7e87bfb1f9ece234a1caa173dc16f2"
dependencies = [
"cached_proc_macro_types",
"darling",
"quote",
"syn",
]
[[package]]
name = "cached_proc_macro_types"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663"
[[package]]
name = "cc"
version = "1.0.77"
@ -76,6 +171,41 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "882e392738ed515520f38708166e9efec85ee154f80bf1fc64510e2fc54bf481"
[[package]]
name = "darling"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "day01"
version = "0.1.0"
@ -175,14 +305,27 @@ dependencies = [
[[package]]
name = "day14"
version = "0.1.0"
dependencies = [
"bitvec_simd",
"hashbrown 0.13.1",
"termion",
]
[[package]]
name = "day15"
version = "0.1.0"
dependencies = [
"regex",
]
[[package]]
name = "day16"
version = "0.1.0"
dependencies = [
"bitvec_simd",
"cached",
"regex",
]
[[package]]
name = "day17"
@ -243,6 +386,67 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "futures"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
[[package]]
name = "futures-io"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
[[package]]
name = "futures-sink"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
[[package]]
name = "futures-task"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
[[package]]
name = "futures-util"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
dependencies = [
"futures-core",
"futures-sink",
"futures-task",
"pin-project-lite",
"pin-utils",
]
[[package]]
name = "getrandom"
version = "0.2.8"
@ -254,6 +458,21 @@ dependencies = [
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038"
dependencies = [
"ahash",
]
[[package]]
name = "heck"
version = "0.4.0"
@ -269,6 +488,21 @@ dependencies = [
"libc",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "io-lifetimes"
version = "1.0.3"
@ -361,6 +595,12 @@ dependencies = [
"syn",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "numtoa"
version = "0.1.0"
@ -379,6 +619,18 @@ version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@ -475,6 +727,17 @@ dependencies = [
"redox_syscall",
]
[[package]]
name = "regex"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
@ -495,6 +758,30 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "safe_arch"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "794821e4ccb0d9f979512f9c1973480123f9bd62a90d74ab0f9426fcf8f4a529"
dependencies = [
"bytemuck",
]
[[package]]
name = "serde"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91"
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
dependencies = [
"serde",
]
[[package]]
name = "strsim"
version = "0.10.0"
@ -543,6 +830,49 @@ dependencies = [
"redox_termios",
]
[[package]]
name = "thiserror"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio"
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
dependencies = [
"autocfg",
"pin-project-lite",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.5"
@ -561,6 +891,16 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wide"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae41ecad2489a1655c8ef8489444b0b113c0a0c795944a3572a0931cf7d2525c"
dependencies = [
"bytemuck",
"safe_arch",
]
[[package]]
name = "winapi"
version = "0.3.9"

View file

@ -8,3 +8,6 @@ build = true
app = true
[dependencies]
bitvec_simd = { git = "https://github.com/Imberflur/bitvec_simd", branch = "master" }
hashbrown = { version = "0.13.1", features = ["nightly"] }
termion = "2.0.1"

View file

@ -1,9 +1,14 @@
use std::ops::{Index, IndexMut, Sub};
use std::ops::Sub;
use bitvec_simd::BitVec;
use hashbrown::HashMap;
use termion::color;
const SOURCE: Point = Point::new(500, 0);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Material {
#[default]
Air,
Stone,
Sand,
@ -12,40 +17,24 @@ pub enum Material {
pub enum InsertResult {
Ok,
Overflow,
ReachedTop,
}
pub enum GravityApplyResult {
Moved,
Fixed,
Outside,
ReachedTop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Point([u16; 2]);
impl Point {
const MAX: Self = Point::new(u16::MAX, u16::MAX);
const MIN: Self = Point::new(u16::MIN, u16::MIN);
pub const fn new(x: u16, y: u16) -> Self {
Self::from([x, y])
}
pub const fn maximize(&self, other: &Self) -> Self {
let sx = self.0[0];
let sy = self.0[1];
let ox = other.0[0];
let oy = other.0[1];
Point::new(sx.max(ox), sy.max(oy))
}
pub const fn minimize(&self, other: &Self) -> Self {
let sx = self.0[0];
let sy = self.0[1];
let ox = other.0[0];
let oy = other.0[1];
Point::new(sx.min(ox), sy.min(oy))
}
pub const fn x(&self) -> u16 {
self.0[0]
}
@ -54,14 +43,6 @@ impl Point {
self.0[1]
}
pub fn x_mut(&mut self) -> &mut u16 {
&mut self.0[0]
}
pub fn y_mut(&mut self) -> &mut u16 {
&mut self.0[1]
}
fn line_to(&self, other: &Point) -> Box<dyn Iterator<Item = Self>> {
let sx = self.0[0];
let sy = self.0[1];
@ -73,136 +54,143 @@ impl Point {
Box::from((sy.min(oy)..=sy.max(oy)).map(move |new_y| Point::new(sx, new_y)))
}
}
const fn down(&self) -> Point {
Point::new(self.x(), self.y() + 1)
}
const fn down_left(&self) -> Point {
Point::new(self.x() - 1, self.y() + 1)
}
const fn down_right(&self) -> Point {
Point::new(self.x() + 1, self.y() + 1)
}
}
pub struct Cave {
min: Point,
max: Point,
grid: Vec<Material>,
pub struct Cave<const HAS_FLOOR: bool> {
grid: HashMap<Point, Material>,
max_stone_y: u16,
}
impl Cave {
pub fn from_span(min: Point, max: Point) -> Self {
let width = (max.x() - min.x()) as usize + 1;
let height = (max.y() - min.y()) as usize + 1;
let grid: Vec<Material> = ::std::iter::repeat(Material::Air)
.take(width * height)
.collect();
Cave { min, max, grid }
}
pub fn get(&self, index: Point) -> &Material {
let normalized = index - self.min;
let idx = normalized.x() as usize + normalized.y() as usize * self.width() as usize;
&self.grid[idx]
}
fn get_mut(&mut self, index: Point) -> &mut Material {
let normalized = index - self.min;
let idx = normalized.x() as usize + normalized.y() as usize * self.width() as usize;
&mut self.grid[idx]
}
fn width(&self) -> u16 {
self.max.x() - self.min.x() + 1
impl<const HAS_FLOOR: bool> Cave<HAS_FLOOR> {
pub fn get(&self, index: &Point) -> Option<&Material> {
self.grid.get(index)
}
pub fn insert_sand(&mut self) -> InsertResult {
let mut pos = SOURCE;
while self.is_in_bounds(pos) {
loop {
match self.apply_gravity(&mut pos) {
GravityApplyResult::Moved => {}
GravityApplyResult::Fixed => {
self[pos] = Material::Sand;
return InsertResult::Ok;
self.insert(pos, Material::Sand);
break InsertResult::Ok;
}
GravityApplyResult::Outside => break InsertResult::Overflow,
GravityApplyResult::ReachedTop => break InsertResult::ReachedTop,
}
}
InsertResult::Overflow
}
fn is_in_bounds(&self, pos: Point) -> bool {
self.min.x() <= pos.x()
&& self.min.y() <= pos.y()
&& self.max.x() >= pos.x()
&& self.max.y() >= pos.y()
pub fn count_sand_using_scanline(&mut self) -> usize {
let mut y = SOURCE.y();
let mut min_x = SOURCE.x();
let mut max_x = SOURCE.x();
let mut bits = BitVec::zeros(SOURCE.x() as usize);
let mut total = 0;
bits.set(SOURCE.x() as usize, true);
while !bits.is_empty() {
if y == self.max_stone_y + 2 {
break;
}
let mut last_left_bit = false;
for x in min_x..=max_x {
let mat = self.get(&Point::new(x, y)).copied().unwrap_or_default();
let set_bit = bits.get(x as usize).unwrap_or_default()
|| last_left_bit
|| bits.get(x as usize + 1).unwrap_or_default();
last_left_bit = bits.get(x as usize).unwrap_or_default();
if mat == Material::Stone {
bits.set(x as usize, false);
} else if set_bit && mat == Material::Air {
bits.set(x as usize, true);
}
}
min_x -= 1;
max_x += 1;
y += 1;
total += bits.count_ones();
}
total
}
fn apply_gravity(&self, pos: &mut Point) -> GravityApplyResult {
if pos.y() == self.max.y() {
*pos.y_mut() += 1;
if HAS_FLOOR && pos.y() == self.max_stone_y + 1 {
GravityApplyResult::Fixed
} else if !HAS_FLOOR && pos.y() >= self.max_stone_y {
*pos = pos.down();
GravityApplyResult::Outside
} else if self.is_air(&pos.down()) {
*pos = pos.down();
GravityApplyResult::Moved
} else if let Material::Air = &self[Point::new(pos.x(), pos.y().saturating_add(1))] {
*pos.y_mut() += 1;
} else if self.is_air(&pos.down_left()) {
*pos = pos.down_left();
GravityApplyResult::Moved
} else if let Material::Air =
&self[Point::new(pos.x().saturating_sub(1), pos.y().saturating_add(1))]
{
*pos.y_mut() += 1;
*pos.x_mut() -= 1;
GravityApplyResult::Moved
} else if let Material::Air =
&self[Point::new(pos.x().saturating_add(1), pos.y().saturating_add(1))]
{
*pos.y_mut() += 1;
*pos.x_mut() += 1;
} else if self.is_air(&pos.down_right()) {
*pos = pos.down_right();
GravityApplyResult::Moved
} else if *pos == SOURCE {
GravityApplyResult::ReachedTop
} else {
GravityApplyResult::Fixed
}
}
fn height(&self) -> u16 {
self.max.y() - self.min.y() + 1
}
}
impl FromIterator<Vec<Point>> for Cave {
fn from_iter<T: IntoIterator<Item = Vec<Point>>>(iter: T) -> Self {
let (stone_points, min, max) = iter.into_iter().fold(
(vec![], Point::MAX, Point::MIN),
|(mut stone_points, mut min, mut max), points| {
let mut last: Option<Point> = None;
for point in points {
min = point.minimize(&min);
max = point.maximize(&max);
match last {
Some(last) => stone_points.extend(last.line_to(&point)),
// Push the first point manually
None => stone_points.push(point.clone()),
}
last = Some(point);
}
(stone_points, min, max)
},
);
let min = min.minimize(&SOURCE);
let max = max.maximize(&SOURCE);
let min = Point::new(min.x().saturating_sub(1), min.y().saturating_sub(1));
let max = Point::new(max.x().saturating_add(1), max.y().saturating_add(1));
let mut cave = Cave::from_span(min, max);
for point in stone_points {
cave[point] = Material::Stone;
fn is_air(&self, pos: &Point) -> bool {
match self.get(pos) {
Some(Material::Air) | None => true,
_ => false,
}
}
pub fn new() -> Self {
Self {
grid: HashMap::new(),
max_stone_y: 0,
}
}
fn insert(&mut self, pos: Point, material: Material) -> Option<Material> {
if material == Material::Stone {
self.max_stone_y = self.max_stone_y.max(pos.y());
}
self.grid.insert(pos, material)
}
}
impl<const HAS_FLOOR: bool> FromIterator<Vec<Point>> for Cave<HAS_FLOOR> {
fn from_iter<T: IntoIterator<Item = Vec<Point>>>(iter: T) -> Self {
let stone_points = iter.into_iter().fold(vec![], |mut stone_points, points| {
let mut last: Option<Point> = None;
for point in points {
match last {
Some(last) => stone_points.extend(last.line_to(&point)),
// Push the first point manually
None => stone_points.push(point.clone()),
}
last = Some(point);
}
stone_points
});
let mut cave = Cave::new();
for point in stone_points {
cave.insert(point, Material::Stone);
}
println!("{cave}");
cave
}
}
impl Index<Point> for Cave {
type Output = Material;
fn index(&self, index: Point) -> &Self::Output {
self.get(index)
}
}
impl IndexMut<Point> for Cave {
fn index_mut(&mut self, index: Point) -> &mut Self::Output {
self.get_mut(index)
}
}
impl const From<[u16; 2]> for Point {
fn from(raw: [u16; 2]) -> Self {
Self(raw)
@ -217,26 +205,54 @@ impl const Sub for Point {
}
}
impl std::fmt::Display for Cave {
impl<const HAS_FLOOR: bool> std::fmt::Display for Cave<HAS_FLOOR> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for y in self.min.y()..=self.max.y() {
for x in self.min.x()..=self.max.x() {
if Point::new(x, y) == SOURCE {
write!(f, "@")?;
let (min_x, max_x, min_y, max_y) = self.grid.keys().fold(
(u16::MAX, u16::MIN, u16::MAX, u16::MIN),
|(min_x, max_x, min_y, max_y), pos| {
(
min_x.min(pos.x()),
max_x.max(pos.x()),
min_y.min(pos.y()),
max_y.max(pos.y()),
)
},
);
let min_x = min_x.min(SOURCE.x());
let max_x = max_x.max(SOURCE.x());
let min_y = min_y.min(SOURCE.y());
let max_y = max_y.max(SOURCE.y());
for y in min_y..=max_y {
for x in min_x..=max_x {
let pos = Point::new(x, y);
let material = self.get(&pos);
if pos == SOURCE {
write!(f, "{}", color::Fg(color::Green))?
} else {
write!(
f,
"{}",
match self[Point::new(x, y)] {
Material::Air => ".",
Material::Sand => "o",
Material::Stone => "#",
match material {
Some(Material::Air) | None => {
write!(f, "{}", color::Fg(color::Rgb(128, 128, 128)))
}
)?;
}
Some(Material::Stone) => write!(f, "{}", color::Fg(color::LightRed)),
Some(Material::Sand) => write!(f, "{}", color::Fg(color::Yellow)),
}?
};
write!(
f,
"{}",
if pos == SOURCE {
"@"
} else {
match material {
Some(Material::Air) | None => ".",
Some(Material::Stone) => "#",
Some(Material::Sand) => "o",
}
}
)?;
}
writeln!(f)?;
}
writeln!(f)
writeln!(f, "{}", color::Fg(color::Reset))
}
}

View file

@ -30,21 +30,27 @@ fn parse_line<S: AsRef<str>>(line: S) -> Vec<Point> {
}
fn solve_first_puzzle<R: BufRead>(reader: R) -> usize {
let mut cave: Cave = reader
let mut cave: Cave<false> = reader
.lines()
.map(Result::unwrap)
.map(|line| parse_line(&line))
.collect();
let mut counter = 0;
while let InsertResult::Ok = cave.insert_sand() {
println!("{cave}");
counter += 1;
}
println!("{cave}");
counter
}
fn solve_second_puzzle<R: BufRead>(reader: R) -> usize {
todo!()
let mut cave: Cave<true> = reader
.lines()
.map(Result::unwrap)
.map(|line| parse_line(&line))
.collect();
println!("{cave}");
cave.count_sand_using_scanline()
}
fn main() {
@ -81,6 +87,6 @@ mod tests {
#[test]
fn example_on_second() {
let reader = Cursor::new(include_str!("../input.test"));
assert_eq!(solve_second_puzzle(reader), 140);
assert_eq!(solve_second_puzzle(reader), 93);
}
}

View file

@ -8,3 +8,4 @@ build = true
app = true
[dependencies]
regex = "1.7.0"

23
day15/input Normal file
View file

@ -0,0 +1,23 @@
Sensor at x=325337, y=2568863: closest beacon is at x=-518661, y=2000000
Sensor at x=3988825, y=837820: closest beacon is at x=4305648, y=2127118
Sensor at x=1611311, y=2053174: closest beacon is at x=2827226, y=1579510
Sensor at x=101890, y=3940049: closest beacon is at x=955472, y=3457514
Sensor at x=3962702, y=2558425: closest beacon is at x=4226981, y=2604726
Sensor at x=2957890, y=2160813: closest beacon is at x=2827226, y=1579510
Sensor at x=3907456, y=3325610: closest beacon is at x=3696221, y=3226373
Sensor at x=3354177, y=3435919: closest beacon is at x=3696221, y=3226373
Sensor at x=3997379, y=3071868: closest beacon is at x=3696221, y=3226373
Sensor at x=145143, y=1714962: closest beacon is at x=-518661, y=2000000
Sensor at x=611563, y=3148864: closest beacon is at x=955472, y=3457514
Sensor at x=3080405, y=3904777: closest beacon is at x=3696221, y=3226373
Sensor at x=644383, y=10732: closest beacon is at x=364635, y=-294577
Sensor at x=3229566, y=1694167: closest beacon is at x=2827226, y=1579510
Sensor at x=1600637, y=3984884: closest beacon is at x=955472, y=3457514
Sensor at x=2959765, y=2820860: closest beacon is at x=2491502, y=2897876
Sensor at x=2235330, y=3427797: closest beacon is at x=2491502, y=2897876
Sensor at x=2428996, y=210881: closest beacon is at x=2827226, y=1579510
Sensor at x=369661, y=687805: closest beacon is at x=364635, y=-294577
Sensor at x=3558476, y=2123614: closest beacon is at x=4305648, y=2127118
Sensor at x=3551529, y=2825104: closest beacon is at x=3696221, y=3226373
Sensor at x=64895, y=3577: closest beacon is at x=364635, y=-294577
Sensor at x=3079531, y=1538659: closest beacon is at x=2827226, y=1579510

View file

@ -1,3 +1,163 @@
fn main() {
println!("Hello, world!");
use regex::Regex;
use std::{
fs::File,
io::{BufRead, BufReader},
};
const TUNING_FACTOR: i64 = 4_000_000;
#[derive(Debug, Default, PartialEq, Eq)]
enum SolvePuzzle {
First,
#[default]
Second,
}
#[derive(Debug, Default)]
struct Sensor {
x: i64,
y: i64,
manhattan_radius: u64,
}
#[derive(Debug, Default)]
struct Beacon {
x: i64,
y: i64,
}
impl Sensor {
pub const fn min_x(&self) -> i64 {
self.x - self.manhattan_radius as i64
}
pub const fn max_x(&self) -> i64 {
self.x + self.manhattan_radius as i64
}
fn is_in_range(&self, x: i64, y: i64) -> bool {
x.abs_diff(self.x) + y.abs_diff(self.y) <= self.manhattan_radius
}
}
fn parse_input<R: BufRead>(reader: R) -> (Vec<Sensor>, Vec<Beacon>) {
let regex =
Regex::new(r"^Sensor at x=(-?\d+), y=(-?\d+): closest beacon is at x=(-?\d+), y=(-?\d+)$")
.unwrap();
reader
.lines()
.map(Result::unwrap)
.map(|line| {
let captures = regex.captures(&line).expect("Regex matches line");
let sensor_x: i64 = captures
.get(1)
.expect("Sensor X position")
.as_str()
.parse()
.expect("Input not a number");
let sensor_y: i64 = captures
.get(2)
.expect("Sensor Y position")
.as_str()
.parse()
.expect("Input not a number");
let beacon_x: i64 = captures
.get(3)
.expect("Beacon X position")
.as_str()
.parse()
.expect("Input not a number");
let beacon_y: i64 = captures
.get(4)
.expect("Beacon Y position")
.as_str()
.parse()
.expect("Input not a number");
let manhattan_radius = sensor_x.abs_diff(beacon_x) + sensor_y.abs_diff(beacon_y);
(
Sensor {
x: sensor_x,
y: sensor_y,
manhattan_radius,
},
Beacon {
x: beacon_x,
y: beacon_y,
},
)
})
.unzip()
}
fn solve_first_puzzle<const LINE: usize, R: BufRead>(reader: R) -> usize {
let (sensors, beacons) = parse_input(reader);
let min_x = sensors.iter().map(Sensor::min_x).min().unwrap_or_default();
let max_x = sensors.iter().map(Sensor::max_x).max().unwrap_or_default();
let mut occupied_counter = 0;
let y = LINE as i64;
for x in min_x..=max_x {
if sensors.iter().any(|sensor| sensor.is_in_range(x, y))
&& !beacons.iter().any(|beacon| beacon.x == x && beacon.y == y)
{
occupied_counter += 1;
}
}
occupied_counter
}
fn solve_second_puzzle<const MAX_X: i64, const MAX_Y: i64, R: BufRead>(reader: R) -> i64 {
let (sensors, _beacons) = parse_input(reader);
for y in 0_i64..=MAX_Y {
let mut x = 0;
while x <= MAX_X {
let sensor = sensors.iter().find(|sensor| sensor.is_in_range(x, y));
match sensor {
Some(sensor) => {
let dist_y = sensor.y.abs_diff(y) as i64;
let width_at_height = sensor.manhattan_radius as i64 - dist_y;
x = sensor.x + width_at_height + 1;
}
// FOUND!
None => return x * TUNING_FACTOR + y,
}
}
}
panic!("No spot found")
}
fn main() {
let mut args = ::std::env::args().skip(1);
let file = args.next().unwrap_or_else(|| String::from("./input"));
let solve = args
.next()
.map(|arg| match arg.as_str() {
"first" => SolvePuzzle::First,
"second" => SolvePuzzle::Second,
_ => unreachable!(),
})
.unwrap_or_default();
let file = BufReader::new(File::open(file).expect("Opening file"));
match solve {
SolvePuzzle::First => println!("{}", solve_first_puzzle::<2_000_000, _>(file)),
SolvePuzzle::Second => println!("{}", solve_second_puzzle::<4_000_000, 4_000_000, _>(file)),
};
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::*;
#[test]
fn example_on_first() {
let reader = Cursor::new(include_str!("../input.test"));
assert_eq!(solve_first_puzzle::<10, _>(reader), 26);
}
#[test]
fn example_on_second() {
let reader = Cursor::new(include_str!("../input.test"));
assert_eq!(solve_second_puzzle::<20, 20, _>(reader), 56000011);
}
}

View file

@ -8,3 +8,6 @@ build = true
app = true
[dependencies]
cached = "0.40.0"
regex = "1.7.0"
bitvec_simd = { git = "https://github.com/Imberflur/bitvec_simd", branch = "master" }

4
day16/input.example Normal file
View file

@ -0,0 +1,4 @@
Valve AA has flow rate=0; tunnel leads to valve BB
Valve BB has flow rate=3; tunnels lead to valves CC, DD
Valve CC has flow rate=18; tunnel leads to valve DD
Valve DD has flow rate=20; tunnels lead to valves BB, CC

View file

@ -1,3 +1,191 @@
fn main() {
println!("Hello, world!");
use std::{
collections::HashMap,
fs::File,
io::{BufRead, BufReader},
ops::Deref,
};
use bitvec_simd::BitVec;
use cached::proc_macro::cached;
use regex::Regex;
#[derive(Debug, Default, PartialEq, Eq)]
enum SolvePuzzle {
First,
#[default]
Second,
}
#[derive(Debug)]
struct Valve {
name: String,
flow: u32,
leads_to: Vec<usize>,
}
#[derive(Debug)]
struct Valves {
inner: Vec<Valve>,
}
impl Valves {
pub fn start_idx(&self) -> usize {
self.inner
.iter()
.enumerate()
.find(|(_idx, valve)| valve.name == "AA")
.expect("Where to start now?")
.0
}
}
fn parse_input<R: BufRead>(reader: R) -> Valves {
let regex = Regex::new(
r"Valve ([A-Z]{2}) has flow rate=(\d+); tunnels? leads? to valves? (([A-Z]{2}(, )?)+)",
)
.unwrap();
let raw = reader
.lines()
.map(Result::unwrap)
.map(|line| {
let captures = regex.captures(&line).expect("Matched line");
let name = captures.get(1).expect("name").as_str().to_string();
let rate: u32 = captures
.get(2)
.expect("flow rate")
.as_str()
.parse()
.expect("flow rate is a number");
let leads_to = captures
.get(3)
.expect("leads to")
.as_str()
.split(", ")
.map(String::from)
.collect::<Vec<_>>();
(name, rate, leads_to)
})
.collect::<Vec<_>>();
let indices: HashMap<String, usize> = raw
.iter()
.enumerate()
.map(|(idx, (name, _, _))| (name.clone(), idx))
.collect();
let inner = raw
.into_iter()
.map(|(name, flow, leads_to)| {
let leads_to = leads_to
.into_iter()
.map(|name| *indices.get(&name).expect("Pipe exists"))
.collect();
Valve {
name,
flow,
leads_to,
}
})
.collect();
Valves { inner }
}
#[cached(
key = "(usize, u32, Vec<usize>)",
convert = r#"{ (current_valve, time_left, opened.clone().into_usizes()) }"#
)]
fn solve(valves: &Valves, current_valve: usize, time_left: u32, opened: &mut BitVec) -> u32 {
if time_left == 0 {
return 0;
}
let curr = &valves[current_valve];
let should_skip_opening = curr
.leads_to
.iter()
.filter(|&&idx| !opened[idx])
.any(|&idx| {
let other = &valves[idx];
let gain = other.flow as i32 - curr.flow as i32;
let min_gain_needed = other.flow as i32;
(time_left as i32 - 1) * gain > min_gain_needed
});
if time_left == 25 {
eprintln!("Checking {}: {:>6?}", curr.name, opened);
}
curr.leads_to
.iter()
.map(|&idx| {
if opened[current_valve] || should_skip_opening {
solve(valves, idx, time_left.saturating_sub(1), opened)
} else {
let if_closed = solve(valves, idx, time_left.saturating_sub(1), opened);
opened.set(current_valve, true);
let mut if_opened = solve(valves, idx, time_left.saturating_sub(2), opened);
opened.set(current_valve, false);
if_opened += (time_left - 1) * curr.flow;
if if_closed >= if_opened {
if_closed
} else {
if_opened
}
}
})
.max()
.unwrap_or_else(|| {
// Leads nowhere
if opened[current_valve] {
0
} else {
(time_left.saturating_sub(1)) * curr.flow
}
})
}
fn solve_first_puzzle<R: BufRead>(reader: R) -> u32 {
let valves = parse_input(reader);
let mut opened = BitVec::zeros(valves.len());
solve(&valves, valves.start_idx(), 30, &mut opened)
}
fn main() {
let mut args = ::std::env::args().skip(1);
let file = args.next().unwrap_or_else(|| String::from("./input"));
let solve = args
.next()
.map(|arg| match arg.as_str() {
"first" => SolvePuzzle::First,
"second" => SolvePuzzle::Second,
_ => unreachable!(),
})
.unwrap_or_default();
let file = BufReader::new(File::open(file).expect("Opening file"));
match solve {
SolvePuzzle::First => println!("{}", solve_first_puzzle(file)),
_ => todo!(),
};
}
impl Deref for Valves {
type Target = Vec<Valve>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::*;
#[test]
fn example_on_first() {
let reader = Cursor::new(include_str!("../input.test"));
assert_eq!(solve_first_puzzle(reader), 1651);
}
#[test]
fn example_on_second() {
let reader = Cursor::new(include_str!("../input.test"));
// todo!()
}
}

View file

@ -20,14 +20,33 @@ enum Wind {
Right,
}
#[derive(Debug)]
struct Repetition {
from_history_idx: usize,
to_history_idx: usize,
height_diff: usize,
}
struct Cave {
height_map: [usize; CAVE_WIDTH],
wind: Vec<Wind>,
wind_pos: usize,
content: Vec<[bool; CAVE_WIDTH]>,
// A list of rocks, their initial wind_pos and final height
history: Vec<(StrangeRock, usize, usize)>,
}
impl Cave {
pub fn max_height(&self) -> usize {
self.height_map.iter().max().copied().unwrap()
if let Some((rev_idx, _)) = self
.content
.iter()
.rev()
.enumerate()
.find(|(_idx, row)| row.iter().any(|&blocked| blocked))
{
self.content.len() - rev_idx
} else {
0
}
}
pub fn use_wind(&mut self) -> Wind {
@ -36,58 +55,104 @@ impl Cave {
wind
}
pub fn drop(&mut self, piece: StrangRock) {
let mut piece_height = self.max_height() + 4;
pub fn drop(&mut self, piece: StrangeRock) {
let initial_wind = self.wind_pos;
let piece_positions = piece.positions();
let curr_max_height = self.max_height();
let mut height = curr_max_height + 3;
let mut pos = 2_usize;
eprintln!("{piece:>10?} {pos} {piece_height}");
loop {
match self.use_wind() {
Wind::Left if pos > 0 => {
let mut collision = false;
for (offset_idx, offset) in piece.offsets().iter().enumerate() {
if self.height_map[pos - 1 + offset_idx] >= piece_height + offset {
collision = true;
}
}
if !collision {
let is_impossible = (height <= curr_max_height)
&& piece_positions
.iter()
.map(|(x, y)| (x + pos - 1, y + height))
.any(|(x, y)| self.get(x, y));
if !is_impossible {
pos = pos.saturating_sub(1);
}
}
Wind::Right if pos < CAVE_WIDTH.saturating_sub(piece.width()) => {
let mut collision = false;
for (offset_idx, offset) in piece.offsets().iter().enumerate() {
if self.height_map[pos + 1 + offset_idx] >= piece_height + offset {
collision = true;
}
}
if !collision {
let is_impossible = (height <= curr_max_height)
&& piece_positions
.iter()
.map(|(x, y)| (x + pos + 1, y + height))
.any(|(x, y)| self.get(x, y));
if !is_impossible {
pos += 1;
}
}
_ => {}
}
piece_height -= 1;
eprintln!("{piece:>10?} {pos} {piece_height}");
eprintln!(" - {:>2?} {:>2?}", self.height_map, piece.offsets());
for (offset_idx, &offset) in piece.offsets().iter().enumerate() {
let height_at_pos = self.height_map[pos + offset_idx];
let piece_height = piece_height + offset;
eprintln!(" - {offset_idx} {offset}: {height_at_pos} {piece_height}");
if piece_height == height_at_pos {
for (height_idx, &height) in piece.height_offsets().iter().enumerate() {
let total_height = piece_height + height + 1;
self.height_map[pos + height_idx] = total_height;
}
eprintln!(" == {:>2?} {:>2?}", self.height_map, piece.offsets());
return;
}
let cant_move_down = height == 0
|| piece_positions
.iter()
.map(|(x, y)| (x + pos, y + height - 1))
.any(|(x, y)| self.get(x, y));
if !cant_move_down {
height -= 1;
}
if cant_move_down {
piece_positions
.iter()
.map(|(x, y)| (x + pos, y + height))
.for_each(|(x, y)| self.block(x, y));
self.history.push((piece, initial_wind, height));
return;
}
}
}
pub fn min_repeat(&self) -> usize {
// Everything has to repeat after visiting every variant with every wind
StrangeRock::TOTAL_VARIANT_COUNT * self.wind.len()
}
pub fn check_repetition(&self) -> Option<Repetition> {
// println!("IS REPEATING? {:>20}", self.history.len());
let min_repeat = self.min_repeat();
if self.history.len() <= min_repeat + 429 {
return None;
}
let to_history_idx = self.history.len() - 1 - 429;
let to = &self.history[to_history_idx];
(min_repeat..)
.take_while(|&idx_sub| idx_sub < self.history.len())
.map(|idx_sub| self.history.len() - idx_sub)
.find_map(|from_history_idx| {
let from = &self.history[from_history_idx];
if to.0 == from.0 && to.1 == from.1 && self.content[to.2] == self.content[from.2] {
Some(Repetition {
from_history_idx,
to_history_idx,
height_diff: to.2 - from.2,
})
} else {
None
}
})
}
pub fn get(&mut self, x: usize, y: usize) -> bool {
debug_assert!(x < CAVE_WIDTH);
while y >= self.content.len() {
self.content.push([false; CAVE_WIDTH]);
}
self.content[y][x]
}
pub fn block(&mut self, x: usize, y: usize) {
debug_assert!(x < CAVE_WIDTH);
while y >= self.content.len() {
self.content.push([false; CAVE_WIDTH]);
}
self.content[y][x] = true;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum StrangRock {
enum StrangeRock {
// ####
Horizontal,
// .#.
@ -108,34 +173,25 @@ enum StrangRock {
Square,
}
impl StrangRock {
const fn width(&self) -> usize {
impl StrangeRock {
pub const TOTAL_VARIANT_COUNT: usize = 5;
pub const fn positions(&self) -> &[(usize, usize)] {
match self {
StrangRock::Horizontal => 4,
StrangRock::Plus => 3,
StrangRock::Corner => 3,
StrangRock::Vertical => 1,
StrangRock::Square => 2,
StrangeRock::Horizontal => &[(0, 0), (1, 0), (2, 0), (3, 0)],
StrangeRock::Plus => &[(1, 0), (0, 1), (1, 1), (2, 1), (1, 2)],
StrangeRock::Corner => &[(0, 0), (1, 0), (2, 0), (2, 1), (2, 2)],
StrangeRock::Vertical => &[(0, 0), (0, 1), (0, 2), (0, 3)],
StrangeRock::Square => &[(0, 0), (1, 0), (0, 1), (1, 1)],
}
}
fn offsets(&self) -> &[usize] {
pub const fn width(&self) -> usize {
match self {
StrangRock::Horizontal => &[0, 0, 0, 0],
StrangRock::Plus => &[1, 0, 1],
StrangRock::Corner => &[0, 0, 0],
StrangRock::Vertical => &[0],
StrangRock::Square => &[0, 0],
}
}
pub fn height_offsets(&self) -> &[usize] {
match self {
StrangRock::Horizontal => &[0, 0, 0, 0],
StrangRock::Plus => &[1, 2, 1],
StrangRock::Corner => &[0, 0, 2],
StrangRock::Vertical => &[3],
StrangRock::Square => &[1, 1],
StrangeRock::Horizontal => 4,
StrangeRock::Plus => 3,
StrangeRock::Corner => 3,
StrangeRock::Vertical => 1,
StrangeRock::Square => 2,
}
}
}
@ -148,22 +204,74 @@ fn read_input<R: BufRead>(reader: R) -> Cave {
.map(Wind::from)
.collect();
let wind_pos = 0;
let height_map = [0; CAVE_WIDTH];
Cave {
height_map,
wind,
wind_pos,
content: vec![],
history: vec![],
}
}
fn solve_first_puzzle<R: BufRead>(reader: R) -> usize {
fn solve_first_puzzle<const ROCK_COUNT: usize, R: BufRead>(reader: R) -> usize {
let mut cave = read_input(reader);
use StrangRock::*;
println!("{}", cave.min_repeat());
use StrangeRock::*;
let pieces = [Horizontal, Plus, Corner, Vertical, Square];
for piece in pieces.into_iter().cycle().take(2022) {
let mut piece_idx = 0;
let mut current_idx = 0;
let mut additional_height = 0;
let mut have_skipped = false;
while current_idx < ROCK_COUNT {
let piece = pieces[piece_idx];
cave.drop(piece);
if !have_skipped {
if let Some(repetition) = cave.check_repetition() {
eprintln!("{repetition:?}");
eprintln!("{:?}", cave.history[repetition.from_history_idx]);
eprintln!("{:?}", cave.history[repetition.to_history_idx]);
eprintln!(
"{:?}",
cave.content[cave.history[repetition.from_history_idx].2]
);
eprintln!(
"{:?}",
cave.content[cave.history[repetition.to_history_idx].2]
);
let number_of_rocks_that_repeated =
repetition.to_history_idx - repetition.from_history_idx;
let repeat = (ROCK_COUNT - current_idx) / number_of_rocks_that_repeated;
eprintln!("XXXX");
println!("{number_of_rocks_that_repeated}");
for _ in 0..number_of_rocks_that_repeated {
piece_idx = (piece_idx + 1) % pieces.len();
cave.drop(pieces[piece_idx]);
}
eprintln!("XXXX");
for x in repetition.from_history_idx..repetition.to_history_idx {
assert_eq!(
cave.content[cave.history[x].2],
cave.content[cave.history[x + number_of_rocks_that_repeated].2],
"X: {} Y: {}, *X: {}, *Y: {}",
x,
x + number_of_rocks_that_repeated,
cave.history[x].2,
cave.history[x + number_of_rocks_that_repeated].2
);
}
eprintln!("XXXX");
current_idx += number_of_rocks_that_repeated * repeat;
eprintln!("Skip {}", number_of_rocks_that_repeated * repeat);
eprintln!("To be simulated: {}", ROCK_COUNT - current_idx);
eprintln!("0..{}", cave.content.len());
additional_height += repetition.height_diff * repeat;
eprintln!("additional height {}", additional_height);
have_skipped = true;
}
}
piece_idx = (piece_idx + 1) % pieces.len();
current_idx += 1;
}
cave.max_height()
cave.max_height() + additional_height
}
fn main() {
@ -179,8 +287,8 @@ fn main() {
.unwrap_or_default();
let file = BufReader::new(File::open(file).expect("Opening file"));
match solve {
SolvePuzzle::First => println!("{}", solve_first_puzzle(file)),
_ => todo!(),
SolvePuzzle::First => println!("{}", solve_first_puzzle::<2022, _>(file)),
SolvePuzzle::Second => println!("{}", solve_first_puzzle::<1_000_000_000_000, _>(file)),
};
}
@ -203,12 +311,15 @@ mod tests {
#[test]
fn example_on_first() {
let reader = Cursor::new(include_str!("../input.test"));
assert_eq!(solve_first_puzzle(reader), 3068);
assert_eq!(solve_first_puzzle::<2022, _>(reader), 3068);
}
#[test]
fn example_on_second() {
let reader = Cursor::new(include_str!("../input.test"));
// todo!()
assert_eq!(
solve_first_puzzle::<1000000000000, _>(reader),
1514285714288
);
}
}