use crate::Algorithm;
use crate::Game;
use crate::Move;
use std::fmt;
#[cfg(test)]
mod tests;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub struct Position(usize);
impl Position {
fn pos_ne(self) -> Position {
Position(self.0 + 11)
}
fn pos_nw(self) -> Position {
Position(self.0 + 9)
}
fn pos_se(self) -> Position {
Position(self.0 - 9)
}
fn pos_sw(self) -> Position {
Position(self.0 - 11)
}
pub fn new(y: usize, x: usize) -> Position {
Position(y * 10 + x)
}
fn get_y(self) -> usize {
self.0 / 10
}
fn get_x(self) -> usize {
self.0 % 10
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub struct FHMove {
idx: usize,
dst: usize,
}
impl FHMove {
fn new_f(dst: Position) -> FHMove {
FHMove { idx: 4, dst: dst.0 }
}
fn new_h(idx: usize, dst: Position) -> FHMove {
FHMove { idx, dst: dst.0 }
}
fn get_idx(&self) -> usize {
self.idx
}
fn get_dst(&self) -> Position {
Position(self.dst)
}
}
impl Move for FHMove {
fn to_str(&self) -> String {
match self.idx {
4 => format!("Fox to position {}", self.dst),
n => format!("Hound {} to position {}", n + 1, self.dst),
}
}
}
pub struct FoxAndHounds {
free_squares: [bool; 100],
move_no: i32,
is_fox_next: bool,
fox_pos: Position,
hounds_pos: [Position; 4],
pub draw_with_colors: bool,
}
impl fmt::Debug for FoxAndHounds {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, " +---+---+---+---+---+---+---+---+")?;
for y in (1..9).rev() {
write!(f, " {}0 | ", y)?;
for x in 1..9 {
let pos = Position::new(y, x);
if self.fox_pos == pos {
if self.draw_with_colors {
write!(f, "\x1B[31;8mF\x1B[0m")?;
} else {
write!(f, "F")?;
}
} else if self.hounds_pos[0] == pos
|| self.hounds_pos[1] == pos
|| self.hounds_pos[2] == pos
|| self.hounds_pos[3] == pos
{
if self.draw_with_colors {
write!(f, "\x1B[33;8mH\x1B[0m")?;
} else {
write!(f, "H")?;
}
} else {
write!(f, " ")?;
}
write!(f, " | ")?;
}
writeln!(f, "\n +---+---+---+---+---+---+---+---+")?;
}
writeln!(f, " 1 2 3 4 5 6 7 8 ")?;
Ok(())
}
}
#[derive(PartialEq, Eq)]
pub enum StartingPiece {
Fox,
Hounds,
}
impl Clone for FoxAndHounds {
fn clone(&self) -> Self {
FoxAndHounds {
move_no: self.move_no,
is_fox_next: self.is_fox_next,
fox_pos: self.fox_pos,
hounds_pos: self.hounds_pos,
free_squares: self.free_squares,
draw_with_colors: self.draw_with_colors,
}
}
}
impl Game for FoxAndHounds {
type Move = FHMove;
fn possible_moves(&self) -> Vec<FHMove> {
if self.fox_pos.0 >= 80 {
return vec![];
}
let mut result = Vec::<FHMove>::with_capacity(8);
if self.is_fox_next {
if self.free_squares[self.fox_pos.0 + 11] {
result.push(FHMove::new_f(Position(self.fox_pos.0 + 11)));
}
if self.free_squares[self.fox_pos.0 + 9] {
result.push(FHMove::new_f(Position(self.fox_pos.0 + 9)));
}
if self.free_squares[self.fox_pos.0 - 9] {
result.push(FHMove::new_f(Position(self.fox_pos.0 - 9)));
}
if self.free_squares[self.fox_pos.0 - 11] {
result.push(FHMove::new_f(Position(self.fox_pos.0 - 11)));
}
} else {
for i in 0..4 {
let hound = self.hounds_pos[i].0;
if self.free_squares[(hound - 9) as usize] {
result.push(FHMove::new_h(i, Position(hound - 9)));
}
if self.free_squares[(hound - 11) as usize] {
result.push(FHMove::new_h(i, Position(hound - 11)));
}
}
result.sort_by_key(|m| 100 - m.get_dst().0);
}
result
}
fn execute_move(&self, mov: FHMove) -> Self {
if self.is_fox_next {
self.check_fox_move(&mov);
} else {
self.check_hounds_move(&mov);
}
let mut res = self.clone();
res.do_move(mov);
res
}
fn do_move(&mut self, mov: FHMove) -> FHMove {
let res;
if self.is_fox_next {
res = FHMove::new_f(Position(self.fox_pos.0));
self.free_squares[self.fox_pos.0] = true;
self.fox_pos = mov.get_dst();
self.free_squares[self.fox_pos.0] = false;
} else {
let src = self.hounds_pos[mov.get_idx()];
res = FHMove::new_h(mov.get_idx(), src);
self.free_squares[src.0] = true;
self.hounds_pos[mov.get_idx()] = mov.get_dst();
self.free_squares[mov.get_dst().0] = false;
}
self.is_fox_next = !self.is_fox_next;
self.move_no += 1;
res
}
fn undo_move(&mut self, mov: FHMove) {
self.is_fox_next = !self.is_fox_next;
self.move_no -= 1;
if self.is_fox_next {
self.free_squares[self.fox_pos.0] = true;
self.fox_pos = mov.get_dst();
self.free_squares[self.fox_pos.0] = false;
} else {
let src = self.hounds_pos[mov.get_idx()];
self.free_squares[src.0] = true;
self.hounds_pos[mov.get_idx()] = mov.get_dst();
self.free_squares[mov.get_dst().0] = false;
}
}
fn get_score(&self) -> i32 {
if self.is_game_over() {
return self.move_no - Self::SCORE_MAX;
}
let fox_y = self.fox_pos.get_y();
if fox_y >= self.hounds_pos[0].get_y()
&& fox_y >= self.hounds_pos[1].get_y()
&& fox_y >= self.hounds_pos[2].get_y()
&& fox_y >= self.hounds_pos[3].get_y()
{
if self.is_fox_next {
Self::SCORE_MAX - self.move_no
} else {
self.move_no - Self::SCORE_MAX
}
} else if self.is_fox_next {
fox_y as i32
} else {
-(fox_y as i32)
}
}
fn score_if_win_next_round(&self) -> i32 {
self.move_no + 1 - Self::SCORE_MAX
}
fn is_game_over(&self) -> bool {
if self.fox_pos.0 >= 80 {
return true;
}
if self.is_fox_next {
if self.free_squares[(self.fox_pos.0 + 11) as usize] {
return false;
}
if self.free_squares[(self.fox_pos.0 + 9) as usize] {
return false;
}
if self.free_squares[(self.fox_pos.0 - 9) as usize] {
return false;
}
if self.free_squares[(self.fox_pos.0 - 11) as usize] {
return false;
}
} else {
for i in 0..4 {
let hound = self.hounds_pos[i];
if self.free_squares[(hound.0 - 9) as usize] {
return false;
}
if self.free_squares[(hound.0 - 11) as usize] {
return false;
}
}
}
true
}
fn calculate_move<T: Algorithm>(
&self,
depth: i32,
do_precalculated_moves: bool,
) -> Option<FHMove> {
let mut mov = None;
if do_precalculated_moves {
mov = match self.move_no {
0 => Some(FHMove { idx: 4, dst: 24 }),
2 => Some(FHMove { idx: 4, dst: 35 }),
4 => Some(FHMove { idx: 4, dst: 44 }),
6 => Some(FHMove { idx: 4, dst: 53 }),
8 => Some(FHMove { idx: 4, dst: 64 }),
1 => Some(FHMove { idx: 3, dst: 77 }),
3 => Some(FHMove { idx: 2, dst: 75 }),
5 => Some(FHMove { idx: 1, dst: 73 }),
7 => Some(FHMove { idx: 0, dst: 71 }),
_ => None,
};
if let Some(m) = mov {
if !self.possible_moves().contains(&m) {
mov = None
}
}
}
if mov.is_none() {
mov = T::compute(self, depth);
}
mov
}
}
impl FoxAndHounds {
pub fn new_fox_starts(fox_starts: bool) -> FoxAndHounds {
let mut result = FoxAndHounds {
move_no: 0,
is_fox_next: fox_starts,
fox_pos: Position(15),
hounds_pos: [Position(82), Position(84), Position(86), Position(88)],
free_squares: [false; 100],
draw_with_colors: false,
};
result.reset_free_squares();
result
}
pub fn new() -> FoxAndHounds {
FoxAndHounds::new_fox_starts(true)
}
fn reset_free_squares(&mut self) {
for i in 0..10 {
for j in 0..10 {
let idx = i * 10 + j;
if i == 0 || i == 9 || j == 0 || j == 9 {
self.free_squares[idx] = false;
} else {
self.free_squares[idx] = (i + j) % 2 == 0;
}
}
}
self.free_squares[self.fox_pos.0] = false;
self.free_squares[self.hounds_pos[0].0] = false;
self.free_squares[self.hounds_pos[1].0] = false;
self.free_squares[self.hounds_pos[2].0] = false;
self.free_squares[self.hounds_pos[3].0] = false;
}
fn check_hounds_move(&self, mov: &FHMove) {
if mov.get_idx() > 3 {
panic!("Illegal move {:?}, invalid index", mov);
}
let src = self.hounds_pos[mov.get_idx()];
let dst = mov.get_dst();
if self.fox_pos == dst
|| self.hounds_pos[0] == dst
|| self.hounds_pos[1] == dst
|| self.hounds_pos[2] == dst
|| self.hounds_pos[3] == dst
{
panic!("Illegal move {:?}, new position already occupied", mov);
}
if dst != src.pos_se() && dst != src.pos_sw() {
panic!("Illegal move {:?}, hound cannot reach that square", mov);
}
if dst.get_y() < 1 || dst.get_y() > 8 || dst.get_x() < 1 || dst.get_x() > 8 {
panic!("Illegal move {:?}, hound moves outside board", mov);
}
}
fn check_fox_move(&self, mov: &FHMove) {
if mov.get_idx() != 4 {
panic!("Index not initialized correctly for fox move {:?}", mov);
}
let src = self.fox_pos;
let dst = mov.get_dst();
if self.hounds_pos[0] == dst
|| self.hounds_pos[1] == dst
|| self.hounds_pos[2] == dst
|| self.hounds_pos[3] == dst
{
panic!("Illegal move {:?}, new position already occupied", mov);
}
if dst != src.pos_ne() && dst != src.pos_nw() && dst != src.pos_se() && dst != src.pos_sw()
{
panic!("Illegal move {:?}, fox cannot reach that square", mov);
}
if dst.get_y() < 1 || dst.get_y() > 8 || dst.get_x() < 1 || dst.get_x() > 8 {
panic!("Illegal move {:?}, fox moves outside board", mov);
}
}
}