use super::day05::{IntCodeComputer, RAM}; use itertools::Itertools; use std::collections::HashMap; use std::fmt; #[allow(dead_code)] pub fn run() { let program: RAM = std::fs::read_to_string("input/day13.txt") .unwrap() .split(",") .map(|it| it.parse::().unwrap()) .enumerate() .collect(); task1(program.clone()); task2_ai(program.clone()); } #[derive(PartialEq)] enum FieldType { Empty, Wall, Block, HorizontalPaddle, Ball, } impl fmt::Display for FieldType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}", match self { Self::Empty => " ", Self::Wall => "X", Self::Block => "B", Self::HorizontalPaddle => "_", Self::Ball => "O", } ) } } impl FieldType { fn from(input: i128) -> Self { match input { 0 => Self::Empty, 1 => Self::Wall, 2 => Self::Block, 3 => Self::HorizontalPaddle, 4 => Self::Ball, _ => unreachable!("unexpected field type {}", input), } } } struct Game { pc: IntCodeComputer, field: HashMap<(i128, i128), FieldType>, score: i128, show_output: bool, } impl Game { fn new(coins: Option, mut ram: RAM, show_output: bool) -> Self { if let Some(c) = coins { ram.insert(0, c); } let pc = IntCodeComputer::new(vec![], ram); Game { pc, field: HashMap::new(), score: 0, show_output, } } /** * true means the game is over, false means input is awaited */ fn run(&mut self) -> bool { self.pc.clear_output(); let done = self.pc.run_until_input_empty(); let output = self.pc.get_output().into_iter().map(|it| *it).collect_vec(); let mut score = None; output .iter() .tuples() .scan(&mut self.field, |map, (x, y, t)| { if *x == -1 && *y == 0 { score = Some(*t); } else { map.insert((*x, *y), FieldType::from(*t)); } Some(()) }) .any(|_| false); // stupid workaround because I cannot access score from within the clojure, // due to then double-borrowing of mutable reference to self if let Some(score) = score { self.score = score; } if self.show_output { self.print(); } done } fn set_input(&mut self, input: Input) { self.pc.set_input(&[input.int()]); } fn print(&self) { let xmin = *self.field.iter().map(|((x, _), _)| x).min().unwrap(); let xmax = *self.field.iter().map(|((x, _), _)| x).max().unwrap(); let ymin = *self.field.iter().map(|((_, y), _)| y).min().unwrap(); let ymax = *self.field.iter().map(|((_, y), _)| y).max().unwrap(); for y in ymin..=ymax { for x in xmin..=xmax { if let Some(f) = self.field.get(&(x, y)) { print!("{}", f); } else { panic!("There is an unspecified tile in the field"); } } println!(); } println!("Score: {}", self.score); } } #[derive(PartialEq)] enum Input { Neutral, Left, Right, } impl Input { fn int(&self) -> i128 { match self { Self::Neutral => 0, Self::Left => -1, Self::Right => 1, } } fn from(input: char) -> Self { match input { 's' => Self::Neutral, 'a' => Self::Left, 'd' => Self::Right, _ => Self::Neutral, } } } fn task1(program: RAM) { let mut game = Game::new(None, program, true); if game.run() { let block_count = game .field .iter() .filter(|(_, ftype)| **ftype == FieldType::Block) .count(); println!("Task 1: There are {} blocks in the field", block_count); } else { unreachable!("In this task the game should end without any input."); } } #[allow(dead_code)] fn task2(program: RAM) { use std::io::{self, BufRead}; let mut game = Game::new(Some(2), program, true); let stdin = io::stdin(); let mut input_iter = stdin.lock().lines(); while !game.run() { // read input let next = input_iter.next().unwrap().unwrap().chars().next(); if let Some(s) = next { game.set_input(Input::from(s)); } else { game.set_input(Input::Neutral); } } println!( "You won against the game! Your final score was {}", game.score ); } fn task2_ai(program: RAM) { let mut game = Game::new(Some(2), program, false); let mut moves = 0; while !game.run() { // always keep the paddle under the ball let x_ball = game .field .iter() .find_map(|((x, _), t)| { if *t == FieldType::Ball { Some(*x) } else { None } }) .unwrap(); let x_paddle = game .field .iter() .find_map(|((x, _), t)| { if *t == FieldType::HorizontalPaddle { Some(*x) } else { None } }) .unwrap(); let direction = if x_ball < x_paddle { Input::Left } else if x_ball > x_paddle { Input::Right } else { Input::Neutral }; game.set_input(direction); moves += 1; } println!("Played for {} moves", moves); println!( "Game is over! There are {} blocks left. Your final score is {}", game.field .iter() .filter(|(_, t)| **t == FieldType::Block) .count(), game.score ); }