diff --git a/Cargo.lock b/Cargo.lock index dcc830c..e9ffc16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,6 +141,7 @@ name = "gammon" version = "0.1.0" dependencies = [ "failure", + "permutator", "rand", "rustyline", ] @@ -196,6 +197,92 @@ dependencies = [ "void", ] +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg", +] + +[[package]] +name = "permutator" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa5132833890643cdaeb330ae7cf7da92e0e11c8729f6f529140666213876a0b" +dependencies = [ + "num", +] + [[package]] name = "ppv-lite86" version = "0.2.6" diff --git a/Cargo.toml b/Cargo.toml index 324d722..16932c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ edition = "2018" rand = "0.7.3" rustyline = "6.1.2" failure = "0.1.8" +permutator = "0.4.0" diff --git a/out.png b/out.png deleted file mode 100644 index 77bc7f1..0000000 Binary files a/out.png and /dev/null differ diff --git a/src/DOSVGA.ttf b/src/DOSVGA.ttf deleted file mode 100644 index bf70112..0000000 Binary files a/src/DOSVGA.ttf and /dev/null differ diff --git a/src/DejaVuSans.ttf b/src/DejaVuSans.ttf deleted file mode 100644 index 9d40c32..0000000 Binary files a/src/DejaVuSans.ttf and /dev/null differ diff --git a/src/coders_crux.ttf b/src/coders_crux.ttf deleted file mode 100644 index 01b5ea3..0000000 Binary files a/src/coders_crux.ttf and /dev/null differ diff --git a/src/fixedsys.ttf b/src/fixedsys.ttf deleted file mode 100644 index 7d6946d..0000000 Binary files a/src/fixedsys.ttf and /dev/null differ diff --git a/src/game.rs b/src/game.rs index f26511f..7e1cb65 100644 --- a/src/game.rs +++ b/src/game.rs @@ -3,6 +3,7 @@ use std::{fmt, mem}; use std::fmt::{Display, Formatter, Write}; use rand::{Rng, rngs::OsRng}; use std::borrow::Borrow; +use permutator::copy::k_permutation; #[derive(Debug,Clone)] struct Board([Bin; 24]); @@ -176,7 +177,7 @@ struct Player { #[derive(Debug, Clone)] pub struct State { - move_number : usize, + turn_number: usize, turn : Color, board: Board, roll: Roll, @@ -186,9 +187,9 @@ pub struct State { #[derive(Debug,Clone,Copy)] pub enum Move { - InBoard(u8, u8), - Enter(u8), - BearOff(u8) + InBoard(/* from */ u8, /* to */ u8), + Enter(/* pos */ u8), + BearOff(/* pos */ u8) } #[derive(Debug)] @@ -283,7 +284,7 @@ impl State { pub fn start(turn : Option) -> Self { // The state is laid out for the white player let mut state = State { - move_number: 0, + turn_number: 0, turn: Color::White, board: Board([ // 1 .. 12 (bottom row, right to left) @@ -325,10 +326,14 @@ impl State { state } + /// Get turn info. Returns a tuple of `(turn_number, turn_color)`, where + /// `turn_number` is incremented after each turn ends (can be used to detect + /// automatic turn skips that result in the same player playing again. pub fn turn(&self) -> (usize, Color) { - (self.move_number, self.turn) + (self.turn_number, self.turn) } + /// Get reference to own player struct fn player(&self) -> &Player { match self.turn { Color::Black => &self.black, @@ -336,6 +341,7 @@ impl State { } } + /// Get mutable reference to own player struct fn player_mut(&mut self) -> &mut Player { match self.turn { Color::Black => &mut self.black, @@ -343,6 +349,7 @@ impl State { } } + /// Get reference to the other player's struct fn other(&self) -> &Player { match self.turn { Color::Black => &self.white, @@ -350,6 +357,7 @@ impl State { } } + /// Get mutable reference to the other player's struct fn other_mut(&mut self) -> &mut Player { match self.turn { Color::Black => &mut self.white, @@ -357,19 +365,21 @@ impl State { } } - + /// Apply a move to the board. + /// + /// turn must match the current turn's color, move must be valid for that player. pub fn apply_move(&self, turn : Color, mv : Move) -> Result { if turn != self.turn { return Err(Error::NotYourTurn); } - print!("{} plays move: ", turn); + // print!("{} plays move: ", turn); let mut next = self.clone(); match mv { Move::InBoard(from, to) => { - println!("In-board {} -> {}", from, to); + // println!("In-board {} -> {}", from, to); if (self.turn == Color::White && to >= from) || (self.turn == Color::Black && to <= from) { return Err(Error::MalformedMove); @@ -397,7 +407,7 @@ impl State { if let Some(c) = old_color { if c != self.turn { - println!("{} stone at position {} is hit.", c, to); + // println!("{} stone at position {} is hit.", c, to); // hit opposite color next.other_mut().to_place += 1; @@ -405,7 +415,7 @@ impl State { } }, Move::Enter(pos) => { - println!("Enter -> {}", pos); + // println!("Enter -> {}", pos); if self.player().to_place == 0 { return Err(Error::NothingToPlace); @@ -436,14 +446,14 @@ impl State { if let Some(c) = old_color { if c != self.turn { - println!("{} stone at position {} is hit.", c, pos); + // println!("{} stone at position {} is hit.", c, pos); // hit opposite color next.other_mut().to_place += 1; } } }, Move::BearOff(pos) => { - println!("Bear off -> {}", pos); + // println!("Bear off -> {}", pos); if (self.turn == Color::White && pos > HOME_MAX) || (self.turn == Color::Black && pos < 18) { return Err(Error::MalformedMove); @@ -469,25 +479,128 @@ impl State { } if next.roll.remaining_moves.is_empty() { - next.turn = self.turn.opposite(); - next.move_number += 1; - next.roll_mut(); - - // TODO check if any moves are possible with this roll, if not, auto-switch again + next = next.yield_turn(); } Ok(next) } + pub fn yield_turn(&self) -> Self { + let mut next = self.clone(); + next.turn = self.turn.opposite(); + next.turn_number += 1; + next.roll_mut(); + next + } + + /// Inject a faked dice roll pub fn spoof_roll(&self, roll : Roll) -> Self { let mut next = self.clone(); next.roll = roll; next } + /// Rull dice (in place) fn roll_mut(&mut self) { self.roll = Roll::new(); } + + pub fn get_max_number_of_possible_moves_with_current_roll(&self) -> usize { + let mut max = 0; + let color = self.turn; + let turn_number = self.turn_number; + + k_permutation(&self.roll.remaining_moves, self.roll.remaining_moves.len(), |permuted| { + if max == self.roll.remaining_moves.len() { + // There is nothing more to look for, discard the remaining iterations (sadly, can't break;) + return; + } + + let mut to_try = vec![self.clone()]; + let mut to_try_next_roll = vec![]; + for (n, roll) in permuted.iter().copied().enumerate() { + let depth = n + 1; + to_try_next_roll.clear(); + for state in to_try.drain(..) { + for mv in PossibleMovesOfLen::new(&state, roll) { + if let Ok(next) = state.apply_move(color, mv) { + max = max.max(depth); + if next.turn_number == turn_number { + to_try_next_roll.push(next); + } + } + } + } + std::mem::swap(&mut to_try, &mut to_try_next_roll) + } + }); + + max + } +} + +struct PossibleMovesOfLen<'a> { + state : &'a State, + pos : i8, + step: i8, + len : u8 +} + +impl<'a> PossibleMovesOfLen<'a> { + pub fn new(state : &'a State, len : u8) -> Self { + Self { + state, + pos: -1, + step: if state.turn == Color::White { + -(len as i8) + } else { + len as i8 + }, + len + } + } +} + +impl<'a> Iterator for PossibleMovesOfLen<'a> { + type Item = Move; + + fn next(&mut self) -> Option { + if self.pos > 23 { + return None; + } + + let state = self.state; + + if state.player().to_place > 0 { + let p = if state.turn == Color::White { + 24 - self.len + } else { + self.len + }; + if state.board.can_place(p, state.turn) { + self.pos = 24; // no more moves + return Some(Move::Enter(p)); + } + } + + 'next: loop { + self.pos += 1; + if self.pos > 23 { + return None; + } + + if state.board.can_move_from(self.pos as u8, state.turn) { + let dest = self.pos + self.step; + if dest < 0 || dest > 23 { + return Some(Move::BearOff(self.pos as u8)); + } else { + if state.board.can_place(dest as u8, state.turn) { + return Some(Move::InBoard(self.pos as u8, dest as u8)); + } + } + } + } + } } impl Display for State { @@ -505,11 +618,17 @@ impl Display for State { self.player().to_place )?; - f.write_str("White <- ")?; + if self.turn == Color::White { + f.write_str("\x1b[1m")?; + } + f.write_str("White <- \x1b[m")?; for n in 0..=23 { write!(f, "{:02} ", n+1)?; } - f.write_str(" -> Black\n")?; + if self.turn == Color::Black { + f.write_str("\x1b[1m")?; + } + f.write_str(" -> Black\x1b[m\n")?; f.write_str(" ")?; for n in 0..=23 { diff --git a/src/lilliput steps.ttf b/src/lilliput steps.ttf deleted file mode 100644 index 7c2317d..0000000 Binary files a/src/lilliput steps.ttf and /dev/null differ diff --git a/src/main.rs b/src/main.rs index 38394fb..2c0574f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,16 @@ fn main() { println!("BOARD:\n{}", game); + // TODO + let pos_moves = game.get_max_number_of_possible_moves_with_current_roll(); + println!("Possible moves with current roll = {}", pos_moves); + + if pos_moves == 0 { + println!("NO MOVES - YIELD!"); + game = game.yield_turn(); + continue 'input; + } + let (sn, color) = game.turn(); let cmd = rl.readline_with_initial(&format!("{}> ", color), (&filled, "")); retry = false; @@ -34,15 +44,21 @@ fn main() { match parse_input(line) { Ok(moves) => { println!("{:?}", moves); + + if moves.len() < pos_moves { + println!("It is possible to use {} moves (given {}), please try again.", pos_moves, moves.len()); + retry = true; + continue 'input; + } + let mut next = game.clone(); - for m in moves { + for (nth, m) in moves.into_iter().enumerate() { match next.apply_move(color, m) { Ok(n) => { next = n; - println!("{}", next); } Err(e) => { - println!("Move failed: {:?}", e); + println!("Move #{} ({:?}) failed: {:?}", nth+1, m, e); retry = true; continue 'input; } @@ -103,7 +119,7 @@ fn parse_input(line : &str) -> Fallible> { let line = line.trim(); let mut moves = vec![]; - let fragments = line.split(','); + let fragments = line.split(|c: char| c==','||c==';'||c==' '); // let mut moves = vec![]; for f in fragments {