1use super::intcode::*;
20use crate::util::hash::*;
21use crate::util::parse::*;
22use std::fmt::Write as _;
23
24pub fn parse(input: &str) -> Vec<i64> {
25 input.iter_signed().collect()
26}
27
28pub fn part1(input: &[i64]) -> String {
29 if cfg!(feature = "frivolity") { play_manually(input) } else { play_automatically(input) }
30}
31
32pub fn part2(_input: &[i64]) -> &'static str {
33 "n/a"
34}
35
36fn play_manually(input: &[i64]) -> String {
38 use std::io::stdin;
39
40 let mut computer = Computer::new(input);
41 let mut output = String::new();
42 let mut input = String::new();
43
44 loop {
45 match computer.run() {
46 State::Output(value) => {
47 let ascii = (value as u8) as char;
48 output.push(ascii);
49 }
50 State::Input => {
51 pretty_print(&output);
52 output.clear();
53 let _unused = stdin().read_line(&mut input);
54 computer.input_ascii(&input);
55 input.clear();
56 }
57 State::Halted => {
58 pretty_print(&output);
59 output.retain(|c| c.is_ascii_digit());
60 break output;
61 }
62 }
63 }
64}
65
66fn pretty_print(output: &str) {
68 use crate::util::ansi::*;
69
70 let mut buffer = String::new();
71 let mut item = GREEN;
72
73 for line in output.lines() {
74 if line.starts_with('=') {
75 let _ = write!(&mut buffer, "{BOLD}{WHITE}{line}{RESET}");
76 } else if line.starts_with('-') {
77 let _ = write!(&mut buffer, "{item}{line}{RESET}");
78 } else if line.starts_with("Items here:") {
79 item = YELLOW;
80 buffer.push_str(line);
81 } else {
82 buffer.push_str(line);
83 }
84 buffer.push('\n');
85 }
86
87 println!("{buffer}");
88}
89
90fn play_automatically(input: &[i64]) -> String {
91 let mut computer = Computer::new(input);
92 let mut stack = Vec::new();
93 let mut path = Vec::new();
94 let mut inventory = Vec::new();
95
96 explore(&mut computer, &mut stack, &mut path, &mut inventory);
98
99 let last = path.pop().unwrap();
101
102 for direction in path {
103 movement_silent(&mut computer, &direction);
104 }
105
106 let combinations: u32 = 1 << inventory.len();
111 let mut output = String::new();
112 let mut too_light = FastSet::new();
113 let mut too_heavy = FastSet::new();
114
115 for i in 1..combinations {
116 let current = gray_code(i);
117 let previous = gray_code(i - 1);
118 let changed = current ^ previous;
119 let index = changed.trailing_zeros() as usize;
120
121 if current & changed == 0 {
124 take_item(&mut computer, &inventory[index]);
125
126 if too_heavy.contains(&previous) {
127 too_heavy.insert(current);
128 continue;
129 }
130 } else {
131 drop_item(&mut computer, &inventory[index]);
132
133 if too_light.contains(&previous) {
134 too_light.insert(current);
135 continue;
136 }
137 }
138
139 if matches!(movement_noisy(&mut computer, &last, &mut output), State::Halted) {
140 output.retain(|b| b.is_ascii_digit());
142 break;
143 } else if output.contains("heavier") {
144 too_light.insert(current);
145 } else {
146 too_heavy.insert(current);
147 }
148
149 output.clear();
150 }
151
152 output
153}
154
155fn explore(
156 computer: &mut Computer,
157 stack: &mut Vec<String>,
158 path: &mut Vec<String>,
159 inventory: &mut Vec<String>,
160) {
161 let direction = stack.last().map_or("none", |d| d.as_str());
162 let reverse = opposite(direction);
163
164 let mut output = String::new();
165 movement_noisy(computer, direction, &mut output);
166
167 for line in output.lines() {
168 if line.starts_with("== Pressure-Sensitive Floor ==") {
169 path.clone_from(stack);
170 return;
171 } else if let Some(suffix) = line.strip_prefix("- ") {
172 if opposite(suffix) == "none" {
173 if !dangerous(suffix) {
174 take_item(computer, suffix);
175 inventory.push(suffix.to_string());
176 }
177 } else if suffix != reverse {
178 stack.push(suffix.to_string());
179 explore(computer, stack, path, inventory);
180 stack.pop();
181 }
182 }
183 }
184
185 movement_silent(computer, reverse);
186}
187
188fn opposite(direction: &str) -> &'static str {
189 match direction {
190 "north" => "south",
191 "south" => "north",
192 "east" => "west",
193 "west" => "east",
194 _ => "none",
195 }
196}
197
198fn dangerous(item: &str) -> bool {
199 matches!(
200 item,
201 "escape pod" | "giant electromagnet" | "infinite loop" | "molten lava" | "photons"
202 )
203}
204
205fn movement_noisy(computer: &mut Computer, direction: &str, output: &mut String) -> State {
206 if direction != "none" {
207 computer.input_ascii(&format!("{direction}\n"));
208 }
209 loop {
210 match computer.run() {
211 State::Output(value) => {
212 let ascii = (value as u8) as char;
213 output.push(ascii);
214 }
215 other => break other,
216 }
217 }
218}
219
220fn movement_silent(computer: &mut Computer, direction: &str) {
221 if direction != "none" {
222 computer.input_ascii(&format!("{direction}\n"));
223 drain_output(computer);
224 }
225}
226
227fn take_item(computer: &mut Computer, item: &str) {
228 computer.input_ascii(&format!("take {item}\n"));
229 drain_output(computer);
230}
231
232fn drop_item(computer: &mut Computer, item: &str) {
233 computer.input_ascii(&format!("drop {item}\n"));
234 drain_output(computer);
235}
236
237fn drain_output(computer: &mut Computer) {
240 while let State::Output(_) = computer.run() {}
241}
242
243fn gray_code(n: u32) -> u32 {
245 n ^ (n >> 1)
246}