From 6a5b143c0fd0a90979d9315b50be2387facb752f Mon Sep 17 00:00:00 2001 From: Justin Wernick Date: Tue, 19 Apr 2022 20:27:05 +0200 Subject: Refile for merging repos --- 2020/src/bin/day_1.rs | 83 ++++++++++++++++++ 2020/src/bin/day_10.rs | 124 ++++++++++++++++++++++++++ 2020/src/bin/day_11.rs | 209 ++++++++++++++++++++++++++++++++++++++++++++ 2020/src/bin/day_12.rs | 233 +++++++++++++++++++++++++++++++++++++++++++++++++ 2020/src/bin/day_2.rs | 104 ++++++++++++++++++++++ 2020/src/bin/day_3.rs | 205 +++++++++++++++++++++++++++++++++++++++++++ 2020/src/bin/day_4.rs | 222 ++++++++++++++++++++++++++++++++++++++++++++++ 2020/src/bin/day_5.rs | 157 +++++++++++++++++++++++++++++++++ 2020/src/bin/day_6.rs | 102 ++++++++++++++++++++++ 2020/src/bin/day_7.rs | 137 +++++++++++++++++++++++++++++ 2020/src/bin/day_8.rs | 154 ++++++++++++++++++++++++++++++++ 2020/src/bin/day_9.rs | 120 +++++++++++++++++++++++++ 2020/src/lib.rs | 1 + 2020/src/main.rs | 3 + 14 files changed, 1854 insertions(+) create mode 100644 2020/src/bin/day_1.rs create mode 100644 2020/src/bin/day_10.rs create mode 100644 2020/src/bin/day_11.rs create mode 100644 2020/src/bin/day_12.rs create mode 100644 2020/src/bin/day_2.rs create mode 100644 2020/src/bin/day_3.rs create mode 100644 2020/src/bin/day_4.rs create mode 100644 2020/src/bin/day_5.rs create mode 100644 2020/src/bin/day_6.rs create mode 100644 2020/src/bin/day_7.rs create mode 100644 2020/src/bin/day_8.rs create mode 100644 2020/src/bin/day_9.rs create mode 100644 2020/src/lib.rs create mode 100644 2020/src/main.rs (limited to '2020/src') diff --git a/2020/src/bin/day_1.rs b/2020/src/bin/day_1.rs new file mode 100644 index 0000000..68a570c --- /dev/null +++ b/2020/src/bin/day_1.rs @@ -0,0 +1,83 @@ +use bevy::prelude::*; +use std::fs::File; +use std::io::{BufRead, BufReader}; + +fn main() { + App::build() + .add_resource(WindowDescriptor { + title: "Advent of Code".to_string(), + width: 1920, + height: 1080, + ..Default::default() + }) + .add_resource(ClearColor(Color::rgb(0., 0., 0.))) + .add_startup_system(setup_camera.system()) + .add_startup_system(read_input_file.system()) + .add_stage("mark") + .add_system_to_stage("mark", mark_sum_pair.system()) + .add_system_to_stage("mark", mark_sum_triple.system()) + .add_stage("report") + .add_system_to_stage("report", print_product_pair.system()) + .add_system_to_stage("report", print_product_triple.system()) + //.add_plugins(DefaultPlugins) + .run(); +} + +fn setup_camera(mut commands: Commands) { + commands.spawn(Camera2dComponents::default()); +} + +struct Expense(i32); +struct FlaggedPair; +struct FlaggedTriple; + +fn read_input_file(mut commands: Commands) { + let f = File::open("./inputs/day_1.txt").unwrap(); // TODO: Error handling on systems? + for line in BufReader::new(f).lines() { + let expense = line.unwrap().trim().parse().unwrap(); + commands.spawn((Expense(expense),)); + } +} + +fn mark_sum_pair(mut commands: Commands, expenses: Query<(Entity, &Expense)>) { + for (entity_1, expense_1) in expenses.iter() { + for (entity_2, expense_2) in expenses.iter() { + if expense_1.0 + expense_2.0 == 2020 { + commands.insert_one(entity_1, FlaggedPair); + commands.insert_one(entity_2, FlaggedPair); + } + } + } +} + +fn mark_sum_triple(mut commands: Commands, expenses: Query<(Entity, &Expense)>) { + for (entity_1, expense_1) in expenses.iter() { + for (entity_2, expense_2) in expenses.iter() { + for (entity_3, expense_3) in expenses.iter() { + if expense_1.0 + expense_2.0 + expense_3.0 == 2020 { + commands.insert_one(entity_1, FlaggedTriple); + commands.insert_one(entity_2, FlaggedTriple); + commands.insert_one(entity_3, FlaggedTriple); + } + } + } + } +} + +fn print_product_pair(flagged_expenses: Query>) { + let mut product = 1; + for expense in flagged_expenses.iter() { + product *= expense.0; + } + // TODO: Put it on the screen! + println!("Product Pair: {}", product); +} + +fn print_product_triple(flagged_expenses: Query>) { + let mut product = 1; + for expense in flagged_expenses.iter() { + product *= expense.0; + } + // TODO: Put it on the screen! + println!("Product Triple: {}", product); +} diff --git a/2020/src/bin/day_10.rs b/2020/src/bin/day_10.rs new file mode 100644 index 0000000..7ef4fa2 --- /dev/null +++ b/2020/src/bin/day_10.rs @@ -0,0 +1,124 @@ +use bevy::{app::AppExit, prelude::*}; +use std::io::{BufRead, BufReader}; +use std::{collections::BTreeMap, fs::File}; + +fn main() { + App::build() + .add_resource(WindowDescriptor { + title: "Advent of Code".to_string(), + width: 1920, + height: 1080, + ..Default::default() + }) + .add_resource(ClearColor(Color::rgb(0., 0., 0.))) + .add_startup_system(setup_camera.system()) + .add_startup_system(read_input_file.system()) + .add_startup_stage("differences") + .add_startup_system_to_stage("differences", add_min_differences.system()) + .add_startup_system_to_stage("differences", add_all_differences.system()) + .add_startup_stage("report") + .add_startup_system_to_stage("report", print_difference_stats.system()) + .add_system(add_next_paths_to_end_sum.system()) + .add_system(report_full_paths_to_end.system()) + .add_plugins(DefaultPlugins) + .run(); +} + +fn setup_camera(mut commands: Commands) { + commands.spawn(Camera2dComponents::default()); +} + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct MinJoltDiff(i64); +#[derive(Clone)] +struct NextJolts(Vec); +struct Jolts(i64); +struct PathsToEnd(u64); + +fn read_input_file(mut commands: Commands) { + let f = File::open("./inputs/day_10.txt").unwrap(); + + commands.spawn((Jolts(0),)); + for line in BufReader::new(f).lines() { + let line = line.unwrap(); + let line = line.trim(); + let num = line.parse().unwrap(); + + commands.spawn((Jolts(num),)); + } +} + +fn add_min_differences( + mut commands: Commands, + jolts_query: Query<(Entity, &Jolts, Option<&MinJoltDiff>)>, +) { + for (entity, jolts, jolt_diff) in jolts_query.iter() { + if jolt_diff.is_none() { + let min_increase = jolts_query + .iter() + .map(|(_, other_jolts, _)| other_jolts) + .filter(|other_jolts| other_jolts.0 > jolts.0) + .map(|other_jolts| MinJoltDiff(other_jolts.0 - jolts.0)) + .min() + .unwrap_or(MinJoltDiff(3)); + commands.insert_one(entity, min_increase); + } + } +} + +fn add_all_differences(mut commands: Commands, jolts_query: Query<(Entity, &Jolts)>) { + for (entity, jolts) in jolts_query.iter() { + let next_entities = jolts_query + .iter() + .filter(|(_, other_jolts)| other_jolts.0 > jolts.0 && other_jolts.0 - jolts.0 <= 3) + .map(|(other_entity, _)| other_entity) + .collect(); + commands.insert_one(entity, NextJolts(next_entities)); + } +} + +fn print_difference_stats(diffs: Query<&MinJoltDiff>) { + let mut buckets: BTreeMap = BTreeMap::new(); + for diff in diffs.iter() { + *buckets.entry(diff.0).or_insert(0) += 1; + } + println!("Buckets: {:?}", buckets); + + println!( + "Bucket 1 * Bucket 3: {}", + buckets.get(&1).unwrap_or(&0) * buckets.get(&3).unwrap_or(&0) + ); +} + +fn add_next_paths_to_end_sum( + mut commands: Commands, + potential_to_add: Query>, + already_added: Query<&PathsToEnd>, +) { + for (entity, next) in potential_to_add.iter() { + if next.0.is_empty() { + commands.insert_one(entity, PathsToEnd(1)); + } else { + let next_paths: Result, _> = next + .0 + .iter() + .map(|next_entity| already_added.get(*next_entity)) + .collect(); + if let Ok(next_paths) = next_paths { + let sum = next_paths.iter().map(|paths| paths.0).sum(); + commands.insert_one(entity, PathsToEnd(sum)); + } + } + } +} + +fn report_full_paths_to_end( + mut exit_events: ResMut>, + jolts: &Jolts, + paths: &PathsToEnd, +) { + if jolts.0 == 0 { + println!("Paths from the start to the end: {}", paths.0); + exit_events.send(AppExit); + } +} diff --git a/2020/src/bin/day_11.rs b/2020/src/bin/day_11.rs new file mode 100644 index 0000000..82bb282 --- /dev/null +++ b/2020/src/bin/day_11.rs @@ -0,0 +1,209 @@ +use bevy::prelude::*; +use std::fs::File; +use std::io::{BufRead, BufReader}; + +fn main() { + App::build() + .add_resource(WindowDescriptor { + title: "Advent of Code".to_string(), + width: 1920, + height: 1080, + ..Default::default() + }) + .add_event::() + .add_event::() + .add_resource(ClearColor(Color::rgb(0., 0., 0.))) + .add_startup_system(setup_camera.system()) + .add_startup_system(read_input_file.system()) + .add_system(iterate_adjacent_seats.system()) + .add_system(iterate_line_of_sight_seats.system()) + .add_system(report_adjacent_done.system()) + .add_system(report_line_of_sight_done.system()) + .add_plugins(DefaultPlugins) + .run(); +} + +fn setup_camera(mut commands: Commands) { + commands.spawn(Camera2dComponents::default()); +} + +#[derive(PartialEq, Eq, Clone, Copy)] +struct Position { + x: i32, + y: i32, +} +impl Position { + fn distance(&self, other: &Position) -> i32 { + (self.x - other.x).abs().max((self.y - other.y).abs()) + } +} +impl std::ops::Add<&Position> for Position { + type Output = Position; + fn add(self, other: &Position) -> Self::Output { + Position { + x: self.x + other.x, + y: self.y + other.y, + } + } +} + +struct AdjacentSeat { + occupied: bool, +} +struct LineOfSightSeat { + occupied: bool, +} + +struct AdjacentStableEvent; +struct LineOfSightStableEvent; + +#[derive(Default)] +struct Bounds { + min_x: i32, + max_x: i32, + min_y: i32, + max_y: i32, +} + +fn read_input_file(mut commands: Commands) { + let f = File::open("./inputs/day_11.txt").unwrap(); + let mut bounds = Bounds::default(); + + for (y, line) in BufReader::new(f).lines().enumerate() { + let line = line.unwrap(); + let line = line.trim(); + for (x, c) in line.chars().enumerate() { + if c == 'L' { + commands.spawn(( + Position { + x: x as i32, + y: y as i32, + }, + AdjacentSeat { occupied: false }, + LineOfSightSeat { occupied: false }, + )); + } + bounds.max_x = x as i32; + } + + bounds.max_y = y as i32; + } + commands.insert_resource(bounds); +} + +fn iterate_adjacent_seats( + mut commands: Commands, + mut stable_events: ResMut>, + seats: Query<(Entity, &Position, &AdjacentSeat)>, +) { + let mut changes: Vec<(Entity, AdjacentSeat)> = Vec::new(); + for (entity, position, seat) in seats.iter() { + let surrounding_count = seats + .iter() + .filter(|(_, other_position, other_seat)| { + other_seat.occupied && position.distance(other_position) == 1 + }) + .count(); + let change = if !seat.occupied && surrounding_count == 0 { + Some(true) + } else if seat.occupied && surrounding_count >= 4 { + Some(false) + } else { + None + }; + if let Some(occupied) = change { + changes.push((entity, AdjacentSeat { occupied })); + } + } + if changes.is_empty() { + stable_events.send(AdjacentStableEvent); + } + + for (entity, seat) in changes { + commands.insert_one(entity, seat); + } +} + +fn iterate_line_of_sight_seats( + mut commands: Commands, + mut stable_events: ResMut>, + bounds: Res, + seats: Query<(Entity, &Position, &LineOfSightSeat)>, +) { + let dir_vectors = [ + Position { x: -1, y: 0 }, + Position { x: -1, y: -1 }, + Position { x: 0, y: -1 }, + Position { x: 1, y: -1 }, + Position { x: 1, y: 0 }, + Position { x: 1, y: 1 }, + Position { x: 0, y: 1 }, + Position { x: -1, y: 1 }, + ]; + + let mut changes: Vec<(Entity, LineOfSightSeat)> = Vec::new(); + for (entity, position, seat) in seats.iter() { + let mut surrounding_count = 0; + for vec in dir_vectors.iter() { + let mut check = position.clone(); + while check.x >= bounds.min_x + && check.x <= bounds.max_x + && check.y >= bounds.min_y + && check.y <= bounds.max_y + { + check = check + vec; + if let Some((_, _, other_seat)) = + seats.iter().find(|(_, other_pos, _)| other_pos == &&check) + { + if other_seat.occupied { + surrounding_count += 1; + } + break; + } + } + } + + let change = if !seat.occupied && surrounding_count == 0 { + Some(true) + } else if seat.occupied && surrounding_count >= 5 { + Some(false) + } else { + None + }; + if let Some(occupied) = change { + changes.push((entity, LineOfSightSeat { occupied })); + } + } + if changes.is_empty() { + stable_events.send(LineOfSightStableEvent); + } + + for (entity, seat) in changes { + commands.insert_one(entity, seat); + } +} + +fn report_adjacent_done( + stable_events: Res>, + mut stable_event_reader: Local>, + seats: Query<&AdjacentSeat>, +) { + for _ in stable_event_reader.iter(&stable_events) { + let occupied = seats.iter().filter(|s| s.occupied).count(); + println!("{} seats end up occupied in the adjacent model", occupied); + } +} + +fn report_line_of_sight_done( + stable_events: Res>, + mut stable_event_reader: Local>, + seats: Query<&LineOfSightSeat>, +) { + for _ in stable_event_reader.iter(&stable_events) { + let occupied = seats.iter().filter(|s| s.occupied).count(); + println!( + "{} seats end up occupied in the line of sight model", + occupied + ); + } +} diff --git a/2020/src/bin/day_12.rs b/2020/src/bin/day_12.rs new file mode 100644 index 0000000..489cdca --- /dev/null +++ b/2020/src/bin/day_12.rs @@ -0,0 +1,233 @@ +use bevy::prelude::*; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::ops::Deref; + +fn main() { + App::build() + .add_resource(WindowDescriptor { + title: "Advent of Code".to_string(), + width: 1920, + height: 1080, + ..Default::default() + }) + .add_resource(ClearColor(Color::rgb(0., 0., 0.))) + .add_startup_system(setup_camera.system()) + .add_startup_system(read_input_file.system()) + .add_system(move_normal_ship.system()) + .add_system(move_waypoint_ship.system()) + .add_plugins(DefaultPlugins) + .run(); +} + +fn setup_camera(mut commands: Commands) { + commands.spawn(Camera2dComponents::default()); +} + +#[derive(PartialEq, Eq, Clone, Copy)] +struct Position { + x: i64, + y: i64, +} +impl Position { + fn manhattan_distance(&self, other: &Position) -> i64 { + (self.x - other.x).abs() + (self.y - other.y).abs() + } +} +impl std::ops::Add<&Position> for &Position { + type Output = Position; + fn add(self, other: &Position) -> Self::Output { + Position { + x: self.x + other.x, + y: self.y + other.y, + } + } +} +impl std::ops::Mul for Position { + type Output = Position; + fn mul(self, other: i64) -> Self::Output { + Position { + x: self.x * other, + y: self.y * other, + } + } +} + +enum Facing { + North, + South, + East, + West, +} +impl Facing { + fn right(&mut self) { + use Facing::*; + *self = match self { + North => East, + East => South, + South => West, + West => North, + } + } + + fn reverse(&mut self) { + self.right(); + self.right(); + } + + fn left(&mut self) { + self.reverse(); + self.right(); + } +} + +struct Instructions(Vec); +#[derive(Clone)] +enum Instruction { + North(i64), + South(i64), + East(i64), + West(i64), + Left(i64), + Right(i64), + Forward(i64), +} + +struct InstructionPointer(usize); + +struct Waypoint(Position); + +fn read_input_file(mut commands: Commands) { + let f = File::open("./inputs/day_12.txt").unwrap(); + + let instructions: Vec = BufReader::new(f) + .lines() + .map(|line| { + let line = line.unwrap(); + let line = line.trim(); + + let val: i64 = line[1..].parse().unwrap(); + let instruction = match line.chars().next().unwrap() { + 'N' => Instruction::North(val), + 'S' => Instruction::South(val), + 'E' => Instruction::East(val), + 'W' => Instruction::West(val), + 'L' => Instruction::Left(val), + 'R' => Instruction::Right(val), + 'F' => Instruction::Forward(val), + other => panic!("Unknown instruction: {}", other), + }; + instruction + }) + .collect(); + + commands.spawn(( + Instructions(instructions.clone()), + InstructionPointer(0), + Facing::East, + Position { x: 0, y: 0 }, + )); + + commands.spawn(( + Waypoint(Position { x: 10, y: 1 }), + Instructions(instructions), + InstructionPointer(0), + Position { x: 0, y: 0 }, + )); +} + +fn move_normal_ship( + mut q: Query< + Without< + Waypoint, + ( + &Instructions, + &mut InstructionPointer, + &mut Facing, + &mut Position, + ), + >, + >, +) { + for (instructions, mut instruction_pointer, mut facing, mut position) in q.iter_mut() { + if let Some(instruction) = instructions.0.get(instruction_pointer.0) { + use Instruction::*; + match instruction { + North(val) => position.y += val, + South(val) => position.y -= val, + East(val) => position.x += val, + West(val) => position.x -= val, + Left(90) | Right(270) => facing.left(), + Left(180) | Right(180) => facing.reverse(), + Left(270) | Right(90) => facing.right(), + Left(val) | Right(val) => panic!("Unknown left/right val: {}", val), + Forward(val) => match facing.deref() { + Facing::North => position.y += val, + Facing::South => position.y -= val, + Facing::East => position.x += val, + Facing::West => position.x -= val, + }, + }; + + instruction_pointer.0 += 1; + } else { + println!( + "Normal Ship at destination: {} x {}, Manhattan distance: {}", + position.x, + position.y, + position.manhattan_distance(&Position { x: 0, y: 0 }) + ); + } + } +} + +fn move_waypoint_ship( + instructions: &Instructions, + mut waypoint: Mut, + mut instruction_pointer: Mut, + mut position: Mut, +) { + if let Some(instruction) = instructions.0.get(instruction_pointer.0) { + use Instruction::*; + match instruction { + North(val) => waypoint.0.y += val, + South(val) => waypoint.0.y -= val, + East(val) => waypoint.0.x += val, + West(val) => waypoint.0.x -= val, + Left(90) | Right(270) => { + waypoint.0 = Position { + x: -waypoint.0.y, + y: waypoint.0.x, + } + } + Left(180) | Right(180) => { + waypoint.0 = Position { + x: -waypoint.0.x, + y: -waypoint.0.y, + } + } + Left(270) | Right(90) => { + waypoint.0 = Position { + x: waypoint.0.y, + y: -waypoint.0.x, + } + } + Left(val) | Right(val) => panic!("Unknown left/right val: {}", val), + Forward(val) => *position = position.deref() + &(waypoint.0 * *val), + }; + + // println!( + // "Position: {} x {}. Waypoint: {} x {}", + // position.x, position.y, waypoint.0.x, waypoint.0.y + // ); + + instruction_pointer.0 += 1; + } else { + println!( + "Waypoint Ship at destination: {} x {}, Manhattan distance: {}", + position.x, + position.y, + position.manhattan_distance(&Position { x: 0, y: 0 }) + ); + } +} diff --git a/2020/src/bin/day_2.rs b/2020/src/bin/day_2.rs new file mode 100644 index 0000000..6c26647 --- /dev/null +++ b/2020/src/bin/day_2.rs @@ -0,0 +1,104 @@ +use bevy::prelude::*; +use std::fs::File; +use std::io::{BufRead, BufReader}; + +fn main() { + App::build() + .add_resource(WindowDescriptor { + title: "Advent of Code".to_string(), + width: 1920, + height: 1080, + ..Default::default() + }) + .add_resource(ClearColor(Color::rgb(0., 0., 0.))) + .add_startup_system(setup_camera.system()) + .add_startup_system(read_input_file.system()) + .add_stage("mark") + .add_system_to_stage("mark", validate_sled_passwords.system()) + .add_system_to_stage("mark", validate_toboggan_passwords.system()) + .add_stage("report") + .add_system_to_stage("report", count_valid_sled_passwords.system()) + .add_system_to_stage("report", count_valid_toboggan_passwords.system()) + //.add_plugins(DefaultPlugins) + .run(); +} + +fn setup_camera(mut commands: Commands) { + commands.spawn(Camera2dComponents::default()); +} + +struct PasswordRule { + num_1: usize, + num_2: usize, + c: char, +} +struct Password(String); +struct SledPasswordValidation(bool); +struct TobogganPasswordValidation(bool); + +fn read_input_file(mut commands: Commands) { + let f = File::open("./inputs/day_2.txt").expect("Failed to read file"); // TODO: Use the asset loading system to load this rather? + for line in BufReader::new(f).lines() { + let line = line.expect("Error reading file"); + let line = line.trim(); + let mut parts = line.split_whitespace(); + let range = parts.next().expect("File was missing a range"); + let chars = parts.next().expect("File was missing chars"); + let password = parts.next().expect("File was missing a password"); + + let mut range_parts = range.split('-'); + + let rule = PasswordRule { + num_1: range_parts + .next() + .expect("Range did not have a minimum") + .parse() + .expect("Minimum was not a number"), + num_2: range_parts + .next() + .expect("Range did not have a maximum") + .parse() + .expect("Maximum was not a number"), + c: chars.chars().next().expect("File was missing chars"), + }; + let password = Password(password.to_owned()); + + commands.spawn((rule, password)); + } +} + +fn validate_sled_passwords( + mut commands: Commands, + passwords: Query>, +) { + for (entity, rule, password) in passwords.iter() { + let actual = password.0.chars().filter(|c| c == &rule.c).count(); + let valid = actual >= rule.num_1 && actual <= rule.num_2; + commands.insert_one(entity, SledPasswordValidation(valid)); + } +} + +fn validate_toboggan_passwords( + mut commands: Commands, + passwords: Query>, +) { + for (entity, rule, password) in passwords.iter() { + let actual_1 = password.0.chars().nth(rule.num_1 - 1); + let actual_2 = password.0.chars().nth(rule.num_2 - 1); + let actual_1_match = actual_1.map_or(false, |c| c == rule.c); + let actual_2_match = actual_2.map_or(false, |c| c == rule.c); + + let valid = actual_1_match != actual_2_match; + commands.insert_one(entity, TobogganPasswordValidation(valid)); + } +} + +fn count_valid_sled_passwords(validation: Query<&SledPasswordValidation>) { + let count = validation.iter().filter(|v| v.0).count(); + println!("Valid Sled Passwords: {}", count); +} + +fn count_valid_toboggan_passwords(validation: Query<&TobogganPasswordValidation>) { + let count = validation.iter().filter(|v| v.0).count(); + println!("Valid Toboggan Passwords: {}", count); +} diff --git a/2020/src/bin/day_3.rs b/2020/src/bin/day_3.rs new file mode 100644 index 0000000..5e6f0bd --- /dev/null +++ b/2020/src/bin/day_3.rs @@ -0,0 +1,205 @@ +use bevy::app::AppExit; +use bevy::prelude::*; +use bevy::render::camera::Camera; +use std::fs::File; +use std::io::{BufRead, BufReader}; + +fn main() { + App::build() + .add_resource(WindowDescriptor { + title: "Advent of Code".to_string(), + width: 1920, + height: 1080, + ..Default::default() + }) + .add_resource(ClearColor(Color::rgb(0., 0., 0.))) + .add_startup_system(setup_camera.system()) + .add_startup_stage("game_entities") + .add_startup_system_to_stage("game_entities", read_input_file.system()) + .add_system(move_tobogganist.system()) + .add_system(collide_with_trees.system()) + .add_system(clear_finished_tobogganists.system()) + .add_system(end_of_slope.system()) + .add_system(translate_positions.system()) + .add_system(tobogganist_cam.system()) + .add_plugins(DefaultPlugins) + .run(); +} + +fn setup_camera(mut commands: Commands, mut materials: ResMut>) { + commands.spawn(Camera2dComponents::default()); + + commands.insert_resource(Materials { + tobogganist_material: materials.add(Color::rgb(0., 0., 255.).into()), + tree_material: materials.add(Color::rgb(0., 255., 0.).into()), + }); +} + +struct Materials { + tobogganist_material: Handle, + tree_material: Handle, +} + +struct Tobogganist; + +struct Tree; +#[derive(PartialEq, Eq)] +struct Position { + x: usize, + y: usize, +} +struct Velocity { + x: usize, + y: usize, +} +struct MapSize { + width: usize, + height: usize, +} +struct TreeHitCount(usize); +struct Score(usize); + +fn read_input_file(mut commands: Commands, materials: Res) { + commands + .spawn(SpriteComponents { + sprite: Sprite::new(Vec2::new(10., 10.)), + material: materials.tobogganist_material.clone(), + ..Default::default() + }) + .with(Tobogganist) + .with(Position { x: 0, y: 0 }) + .with(Velocity { x: 1, y: 1 }) + .with(TreeHitCount(0)); + + commands + .spawn(SpriteComponents { + sprite: Sprite::new(Vec2::new(10., 10.)), + material: materials.tobogganist_material.clone(), + ..Default::default() + }) + .with(Tobogganist) + .with(Position { x: 0, y: 0 }) + .with(Velocity { x: 3, y: 1 }) + .with(TreeHitCount(0)); + + commands + .spawn(SpriteComponents { + sprite: Sprite::new(Vec2::new(10., 10.)), + material: materials.tobogganist_material.clone(), + ..Default::default() + }) + .with(Tobogganist) + .with(Position { x: 0, y: 0 }) + .with(Velocity { x: 5, y: 1 }) + .with(TreeHitCount(0)); + + commands + .spawn(SpriteComponents { + sprite: Sprite::new(Vec2::new(10., 10.)), + material: materials.tobogganist_material.clone(), + ..Default::default() + }) + .with(Tobogganist) + .with(Position { x: 0, y: 0 }) + .with(Velocity { x: 7, y: 1 }) + .with(TreeHitCount(0)); + + commands + .spawn(SpriteComponents { + sprite: Sprite::new(Vec2::new(10., 10.)), + material: materials.tobogganist_material.clone(), + ..Default::default() + }) + .with(Tobogganist) + .with(Position { x: 0, y: 0 }) + .with(Velocity { x: 1, y: 2 }) + .with(TreeHitCount(0)); + + let f = File::open("./inputs/day_3.txt").expect("Failed to read file"); // TODO: Use the asset loading system to load this rather? + let mut width = 0; + let mut height = 0; + for (y, line) in BufReader::new(f).lines().enumerate() { + let line = line.expect("Error reading file"); + let line = line.trim(); + width = width.max(line.len()); + for (x, c) in line.chars().enumerate() { + if c == '#' { + commands + .spawn(SpriteComponents { + sprite: Sprite::new(Vec2::new(10., 10.)), + material: materials.tree_material.clone(), + ..Default::default() + }) + .with(Tree) + .with(Position { x, y }) + .with(TreeHitCount(0)); + } + } + height = y + 1; + } + commands.insert_resource(MapSize { width, height }); + commands.insert_resource(Score(1)); +} + +fn translate_positions(mut transform: Mut, position: &Position) { + transform.translation = Vec3::new(position.x as f32 * 10., position.y as f32 * -10., 0.); +} + +fn tobogganist_cam( + tobogganist: Query>, + mut camera: Query>, +) { + for mut camera_transform in camera.iter_mut() { + if let Some(tobogganist_transform) = tobogganist.iter().next() { + camera_transform.translation = Vec3::new(0., tobogganist_transform.translation.y(), 0.); + } + } +} + +fn move_tobogganist(map_size: Res, mut position: Mut, velocity: &Velocity) { + position.x += velocity.x; + position.x %= map_size.width; + position.y += velocity.y; +} + +fn collide_with_trees( + mut tobogganists: Query>, + trees: Query>, +) { + for (tobogganist_position, mut tree_hit_count) in tobogganists.iter_mut() { + for tree_position in trees.iter() { + if *tree_position == *tobogganist_position { + tree_hit_count.0 += 1; + } + } + } +} + +fn clear_finished_tobogganists( + mut commands: Commands, + map_size: Res, + mut score: ResMut, + tobogganists: Query>, +) { + for (entity, position, velocity, tree_hit_count) in tobogganists.iter() { + if position.y >= map_size.height { + println!( + "A tobogganist finished heading at incline {} x {}, after hitting {} trees", + velocity.x, velocity.y, tree_hit_count.0 + ); + score.0 *= tree_hit_count.0; + commands.despawn(entity); + } + } +} + +fn end_of_slope( + score: Res, + mut exit_events: ResMut>, + tobogganists: Query<&Tobogganist>, +) { + if tobogganists.iter().count() == 0 { + println!("All tobogganists finished. Total score: {}", score.0); + exit_events.send(AppExit); + } +} diff --git a/2020/src/bin/day_4.rs b/2020/src/bin/day_4.rs new file mode 100644 index 0000000..50b8eb5 --- /dev/null +++ b/2020/src/bin/day_4.rs @@ -0,0 +1,222 @@ +use bevy::prelude::*; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::str::FromStr; +use thiserror::Error; + +fn main() { + App::build() + .add_resource(WindowDescriptor { + title: "Advent of Code".to_string(), + width: 1920, + height: 1080, + ..Default::default() + }) + .add_resource(ClearColor(Color::rgb(0., 0., 0.))) + .add_startup_system(setup_camera.system()) + .add_startup_system(read_input_file.system()) + .add_system(count_valid.system()) + //.add_plugins(DefaultPlugins) + .run(); +} + +fn setup_camera(mut commands: Commands) { + commands.spawn(Camera2dComponents::default()); +} + +#[derive(Error, Debug)] +enum ValidationError { + #[error("{0}")] + FromIntError(#[from] std::num::ParseIntError), + #[error("value is out of range")] + OutOfRange, + #[error("value has an invalid unit")] + InvalidUnit, + #[error("value has an invalid characters")] + InvalidChars, +} + +struct BirthYear(u32); +impl FromStr for BirthYear { + type Err = ValidationError; + fn from_str(s: &str) -> Result { + let num: u32 = s.parse()?; + if num >= 1920 && num <= 2002 { + Ok(Self(num)) + } else { + Err(ValidationError::OutOfRange) + } + } +} + +struct IssueYear(u32); +impl FromStr for IssueYear { + type Err = ValidationError; + fn from_str(s: &str) -> Result { + let num: u32 = s.parse()?; + if num >= 2010 && num <= 2020 { + Ok(Self(num)) + } else { + Err(ValidationError::OutOfRange) + } + } +} + +struct ExpirationYear(u32); +impl FromStr for ExpirationYear { + type Err = ValidationError; + fn from_str(s: &str) -> Result { + let num: u32 = s.parse()?; + if num >= 2020 && num <= 2030 { + Ok(Self(num)) + } else { + Err(ValidationError::OutOfRange) + } + } +} + +struct Height(u32); +impl FromStr for Height { + type Err = ValidationError; + fn from_str(s: &str) -> Result { + let inches = s.ends_with("in"); + let centimeters = s.ends_with("cm"); + if !inches && !centimeters { + return Err(ValidationError::InvalidUnit); + } + + let num: u32 = s[0..s.len() - 2].parse()?; + let num_cm = if inches { + ((num as f32) * 2.54).round() as u32 + } else { + num + }; + + if num_cm >= 150 && num_cm <= 193 { + Ok(Self(num_cm)) + } else { + Err(ValidationError::OutOfRange) + } + } +} + +struct HairColor(String); +impl FromStr for HairColor { + type Err = ValidationError; + fn from_str(s: &str) -> Result { + if !s.starts_with("#") { + return Err(ValidationError::InvalidChars); + } + if s.len() != 7 { + return Err(ValidationError::InvalidChars); + } + if !s + .chars() + .skip(1) + .all(|c| (c >= 'a' && c <= 'f') || c >= '0' && c <= '9') + { + return Err(ValidationError::InvalidChars); + } + + Ok(Self(s.to_string())) + } +} + +struct EyeColor(String); +impl FromStr for EyeColor { + type Err = ValidationError; + fn from_str(s: &str) -> Result { + let valid_vals = ["amb", "blu", "brn", "gry", "grn", "hzl", "oth"]; + if valid_vals.contains(&s) { + Ok(Self(s.to_string())) + } else { + Err(ValidationError::InvalidChars) + } + } +} + +struct PassportId(String); +impl FromStr for PassportId { + type Err = ValidationError; + fn from_str(s: &str) -> Result { + if s.len() != 9 { + return Err(ValidationError::InvalidChars); + } + if !s.chars().all(|c| c.is_ascii_digit()) { + return Err(ValidationError::InvalidChars); + } + + Ok(Self(s.to_string())) + } +} + +struct CountryId(String); +impl FromStr for CountryId { + type Err = ValidationError; + fn from_str(s: &str) -> Result { + Ok(Self(s.to_string())) + } +} + +fn read_input_file(mut commands: Commands) { + let f = File::open("./inputs/day_4.txt").expect("Failed to read file"); + + commands.spawn(()); + for line in BufReader::new(f).lines() { + let line = line.unwrap(); + let line = line.trim(); + if line.is_empty() { + commands.spawn(()); + } else { + for section in line.split_whitespace() { + let mut section_parts = section.split(':'); + let name = section_parts.next().unwrap(); + let value = section_parts.next().unwrap(); + match name { + "byr" => { + BirthYear::from_str(value).ok().map(|c| commands.with(c)); + } + "iyr" => { + IssueYear::from_str(value).ok().map(|c| commands.with(c)); + } + "eyr" => { + ExpirationYear::from_str(value) + .ok() + .map(|c| commands.with(c)); + } + "hgt" => { + Height::from_str(value).ok().map(|c| commands.with(c)); + } + "hcl" => { + HairColor::from_str(value).ok().map(|c| commands.with(c)); + } + "ecl" => { + EyeColor::from_str(value).ok().map(|c| commands.with(c)); + } + "pid" => { + PassportId::from_str(value).ok().map(|c| commands.with(c)); + } + "cid" => { + CountryId::from_str(value).ok().map(|c| commands.with(c)); + } + _ => {} + } + } + } + } +} + +fn count_valid( + passports: Query<( + &BirthYear, + &IssueYear, + &ExpirationYear, + &Height, + &HairColor, + &EyeColor, + &PassportId, + )>, +) { + let count = passports.iter().count(); + println!("There are {} valid passports", count); +} diff --git a/2020/src/bin/day_5.rs b/2020/src/bin/day_5.rs new file mode 100644 index 0000000..15c8a70 --- /dev/null +++ b/2020/src/bin/day_5.rs @@ -0,0 +1,157 @@ +use bevy::prelude::*; +use std::fs::File; +use std::io::{BufRead, BufReader}; + +fn main() { + App::build() + .add_resource(WindowDescriptor { + title: "Advent of Code".to_string(), + width: 1920, + height: 1080, + ..Default::default() + }) + .add_resource(ClearColor(Color::rgb(0., 0., 0.))) + .add_startup_system(setup_camera.system()) + .add_startup_system(read_input_file.system()) + .add_startup_stage("search") + .add_startup_system_to_stage("search", find_unoccupied_seat.system()) + .add_startup_stage("metadata") + .add_startup_system_to_stage("metadata", add_seat_ids.system()) + .add_startup_system_to_stage("metadata", add_seat_sprites.system()) + .add_startup_stage("report") + .add_startup_system_to_stage("report", print_highest_seat_id.system()) + .add_startup_system_to_stage("report", print_my_seat_id.system()) + .add_plugins(DefaultPlugins) + .run(); +} + +fn setup_camera(mut commands: Commands, mut materials: ResMut>) { + commands.spawn(Camera2dComponents { + transform: Transform::from_translation(Vec3::new(80. / 2., 1280. / 2., 0.)), + ..Default::default() + }); + + commands.insert_resource(Materials { + my_seat_material: materials.add(Color::rgb(0., 0., 255.).into()), + seat_material: materials.add(Color::rgb(0., 255., 0.).into()), + }); +} + +struct Materials { + my_seat_material: Handle, + seat_material: Handle, +} + +#[derive(PartialEq, Eq)] +struct Position { + x: u32, + y: u32, +} +struct SeatId(u32); +struct MySeat; + +fn read_input_file(mut commands: Commands) { + let f = File::open("./inputs/day_5.txt").unwrap(); + for line in BufReader::new(f).lines() { + let mut max_y = 128; + let mut min_y = 0; + let mut max_x = 8; + let mut min_x = 0; + for c in line.unwrap().trim().chars() { + let mid_x = (min_x + max_x) / 2; + let mid_y = (min_y + max_y) / 2; + match c { + 'F' => { + max_y = mid_y; + } + 'B' => { + min_y = mid_y; + } + 'L' => { + max_x = mid_x; + } + 'R' => { + min_x = mid_x; + } + _ => panic!("Unexpected character {}", c), + } + } + + assert_eq!(max_y - min_y, 1); + assert_eq!(max_x - min_x, 1); + + commands.spawn((Position { x: min_x, y: min_y },)); + } +} + +fn find_unoccupied_seat(mut commands: Commands, seats: Query<&Position>) { + for y in 1..127 { + for x in 0..8 { + let potential_position = Position { x, y }; + let required_left = if x == 0 { + Position { x: 7, y: y - 1 } + } else { + Position { x: x - 1, y } + }; + let required_right = if x == 7 { + Position { x: 0, y: y + 1 } + } else { + Position { x: x + 1, y } + }; + + let required_surrounds = [required_left, required_right]; + if !seats.iter().any(|p| p == &potential_position) { + if required_surrounds + .iter() + .all(|required| seats.iter().any(|p| p == required)) + { + commands.spawn((MySeat, potential_position)); + } + } + } + } +} + +fn add_seat_ids(mut commands: Commands, seats: Query<(Entity, &Position)>) { + for (entity, position) in seats.iter() { + let seat_id = position.y * 8 + position.x; + commands.insert_one(entity, SeatId(seat_id)); + } +} + +fn add_seat_sprites( + mut commands: Commands, + materials: Res, + seats: Query<(Entity, &Position, Option<&MySeat>)>, +) { + for (entity, position, my_seat) in seats.iter() { + commands.insert( + entity, + SpriteComponents { + sprite: Sprite::new(Vec2::new(10., 10.)), + material: if my_seat.is_some() { + materials.my_seat_material.clone() + } else { + materials.seat_material.clone() + }, + transform: Transform::from_translation(Vec3::new( + position.x as f32 * 10., + position.y as f32 * 10., + 0., + )), + ..Default::default() + }, + ); + } +} + +fn print_highest_seat_id(seats: Query<&SeatId>) { + let max = seats.iter().map(|s| s.0).max(); + println!("Max seat id: {:?}", max); +} + +fn print_my_seat_id(seats: Query>) { + for seat_id in seats.iter() { + println!("My seat id: {}", seat_id.0); + } +} diff --git a/2020/src/bin/day_6.rs b/2020/src/bin/day_6.rs new file mode 100644 index 0000000..68efbce --- /dev/null +++ b/2020/src/bin/day_6.rs @@ -0,0 +1,102 @@ +use bevy::prelude::*; +use std::collections::{BTreeMap, BTreeSet}; +use std::fs::File; +use std::io::{BufRead, BufReader}; + +fn main() { + App::build() + .add_resource(WindowDescriptor { + title: "Advent of Code".to_string(), + width: 1920, + height: 1080, + ..Default::default() + }) + .add_resource(ClearColor(Color::rgb(0., 0., 0.))) + .add_startup_system(setup_camera.system()) + .add_startup_system(read_input_file.system()) + .add_stage("calculate") + .add_system_to_stage("calculate", calculate_anyone_groups.system()) + .add_system_to_stage("calculate", calculate_everyone_groups.system()) + .add_stage("report") + .add_system_to_stage("report", count_anyone_group_sizes.system()) + .add_system_to_stage("report", count_everyone_group_sizes.system()) + //.add_plugins(DefaultPlugins) + .run(); +} + +fn setup_camera(mut commands: Commands) { + commands.spawn(Camera2dComponents::default()); +} + +struct Individual; +struct Anyone; +struct Everyone; + +struct Group(usize); +struct Answers(BTreeSet); + +fn read_input_file(mut commands: Commands) { + let f = File::open("./inputs/day_6.txt").unwrap(); + + let mut current_group = 0; + for line in BufReader::new(f).lines() { + let line = line.unwrap(); + let line = line.trim(); + if line.is_empty() { + current_group += 1; + } else { + let answers = line.chars().collect(); + commands.spawn((Individual, Group(current_group), Answers(answers))); + } + } +} + +fn calculate_anyone_groups( + mut commands: Commands, + individuals: Query>, +) { + let mut anyone_groups = BTreeMap::new(); + for (group_id, answer) in individuals.iter() { + let updated = anyone_groups + .entry(group_id.0) + .or_insert(BTreeSet::new()) + .union(&answer.0) + .cloned() + .collect(); + anyone_groups.insert(group_id.0, updated); + } + + for (group_id, anyone) in anyone_groups.into_iter() { + commands.spawn((Anyone, Group(group_id), Answers(anyone))); + } +} + +fn calculate_everyone_groups( + mut commands: Commands, + individuals: Query>, +) { + let mut anyone_groups = BTreeMap::new(); + for (group_id, answer) in individuals.iter() { + let updated = anyone_groups + .entry(group_id.0) + .or_insert(answer.0.clone()) + .intersection(&answer.0) + .cloned() + .collect(); + anyone_groups.insert(group_id.0, updated); + } + + for (group_id, anyone) in anyone_groups.into_iter() { + commands.spawn((Everyone, Group(group_id), Answers(anyone))); + } +} + +fn count_anyone_group_sizes(groups: Query>) { + let sum: usize = groups.iter().map(|g| g.0.len()).sum(); + println!("Anyone: {}", sum); +} + +fn count_everyone_group_sizes(groups: Query>) { + let sum: usize = groups.iter().map(|g| g.0.len()).sum(); + println!("Everyone: {}", sum); +} diff --git a/2020/src/bin/day_7.rs b/2020/src/bin/day_7.rs new file mode 100644 index 0000000..45cb9ee --- /dev/null +++ b/2020/src/bin/day_7.rs @@ -0,0 +1,137 @@ +use bevy::prelude::*; +use std::collections::{BTreeMap, BTreeSet}; +use std::fs::File; +use std::io::{BufRead, BufReader}; + +fn main() { + App::build() + .add_resource(WindowDescriptor { + title: "Advent of Code".to_string(), + width: 1920, + height: 1080, + ..Default::default() + }) + .add_resource(ClearColor(Color::rgb(0., 0., 0.))) + .add_startup_system(setup_camera.system()) + .add_startup_system(read_input_file.system()) + .add_system(trim_empty_bags.system()) + .add_system(add_known_sizes.system()) + .add_system(report_gold_bag_known_size.system()) + //.add_system(debug_print_bags.system()) + .add_plugins(DefaultPlugins) + .run(); +} + +fn setup_camera(mut commands: Commands) { + commands.spawn(Camera2dComponents::default()); +} + +const TARGET_COLOUR: &str = "shiny gold"; + +struct Bag { + colour: String, + required: BTreeMap, +} +struct CantContainShinyGold; +#[derive(Clone)] +struct KnownSize(usize); + +fn read_input_file(mut commands: Commands) { + let f = File::open("./inputs/day_7.txt").unwrap(); + + for line in BufReader::new(f).lines() { + let line = line.unwrap(); + let line = line.trim(); + + let (src_colour, contents) = split_2(line, "bags contain"); + let contents_parts = contents + .trim_end_matches('.') + .split(',') + .filter(|part| part != &"no other bags") + .map(|part| { + let (quantity, colour) = split_2( + part.trim().trim_end_matches("bags").trim_end_matches("bag"), + " ", + ); + (colour, quantity.parse().unwrap()) + }) + .collect(); + commands.spawn((Bag { + colour: src_colour, + required: contents_parts, + },)); + } +} + +fn split_2(line: &str, split: &str) -> (String, String) { + let parts = line.splitn(2, split).collect::>(); + assert_eq!(parts.len(), 2); + (parts[0].trim().to_string(), parts[1].trim().to_string()) +} + +fn debug_print_bags(bags: Query<&Bag>) { + for bag in bags.iter() { + println!("{} requires {:?}", bag.colour, bag.required); + } +} + +fn trim_empty_bags( + mut commands: Commands, + bags: Query>, +) { + let mut colours_cleared = BTreeSet::new(); + for (entity, bag) in bags.iter() { + let required_not_relevant = bag.required.keys().all(|required| { + !bags + .iter() + .any(|can_contain_shiny_gold| &can_contain_shiny_gold.1.colour == required) + }); + if required_not_relevant && bag.colour != TARGET_COLOUR { + colours_cleared.insert(bag.colour.clone()); + commands.insert_one(entity, CantContainShinyGold); + } + } + + if colours_cleared.is_empty() { + println!( + "{} bags can hold the shiny gold one", + bags.iter().count() - 1 + ); + } +} + +fn add_known_sizes(mut commands: Commands, bags: Query<(Entity, &Bag, Option<&KnownSize>)>) { + for (entity, bag, size) in bags.iter() { + if size.is_some() { + continue; + } + + let mut new_size = Some(0); + for (required_colour, required_quantity) in bag.required.iter() { + if new_size.is_none() { + break; + } + + let inner_bag_size = bags + .iter() + .find(|(_, bag, _)| &bag.colour == required_colour) + .and_then(|(_, _, size)| size.clone()); + new_size = new_size + .zip(inner_bag_size) + .map(|(a, b)| a + required_quantity * b.0) + } + + if let Some(new_size) = new_size { + commands.insert_one(entity, KnownSize(new_size + 1)); + } + } +} + +fn report_gold_bag_known_size(bags: Query<(&Bag, &KnownSize)>) { + println!("{} have a known size", bags.iter().count()); + for (bag, size) in bags.iter() { + if bag.colour == TARGET_COLOUR { + println!("The shiny gold bag must contain {} other bags!", size.0 - 1) + } + } +} diff --git a/2020/src/bin/day_8.rs b/2020/src/bin/day_8.rs new file mode 100644 index 0000000..2c0c227 --- /dev/null +++ b/2020/src/bin/day_8.rs @@ -0,0 +1,154 @@ +use bevy::{app::AppExit, prelude::*}; +use std::convert::TryFrom; +use std::io::{BufRead, BufReader}; +use std::{collections::BTreeSet, fs::File}; + +fn main() { + App::build() + .add_resource(WindowDescriptor { + title: "Advent of Code".to_string(), + width: 1920, + height: 1080, + ..Default::default() + }) + .add_resource(ClearColor(Color::rgb(0., 0., 0.))) + .add_startup_system(setup_camera.system()) + .add_startup_system(read_input_file.system()) + .add_stage("check_line") + .add_system_to_stage("check_line", track_program_line_execution.system()) + .add_system_to_stage("check_line", track_program_termination.system()) + .add_stage("exec") + .add_system_to_stage("exec", tick_computer.system()) + .add_system_to_stage("exec", exit.system()) + .add_plugins(DefaultPlugins) + .run(); +} + +fn setup_camera(mut commands: Commands) { + commands.spawn(Camera2dComponents::default()); +} + +struct Program { + ops: Vec, +} +#[derive(Clone)] +enum Op { + Acc(i32), + Jmp(i32), + Nop(i32), +} +struct UnalteredProgram; +#[derive(Default)] +struct Computer { + program_counter: i32, + accumulator: i32, + termiated: bool, +} +#[derive(Default)] +struct LineTracker(BTreeSet); + +impl Computer { + fn exec(&mut self, program: &Program) { + if self.termiated { + return; + } + + let x = usize::try_from(self.program_counter).ok(); + match x.and_then(|x| program.ops.get(x)) { + None => {} + Some(Op::Acc(val)) => { + self.accumulator += val; + self.program_counter += 1; + } + Some(Op::Jmp(val)) => { + self.program_counter += val; + } + Some(Op::Nop(_)) => { + self.program_counter += 1; + } + } + if self.program_counter >= program.ops.len() as i32 { + self.termiated = true; + } + } +} + +fn read_input_file(mut commands: Commands) { + let f = File::open("./inputs/day_8.txt").unwrap(); + + let ops: Vec = BufReader::new(f) + .lines() + .map(|line| { + let line = line.unwrap(); + let line = line.trim(); + + let mut line_parts = line.split_whitespace(); + let opcode = line_parts.next().unwrap(); + let val: i32 = line_parts.next().unwrap().parse().unwrap(); + let op = match opcode { + "acc" => Op::Acc(val), + "jmp" => Op::Jmp(val), + "nop" => Op::Nop(val), + _ => panic!("Invalid opcode"), + }; + op + }) + .collect(); + commands.spawn(( + UnalteredProgram, + Program { ops: ops.clone() }, + Computer::default(), + LineTracker::default(), + )); + + for i in 0..ops.len() { + let mut ops = ops.clone(); + ops[i] = match ops[i] { + Op::Nop(v) => Op::Jmp(v), + Op::Jmp(v) => Op::Nop(v), + Op::Acc(v) => Op::Acc(v), + }; + commands.spawn(( + Program { ops: ops.clone() }, + Computer::default(), + LineTracker::default(), + )); + } +} + +fn track_program_line_execution( + mut commands: Commands, + entity: Entity, + mut line_tracker: Mut, + computer: &Computer, + unaltered: Option<&UnalteredProgram>, +) { + if line_tracker.0.contains(&computer.program_counter) { + if unaltered.is_some() { + println!( + "About to execute a line for the second time! Line {}. Accumulator: {}", + computer.program_counter, computer.accumulator + ); + } + commands.despawn(entity); + } else { + line_tracker.0.insert(computer.program_counter); + } +} + +fn track_program_termination(mut commands: Commands, entity: Entity, computer: &Computer) { + if computer.termiated { + println!("Program terminated! Accumulator: {}", computer.accumulator); + commands.despawn(entity); + } +} + +fn tick_computer(mut computer: Mut, program: &Program) { + computer.exec(program); +} + +fn exit(mut exit_events: ResMut>, computers: Query<&Computer>) { + if computers.iter().len() == 0 { + exit_events.send(AppExit); + } +} diff --git a/2020/src/bin/day_9.rs b/2020/src/bin/day_9.rs new file mode 100644 index 0000000..b5a02e6 --- /dev/null +++ b/2020/src/bin/day_9.rs @@ -0,0 +1,120 @@ +use bevy::{app::AppExit, prelude::*}; +use std::fs::File; +use std::io::{BufRead, BufReader}; + +fn main() { + App::build() + .add_resource(WindowDescriptor { + title: "Advent of Code".to_string(), + width: 1920, + height: 1080, + ..Default::default() + }) + .add_resource(ClearColor(Color::rgb(0., 0., 0.))) + .add_startup_system(setup_camera.system()) + .add_startup_system(read_input_file.system()) + .add_stage("add") + .add_system_to_stage("add", check_next_num.system()) + .add_stage("remove") + .add_system_to_stage("remove", remove_oldest_line.system()) + .add_stage("find_range") + .add_system_to_stage("find_range", find_contiguous_range.system()) + .add_plugins(DefaultPlugins) + .run(); +} + +fn setup_camera(mut commands: Commands) { + commands.spawn(Camera2dComponents::default()); +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +struct LineNum(usize); +#[derive(Clone)] +struct Num(i64); +struct Active; +struct Invalid; +struct Done; + +fn read_input_file(mut commands: Commands) { + let f = File::open("./inputs/day_9.txt").unwrap(); + + for (line_num, line) in BufReader::new(f).lines().enumerate() { + let line = line.unwrap(); + let line = line.trim(); + let num = line.parse().unwrap(); + commands.spawn((LineNum(line_num), Num(num))); + if line_num < 25 { + commands.with(Active); + } + } +} + +fn check_next_num( + mut commands: Commands, + active: Query>, + next: Query>>, +) { + if let Some((min_entity, line, num)) = next.iter().min_by_key(|(_, line, _)| line.0) { + let mut is_sum = false; + for active_1 in active.iter() { + for active_2 in active.iter() { + is_sum = is_sum || (num.0 == active_1.0 + active_2.0) + } + } + commands.insert_one(min_entity, Active); + + if !is_sum { + commands.insert_one(min_entity, Invalid); + println!( + "{} on line {} was not a sum of the previous lot!", + num.0, line.0 + ); + } + } +} + +fn remove_oldest_line( + mut commands: Commands, + line_nums: Query>>, +) { + if let Some((min_entity, _)) = line_nums.iter().min_by_key(|(_, line)| line.0) { + commands.remove_one::(min_entity); + commands.insert_one(min_entity, Done); + } else { + println!("All numbers cleared"); + } +} + +fn find_contiguous_range( + mut exit_events: ResMut>, + line_nums: Query<(&LineNum, &Num)>, + invalid: Query>, +) { + for invalid in invalid.iter() { + let mut nums: Vec<(LineNum, Num)> = line_nums + .iter() + .map(|(line, num)| (line.clone(), num.clone())) + .collect(); + + nums.sort_by_key(|(line, _num)| line.clone()); + let nums: Vec = nums.into_iter().map(|(_line, num)| num.0).collect(); + + for min in 0..nums.len() { + for max in min + 2..nums.len() + 1 { + let sum: i64 = nums[min..max].iter().sum(); + if sum == invalid.0 { + let min_in_range = nums[min..max].iter().min().unwrap(); + let max_in_range = nums[min..max].iter().max().unwrap(); + println!( + "Range found! {} + {} = {}", + min_in_range, + max_in_range, + min_in_range + max_in_range + ); + } + } + } + + exit_events.send(AppExit); + } +} diff --git a/2020/src/lib.rs b/2020/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/2020/src/lib.rs @@ -0,0 +1 @@ + diff --git a/2020/src/main.rs b/2020/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/2020/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} -- cgit v1.2.3