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) } } }