1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
|
use itertools::Itertools;
use lazy_static::lazy_static;
use pathfinding::prelude::{astar, idastar};
use rand::prelude::*;
use rand::rngs::OsRng;
use std::thread;
use std::time::Instant;
#[cfg(test)]
const SIDE: u8 = 3;
#[cfg(not(test))]
const SIDE: u8 = 4;
const LIMIT: usize = (SIDE * SIDE) as usize;
#[allow(clippy::derived_hash_with_manual_eq)]
#[derive(Clone, Debug, Hash)]
struct Game {
positions: [u8; LIMIT], // Correct position of piece at every index
hole_idx: u8, // Current index of the hole
weight: u8, // Current sum of pieces Manhattan distances
}
impl PartialEq for Game {
fn eq(&self, other: &Self) -> bool {
self.hole_idx == other.hole_idx
&& self.weight == other.weight
&& self.positions == other.positions
}
}
impl Eq for Game {}
lazy_static! {
static ref GOAL: Game = Game {
positions: {
let mut p = [0u8; LIMIT];
for (i, e) in p.iter_mut().enumerate() {
*e = u8::try_from(i).unwrap();
}
p
},
hole_idx: 0,
weight: 0,
};
static ref SUCCESSORS: Vec<Vec<u8>> = (0..SIDE * SIDE)
.map(|idx| (0..4)
.filter_map(|dir| match dir {
0 if idx % SIDE > 0 => Some(idx - 1),
1 if idx >= SIDE => Some(idx - SIDE),
2 if idx % SIDE < SIDE - 1 => Some(idx + 1),
3 if idx < SIDE * SIDE - SIDE => Some(idx + SIDE),
_ => None,
})
.collect::<Vec<_>>())
.collect();
}
impl Game {
/// Move the hole to the given index.
fn switch(&self, idx: u8) -> Self {
let mut g = self.clone();
g.positions.swap(self.hole_idx as usize, idx as usize);
g.hole_idx = idx;
g.weight = g.weight
+ g.distance(self.hole_idx) // Distance of the moved piece at its new index
- self.distance(idx); // Distance of the moved piece at its previous index
g
}
#[inline]
const fn x(pos: u8) -> u8 {
pos % SIDE
}
#[inline]
const fn y(pos: u8) -> u8 {
pos / SIDE
}
// Compute the Manhattan distance between the piece at idx and its correct position.
fn distance(&self, idx: u8) -> u8 {
let (actual_x, actual_y) = (Self::x(idx), Self::y(idx));
let (correct_x, correct_y) = (
Self::x(self.positions[idx as usize]),
Self::y(self.positions[idx as usize]),
);
actual_x.abs_diff(correct_x) + actual_y.abs_diff(correct_y)
}
fn solved(&self) -> bool {
self.positions == GOAL.positions
}
// Here we try to illustrate that we can return an iterator without building a Vec.
// However, since the successors are the current board with the hole moved one
// position, we need to build a clone of the current board that will be reused in
// this iterator.
fn successors(&self) -> impl Iterator<Item = (Self, u8)> {
let game = self.clone();
SUCCESSORS[self.hole_idx as usize]
.iter()
.map(move |&n| (game.switch(n), 1))
}
fn is_solvable(&self) -> bool {
let mut inversions = 0;
for i in 0..LIMIT {
let c = self.positions[i];
if c != 0 {
for j in i + 1..LIMIT {
let d = self.positions[j];
if d != 0 && d < c {
inversions ^= 1;
}
}
}
}
if SIDE % 2 == 1 {
inversions == 0
} else {
Self::y(self.hole_idx) % 2 == inversions
}
}
fn from_array(positions: [u8; LIMIT]) -> Self {
let hole_idx =
u8::try_from(positions.iter().find_position(|&&n| n == 0).unwrap().0).unwrap();
let mut game = Self {
positions,
hole_idx,
weight: 0,
};
game.weight = (0..u8::try_from(LIMIT).unwrap())
.filter(|&n| n != game.hole_idx)
.map(|n| game.distance(n))
.sum();
game
}
fn shuffled() -> Self {
let mut rng = OsRng;
let mut positions = Self::default().positions;
loop {
positions.shuffle(&mut rng);
let game = Self::from_array(positions);
if game.is_solvable() {
return game;
}
}
}
}
impl Default for Game {
fn default() -> Self {
GOAL.clone()
}
}
#[test]
fn test() {
// main() already contains checks.
main();
}
fn main() {
let b = Game::shuffled();
println!("{b:?}");
assert!(b.is_solvable());
let start = Instant::now();
let (astar_result, idastar_result) = thread::scope(|s| {
let idastar_handle = s.spawn({
|| {
let result = idastar(&b, Game::successors, |b| b.weight, Game::solved).unwrap();
println!("idastar: {} moves in {:.3?}", result.1, start.elapsed(),);
assert!(result.0.last().unwrap().weight == 0);
result.1
}
});
(
{
let result = astar(&b, Game::successors, |b| b.weight, Game::solved).unwrap();
println!("astar: {} moves in {:.3?}", result.1, start.elapsed(),);
assert!(result.0.last().unwrap().weight == 0);
result.1
},
idastar_handle.join().unwrap(),
)
});
println!("Total execution time: {:.3?}", start.elapsed());
assert_eq!(idastar_result, astar_result);
assert!(idastar_result >= b.weight);
}
|