forked from jana/hanoigame
solver
This commit is contained in:
parent
c07c5dd117
commit
4f7790d1d5
1 changed files with 244 additions and 22 deletions
254
src/main.rs
254
src/main.rs
|
|
@ -1,9 +1,10 @@
|
||||||
use palette::{IntoColor, OklabHue, Oklch, Srgb};
|
use palette::{IntoColor, OklabHue, Oklch, Srgb};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{BinaryHeap, HashMap, HashSet, VecDeque},
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
io::{self, Read, Write, stdin, stdout},
|
hash::{DefaultHasher, Hash, Hasher},
|
||||||
num::NonZero,
|
io::{self, Write, stdin, stdout},
|
||||||
|
num::{self, NonZero},
|
||||||
ops::ControlFlow,
|
ops::ControlFlow,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -36,6 +37,14 @@ impl Display for Ring {
|
||||||
let color: Srgb = color.into_color();
|
let color: Srgb = color.into_color();
|
||||||
let color: Srgb<u8> = color.into_format();
|
let color: Srgb<u8> = color.into_format();
|
||||||
|
|
||||||
|
if f.alternate() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}{BLOCK}{:^5}{BLOCK}{RESET}",
|
||||||
|
crate_cli_color((color.red, color.green, color.blue)),
|
||||||
|
self.0
|
||||||
|
)
|
||||||
|
} else {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{}{BLOCK}{BLOCK}{BLOCK}{BLOCK}{BLOCK}{BLOCK}{BLOCK}{RESET}",
|
"{}{BLOCK}{BLOCK}{BLOCK}{BLOCK}{BLOCK}{BLOCK}{BLOCK}{RESET}",
|
||||||
|
|
@ -43,8 +52,9 @@ impl Display for Ring {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct Tower<const TOWER_HEIGHT: usize = 4> {
|
pub struct Tower<const TOWER_HEIGHT: usize = 4> {
|
||||||
pub rings: [Option<Ring>; TOWER_HEIGHT],
|
pub rings: [Option<Ring>; TOWER_HEIGHT],
|
||||||
}
|
}
|
||||||
|
|
@ -70,6 +80,34 @@ impl<const TOWER_HEIGHT: usize> Tower<TOWER_HEIGHT> {
|
||||||
tower
|
tower
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn burried_score(&self) -> usize {
|
||||||
|
let mut ring_type = None;
|
||||||
|
let mut same_ring_type_score = 0;
|
||||||
|
let mut max_same_ring_type_score = 0;
|
||||||
|
let mut burried_score = 0;
|
||||||
|
|
||||||
|
for i in self.rings.iter().rev() {
|
||||||
|
if let Some(ring) = i {
|
||||||
|
if let Some(existing_ring_type) = ring_type {
|
||||||
|
if existing_ring_type == *ring {
|
||||||
|
same_ring_type_score += 2;
|
||||||
|
} else {
|
||||||
|
max_same_ring_type_score =
|
||||||
|
max_same_ring_type_score.max(same_ring_type_score);
|
||||||
|
burried_score += 2usize.pow(max_same_ring_type_score);
|
||||||
|
same_ring_type_score = 0;
|
||||||
|
ring_type = Some(*ring);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
same_ring_type_score = 0;
|
||||||
|
ring_type = Some(*ring);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
burried_score
|
||||||
|
}
|
||||||
|
|
||||||
fn get_movable_top(&self) -> Result<(Ring, usize), MoveError> {
|
fn get_movable_top(&self) -> Result<(Ring, usize), MoveError> {
|
||||||
let mut topmost_ring = None;
|
let mut topmost_ring = None;
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
|
|
@ -112,7 +150,11 @@ impl<const TOWER_HEIGHT: usize> Tower<TOWER_HEIGHT> {
|
||||||
assert_eq!(num, 0);
|
assert_eq!(num, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_movable_top(&mut self, (ring, num): (Ring, usize)) -> Result<usize, MoveError> {
|
fn add_movable_top(
|
||||||
|
&mut self,
|
||||||
|
(ring, num): (Ring, usize),
|
||||||
|
dummy: bool,
|
||||||
|
) -> Result<usize, MoveError> {
|
||||||
if self.top_ring().is_some_and(|top| top != ring) {
|
if self.top_ring().is_some_and(|top| top != ring) {
|
||||||
return Err(MoveError::DestinationWrongTop);
|
return Err(MoveError::DestinationWrongTop);
|
||||||
}
|
}
|
||||||
|
|
@ -120,6 +162,10 @@ impl<const TOWER_HEIGHT: usize> Tower<TOWER_HEIGHT> {
|
||||||
let height_left = TOWER_HEIGHT - self.height();
|
let height_left = TOWER_HEIGHT - self.height();
|
||||||
let num_rings_moved = num.min(height_left);
|
let num_rings_moved = num.min(height_left);
|
||||||
|
|
||||||
|
if dummy {
|
||||||
|
return Ok(num_rings_moved);
|
||||||
|
}
|
||||||
|
|
||||||
self.rings
|
self.rings
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.filter(|i| i.is_none())
|
.filter(|i| i.is_none())
|
||||||
|
|
@ -189,6 +235,7 @@ pub enum MoveError {
|
||||||
SourceEmpty,
|
SourceEmpty,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Move {
|
pub struct Move {
|
||||||
pub from_tower: usize,
|
pub from_tower: usize,
|
||||||
pub to_tower: usize,
|
pub to_tower: usize,
|
||||||
|
|
@ -233,30 +280,180 @@ impl<const TOWER_HEIGHT: usize> Game<TOWER_HEIGHT> {
|
||||||
Self { ring_sets, towers }
|
Self { ring_sets, towers }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_move(&mut self, m: Move) -> Result<usize, MoveError> {
|
pub fn possible_moves(&mut self) -> Vec<Move> {
|
||||||
|
let mut res = Vec::new();
|
||||||
|
|
||||||
|
for from_tower in 0..self.towers.len() {
|
||||||
|
for to_tower in 0..self.towers.len() {
|
||||||
|
if from_tower == to_tower {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let m = Move::new(from_tower, to_tower);
|
||||||
|
if self.try_make_move(m, true).is_ok() {
|
||||||
|
res.push(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn burried_ring_score(&self) -> usize {
|
||||||
|
self.towers.iter().map(|i| i.burried_score()).sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn solve(&self) -> Option<Vec<Move>> {
|
||||||
|
let mut working_instance = Self {
|
||||||
|
towers: Vec::new(),
|
||||||
|
ring_sets: self.ring_sets.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct State<const TOWER_HEIGHT: usize> {
|
||||||
|
towers: Vec<Tower<TOWER_HEIGHT>>,
|
||||||
|
m: Move,
|
||||||
|
parent_index: usize,
|
||||||
|
}
|
||||||
|
impl<const TOWER_HEIGHT: usize> Hash for State<TOWER_HEIGHT> {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.towers.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let hash_state = |s: &[Tower<TOWER_HEIGHT>]| {
|
||||||
|
let mut hasher = DefaultHasher::default();
|
||||||
|
|
||||||
|
for x in s.iter().filter(|i| i.height() != 0).enumerate() {
|
||||||
|
x.hash(&mut hasher);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher.finish()
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(PartialEq, PartialOrd)]
|
||||||
|
struct Ordf32(f32);
|
||||||
|
impl Eq for Ordf32 {}
|
||||||
|
impl Ord for Ordf32 {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.0.total_cmp(&other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut states = Vec::new();
|
||||||
|
let mut todo = BinaryHeap::<(Ordf32, usize, usize)>::new();
|
||||||
|
let mut had = HashSet::new();
|
||||||
|
let mut ctr = 0;
|
||||||
|
|
||||||
|
states.push(State {
|
||||||
|
towers: self.towers.clone(),
|
||||||
|
parent_index: 0,
|
||||||
|
m: Move::new(0, 0),
|
||||||
|
});
|
||||||
|
todo.push((Ordf32(0.0), 1000000000, 0));
|
||||||
|
|
||||||
|
while let Some((score, depth, state_index)) = todo.pop() {
|
||||||
|
ctr += 1;
|
||||||
|
let state = states[state_index].clone();
|
||||||
|
|
||||||
|
let hash = hash_state(&state.towers);
|
||||||
|
|
||||||
|
// already had
|
||||||
|
if had.contains(&hash) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
had.insert(hash);
|
||||||
|
|
||||||
|
working_instance.towers = state.towers.clone();
|
||||||
|
|
||||||
|
if ctr % 100000 == 0 {
|
||||||
|
println!("{working_instance}");
|
||||||
|
println!("{} {depth}", score.0);
|
||||||
|
stdout().flush().unwrap();
|
||||||
|
stdin().read_line(&mut String::new()).unwrap();
|
||||||
|
}
|
||||||
|
if working_instance.check_done() {
|
||||||
|
println!("done!");
|
||||||
|
let mut res = Vec::new();
|
||||||
|
|
||||||
|
let mut curr = state_index;
|
||||||
|
while curr != 0 {
|
||||||
|
res.push(states[curr].m);
|
||||||
|
curr = states[curr].parent_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.reverse();
|
||||||
|
|
||||||
|
return Some(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
for m in working_instance.possible_moves() {
|
||||||
|
working_instance.towers = state.towers.clone();
|
||||||
|
working_instance.make_move(m).unwrap();
|
||||||
|
|
||||||
|
let num_solved = working_instance.num_solved();
|
||||||
|
let brs = working_instance.burried_ring_score();
|
||||||
|
|
||||||
|
let new_state_hash = hash_state(&working_instance.towers);
|
||||||
|
if had.contains(&new_state_hash) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_state_index = states.len();
|
||||||
|
states.push(State {
|
||||||
|
towers: working_instance.towers.clone(),
|
||||||
|
parent_index: state_index,
|
||||||
|
m,
|
||||||
|
});
|
||||||
|
|
||||||
|
let score = (num_solved as f32 * 10.0) + (-(brs as f32) * 100.0);
|
||||||
|
|
||||||
|
todo.push((Ordf32(score), depth - 1, new_state_index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_make_move(&mut self, m: Move, dummy: bool) -> Result<usize, MoveError> {
|
||||||
let Move {
|
let Move {
|
||||||
from_tower,
|
from_tower,
|
||||||
to_tower,
|
to_tower,
|
||||||
} = m;
|
} = m;
|
||||||
|
|
||||||
let (ring, mut num) = self.towers[from_tower].get_movable_top()?;
|
let (ring, mut num) = self.towers[from_tower].get_movable_top()?;
|
||||||
num = self.towers[to_tower].add_movable_top((ring, num))?;
|
num = self.towers[to_tower].add_movable_top((ring, num), dummy)?;
|
||||||
|
|
||||||
|
if dummy {
|
||||||
|
return Ok(num);
|
||||||
|
}
|
||||||
|
|
||||||
self.towers[from_tower].remove_movable_top((ring, num));
|
self.towers[from_tower].remove_movable_top((ring, num));
|
||||||
|
|
||||||
Ok(num)
|
Ok(num)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn make_move(&mut self, m: Move) -> Result<usize, MoveError> {
|
||||||
|
self.try_make_move(m, false)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check_done(&self) -> bool {
|
pub fn check_done(&self) -> bool {
|
||||||
|
self.num_solved() == self.towers.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn num_solved(&self) -> usize {
|
||||||
|
let mut num_solved = 0;
|
||||||
for t in &self.towers {
|
for t in &self.towers {
|
||||||
t.check();
|
t.check();
|
||||||
|
|
||||||
// If the tower itself didn't have matching rings, return false
|
// If the tower itself didn't have matching rings, return false
|
||||||
let Some((ring_type, c)) = t.count_if_single_ring_type() else {
|
let Some((ring_type, c)) = t.count_if_single_ring_type() else {
|
||||||
return false;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the tower had no rings, continue
|
// If the tower had no rings, trivially solved
|
||||||
if c == 0 {
|
if c == 0 {
|
||||||
|
num_solved += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -265,11 +462,13 @@ impl<const TOWER_HEIGHT: usize> Game<TOWER_HEIGHT> {
|
||||||
// if this tower has only a subset of the total, we're not done.
|
// if this tower has only a subset of the total, we're not done.
|
||||||
let ring_type = ring_type.unwrap();
|
let ring_type = ring_type.unwrap();
|
||||||
if *self.ring_sets.get(&ring_type).unwrap() != c {
|
if *self.ring_sets.get(&ring_type).unwrap() != c {
|
||||||
return false;
|
continue;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
num_solved += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
num_solved
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ask_tower(&self, msg: &str) -> io::Result<Input> {
|
fn ask_tower(&self, msg: &str) -> io::Result<Input> {
|
||||||
|
|
@ -477,10 +676,14 @@ impl GameGenerator {
|
||||||
impl<const TOWER_HEIGHT: usize> Display for Game<TOWER_HEIGHT> {
|
impl<const TOWER_HEIGHT: usize> Display for Game<TOWER_HEIGHT> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
for i in (0..TOWER_HEIGHT).rev() {
|
for i in (0..TOWER_HEIGHT).rev() {
|
||||||
for _ in 0..3 {
|
for m in 0..3 {
|
||||||
for t in &self.towers {
|
for t in &self.towers {
|
||||||
if let Some(r) = t.rings[i] {
|
if let Some(r) = t.rings[i] {
|
||||||
|
if m == 1 {
|
||||||
|
write!(f, " {r:#} ")?;
|
||||||
|
} else {
|
||||||
write!(f, " {r:} ")?;
|
write!(f, " {r:} ")?;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
write!(f, " ")?;
|
write!(f, " ")?;
|
||||||
}
|
}
|
||||||
|
|
@ -499,6 +702,11 @@ impl<const TOWER_HEIGHT: usize> Display for Game<TOWER_HEIGHT> {
|
||||||
let num = format!(" {} ", t + 1);
|
let num = format!(" {} ", t + 1);
|
||||||
write!(f, "{num:━^13}")?;
|
write!(f, "{num:━^13}")?;
|
||||||
}
|
}
|
||||||
|
writeln!(f)?;
|
||||||
|
for t in 0..self.towers.len() {
|
||||||
|
let num = format!(" {} ", self.towers[t].burried_score());
|
||||||
|
write!(f, "{num:^13}")?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -534,13 +742,27 @@ impl<const TOWER_HEIGHT: usize> UndoStack<TOWER_HEIGHT> {
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> io::Result<()> {
|
||||||
let mut g = GameGenerator {
|
let mut g = GameGenerator {
|
||||||
num_extra_towers: 2,
|
num_extra_towers: 2,
|
||||||
num_ring_types: 8,
|
num_ring_types: 12,
|
||||||
}
|
}
|
||||||
.generate::<4>();
|
.generate::<4>();
|
||||||
|
|
||||||
|
if let Some(solution) = g.solve() {
|
||||||
|
let mut g = Game {
|
||||||
|
towers: g.towers.clone(),
|
||||||
|
ring_sets: g.ring_sets.clone(),
|
||||||
|
};
|
||||||
|
for i in solution {
|
||||||
println!("{CLEAR_SCREEN}");
|
println!("{CLEAR_SCREEN}");
|
||||||
let mut u = UndoStack::new();
|
g.make_move(i).unwrap();
|
||||||
while let ControlFlow::Continue(()) = g.cli_move(&mut u)? {}
|
println!("{g}");
|
||||||
|
stdout().flush().unwrap();
|
||||||
|
stdin().read_line(&mut String::new()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// println!("{CLEAR_SCREEN}");
|
||||||
|
// let mut u = UndoStack::new();
|
||||||
|
// while let ControlFlow::Continue(()) = g.cli_move(&mut u)? {}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue