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 let item = String::from(suffix);
174 if !dangerous(&item) {
175 take_item(computer, &item);
176 inventory.push(item);
177 }
178 } else {
179 let direction = String::from(suffix);
180 if direction != reverse {
181 stack.push(direction);
182 explore(computer, stack, path, inventory);
183 stack.pop();
184 }
185 }
186 }
187 }
188
189 movement_silent(computer, reverse);
190}
191
192fn opposite(direction: &str) -> &'static str {
193 match direction {
194 "north" => "south",
195 "south" => "north",
196 "east" => "west",
197 "west" => "east",
198 _ => "none",
199 }
200}
201
202fn dangerous(item: &str) -> bool {
203 matches!(
204 item,
205 "escape pod" | "giant electromagnet" | "infinite loop" | "molten lava" | "photons"
206 )
207}
208
209fn movement_noisy(computer: &mut Computer, direction: &str, output: &mut String) -> State {
210 if direction != "none" {
211 computer.input_ascii(&format!("{direction}\n"));
212 }
213 loop {
214 match computer.run() {
215 State::Output(value) => {
216 let ascii = (value as u8) as char;
217 output.push(ascii);
218 }
219 other => break other,
220 }
221 }
222}
223
224fn movement_silent(computer: &mut Computer, direction: &str) {
225 if direction != "none" {
226 computer.input_ascii(&format!("{direction}\n"));
227 drain_output(computer);
228 }
229}
230
231fn take_item(computer: &mut Computer, item: &str) {
232 computer.input_ascii(&format!("take {item}\n"));
233 drain_output(computer);
234}
235
236fn drop_item(computer: &mut Computer, item: &str) {
237 computer.input_ascii(&format!("drop {item}\n"));
238 drain_output(computer);
239}
240
241fn drain_output(computer: &mut Computer) {
244 while let State::Output(_) = computer.run() {}
245}
246
247fn gray_code(n: u32) -> u32 {
249 n ^ (n >> 1)
250}