wip rng seed
This commit is contained in:
parent
44c040b414
commit
0b09a517a2
|
|
@ -1,19 +1,26 @@
|
||||||
|
use dice::DiceRoller;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use store::{CheckerMove, GameEvent, GameState, PlayerId};
|
use store::{CheckerMove, GameEvent, GameState, PlayerId};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct AppArgs {
|
||||||
|
pub seed: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
// Application.
|
// Application.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
// should the application exit?
|
// should the application exit?
|
||||||
pub should_quit: bool,
|
pub should_quit: bool,
|
||||||
pub game: GameState,
|
pub game: GameState,
|
||||||
|
pub dice_roller: DiceRoller,
|
||||||
first_move: Option<CheckerMove>,
|
first_move: Option<CheckerMove>,
|
||||||
player_id: Option<PlayerId>,
|
player_id: Option<PlayerId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
// Constructs a new instance of [`App`].
|
// Constructs a new instance of [`App`].
|
||||||
pub fn new() -> Self {
|
pub fn new(args: AppArgs) -> Self {
|
||||||
// Self::default()
|
// Self::default()
|
||||||
|
|
||||||
let mut state = GameState::default();
|
let mut state = GameState::default();
|
||||||
|
|
@ -23,6 +30,7 @@ impl App {
|
||||||
println!("player_id ? {:?}", player_id);
|
println!("player_id ? {:?}", player_id);
|
||||||
Self {
|
Self {
|
||||||
game: state,
|
game: state,
|
||||||
|
dice_roller: DiceRoller::new(args.seed),
|
||||||
should_quit: false,
|
should_quit: false,
|
||||||
first_move: None,
|
first_move: None,
|
||||||
player_id,
|
player_id,
|
||||||
|
|
@ -171,7 +179,7 @@ Rolled dice : 0 & 0
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
12 11 10 9 8 7 6 5 4 3 2 1
|
12 11 10 9 8 7 6 5 4 3 2 1
|
||||||
";
|
";
|
||||||
let mut app = App::new();
|
let mut app = App::new(AppArgs::default());
|
||||||
app.input("1 4");
|
app.input("1 4");
|
||||||
app.input("1 5");
|
app.input("1 5");
|
||||||
self::assert_eq!(app.display(), expected);
|
self::assert_eq!(app.display(), expected);
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,38 @@
|
||||||
pub mod app;
|
pub mod app;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use app::App;
|
use app::{App, AppArgs};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
|
// see pico-args example at https://github.com/RazrFalcon/pico-args/blob/master/examples/app.rs
|
||||||
|
const HELP: &str = "\
|
||||||
|
Trictrac CLI
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
trictrac-cli [OPTIONS]
|
||||||
|
|
||||||
|
FLAGS:
|
||||||
|
-h, --help Prints help information
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
--seed SEED Sets the random generator seed
|
||||||
|
|
||||||
|
ARGS:
|
||||||
|
<INPUT>
|
||||||
|
";
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
let args = match parse_args() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error: {}.", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// println!("{:#?}", args);
|
||||||
|
|
||||||
// Create an application.
|
// Create an application.
|
||||||
let mut app = App::new();
|
let mut app = App::new(args);
|
||||||
|
|
||||||
// Start the main loop.
|
// Start the main loop.
|
||||||
while !app.should_quit {
|
while !app.should_quit {
|
||||||
|
|
@ -19,3 +45,32 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_args() -> Result<AppArgs, pico_args::Error> {
|
||||||
|
let mut pargs = pico_args::Arguments::from_env();
|
||||||
|
|
||||||
|
// Help has a higher priority and should be handled separately.
|
||||||
|
if pargs.contains(["-h", "--help"]) {
|
||||||
|
print!("{}", HELP);
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = AppArgs {
|
||||||
|
// Parses an optional value that implements `FromStr`.
|
||||||
|
seed: pargs.opt_value_from_str("--seed")?,
|
||||||
|
// Parses an optional value from `&str` using a specified function.
|
||||||
|
// width: pargs.opt_value_from_fn("--width", parse_width)?.unwrap_or(10),
|
||||||
|
};
|
||||||
|
|
||||||
|
// It's up to the caller what to do with the remaining arguments.
|
||||||
|
let remaining = pargs.finish();
|
||||||
|
if !remaining.is_empty() {
|
||||||
|
eprintln!("Warning: unused arguments left: {:?}.", remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn parse_width(s: &str) -> Result<u32, &'static str> {
|
||||||
|
// s.parse().map_err(|_| "not a number")
|
||||||
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,56 @@
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use rand::distributions::{Distribution, Uniform};
|
use rand::distributions::{Distribution, Uniform};
|
||||||
|
use rand::{rngs::StdRng, SeedableRng};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Represents the two dices
|
pub struct DiceRoller {
|
||||||
|
rng: StdRng,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DiceRoller {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiceRoller {
|
||||||
|
fn new(opt_seed: Option<u64>) -> Self {
|
||||||
|
Self {
|
||||||
|
rng: match opt_seed {
|
||||||
|
None => StdRng::from_rng(rand::thread_rng()).unwrap(),
|
||||||
|
Some(seed) => SeedableRng::seed_from_u64(seed),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Roll the dices which generates two random numbers between 1 and 6, replicating a perfect
|
||||||
|
/// dice. We use the operating system's random number generator.
|
||||||
|
pub fn roll(&mut self) -> Dice {
|
||||||
|
let between = Uniform::new_inclusive(1, 6);
|
||||||
|
|
||||||
|
let v = (between.sample(&mut self.rng), between.sample(&mut self.rng));
|
||||||
|
|
||||||
|
Dice { values: (v.0, v.1) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heads or tails
|
||||||
|
// pub fn coin(self) -> bool {
|
||||||
|
// let between = Uniform::new_inclusive(1, 2);
|
||||||
|
// let mut rng = rand::thread_rng();
|
||||||
|
// between.sample(&mut rng) == 1
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the two dice
|
||||||
///
|
///
|
||||||
/// Trictrac is always played with two dices.
|
/// Trictrac is always played with two dice.
|
||||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Deserialize, Default)]
|
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Deserialize, Default)]
|
||||||
pub struct Dices {
|
pub struct Dice {
|
||||||
/// The two dice values
|
/// The two dice values
|
||||||
pub values: (u8, u8),
|
pub values: (u8, u8),
|
||||||
}
|
}
|
||||||
impl Dices {
|
|
||||||
/// Roll the dices which generates two random numbers between 1 and 6, replicating a perfect
|
|
||||||
/// dice. We use the operating system's random number generator.
|
|
||||||
pub fn roll(self) -> Self {
|
|
||||||
let between = Uniform::new_inclusive(1, 6);
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
let v = (between.sample(&mut rng), between.sample(&mut rng));
|
|
||||||
|
|
||||||
Dices { values: (v.0, v.1) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Heads or tails
|
|
||||||
pub fn coin(self) -> bool {
|
|
||||||
let between = Uniform::new_inclusive(1, 2);
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
between.sample(&mut rng) == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
|
impl Dice {
|
||||||
pub fn to_bits_string(self) -> String {
|
pub fn to_bits_string(self) -> String {
|
||||||
format!("{:0>3b}{:0>3b}", self.values.0, self.values.1)
|
format!("{:0>3b}{:0>3b}", self.values.0, self.values.1)
|
||||||
}
|
}
|
||||||
|
|
@ -61,14 +83,21 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_roll() {
|
fn test_roll() {
|
||||||
let dices = Dices::default().roll();
|
let dice = DiceRoller::default().roll();
|
||||||
assert!(dices.values.0 >= 1 && dices.values.0 <= 6);
|
assert!(dice.values.0 >= 1 && dice.values.0 <= 6);
|
||||||
assert!(dices.values.1 >= 1 && dices.values.1 <= 6);
|
assert!(dice.values.1 >= 1 && dice.values.1 <= 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_seed() {
|
||||||
|
let dice = DiceRoller::new(Some(123)).roll();
|
||||||
|
assert!(dice.values.0 == 3);
|
||||||
|
assert!(dice.values.1 == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_to_bits_string() {
|
fn test_to_bits_string() {
|
||||||
let dices = Dices { values: (4, 2) };
|
let dice = Dice { values: (4, 2) };
|
||||||
assert!(dices.to_bits_string() == "100010");
|
assert!(dice.to_bits_string() == "100010");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! # Play a TricTrac Game
|
//! # Play a TricTrac Game
|
||||||
use crate::board::{Board, CheckerMove, Field, Move};
|
use crate::board::{Board, CheckerMove, Field, Move};
|
||||||
use crate::dice::{Dices, Roll};
|
use crate::dice::{Dice, DiceRoller, Roll};
|
||||||
use crate::player::{Color, Player, PlayerId};
|
use crate::player::{Color, Player, PlayerId};
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
|
|
@ -39,7 +39,7 @@ pub struct GameState {
|
||||||
pub players: HashMap<PlayerId, Player>,
|
pub players: HashMap<PlayerId, Player>,
|
||||||
pub history: Vec<GameEvent>,
|
pub history: Vec<GameEvent>,
|
||||||
/// last dice pair rolled
|
/// last dice pair rolled
|
||||||
pub dices: Dices,
|
pub dice: Dice,
|
||||||
/// true if player needs to roll first
|
/// true if player needs to roll first
|
||||||
roll_first: bool,
|
roll_first: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +48,7 @@ pub struct GameState {
|
||||||
impl fmt::Display for GameState {
|
impl fmt::Display for GameState {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
s.push_str(&format!("Dices: {:?}\n", self.dices));
|
s.push_str(&format!("Dice: {:?}\n", self.dice));
|
||||||
// s.push_str(&format!("Who plays: {}\n", self.who_plays().map(|player| &player.name ).unwrap_or("")));
|
// s.push_str(&format!("Who plays: {}\n", self.who_plays().map(|player| &player.name ).unwrap_or("")));
|
||||||
s.push_str(&format!("Board: {:?}\n", self.board));
|
s.push_str(&format!("Board: {:?}\n", self.board));
|
||||||
write!(f, "{}", s)
|
write!(f, "{}", s)
|
||||||
|
|
@ -64,7 +64,7 @@ impl Default for GameState {
|
||||||
active_player_id: 0,
|
active_player_id: 0,
|
||||||
players: HashMap::new(),
|
players: HashMap::new(),
|
||||||
history: Vec::new(),
|
history: Vec::new(),
|
||||||
dices: Dices::default(),
|
dice: Dice::default(),
|
||||||
roll_first: true,
|
roll_first: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +109,7 @@ impl GameState {
|
||||||
pos_bits.push_str(step_bits);
|
pos_bits.push_str(step_bits);
|
||||||
|
|
||||||
// dice roll -> 6 bits
|
// dice roll -> 6 bits
|
||||||
let dice_bits = self.dices.to_bits_string();
|
let dice_bits = self.dice.to_bits_string();
|
||||||
pos_bits.push_str(&dice_bits);
|
pos_bits.push_str(&dice_bits);
|
||||||
|
|
||||||
// points 10bits x2 joueurs = 20bits
|
// points 10bits x2 joueurs = 20bits
|
||||||
|
|
@ -243,7 +243,7 @@ impl GameState {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check moves conforms to the dices
|
// Check moves conforms to the dice
|
||||||
if !self.moves_follows_dices(color, moves) {
|
if !self.moves_follows_dices(color, moves) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -278,7 +278,7 @@ impl GameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn moves_follows_dices(&self, color: &Color, moves: &(CheckerMove, CheckerMove)) -> bool {
|
fn moves_follows_dices(&self, color: &Color, moves: &(CheckerMove, CheckerMove)) -> bool {
|
||||||
let (dice1, dice2) = self.dices.values;
|
let (dice1, dice2) = self.dice.values;
|
||||||
let (move1, move2): &(CheckerMove, CheckerMove) = moves.into();
|
let (move1, move2): &(CheckerMove, CheckerMove) = moves.into();
|
||||||
let dist1 = (move1.get_to() - move1.get_from()) as u8;
|
let dist1 = (move1.get_to() - move1.get_from()) as u8;
|
||||||
let dist2 = (move2.get_to() - move2.get_from()) as u8;
|
let dist2 = (move2.get_to() - move2.get_from()) as u8;
|
||||||
|
|
@ -473,9 +473,9 @@ pub enum GameEvent {
|
||||||
|
|
||||||
impl Roll for GameState {
|
impl Roll for GameState {
|
||||||
fn roll(&mut self) -> &mut Self {
|
fn roll(&mut self) -> &mut Self {
|
||||||
self.dices = self.dices.roll();
|
self.dice = self.dice.roll();
|
||||||
if self.who_plays().is_none() {
|
if self.who_plays().is_none() {
|
||||||
let active_color = match self.dices.coin() {
|
let active_color = match self.dice.coin() {
|
||||||
false => Color::Black,
|
false => Color::Black,
|
||||||
true => Color::White,
|
true => Color::White,
|
||||||
};
|
};
|
||||||
|
|
@ -504,7 +504,7 @@ impl Move for GameState {
|
||||||
// self.board.set(player, new_position as usize, 1)?;
|
// self.board.set(player, new_position as usize, 1)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// switch to other player if all dices have been consumed
|
// switch to other player if all dice have been consumed
|
||||||
self.switch_active_player();
|
self.switch_active_player();
|
||||||
self.roll_first = true;
|
self.roll_first = true;
|
||||||
|
|
||||||
|
|
@ -530,7 +530,7 @@ impl Move for GameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if dice value has actually been rolled
|
// check if dice value has actually been rolled
|
||||||
if dice != self.dices.values.0 && dice != self.dices.values.1 {
|
if dice != self.dice.values.0 && dice != self.dice.values.1 {
|
||||||
return Err(Error::DiceInvalid);
|
return Err(Error::DiceInvalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -589,16 +589,16 @@ mod tests {
|
||||||
goes_first: player_id,
|
goes_first: player_id,
|
||||||
});
|
});
|
||||||
state.consume(&GameEvent::Roll { player_id });
|
state.consume(&GameEvent::Roll { player_id });
|
||||||
let dices = state.dices.values;
|
let dice = state.dice.values;
|
||||||
let moves = (
|
let moves = (
|
||||||
CheckerMove::new(1, (1 + dices.0).into()).unwrap(),
|
CheckerMove::new(1, (1 + dice.0).into()).unwrap(),
|
||||||
CheckerMove::new((1 + dices.0).into(), (1 + dices.0 + dices.1).into()).unwrap(),
|
CheckerMove::new((1 + dice.0).into(), (1 + dice.0 + dice.1).into()).unwrap(),
|
||||||
);
|
);
|
||||||
assert!(state.moves_follows_dices(&Color::White, &moves));
|
assert!(state.moves_follows_dices(&Color::White, &moves));
|
||||||
|
|
||||||
let badmoves = (
|
let badmoves = (
|
||||||
CheckerMove::new(1, (2 + dices.0).into()).unwrap(),
|
CheckerMove::new(1, (2 + dice.0).into()).unwrap(),
|
||||||
CheckerMove::new((1 + dices.0).into(), (1 + dices.0 + dices.1).into()).unwrap(),
|
CheckerMove::new((1 + dice.0).into(), (1 + dice.0 + dice.1).into()).unwrap(),
|
||||||
);
|
);
|
||||||
assert!(!state.moves_follows_dices(&Color::White, &badmoves));
|
assert!(!state.moves_follows_dices(&Color::White, &badmoves));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue