1use std::fmt::Write as _;
20
21use super::intcode::*;
22use crate::util::bitset::*;
23use crate::util::parse::*;
24
25pub fn parse(input: &str) -> Vec<i64> {
26 input.iter_signed().collect()
27}
28
29pub fn part1(input: &[i64]) -> String {
30 if cfg!(feature = "frivolity") { play_manually(input) } else { play_automatically(input) }
31}
32
33pub fn part2(_input: &[i64]) -> &'static str {
34 "n/a"
35}
36
37fn play_manually(input: &[i64]) -> String {
39 use std::io::stdin;
40
41 let mut computer = Computer::new(input);
42 let mut output = String::new();
43 let mut input = String::new();
44
45 loop {
46 match computer.run() {
47 State::Output(value) => {
48 let ascii = (value as u8) as char;
49 output.push(ascii);
50 }
51 State::Input => {
52 pretty_print(&output);
53 output.clear();
54 let _unused = stdin().read_line(&mut input);
55 computer.input_ascii(&input);
56 input.clear();
57 }
58 State::Halted => {
59 pretty_print(&output);
60 output.retain(|c| c.is_ascii_digit());
61 break output;
62 }
63 }
64 }
65}
66
67fn pretty_print(output: &str) {
69 use crate::util::ansi::*;
70
71 let mut buffer = String::new();
72 let mut item = GREEN;
73
74 for line in output.lines() {
75 if line.starts_with('=') {
76 let _ = write!(&mut buffer, "{BOLD}{WHITE}{line}{RESET}");
77 } else if line.starts_with('-') {
78 let _ = write!(&mut buffer, "{item}{line}{RESET}");
79 } else if line.starts_with("Items here:") {
80 item = YELLOW;
81 buffer.push_str(line);
82 } else {
83 buffer.push_str(line);
84 }
85 buffer.push('\n');
86 }
87
88 println!("{buffer}");
89}
90
91fn play_automatically(input: &[i64]) -> String {
92 let mut computer = Computer::new(input);
93 let mut stack = Vec::new();
94 let mut path = Vec::new();
95 let mut inventory = Vec::new();
96
97 explore(&mut computer, &mut stack, &mut path, &mut inventory);
99
100 let last = path.pop().unwrap();
102
103 for direction in path {
104 movement_silent(&mut computer, &direction);
105 }
106
107 let combinations: u32 = 1 << inventory.len();
112 let mut have = combinations - 1;
113 let mut want = have;
114 let mut output = String::new();
115 let mut too_light = Vec::with_capacity(combinations as usize);
116 let mut too_heavy = Vec::with_capacity(combinations as usize);
117
118 'outer: for i in 1..combinations {
119 let current = gray_code(i);
120 let previous = gray_code(i - 1);
121 let changed = current ^ previous;
122
123 want ^= changed;
128 for heavy in &too_heavy {
129 if (want & heavy) == *heavy {
130 continue 'outer;
132 }
133 }
134 for light in &too_light {
135 if (want & light) == want {
136 continue 'outer;
138 }
139 }
140
141 sync_items(&mut computer, have, want, &inventory);
142 have = want;
143 if matches!(movement_noisy(&mut computer, &last, &mut output), State::Halted) {
144 output.retain(|b| b.is_ascii_digit());
146 break;
147 } else if output.contains("heavier") {
148 too_light.push(want);
149 } else {
150 too_heavy.push(want);
151 }
152
153 output.clear();
154 }
155
156 output
157}
158
159fn explore(
160 computer: &mut Computer,
161 stack: &mut Vec<String>,
162 path: &mut Vec<String>,
163 inventory: &mut Vec<String>,
164) {
165 let direction = stack.last().map_or("none", String::as_str);
166 let reverse = opposite(direction);
167
168 let mut output = String::new();
169 movement_noisy(computer, direction, &mut output);
170
171 for line in output.lines() {
172 if line.starts_with("== Pressure-Sensitive Floor ==") {
173 path.clone_from(stack);
174 return;
175 } else if let Some(suffix) = line.strip_prefix("- ") {
176 if opposite(suffix) == "none" {
177 if !dangerous(suffix) {
178 take_item(computer, suffix);
179 inventory.push(suffix.to_string());
180 }
181 } else if suffix != reverse {
182 stack.push(suffix.to_string());
183 explore(computer, stack, path, inventory);
184 stack.pop();
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 sync_items(computer: &mut Computer, have: u32, want: u32, inventory: &[String]) {
242 for i in (want ^ have).biterator() {
243 if have & (1 << i) != 0 {
244 drop_item(computer, &inventory[i]);
245 } else {
246 take_item(computer, &inventory[i]);
247 }
248 }
249}
250
251fn drain_output(computer: &mut Computer) {
254 while let State::Output(_) = computer.run() {}
255}
256
257fn gray_code(n: u32) -> u32 {
259 n ^ (n >> 1)
260}