diff --git a/data/day16.txt b/data/day16.txt new file mode 100644 index 0000000..cf8974a --- /dev/null +++ b/data/day16.txt @@ -0,0 +1 @@ +C20D718021600ACDC372CD8DE7A057252A49C940239D68978F7970194EA7CCB310088760088803304A0AC1B100721EC298D3307440041CD8B8005D12DFD27CBEEF27D94A4E9B033006A45FE71D665ACC0259C689B1F99679F717003225900465800804E39CE38CE161007E52F1AEF5EE6EC33600BCC29CFFA3D8291006A92CA7E00B4A8F497E16A675EFB6B0058F2D0BD7AE1371DA34E730F66009443C00A566BFDBE643135FEDF321D000C6269EA66545899739ADEAF0EB6C3A200B6F40179DE31CB7B277392FA1C0A95F6E3983A100993801B800021B0722243D00042E0DC7383D332443004E463295176801F29EDDAA853DBB5508802859F2E9D2A9308924F9F31700AA4F39F720C733A669EC7356AC7D8E85C95E123799D4C44C0109C0AF00427E3CC678873F1E633C4020085E60D340109E3196023006040188C910A3A80021B1763FC620004321B4138E52D75A20096E4718D3E50016B19E0BA802325E858762D1802B28AD401A9880310E61041400043E2AC7E8A4800434DB24A384A4019401C92C154B43595B830002BC497ED9CC27CE686A6A43925B8A9CFFE3A9616E5793447004A4BBB749841500B26C5E6E306899C5B4C70924B77EF254B48688041CD004A726ED3FAECBDB2295AEBD984E08E0065C101812E006380126005A80124048CB010D4C03DC900E16A007200B98E00580091EE004B006902004B00410000AF00015933223100688010985116A311803D05E3CC4B300660BC7283C00081CF26491049F3D690E9802739661E00D400010A8B91F2118803310A2F43396699D533005E37E8023311A4BB9961524A4E2C027EC8C6F5952C2528B333FA4AD386C0A56F39C7DB77200C92801019E799E7B96EC6F8B7558C014977BD00480010D89D106240803518E31C4230052C01786F272FF354C8D4D437DF52BC2C300567066550A2A900427E0084C254739FB8E080111E0 \ No newline at end of file diff --git a/src/bin/day16.rs b/src/bin/day16.rs new file mode 100644 index 0000000..9f26821 --- /dev/null +++ b/src/bin/day16.rs @@ -0,0 +1,284 @@ +use std::collections::VecDeque; +use std::str::FromStr; +use thiserror::Error; + +const REAL_DATA: &str = include_str!("../../data/day16.txt"); + +#[derive(Debug, Error, PartialEq)] +enum Oopsie { + #[error("Bad digit in input: '{0}'")] + BadDigit(char), + #[error("Ran out of bits (looking for {1}) pulling value for type {0}")] + RanOuttaBits(&'static str, usize), + #[error("Invalid split attempt: stream length is {0}, but requested split at {1}")] + InvalidSplit(usize, usize), +} + +struct BitStream { + bits: VecDeque, +} + +impl FromStr for BitStream { + type Err = Oopsie; + + fn from_str(s: &str) -> Result { + let mut bits = VecDeque::with_capacity(s.len() * 4); + + for c in s.chars() { + let value = c.to_digit(16).ok_or(Oopsie::BadDigit(c))?; + bits.push_back(value & 0b1000 > 0); + bits.push_back(value & 0b0100 > 0); + bits.push_back(value & 0b0010 > 0); + bits.push_back(value & 0b0001 > 0); + } + + Ok(BitStream { bits }) + } +} + +macro_rules! next_chunk { + ($id: ident, $t: ty) => { + fn $id(&mut self, bits: usize) -> Result<$t, Oopsie> { + let mut res = 0; + + for _ in 0..bits { + res <<= 1; + match self.bits.pop_front() { + None => return Err(Oopsie::RanOuttaBits(stringify!($t), bits)), + Some(true) => res += 1, + Some(false) => {} + } + } + + Ok(res) + } + }; +} + +impl BitStream { + fn next_bit(&mut self) -> Result { + self.bits.pop_front().ok_or(Oopsie::RanOuttaBits("bool", 1)) + } + + next_chunk!(next_u8, u8); + next_chunk!(next_u16, u16); + next_chunk!(next_u64, u64); + + fn take(&mut self, size: usize) -> Result { + if self.bits.len() < size { + Err(Oopsie::InvalidSplit(self.bits.len(), size)) + } else { + let rest = self.bits.split_off(size); + let retval = self.bits.clone(); + self.bits = rest; + + Ok(BitStream { bits: retval }) + } + } + + fn is_empty(&self) -> bool { + self.bits.is_empty() + } +} + +#[derive(Debug, PartialEq)] +enum Message { + Literal(u8, u64), + Sum(u8, Vec), + Product(u8, Vec), + Minimum(u8, Vec), + Maximum(u8, Vec), + GreaterThan(u8, Vec), + LessThan(u8, Vec), + EqualTo(u8, Vec), + Sequence(u8, u8, Vec), +} + +impl Message { + fn sequence(version: u8, type_id: u8, sequence: Vec) -> Message { + match type_id { + 0 => Message::Sum(version, sequence), + 1 => Message::Product(version, sequence), + 2 => Message::Minimum(version, sequence), + 3 => Message::Maximum(version, sequence), + 5 if sequence.len() == 2 => Message::GreaterThan(version, sequence), + 6 if sequence.len() == 2 => Message::LessThan(version, sequence), + 7 if sequence.len() == 2 => Message::EqualTo(version, sequence), + _ => Message::Sequence(version, type_id, sequence), + } + } + + fn version_sum(&self) -> usize { + match self { + Message::Literal(x, _) => *x as usize, + Message::Sum(x, seq) => { + (*x as usize) + seq.iter().map(|v| v.version_sum()).sum::() + } + Message::Product(x, seq) => { + (*x as usize) + seq.iter().map(|v| v.version_sum()).sum::() + } + Message::Minimum(x, seq) => { + (*x as usize) + seq.iter().map(|v| v.version_sum()).sum::() + } + Message::Maximum(x, seq) => { + (*x as usize) + seq.iter().map(|v| v.version_sum()).sum::() + } + Message::GreaterThan(x, seq) => { + (*x as usize) + seq.iter().map(|v| v.version_sum()).sum::() + } + Message::LessThan(x, seq) => { + (*x as usize) + seq.iter().map(|v| v.version_sum()).sum::() + } + Message::EqualTo(x, seq) => { + (*x as usize) + seq.iter().map(|v| v.version_sum()).sum::() + } + Message::Sequence(x, _, seq) => { + (*x as usize) + seq.iter().map(|v| v.version_sum()).sum::() + } + } + } + + fn eval(&self) -> u64 { + match self { + Message::Literal(_, x) => *x, + Message::Sum(_, seq) => seq.iter().map(|x| x.eval()).sum(), + Message::Product(_, seq) => seq.iter().map(|x| x.eval()).product(), + Message::Minimum(_, seq) => seq.iter().map(|x| x.eval()).min().unwrap(), + Message::Maximum(_, seq) => seq.iter().map(|x| x.eval()).max().unwrap(), + Message::GreaterThan(_, seq) => { + if seq[0].eval() > seq[1].eval() { + 1 + } else { + 0 + } + } + Message::LessThan(_, seq) => { + if seq[0].eval() < seq[1].eval() { + 1 + } else { + 0 + } + } + Message::EqualTo(_, seq) => { + if seq[0].eval() == seq[1].eval() { + 1 + } else { + 0 + } + } + Message::Sequence(_, _, _) => panic!("Tried to evaluate unknown sequence!"), + } + } +} + +impl<'a> TryFrom<&'a mut BitStream> for Message { + type Error = Oopsie; + + fn try_from(value: &mut BitStream) -> Result { + let version = value.next_u8(3)?; + let type_id = value.next_u8(3)?; + + if type_id == 4 { + let mut literal = 0u64; + let mut keep_going = true; + + while keep_going { + keep_going = value.next_bit()?; + literal = (literal << 4) + value.next_u64(4)?; + } + + Ok(Message::Literal(version, literal)) + } else { + let length_type_id = value.next_bit()?; + let mut seq = Vec::new(); + + if length_type_id { + let subpart_count = value.next_u16(11)?; + + for _ in 0..subpart_count { + seq.push(Message::try_from(&mut *value)?); + } + } else { + let subpart_len = value.next_u16(15)? as usize; + let mut my_bits = value.take(subpart_len)?; + + while !my_bits.is_empty() { + seq.push(Message::try_from(&mut my_bits)?); + } + } + + Ok(Message::sequence(version, type_id, seq)) + } + } +} + +impl FromStr for Message { + type Err = Oopsie; + + fn from_str(s: &str) -> Result { + let mut bits = BitStream::from_str(s)?; + Message::try_from(&mut bits) + } +} + +#[test] +fn basic_parsing() { + assert_eq!( + Ok(Message::Literal(6, 2021)), + Message::try_from(&mut BitStream::from_str("D2FE28").unwrap()) + ); + assert_eq!(Ok(Message::Literal(6, 2021)), Message::from_str("D2FE28")); + assert_eq!( + Ok(Message::LessThan( + 1, + vec![Message::Literal(6, 10), Message::Literal(2, 20)] + )), + Message::from_str("38006F45291200") + ); + assert_eq!( + Ok(Message::Maximum( + 7, + vec![ + Message::Literal(2, 1), + Message::Literal(4, 2), + Message::Literal(1, 3) + ] + )), + Message::from_str("EE00D40C823060") + ); +} + +#[test] +fn example_tests() { + assert_eq!( + 16, + Message::from_str("8A004A801A8002F478") + .unwrap() + .version_sum() + ); + assert_eq!( + 12, + Message::from_str("620080001611562C8802118E34") + .unwrap() + .version_sum() + ); + assert_eq!( + 23, + Message::from_str("C0015000016115A2E0802F182340") + .unwrap() + .version_sum() + ); + assert_eq!( + 31, + Message::from_str("A0016C880162017C3686B18A3D4780") + .unwrap() + .version_sum() + ); +} + +fn main() -> Result<(), Oopsie> { + let message = Message::from_str(REAL_DATA)?; + println!("Version sum: {}", message.version_sum()); + println!("Input computed value: {}", message.eval()); + Ok(()) +} diff --git a/src/bin/day17.rs b/src/bin/day17.rs new file mode 100644 index 0000000..437e953 --- /dev/null +++ b/src/bin/day17.rs @@ -0,0 +1,128 @@ +use std::cmp::{max, Ordering}; +use std::ops::RangeInclusive; + +struct TargetArea { + target_x: RangeInclusive, + target_y: RangeInclusive, +} + +impl TargetArea { + fn contains(&self, x: isize, y: isize) -> bool { + self.target_x.contains(&x) && self.target_y.contains(&y) + } +} + +struct Probe { + pos_x: isize, + pos_y: isize, + vel_x: isize, + vel_y: isize, +} + +#[derive(Debug, PartialEq)] +enum Miss { + MissedShort, + MissedLong, + Skipped, +} + +impl Probe { + fn new(vel_x: isize, vel_y: isize) -> Probe { + Probe { + pos_x: 0, + pos_y: 0, + vel_x, + vel_y, + } + } + + fn step(&mut self) { + self.pos_x += self.vel_x; + self.pos_y += self.vel_y; + + match self.vel_x.cmp(&0) { + Ordering::Equal => {} + Ordering::Greater => self.vel_x -= 1, + Ordering::Less => self.vel_x += 1, + } + + self.vel_y -= 1; + } + + fn hits_target(&mut self, target: &TargetArea) -> Result<(isize, isize, isize), Miss> { + let mut max_y = self.pos_y; + + loop { + max_y = max(self.pos_y, max_y); + + if target.contains(self.pos_x, self.pos_y) { + return Ok((self.pos_x, self.pos_y, max_y)); + } + + if self.vel_x == 0 && &self.pos_y < target.target_y.end() { + if &self.pos_x < target.target_x.start() { + return Err(Miss::MissedShort); + } + + if &self.pos_x > target.target_x.end() { + return Err(Miss::MissedLong); + } + + return Err(Miss::Skipped); + } + + self.step(); + } + } +} + +fn highest_hit(target: &TargetArea) -> (isize, isize, isize, usize) { + let mut vel_x = 1; + let mut vel_y = 1; + let mut max_y = 0; + let mut count = 0; + + for attempt_x in 1..250 { + for attempt_y in -250..250 { + let mut probe = Probe::new(attempt_x, attempt_y); + + if let Ok((x, y, new_max_y)) = probe.hits_target(target) { + println!( + "Hit with <{},{}> at ({},{}) with highest y {}", + attempt_x, attempt_y, x, y, new_max_y + ); + if new_max_y > max_y { + vel_x = attempt_x; + vel_y = attempt_y; + max_y = new_max_y; + } + count += 1; + } + } + } + + (vel_x, vel_y, max_y, count) +} + +fn main() { + let test_target = TargetArea { + target_x: 20..=30, + target_y: -10..=-5, + }; + let mut test_probe = Probe::new(7, 2); + + assert_eq!(Ok((28, -7, 3)), test_probe.hits_target(&test_target)); + assert_eq!((6, 9, 45, 112), highest_hit(&test_target)); + + let real_target = TargetArea { + target_x: 111..=161, + target_y: -154..=-101, + }; + println!( + "target_y: {:?} (start {}, end {})", + real_target.target_y, + real_target.target_y.start(), + real_target.target_y.end() + ); + println!("Search results answer: {:?}", highest_hit(&real_target)); +}