155 lines
3.7 KiB
Rust
155 lines
3.7 KiB
Rust
use std::{
|
|
collections::{HashSet, VecDeque},
|
|
fmt::Display,
|
|
};
|
|
|
|
#[cfg(debug_assertions)]
|
|
// const PASSCODE: &str = "ihgpwlah";
|
|
const PASSCODE: &str = "ulqzkmiv";
|
|
|
|
#[cfg(not(debug_assertions))]
|
|
const PASSCODE: &str = "hhhxzeay";
|
|
|
|
fn main() {
|
|
let state = State::new();
|
|
|
|
let result = bfs(state);
|
|
println!("{result}");
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
struct State {
|
|
route: Route,
|
|
position: (usize, usize),
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
struct Route(Vec<Direction>);
|
|
|
|
impl Display for Route {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
for dir in &self.0 {
|
|
let the_char = match dir {
|
|
Direction::Up => 'U',
|
|
Direction::Down => 'D',
|
|
Direction::Left => 'L',
|
|
Direction::Right => 'R',
|
|
};
|
|
write!(f, "{the_char}")?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Route {
|
|
fn new() -> Self {
|
|
Self(vec![])
|
|
}
|
|
|
|
fn push(&mut self, dir: Direction) {
|
|
self.0.push(dir);
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
enum Direction {
|
|
Up,
|
|
Down,
|
|
Left,
|
|
Right,
|
|
}
|
|
|
|
impl State {
|
|
fn new() -> Self {
|
|
Self {
|
|
route: Route::new(),
|
|
position: (0, 0),
|
|
}
|
|
}
|
|
|
|
fn is_goal_state(&self) -> bool {
|
|
self.position == (3, 3)
|
|
}
|
|
|
|
fn neighbor_states(&self, passcode: &str) -> impl Iterator<Item = State> {
|
|
let input = format!("{}{}", passcode, self.route);
|
|
|
|
let mut hash = format!("{:x}", md5::compute(input));
|
|
hash.truncate(4);
|
|
|
|
let possible_directions = self.generate_directions_from_hash(&hash);
|
|
|
|
let mut result = vec![];
|
|
|
|
for direction in possible_directions {
|
|
let mut new_state = self.clone();
|
|
new_state.update_position(&direction);
|
|
|
|
new_state.route.push(direction);
|
|
result.push(new_state)
|
|
}
|
|
result.into_iter()
|
|
}
|
|
|
|
fn generate_directions_from_hash(&self, hash: &str) -> impl Iterator<Item = Direction> {
|
|
hash.chars()
|
|
.zip([
|
|
Direction::Up,
|
|
Direction::Down,
|
|
Direction::Left,
|
|
Direction::Right,
|
|
])
|
|
.filter(|(c, _)| ('b'..='f').contains(c))
|
|
.map(|(_, dir)| dir)
|
|
.filter(|dir| match dir {
|
|
Direction::Up => self.position.1 > 0,
|
|
Direction::Down => self.position.1 < 3,
|
|
Direction::Left => self.position.0 > 0,
|
|
Direction::Right => self.position.0 < 3,
|
|
})
|
|
}
|
|
|
|
fn update_position(&mut self, dir: &Direction) {
|
|
match *dir {
|
|
Direction::Up => {
|
|
self.position.1 -= 1;
|
|
}
|
|
Direction::Down => {
|
|
self.position.1 += 1;
|
|
}
|
|
Direction::Left => {
|
|
self.position.0 -= 1;
|
|
}
|
|
Direction::Right => {
|
|
self.position.0 += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn bfs(start: State) -> Route {
|
|
let mut queue = VecDeque::new();
|
|
let mut visited = HashSet::new();
|
|
visited.insert(start.clone());
|
|
|
|
queue.push_back(start);
|
|
|
|
while let Some(current_state) = queue.pop_front() {
|
|
if current_state.is_goal_state() {
|
|
// return current_state.route;
|
|
continue;
|
|
}
|
|
|
|
for neighbor in current_state.neighbor_states(PASSCODE) {
|
|
println!("{} {}", neighbor.route.0.len(), neighbor.is_goal_state());
|
|
if !visited.contains(&neighbor) {
|
|
visited.insert(neighbor.clone());
|
|
queue.push_back(neighbor);
|
|
}
|
|
}
|
|
}
|
|
|
|
panic!("goal state unreachable");
|
|
}
|