Ondřej Hruška 5 years ago
parent 4c795da09b
commit 109220a3f9
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 79
      Cargo.lock
  2. 1
      Cargo.toml
  3. 553
      src/game.rs
  4. 141
      src/main.rs

79
Cargo.lock generated

@ -1,5 +1,84 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "gammon"
version = "0.1.0"
dependencies = [
"rand",
]
[[package]]
name = "getrandom"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
[[package]]
name = "ppv-lite86"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom",
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"

@ -7,3 +7,4 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.7.3"

@ -0,0 +1,553 @@
use std::default::Default;
use std::{fmt, mem};
use std::fmt::{Display, Formatter, Write};
use rand::{Rng, rngs::OsRng};
use std::borrow::Borrow;
#[derive(Debug,Clone)]
struct Board([Bin; 24]);
impl Board {
fn can_bear_off(&self, color : Color) -> bool {
for i in (HOME_MAX)+1..=BOARD_MAX {
match (self.0)[i as usize].as_color() {
Some(c) if c == color => {
return false;
},
_ => {
// empty or the other color
}
}
}
true
}
fn can_place(&self, pos: u8, color : Color) -> bool {
if pos > BOARD_MAX {
false
} else {
let c = (self.0)[pos as usize].as_color();
c.is_none() ||
c.unwrap() == color ||
(self.0)[pos as usize].as_count() == 1
}
}
fn can_move_from(&self, pos: u8, color : Color) -> bool {
if pos > BOARD_MAX {
false
} else {
let c = (self.0)[pos as usize].as_color();
c.is_some() &&
c.unwrap() == color
}
}
#[must_use]
fn apply_move_mut(&mut self, from : u8, to : u8) -> Option<Color> {
let old_color = self.0[to as usize].as_color();
let color = self.0[from as usize].subtract_mut();
self.0[to as usize].add_mut(color);
old_color
}
#[must_use]
fn apply_enter_mut(&mut self, pos : u8, color : Color) -> Option<Color> {
let old_color = self.0[pos as usize].as_color();
self.0[pos as usize].add_mut(color);
old_color
}
fn apply_bearoff_mut(&mut self, pos : u8) {
self.0[pos as usize].subtract_mut();
}
}
#[derive(Debug,Clone,Copy,PartialEq,Eq)]
pub enum Color {
Black,
White,
}
impl Color {
fn opposite(self) -> Color {
match self {
Color::Black => Color::White,
Color::White => Color::Black,
}
}
}
#[derive(Debug,Clone,Copy)]
pub enum Bin {
Empty,
Black(u8),
White(u8),
}
impl Bin {
pub fn as_color(self) -> Option<Color> {
match self {
Bin::Empty => None,
Bin::Black(_) => Some(Color::Black),
Bin::White(_) => Some(Color::White),
}
}
pub fn as_count(self) -> u8 {
match self {
Bin::Empty => 0,
Bin::Black(n) | Bin::White(n) => n,
}
}
fn add_mut(&mut self, color : Color) {
mem::replace(self, match self.borrow() {
Bin::Empty => {
match color {
Color::Black => Bin::Black(1),
Color::White => Bin::White(1),
}
},
Bin::Black(n) => Bin::Black(*n + 1),
Bin::White(n) => Bin::White(*n + 1),
});
}
fn subtract_mut(&mut self) -> Color {
match self {
Bin::Empty => {
panic!("Subtract from empty bin");
},
Bin::Black(n) => {
*n -= 1u8;
if *n == 0 {
mem::replace(self, Bin::Empty);
}
Color::Black
},
Bin::White(n) => {
*n -= 1u8;
if *n == 0 {
mem::replace(self, Bin::Empty);
}
Color::White
},
}
}
}
#[derive(Debug, Clone)]
struct Player {
born_off : u8,
to_place : u8,
}
#[derive(Debug, Clone)]
pub struct State {
turn : Color,
board: Board,
roll: Roll,
player : Player,
other : Player,
}
#[derive(Debug,Clone,Copy)]
pub enum Move {
InBoard {
from: u8,
to : u8
},
Enter(u8),
BearOff(u8)
}
#[derive(Debug)]
pub enum Error {
Internal,
MalformedMove,
NoSourceStone,
TargetOccupied,
NotBearingOff,
NotAllPlaced,
NothingToPlace,
NoMatchingRoll,
NoValidMoves,
NotYourTurn,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// TODO
write!(f, "{:?}", self)
}
}
impl std::error::Error for Error {}
#[derive(Debug, Clone)]
pub struct Roll {
/// The dice as rolled
dice: (u8, u8),
/// Remaining moves based on the dice and steps already performed
remaining_moves: Vec<u8>,
}
impl Roll {
pub fn new() -> Self {
Self::from_dice((
OsRng.gen_range(1, 7),
OsRng.gen_range(1, 7)
))
}
pub fn from_dice(dice: (u8,u8)) -> Self {
let remaining_moves;
if dice.0 == dice.1 {
remaining_moves = vec![dice.0, dice.0, dice.0, dice.0];
} else {
remaining_moves = vec![dice.0, dice.1];
}
Roll {
dice,
remaining_moves
}
}
pub fn have_equal(&self, eq : u8) -> bool {
self.remaining_moves.iter()
.any(|v| *v == eq)
}
pub fn have_equal_or_greater(&self, mineq : u8) -> bool {
self.remaining_moves.iter()
.any(|v| *v >= mineq)
}
pub fn get_equal_or_greater(&self, mineq : u8) -> Option<u8> {
self.remaining_moves.iter()
.find(|v| *v >= &mineq)
.cloned()
}
fn remove_mut(&mut self, needed : u8) {
let nth = self.remaining_moves.iter().position(|v| *v == needed).unwrap();
self.remaining_moves.remove(nth);
}
/// # Panics
/// if not available
pub fn remove(&mut self, needed : u8) -> Roll {
let mut next = self.clone();
next.remove_mut(needed);
next
}
}
// The board is reversed based on the active player's color to make the algorithm unified.
// The movement is always to lower positions, with home at 5-0
const BOARD_MAX : u8 = 23;
const HOME_MAX : u8 = 5;
impl State {
pub fn start(turn : Option<Color>) -> Self {
// The state is laid out for the white player
let mut state = State {
turn: Color::White,
board: Board([
// 1 .. 12 (bottom row, right to left)
Bin::Black(2), // white player's home side
Bin::Empty,
Bin::Empty,
Bin::Empty,
Bin::Empty,
Bin::White(5), // 5 in 0-based
Bin::Empty,
Bin::White(3),
Bin::Empty,
Bin::Empty,
Bin::Empty,
Bin::Black(5),
// 13 .. 24 (top row, left to right)
Bin::White(5), // 12 in 0-based
Bin::Empty,
Bin::Empty,
Bin::Empty,
Bin::Black(3),
Bin::Empty,
Bin::Black(5), // 18 in 0-based
Bin::Empty,
Bin::Empty,
Bin::Empty,
Bin::Empty,
Bin::White(2), // black player's home side
]),
roll: Roll::new(),
player: Player { born_off: 0, to_place: 0 },
other: Player { born_off: 0, to_place: 0 }
};
if turn.map(|c| c == Color::Black).unwrap_or_else(|| OsRng.gen()) {
// flip the board so black starts
state.switch_sides_mut();
}
state
}
pub fn check_move(&self, mv : Move) -> Result<(), Error> {
match mv {
Move::InBoard{from, to} => {
if to > from {
// can only go lower
return Err(Error::MalformedMove);
}
if self.player.to_place != 0 {
return Err(Error::NotAllPlaced);
}
if !self.board.can_move_from(from, self.turn) {
return Err(Error::NoSourceStone);
}
if !self.board.can_place(to, self.turn) {
return Err(Error::TargetOccupied);
}
let needed = from - to;
if !self.roll.have_equal(needed) {
return Err(Error::NoMatchingRoll);
}
Ok(())
},
Move::Enter(pos) => {
if self.player.to_place == 0 {
return Err(Error::NothingToPlace);
}
if pos > BOARD_MAX {
return Err(Error::MalformedMove);
}
let needed = 24 - pos;
if !self.roll.have_equal(needed) {
return Err(Error::NoMatchingRoll);
}
if !self.board.can_place(pos, self.turn) {
return Err(Error::TargetOccupied);
}
Ok(())
},
Move::BearOff(pos) => {
if pos > HOME_MAX {
return Err(Error::MalformedMove);
}
if self.player.to_place != 0 {
return Err(Error::NotAllPlaced);
}
if !self.board.can_bear_off(self.turn) {
// still have checkers in higher positions
return Err(Error::NotBearingOff);
}
if !self.roll.have_equal_or_greater(pos + 1) {
return Err(Error::NoMatchingRoll);
// TODO must always bear off the highest possible, if multiple checkers are available
}
Ok(())
},
}
}
pub fn apply_move(&self, turn : Color, mv : Move) -> Result<Self, Error> {
if turn != self.turn {
return Err(Error::NotYourTurn);
}
self.check_move(mv)?;
print!("{:?} plays move: ", turn);
let mut next = self.clone();
match mv {
Move::InBoard { from, to } => {
println!("In-board {} -> {}", from, to);
let old_color = next.board.apply_move_mut(from, to);
let needed = from - to; // move is always downward
next.roll.remove_mut(needed);
if let Some(c) = old_color {
if c != self.turn {
println!("{:?} stone at position {} is hit.", c, to);
// hit opposite color
next.other.to_place += 1;
}
}
},
Move::Enter(pos) => {
println!("Enter -> {}", pos);
let old_color = next.board.apply_enter_mut(pos, self.turn);
let needed = 24 - pos;
next.roll.remove_mut(needed);
next.player.to_place -= 1;
if let Some(c) = old_color {
if c != self.turn {
println!("{:?} stone at position {} is hit.", c, pos);
// hit opposite color
next.other.to_place += 1;
}
}
},
Move::BearOff(pos) => {
println!("Bear off -> {}", pos);
let needed = next.roll.get_equal_or_greater(pos + 1).unwrap();
next.roll.remove_mut(needed);
},
}
if next.roll.remaining_moves.is_empty() {
next.switch_sides_mut();
next.roll_mut();
// TODO check if any moves are possible with this roll, if not, auto-switch again
}
Ok(next)
}
pub fn spoof_roll(&self, roll : Roll) -> Self {
let mut next = self.clone();
next.roll = roll;
next
}
fn roll_mut(&mut self) {
self.roll = Roll::new();
}
fn switch_sides_mut(&mut self) {
self.board.0.reverse();
mem::swap(&mut self.player, &mut self.other);
self.turn = self.turn.opposite();
}
}
impl Display for State {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "\n# {:?} turn, roll ({},{}), moves to play: [{}]\n",
self.turn,
self.roll.dice.0,
self.roll.dice.1,
self.roll.remaining_moves.iter().map(ToString::to_string).collect::<Vec<_>>().join(",")
)?;
write!(f, "# bearing off? {}, born off: {}, to place: {}\n",
if self.board.can_bear_off(self.turn) { "YES" } else { "no" },
self.player.born_off,
self.player.to_place
)?;
for n in 0..=23 {
write!(f, "{:02} ", n)?;
}
f.write_char('\n')?;
for n in 0..=23 {
match self.board.0[n] {
Bin::Empty => {
write!(f, "--- ", )?;
},
Bin::Black(i) => {
write!(f, "K{:<3} ", i)?;
},
Bin::White(i) => {
write!(f, "W{:<3} ", i)?;
},
}
}
f.write_str("\n")?;
Ok(())
}
}
// impl Display for Game {
// fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
// write!(f, "{}'s turn, roll {}+{}", self.turn, self.roll.0, self.roll.1)?;
//
// let pl = self.cur_player();
//
// if pl.bearing_off {
// write!(f, ", born off {}/15", pl.born_off)?;
// }
//
// if pl.on_bar > 0 {
// write!(f, ", {} on bar!", pl.on_bar)?;
// }
// else {
// write!(f, ", normal move")?;
// }
//
// f.write_str("\n")?;
//
// for n in (13..=24) {
// write!(f, "{} ", ('m' as u8 + (n - 13) as u8) as char)?;
// }
// write!(f, "\n")?;
//
// for (i, b) in (&self.bins[12..=23]).iter().enumerate() {
// match *b {
// Bin::Empty => {
// if i%2==0 {
// write!(f, "░░")?
// } else {
// write!(f, "▒▒")?
// }
// },
// Bin::Black(n) => write!(f, "{:X}#", n)?,
// Bin::White(n) => write!(f, "{:X}:", n)?
// }
// }
// write!(f, "|:{}\n", self.players[Side::Black as usize].born_off)?;
//
// for (i, b) in (&self.bins[0..=11]).iter().rev().enumerate() {
// match *b {
// Bin::Empty => {
// if i%2==0 {
// write!(f, "░░")?
// } else {
// write!(f, "▒▒")?
// }
// },
// Bin::Black(n) => write!(f, "{:X}#", n)?, //█
// Bin::White(n) => write!(f, "{:X}:", n)? //░
// }
// }
//
// write!(f, "|#{}\n", self.players[Side::Black as usize].born_off)?;
// for n in (1..=12).rev() {
// write!(f, "{} ", ('a' as u8 + (n-1) as u8) as char)?;
// }
// write!(f, "\n")?;
//
// Ok(())
// }
// }

@ -1,134 +1,31 @@
use std::default::Default;
use std::fmt;
use std::fmt::Display;
use crate::game::{Move, Color, Roll};
#[derive(Debug,Clone,Copy,Eq,PartialEq)]
pub enum Side {
Black = 0,
White = 1,
}
#[derive(Debug,Clone,Copy)]
pub enum Phase {
PickSides,
Turn(Side),
}
#[derive(Debug,Clone,Default)]
pub struct Player {
pub born_off: u32,
pub on_bar: u32,
}
#[derive(Debug,Clone,Copy)]
pub enum Bin {
Empty,
Black(u32),
White(u32),
}
#[derive(Debug)]
pub struct Game {
pub phase : Phase,
pub multiplier : u32,
pub roll : [u8; 2],
pub players : [Player; 2],
pub bins : [Bin; 24],
}
mod game;
impl Default for Game {
fn default() -> Self {
Game {
phase: Phase::PickSides,
multiplier: 1,
roll: [0; 2],
players: [
Player::default(),
Player::default(),
],
bins: [Bin::Empty; 24],
}
}
}
impl Display for Game {
fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
for n in (13..=24) {
write!(f, "{} ", ('D' as u8 + (n - 13) as u8) as char)?;
}
write!(f, "\n")?;
for (i, b) in (&self.bins[12..=23]).iter().enumerate() {
match *b {
Bin::Empty => {
if i%2==0 {
write!(f, "░░")?
} else {
write!(f, "▒▒")?
}
},
Bin::Black(n) => write!(f, "{:X}#", n)?,
Bin::White(n) => write!(f, "{:X}:", n)?
}
}
write!(f, "|:{}\n", self.players[Side::Black as usize].born_off)?;
for (i, b) in (&self.bins[0..=11]).iter().rev().enumerate() {
match *b {
Bin::Empty => {
if i%2==0 {
write!(f, "░░")?
} else {
write!(f, "▒▒")?
}
},
Bin::Black(n) => write!(f, "{:X}#", n)?, //█
Bin::White(n) => write!(f, "{:X}:", n)? //░
}
}
fn main() {
let g = game::State::start(Some(Color::Black));
write!(f, "|#{}\n", self.players[Side::Black as usize].born_off)?;
for n in (1..=12).rev() {
write!(f, "{:X} ", n)?;
}
write!(f, "\n")?;
let g = g.spoof_roll(Roll::from_dice((3, 5)));
Ok(())
}
}
println!("{}", g);
impl Game {
fn clear(&mut self) {
for i in 0..24 {
self.bins[i] = Bin::Empty;
}
}
let g = g.apply_move(Color::Black, Move::InBoard { from: 23, to: 20 }).unwrap();
let g = g.apply_move(Color::Black, Move::InBoard { from: 12, to: 7 }).unwrap();
fn set(&mut self, n : u32, val : Bin) {
assert!(n > 0 && n <= 24);
let i = n - 1;
self.bins[i as usize] = val;
}
// White turn
let g = g.spoof_roll(Roll::from_dice((4, 6)));
println!("{}", g);
fn starting_position(&mut self) {
self.clear();
self.set(1, Bin::White(2));
self.set(6, Bin::Black(5));
self.set(8, Bin::Black(3));
self.set(12, Bin::White(5));
self.set(13, Bin::Black(5));
self.set(17, Bin::White(3));
self.set(19, Bin::White(5));
self.set(24, Bin::Black(2));
}
}
let g = g.apply_move(Color::White, Move::InBoard { from: 23, to: 17 }).unwrap();
let g = g.apply_move(Color::White, Move::InBoard { from: 7, to: 3 }).unwrap();
fn main() {
let mut board = Game::default();
board.starting_position();
// Black turn, need to place the hit stone
let g = g.spoof_roll(Roll::from_dice((1, 2)));
println!("{}", g);
println!("{}", board);
let g = g.apply_move(Color::Black, Move::Enter(22)).unwrap();
let g = g.apply_move(Color::Black, Move::InBoard { from: 22, to: 21 }).unwrap();
println!("{}", g);
}

Loading…
Cancel
Save