coooooolors
This commit is contained in:
parent
6ab78f2b1c
commit
d00a7a6fec
3 changed files with 238 additions and 53 deletions
261
src/gamestate.rs
261
src/gamestate.rs
|
|
@ -1,9 +1,83 @@
|
||||||
use std::fmt::Display;
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
use simple_mcts::Game;
|
|
||||||
|
|
||||||
use crate::pathfind::GoalDistanceMap;
|
use crate::pathfind::GoalDistanceMap;
|
||||||
|
|
||||||
|
pub trait BoardRepresentation: Default + Clone + Copy {
|
||||||
|
fn can_walk_between(&self, from_x: u8, from_y: u8, to_x: u8, to_y: u8) -> bool;
|
||||||
|
fn blocked_by_player(
|
||||||
|
&self,
|
||||||
|
from_x: u8,
|
||||||
|
from_y: u8,
|
||||||
|
to_x: u8,
|
||||||
|
to_y: u8,
|
||||||
|
) -> Option<PlayerIdentifier> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_place(&self, x: u8, y: u8, vertical: bool) -> bool;
|
||||||
|
fn place(&mut self, x: u8, y: u8, vertical: bool, current_player: PlayerIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Copy)]
|
||||||
|
pub struct Compact(WallState);
|
||||||
|
impl BoardRepresentation for Compact {
|
||||||
|
fn can_walk_between(&self, from_x: u8, from_y: u8, to_x: u8, to_y: u8) -> bool {
|
||||||
|
self.0.can_walk_between(from_x, from_y, to_x, to_y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_place(&self, x: u8, y: u8, vertical: bool) -> bool {
|
||||||
|
self.0.can_place(x, y, vertical)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn place(&mut self, x: u8, y: u8, vertical: bool, _current_player: PlayerIdentifier) {
|
||||||
|
self.0.place(x, y, vertical);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Copy)]
|
||||||
|
pub struct PerPlayer {
|
||||||
|
p1: WallState,
|
||||||
|
p2: WallState,
|
||||||
|
}
|
||||||
|
impl BoardRepresentation for PerPlayer {
|
||||||
|
fn can_walk_between(&self, from_x: u8, from_y: u8, to_x: u8, to_y: u8) -> bool {
|
||||||
|
self.p1.can_walk_between(from_x, from_y, to_x, to_y)
|
||||||
|
&& self.p2.can_walk_between(from_x, from_y, to_x, to_y)
|
||||||
|
}
|
||||||
|
fn blocked_by_player(
|
||||||
|
&self,
|
||||||
|
from_x: u8,
|
||||||
|
from_y: u8,
|
||||||
|
to_x: u8,
|
||||||
|
to_y: u8,
|
||||||
|
) -> Option<PlayerIdentifier> {
|
||||||
|
match (
|
||||||
|
!self.p1.can_walk_between(from_x, from_y, to_x, to_y),
|
||||||
|
!self.p2.can_walk_between(from_x, from_y, to_x, to_y),
|
||||||
|
) {
|
||||||
|
(false, false) => None,
|
||||||
|
(true, false) => Some(PlayerIdentifier::P1),
|
||||||
|
(false, true) => Some(PlayerIdentifier::P2),
|
||||||
|
(true, true) => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_place(&self, x: u8, y: u8, vertical: bool) -> bool {
|
||||||
|
self.p1.can_place(x, y, vertical) && self.p2.can_place(x, y, vertical)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn place(&mut self, x: u8, y: u8, vertical: bool, current_player: PlayerIdentifier) {
|
||||||
|
match current_player {
|
||||||
|
PlayerIdentifier::P1 => {
|
||||||
|
self.p1.place(x, y, vertical);
|
||||||
|
}
|
||||||
|
PlayerIdentifier::P2 => {
|
||||||
|
self.p2.place(x, y, vertical);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub struct PlayerState {
|
pub struct PlayerState {
|
||||||
xy: u8,
|
xy: u8,
|
||||||
|
|
@ -145,17 +219,24 @@ impl PlayerIdentifier {
|
||||||
PlayerIdentifier::P2 => 0,
|
PlayerIdentifier::P2 => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn color(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
PlayerIdentifier::P1 => "\x1b[38;2;0;0;255m",
|
||||||
|
PlayerIdentifier::P2 => "\x1b[38;2;255;0;0m",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct GameState {
|
pub struct GameState<R: BoardRepresentation = PerPlayer> {
|
||||||
pub p1: PlayerState,
|
pub p1: PlayerState,
|
||||||
pub p2: PlayerState,
|
pub p2: PlayerState,
|
||||||
pub walls: WallState,
|
pub walls: R,
|
||||||
pub current_player: PlayerIdentifier,
|
pub current_player: PlayerIdentifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameState {
|
impl<R: BoardRepresentation> GameState<R> {
|
||||||
pub fn current_player_state(&self) -> &PlayerState {
|
pub fn current_player_state(&self) -> &PlayerState {
|
||||||
match self.current_player {
|
match self.current_player {
|
||||||
PlayerIdentifier::P1 => &self.p1,
|
PlayerIdentifier::P1 => &self.p1,
|
||||||
|
|
@ -186,9 +267,13 @@ impl GameState {
|
||||||
PlayerIdentifier::P2 => -1.0 * outcome_for_p1,
|
PlayerIdentifier::P2 => -1.0 * outcome_for_p1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn place(&mut self, x: u8, y: u8, vertical: bool) {
|
||||||
|
self.walls.place(x, y, vertical, self.current_player)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GameState {
|
impl<R: BoardRepresentation> Default for GameState<R> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
p1: PlayerState::P1_START,
|
p1: PlayerState::P1_START,
|
||||||
|
|
@ -204,6 +289,36 @@ impl Display for GameState {
|
||||||
let d1 = GoalDistanceMap::new(&self.walls, PlayerIdentifier::P1);
|
let d1 = GoalDistanceMap::new(&self.walls, PlayerIdentifier::P1);
|
||||||
let d2 = GoalDistanceMap::new(&self.walls, PlayerIdentifier::P2);
|
let d2 = GoalDistanceMap::new(&self.walls, PlayerIdentifier::P2);
|
||||||
|
|
||||||
|
let crossing = |f: &mut Formatter<'_>,
|
||||||
|
wall_above,
|
||||||
|
wall_below,
|
||||||
|
wall_left,
|
||||||
|
wall_right,
|
||||||
|
pi: Option<PlayerIdentifier>| {
|
||||||
|
if let Some(pi) = pi {
|
||||||
|
write!(f, "{}", pi.color())?;
|
||||||
|
}
|
||||||
|
match (wall_above, wall_below, wall_left, wall_right) {
|
||||||
|
(false, false, false, false) => write!(f, "·"),
|
||||||
|
(false, true, false, false) => write!(f, "╵"),
|
||||||
|
(true, false, false, false) => write!(f, "╷"),
|
||||||
|
(false, false, true, false) => write!(f, "╴"),
|
||||||
|
(false, false, false, true) => write!(f, "╶"),
|
||||||
|
(true, true, false, false) => write!(f, "│"),
|
||||||
|
(false, false, true, true) => write!(f, "─"),
|
||||||
|
(true, false, true, false) => write!(f, "┐"),
|
||||||
|
(false, true, false, true) => write!(f, "└"),
|
||||||
|
(false, true, true, false) => write!(f, "┘"),
|
||||||
|
(true, false, false, true) => write!(f, "┌"),
|
||||||
|
(true, true, true, false) => write!(f, "┤"),
|
||||||
|
(true, false, true, true) => write!(f, "┬"),
|
||||||
|
(false, true, true, true) => write!(f, "┴"),
|
||||||
|
(true, true, false, true) => write!(f, "├"),
|
||||||
|
(true, true, true, true) => write!(f, "┼"),
|
||||||
|
}?;
|
||||||
|
write!(f, "\x1b[0m")
|
||||||
|
};
|
||||||
|
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"P1: {} walls, {} away from win",
|
"P1: {} walls, {} away from win",
|
||||||
|
|
@ -216,17 +331,42 @@ impl Display for GameState {
|
||||||
self.p2.walls_left,
|
self.p2.walls_left,
|
||||||
d2.at(self.p2.x(), self.p2.y())
|
d2.at(self.p2.x(), self.p2.y())
|
||||||
)?;
|
)?;
|
||||||
writeln!(f, "╭──┬──┬──┬──┬──┬──┬──┬──┬──╮")?;
|
write!(f, "╭──")?;
|
||||||
|
for x in 1..9 {
|
||||||
|
crossing(
|
||||||
|
f,
|
||||||
|
!self.walls.can_walk_between(x - 1, 0, x, 0),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
self.walls.blocked_by_player(x - 1, 0, x, 0),
|
||||||
|
)?;
|
||||||
|
write!(f, "──")?;
|
||||||
|
}
|
||||||
|
writeln!(f, "╮")?;
|
||||||
|
|
||||||
for y in 0..9 {
|
for y in 0..9 {
|
||||||
if y > 0 {
|
if y > 0 {
|
||||||
write!(f, "├")?;
|
crossing(
|
||||||
|
f,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
!self.walls.can_walk_between(0, y - 1, 0, y),
|
||||||
|
self.walls.blocked_by_player(0, y - 1, 0, y),
|
||||||
|
)?;
|
||||||
|
|
||||||
for x in 0..9 {
|
for x in 0..9 {
|
||||||
let wall = if self.walls.can_walk_between(x, y - 1, x, y) {
|
let wall = if self.walls.can_walk_between(x, y - 1, x, y) {
|
||||||
' '
|
" ".to_string()
|
||||||
} else {
|
} else {
|
||||||
'─'
|
if let Some(pi) = self.walls.blocked_by_player(x, y - 1, x, y) {
|
||||||
|
format!("{}─", pi.color())
|
||||||
|
} else {
|
||||||
|
"─".to_string()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
write!(f, "{wall}{wall}")?;
|
write!(f, "{wall}{wall}\x1b[0m")?;
|
||||||
if x != 8 {
|
if x != 8 {
|
||||||
let wall_above = !self.walls.can_walk_between(x, y, x + 1, y);
|
let wall_above = !self.walls.can_walk_between(x, y, x + 1, y);
|
||||||
let wall_below =
|
let wall_below =
|
||||||
|
|
@ -235,45 +375,81 @@ impl Display for GameState {
|
||||||
let wall_right =
|
let wall_right =
|
||||||
y != 0 && !self.walls.can_walk_between(x + 1, y, x + 1, y - 1);
|
y != 0 && !self.walls.can_walk_between(x + 1, y, x + 1, y - 1);
|
||||||
|
|
||||||
match (wall_above, wall_below, wall_left, wall_right) {
|
let mut neighboring_colors = Vec::with_capacity(4);
|
||||||
(false, false, false, false) => write!(f, "·")?,
|
|
||||||
(false, true, false, false) => write!(f, "╵")?,
|
if wall_above && let Some(pi) = self.walls.blocked_by_player(x, y, x + 1, y)
|
||||||
(true, false, false, false) => write!(f, "╷")?,
|
{
|
||||||
(false, false, true, false) => write!(f, "╴")?,
|
neighboring_colors.push(pi)
|
||||||
(false, false, false, true) => write!(f, "╶")?,
|
}
|
||||||
(true, true, false, false) => write!(f, "│")?,
|
if wall_below
|
||||||
(false, false, true, true) => write!(f, "─")?,
|
&& let Some(pi) = self.walls.blocked_by_player(x, y - 1, x + 1, y - 1)
|
||||||
(false, true, true, false) => write!(f, "┐")?,
|
{
|
||||||
(true, false, false, true) => write!(f, "└")?,
|
neighboring_colors.push(pi)
|
||||||
(true, false, true, false) => write!(f, "┘")?,
|
}
|
||||||
(false, true, false, true) => write!(f, "┌")?,
|
if wall_left && let Some(pi) = self.walls.blocked_by_player(x, y, x, y - 1)
|
||||||
(true, true, true, false) => write!(f, "┤")?,
|
{
|
||||||
(false, true, true, true) => write!(f, "┬")?,
|
neighboring_colors.push(pi)
|
||||||
(true, false, true, true) => write!(f, "┴")?,
|
}
|
||||||
(true, true, false, true) => write!(f, "├")?,
|
if wall_right
|
||||||
(true, true, true, true) => write!(f, "┼")?,
|
&& let Some(pi) = self.walls.blocked_by_player(x + 1, y, x + 1, y - 1)
|
||||||
|
{
|
||||||
|
neighboring_colors.push(pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
// write!(f, "┼")?;
|
let num_p1 = neighboring_colors
|
||||||
|
.iter()
|
||||||
|
.filter(|i| matches!(i, PlayerIdentifier::P1))
|
||||||
|
.count();
|
||||||
|
let num_p2 = neighboring_colors
|
||||||
|
.iter()
|
||||||
|
.filter(|i| matches!(i, PlayerIdentifier::P2))
|
||||||
|
.count();
|
||||||
|
|
||||||
|
crossing(
|
||||||
|
f,
|
||||||
|
wall_above,
|
||||||
|
wall_below,
|
||||||
|
wall_left,
|
||||||
|
wall_right,
|
||||||
|
if num_p1 == 0 && num_p2 == 0 {
|
||||||
|
None
|
||||||
|
} else if num_p1 > num_p2 {
|
||||||
|
Some(PlayerIdentifier::P1)
|
||||||
|
} else {
|
||||||
|
Some(PlayerIdentifier::P2)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write!(f, "┤\n│")?;
|
crossing(
|
||||||
|
f,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
!self.walls.can_walk_between(8, y - 1, 8, y),
|
||||||
|
false,
|
||||||
|
self.walls.blocked_by_player(8, y - 1, 8, y),
|
||||||
|
)?;
|
||||||
|
write!(f, "\n│")?;
|
||||||
} else {
|
} else {
|
||||||
write!(f, "│")?;
|
write!(f, "│")?;
|
||||||
}
|
}
|
||||||
for x in 0..9 {
|
for x in 0..9 {
|
||||||
if x > 0 {
|
if x > 0 {
|
||||||
let wall = if self.walls.can_walk_between(x - 1, y, x, y) {
|
let wall = if self.walls.can_walk_between(x - 1, y, x, y) {
|
||||||
' '
|
" ".to_string()
|
||||||
} else {
|
} else {
|
||||||
'│'
|
if let Some(pi) = self.walls.blocked_by_player(x - 1, y, x, y) {
|
||||||
|
format!("{}│", pi.color())
|
||||||
|
} else {
|
||||||
|
"│".to_string()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
write!(f, "{wall}")?;
|
write!(f, "{wall}\x1b[0m")?;
|
||||||
}
|
}
|
||||||
let player = if self.p1.x() == x && self.p1.y() == y {
|
let player = if self.p1.x() == x && self.p1.y() == y {
|
||||||
"\x1b[1mP1\x1b[0m".to_owned()
|
format!("\x1b[1m{}P1\x1b[0m", PlayerIdentifier::P1.color())
|
||||||
} else if self.p2.x() == x && self.p2.y() == y {
|
} else if self.p2.x() == x && self.p2.y() == y {
|
||||||
"\x1b[1mP2\x1b[0m".to_owned()
|
format!("\x1b[1m{}P2\x1b[0m", PlayerIdentifier::P2.color())
|
||||||
} else {
|
} else {
|
||||||
// format!("{:^2}", dm.at(x, y))
|
// format!("{:^2}", dm.at(x, y))
|
||||||
" ".to_owned()
|
" ".to_owned()
|
||||||
|
|
@ -282,7 +458,18 @@ impl Display for GameState {
|
||||||
}
|
}
|
||||||
writeln!(f, "│")?;
|
writeln!(f, "│")?;
|
||||||
}
|
}
|
||||||
writeln!(f, "╰──┴──┴──┴──┴──┴──┴──┴──┴──╯")?;
|
write!(f, "╰──")?;
|
||||||
|
for x in 1..9 {
|
||||||
|
crossing(
|
||||||
|
f,
|
||||||
|
false,
|
||||||
|
!self.walls.can_walk_between(x - 1, 8, x, 8),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
self.walls.blocked_by_player(x - 1, 8, x, 8),
|
||||||
|
)?;
|
||||||
|
write!(f, "──")?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
src/main.rs
26
src/main.rs
|
|
@ -1,22 +1,21 @@
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use simple_mcts::Game;
|
use simple_mcts::Game;
|
||||||
use simple_mcts::GameEvaluator;
|
use simple_mcts::GameEvaluator;
|
||||||
use simple_mcts::Mcts;
|
use simple_mcts::Mcts;
|
||||||
|
use simple_mcts::MctsBatchConfig;
|
||||||
use simple_mcts::MctsError;
|
use simple_mcts::MctsError;
|
||||||
|
|
||||||
|
use crate::gamestate::BoardRepresentation;
|
||||||
use crate::gamestate::GameState;
|
use crate::gamestate::GameState;
|
||||||
use crate::gamestate::PlayerIdentifier;
|
use crate::gamestate::PerPlayer;
|
||||||
|
|
||||||
mod gamestate;
|
mod gamestate;
|
||||||
mod pathfind;
|
mod pathfind;
|
||||||
|
|
||||||
struct Quoridor {
|
struct Quoridor<R: BoardRepresentation = PerPlayer> {
|
||||||
state: GameState,
|
state: GameState<R>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Quoridor {
|
impl<R: BoardRepresentation> Default for Quoridor<R> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: Default::default(),
|
state: Default::default(),
|
||||||
|
|
@ -27,8 +26,8 @@ impl Default for Quoridor {
|
||||||
const NUM_NEXT_STATES: usize = 64 /* horizontal */ + 64 /* vertical */ +
|
const NUM_NEXT_STATES: usize = 64 /* horizontal */ + 64 /* vertical */ +
|
||||||
4 /* move directions */ + 2 /* blocked jumps */;
|
4 /* move directions */ + 2 /* blocked jumps */;
|
||||||
|
|
||||||
impl Game<NUM_NEXT_STATES> for Quoridor {
|
impl<R: BoardRepresentation> Game<NUM_NEXT_STATES> for Quoridor<R> {
|
||||||
type State = GameState;
|
type State = GameState<R>;
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -101,7 +100,7 @@ impl Game<NUM_NEXT_STATES> for Quoridor {
|
||||||
let mut set_block = |i: usize, vertical| {
|
let mut set_block = |i: usize, vertical| {
|
||||||
let x = (i / 8) as u8;
|
let x = (i / 8) as u8;
|
||||||
let y = (i % 8) as u8;
|
let y = (i % 8) as u8;
|
||||||
self.state.walls.place(x, y, vertical);
|
self.state.place(x, y, vertical);
|
||||||
};
|
};
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
|
|
@ -170,13 +169,10 @@ impl GameEvaluator<Quoridor, NUM_NEXT_STATES> for ProgressEvaluator {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), MctsError> {
|
fn main() -> Result<(), MctsError> {
|
||||||
let mut g = Quoridor::default();
|
|
||||||
g.state.walls.place(4, 4, false);
|
|
||||||
|
|
||||||
let mut mcts: Mcts<Quoridor, _> = Mcts::<Quoridor, _>::new();
|
let mut mcts: Mcts<Quoridor, _> = Mcts::<Quoridor, _>::new();
|
||||||
let evaluator = ProgressEvaluator;
|
let evaluator = ProgressEvaluator;
|
||||||
|
|
||||||
while g.get_result().is_none() {
|
while mcts.get_game().get_result().is_none() {
|
||||||
for _ in 0..5000 {
|
for _ in 0..5000 {
|
||||||
mcts.iterate(&evaluator)?;
|
mcts.iterate(&evaluator)?;
|
||||||
}
|
}
|
||||||
|
|
@ -196,6 +192,8 @@ fn main() -> Result<(), MctsError> {
|
||||||
// println!("{top_5:?}");
|
// println!("{top_5:?}");
|
||||||
// let best_action_index = x.last().map(|(index, _)| *index).unwrap_or(0);
|
// let best_action_index = x.last().map(|(index, _)| *index).unwrap_or(0);
|
||||||
|
|
||||||
|
println!("\x1b[2J");
|
||||||
|
println!("\x1b[2H");
|
||||||
println!("best action: {best_action_index}");
|
println!("best action: {best_action_index}");
|
||||||
mcts.play(best_action_index)?;
|
mcts.play(best_action_index)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use crate::gamestate::{PlayerIdentifier, WallState};
|
use crate::gamestate::{BoardRepresentation, PlayerIdentifier, WallState};
|
||||||
|
|
||||||
pub struct GoalDistanceMap {
|
pub struct GoalDistanceMap {
|
||||||
distances: [[u8; 9]; 9],
|
distances: [[u8; 9]; 9],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GoalDistanceMap {
|
impl GoalDistanceMap {
|
||||||
pub fn new(w: &WallState, for_player: PlayerIdentifier) -> Self {
|
pub fn new<R: BoardRepresentation>(w: &R, for_player: PlayerIdentifier) -> Self {
|
||||||
let mut todo = VecDeque::with_capacity(9 * 9);
|
let mut todo = VecDeque::with_capacity(9 * 9);
|
||||||
let mut res = [[u8::MAX; 9]; 9];
|
let mut res = [[u8::MAX; 9]; 9];
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue