1use std::fmt::Write as _;
20
21use crate::util::bitset::*;
22use crate::util::intcode::*;
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) => output.push(char::from(value as u8)),
48 State::Input => {
49 pretty_print(&output);
50 output.clear();
51 let _unused = stdin().read_line(&mut input);
52 computer.input_ascii(&input);
53 input.clear();
54 }
55 State::Halted => {
56 pretty_print(&output);
57 output.retain(|c| c.is_ascii_digit());
58 break output;
59 }
60 }
61 }
62}
63
64fn pretty_print(output: &str) {
66 use crate::util::ansi::*;
67
68 let mut buffer = String::new();
69 let mut item = GREEN;
70
71 for line in output.lines() {
72 if line.starts_with('=') {
73 let _ = write!(&mut buffer, "{BOLD}{WHITE}{line}{RESET}");
74 } else if line.starts_with('-') {
75 let _ = write!(&mut buffer, "{item}{line}{RESET}");
76 } else if line.starts_with("Items here:") {
77 item = YELLOW;
78 buffer.push_str(line);
79 } else {
80 buffer.push_str(line);
81 }
82 buffer.push('\n');
83 }
84
85 println!("{buffer}");
86}
87
88fn play_automatically(input: &[i64]) -> String {
89 let mut computer = Computer::new(input);
90 let mut stack = Vec::new();
91 let mut path = Vec::new();
92 let mut inventory = Vec::new();
93
94 explore(&mut computer, &mut stack, &mut path, &mut inventory);
96
97 let last = path.pop().unwrap();
99
100 for direction in path {
101 movement_silent(&mut computer, &direction);
102 }
103
104 let combinations = 1 << inventory.len();
109 let mut have = combinations - 1;
110 let mut want = have;
111 let mut output = String::new();
112 let mut too_light = Vec::with_capacity(combinations as usize);
113 let mut too_heavy = Vec::with_capacity(combinations as usize);
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
120 want ^= changed;
125
126 if too_heavy.iter().any(|&heavy| want & heavy == heavy)
128 || too_light.iter().any(|&light| want & light == want)
129 {
130 continue;
131 }
132
133 sync_items(&mut computer, have, want, &inventory);
134 have = want;
135
136 if matches!(movement_noisy(&mut computer, &last, &mut output), State::Halted) {
137 output.retain(|b| b.is_ascii_digit());
139 break;
140 } else if output.contains("heavier") {
141 too_light.push(want);
142 } else {
143 too_heavy.push(want);
144 }
145
146 output.clear();
147 }
148
149 output
150}
151
152fn explore(
153 computer: &mut Computer,
154 stack: &mut Vec<String>,
155 path: &mut Vec<String>,
156 inventory: &mut Vec<String>,
157) {
158 let direction = stack.last().map_or("none", String::as_str);
159 let reverse = opposite(direction);
160
161 let mut output = String::new();
162 movement_noisy(computer, direction, &mut output);
163
164 for line in output.lines() {
165 if line.starts_with("== Pressure-Sensitive Floor ==") {
166 path.clone_from(stack);
167 return;
168 } else if let Some(suffix) = line.strip_prefix("- ") {
169 if opposite(suffix) == "none" {
170 if !dangerous(suffix) {
171 take_item(computer, suffix);
172 inventory.push(suffix.to_string());
173 }
174 } else if suffix != reverse {
175 stack.push(suffix.to_string());
176 explore(computer, stack, path, inventory);
177 stack.pop();
178 }
179 }
180 }
181
182 movement_silent(computer, reverse);
183}
184
185fn opposite(direction: &str) -> &'static str {
186 match direction {
187 "north" => "south",
188 "south" => "north",
189 "east" => "west",
190 "west" => "east",
191 _ => "none",
192 }
193}
194
195fn dangerous(item: &str) -> bool {
196 matches!(
197 item,
198 "escape pod" | "giant electromagnet" | "infinite loop" | "molten lava" | "photons"
199 )
200}
201
202fn movement_noisy(computer: &mut Computer, direction: &str, output: &mut String) -> State {
203 if direction != "none" {
204 computer.input_ascii(&format!("{direction}\n"));
205 }
206 loop {
207 match computer.run() {
208 State::Output(value) => output.push(char::from(value as u8)),
209 other => break other,
210 }
211 }
212}
213
214fn movement_silent(computer: &mut Computer, direction: &str) {
215 if direction != "none" {
216 computer.input_ascii(&format!("{direction}\n"));
217 drain_output(computer);
218 }
219}
220
221fn take_item(computer: &mut Computer, item: &str) {
222 computer.input_ascii(&format!("take {item}\n"));
223 drain_output(computer);
224}
225
226fn drop_item(computer: &mut Computer, item: &str) {
227 computer.input_ascii(&format!("drop {item}\n"));
228 drain_output(computer);
229}
230
231fn sync_items(computer: &mut Computer, have: u32, want: u32, inventory: &[String]) {
232 for i in (want ^ have).biterator() {
233 if have & (1 << i) != 0 {
234 drop_item(computer, &inventory[i]);
235 } else {
236 take_item(computer, &inventory[i]);
237 }
238 }
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}