1use super::intcode::*;
20use crate::util::bitset::*;
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 have = combinations - 1;
112 let mut want = have;
113 let mut output = String::new();
114 let mut too_light = Vec::with_capacity(combinations as usize);
115 let mut too_heavy = Vec::with_capacity(combinations as usize);
116
117 'outer: for i in 1..combinations {
118 let current = gray_code(i);
119 let previous = gray_code(i - 1);
120 let changed = current ^ previous;
121
122 want ^= changed;
127 for heavy in &too_heavy {
128 if (want & heavy) == *heavy {
129 continue 'outer;
131 }
132 }
133 for light in &too_light {
134 if (want & light) == want {
135 continue 'outer;
137 }
138 }
139
140 sync_items(&mut computer, have, want, &inventory);
141 have = want;
142 if matches!(movement_noisy(&mut computer, &last, &mut output), State::Halted) {
143 output.retain(|b| b.is_ascii_digit());
145 break;
146 } else if output.contains("heavier") {
147 too_light.push(want);
148 } else {
149 too_heavy.push(want);
150 }
151
152 output.clear();
153 }
154
155 output
156}
157
158fn explore(
159 computer: &mut Computer,
160 stack: &mut Vec<String>,
161 path: &mut Vec<String>,
162 inventory: &mut Vec<String>,
163) {
164 let direction = stack.last().map_or("none", String::as_str);
165 let reverse = opposite(direction);
166
167 let mut output = String::new();
168 movement_noisy(computer, direction, &mut output);
169
170 for line in output.lines() {
171 if line.starts_with("== Pressure-Sensitive Floor ==") {
172 path.clone_from(stack);
173 return;
174 } else if let Some(suffix) = line.strip_prefix("- ") {
175 if opposite(suffix) == "none" {
176 if !dangerous(suffix) {
177 take_item(computer, suffix);
178 inventory.push(suffix.to_string());
179 }
180 } else if suffix != reverse {
181 stack.push(suffix.to_string());
182 explore(computer, stack, path, inventory);
183 stack.pop();
184 }
185 }
186 }
187
188 movement_silent(computer, reverse);
189}
190
191fn opposite(direction: &str) -> &'static str {
192 match direction {
193 "north" => "south",
194 "south" => "north",
195 "east" => "west",
196 "west" => "east",
197 _ => "none",
198 }
199}
200
201fn dangerous(item: &str) -> bool {
202 matches!(
203 item,
204 "escape pod" | "giant electromagnet" | "infinite loop" | "molten lava" | "photons"
205 )
206}
207
208fn movement_noisy(computer: &mut Computer, direction: &str, output: &mut String) -> State {
209 if direction != "none" {
210 computer.input_ascii(&format!("{direction}\n"));
211 }
212 loop {
213 match computer.run() {
214 State::Output(value) => {
215 let ascii = (value as u8) as char;
216 output.push(ascii);
217 }
218 other => break other,
219 }
220 }
221}
222
223fn movement_silent(computer: &mut Computer, direction: &str) {
224 if direction != "none" {
225 computer.input_ascii(&format!("{direction}\n"));
226 drain_output(computer);
227 }
228}
229
230fn take_item(computer: &mut Computer, item: &str) {
231 computer.input_ascii(&format!("take {item}\n"));
232 drain_output(computer);
233}
234
235fn drop_item(computer: &mut Computer, item: &str) {
236 computer.input_ascii(&format!("drop {item}\n"));
237 drain_output(computer);
238}
239
240fn sync_items(computer: &mut Computer, have: u32, want: u32, inventory: &[String]) {
241 for i in (want ^ have).biterator() {
242 if have & (1 << i) != 0 {
243 drop_item(computer, &inventory[i]);
244 } else {
245 take_item(computer, &inventory[i]);
246 }
247 }
248}
249
250fn drain_output(computer: &mut Computer) {
253 while let State::Output(_) = computer.run() {}
254}
255
256fn gray_code(n: u32) -> u32 {
258 n ^ (n >> 1)
259}