summaryrefslogtreecommitdiff
path: root/2020/src/bin
diff options
context:
space:
mode:
Diffstat (limited to '2020/src/bin')
-rw-r--r--2020/src/bin/day_1.rs83
-rw-r--r--2020/src/bin/day_10.rs124
-rw-r--r--2020/src/bin/day_11.rs209
-rw-r--r--2020/src/bin/day_12.rs233
-rw-r--r--2020/src/bin/day_2.rs104
-rw-r--r--2020/src/bin/day_3.rs205
-rw-r--r--2020/src/bin/day_4.rs222
-rw-r--r--2020/src/bin/day_5.rs157
-rw-r--r--2020/src/bin/day_6.rs102
-rw-r--r--2020/src/bin/day_7.rs137
-rw-r--r--2020/src/bin/day_8.rs154
-rw-r--r--2020/src/bin/day_9.rs120
12 files changed, 1850 insertions, 0 deletions
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<With<FlaggedPair, &Expense>>) {
+ 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<With<FlaggedTriple, &Expense>>) {
+ 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<Entity>);
+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<i64, usize> = 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<Without<PathsToEnd, (Entity, &NextJolts)>>,
+ 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<Vec<&PathsToEnd>, _> = 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<Events<AppExit>>,
+ 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::<AdjacentStableEvent>()
+ .add_event::<LineOfSightStableEvent>()
+ .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<Events<AdjacentStableEvent>>,
+ 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<Events<LineOfSightStableEvent>>,
+ bounds: Res<Bounds>,
+ 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<Events<AdjacentStableEvent>>,
+ mut stable_event_reader: Local<EventReader<AdjacentStableEvent>>,
+ 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<Events<LineOfSightStableEvent>>,
+ mut stable_event_reader: Local<EventReader<LineOfSightStableEvent>>,
+ 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<i64> 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<Instruction>);
+#[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<Instruction> = 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<Waypoint>,
+ mut instruction_pointer: Mut<InstructionPointer>,
+ mut position: Mut<Position>,
+) {
+ 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<Without<SledPasswordValidation, (Entity, &PasswordRule, &Password)>>,
+) {
+ 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<Without<TobogganPasswordValidation, (Entity, &PasswordRule, &Password)>>,
+) {
+ 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<Assets<ColorMaterial>>) {
+ 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<ColorMaterial>,
+ tree_material: Handle<ColorMaterial>,
+}
+
+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<Materials>) {
+ 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<Transform>, position: &Position) {
+ transform.translation = Vec3::new(position.x as f32 * 10., position.y as f32 * -10., 0.);
+}
+
+fn tobogganist_cam(
+ tobogganist: Query<With<Tobogganist, &Transform>>,
+ mut camera: Query<With<Camera, &mut Transform>>,
+) {
+ 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<MapSize>, mut position: Mut<Position>, velocity: &Velocity) {
+ position.x += velocity.x;
+ position.x %= map_size.width;
+ position.y += velocity.y;
+}
+
+fn collide_with_trees(
+ mut tobogganists: Query<With<Tobogganist, (&Position, &mut TreeHitCount)>>,
+ trees: Query<With<Tree, &Position>>,
+) {
+ 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<MapSize>,
+ mut score: ResMut<Score>,
+ tobogganists: Query<With<Tobogganist, (Entity, &Position, &Velocity, &TreeHitCount)>>,
+) {
+ 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<Score>,
+ mut exit_events: ResMut<Events<AppExit>>,
+ 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<Self, Self::Err> {
+ 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<Self, Self::Err> {
+ 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<Self, Self::Err> {
+ 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<Self, Self::Err> {
+ 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<Self, Self::Err> {
+ 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<Self, Self::Err> {
+ 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<Self, Self::Err> {
+ 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<Self, Self::Err> {
+ 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<Assets<ColorMaterial>>) {
+ 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<ColorMaterial>,
+ seat_material: Handle<ColorMaterial>,
+}
+
+#[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<Materials>,
+ 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<With<MySeat, &SeatId>>) {
+ 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<char>);
+
+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<With<Individual, (&Group, &Answers)>>,
+) {
+ 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<With<Individual, (&Group, &Answers)>>,
+) {
+ 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<With<Anyone, &Answers>>) {
+ let sum: usize = groups.iter().map(|g| g.0.len()).sum();
+ println!("Anyone: {}", sum);
+}
+
+fn count_everyone_group_sizes(groups: Query<With<Everyone, &Answers>>) {
+ 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<String, usize>,
+}
+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::<Vec<_>>();
+ 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<Without<CantContainShinyGold, (Entity, &Bag)>>,
+) {
+ 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<Op>,
+}
+#[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<i32>);
+
+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<Op> = 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<LineTracker>,
+ 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<Computer>, program: &Program) {
+ computer.exec(program);
+}
+
+fn exit(mut exit_events: ResMut<Events<AppExit>>, 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<With<Active, &Num>>,
+ next: Query<Without<Done, Without<Active, (Entity, &LineNum, &Num)>>>,
+) {
+ 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<Without<Done, With<Active, (Entity, &LineNum)>>>,
+) {
+ if let Some((min_entity, _)) = line_nums.iter().min_by_key(|(_, line)| line.0) {
+ commands.remove_one::<Active>(min_entity);
+ commands.insert_one(min_entity, Done);
+ } else {
+ println!("All numbers cleared");
+ }
+}
+
+fn find_contiguous_range(
+ mut exit_events: ResMut<Events<AppExit>>,
+ line_nums: Query<(&LineNum, &Num)>,
+ invalid: Query<With<Invalid, &Num>>,
+) {
+ 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<i64> = 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);
+ }
+}