coooooolors

This commit is contained in:
Jana Dönszelmann 2025-09-12 01:32:53 -07:00
parent 6ab78f2b1c
commit d00a7a6fec
No known key found for this signature in database
3 changed files with 238 additions and 53 deletions

View file

@ -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(())
} }
} }

View file

@ -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)?;

View file

@ -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];