diff --git a/Cargo.lock b/Cargo.lock index d9a25e2..59b01ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,6 +223,24 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -296,12 +314,43 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "serde" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "subtle" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "termcolor" version = "1.3.0" @@ -314,6 +363,9 @@ dependencies = [ [[package]] name = "trictrac" version = "0.1.0" +dependencies = [ + "serde", +] [[package]] name = "trictrac-client" @@ -338,6 +390,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + [[package]] name = "universal-hash" version = "0.5.1" diff --git a/server/Cargo.toml b/server/Cargo.toml index bbea34c..a5f4508 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +store = { path = "../store" } env_logger = "0.10.0" log = "0.4.20" pico-args = "0.5.0" diff --git a/server/src/main.rs b/server/src/main.rs index 15cda61..9cba4bd 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,6 +1,7 @@ use log::{info, trace}; use std::net::{SocketAddr, UdpSocket, IpAddr, Ipv4Addr}; use std::time::{Duration, Instant, SystemTime}; +use store::EndGameReason; use renet::{ transport::{ @@ -45,13 +46,80 @@ fn main() { while let Some(event) = server.get_event() { match event { ServerEvent::ClientConnected { client_id } => { + let user_data = transport.user_data(client_id).unwrap(); info!("🎉 Client {} connected.", client_id); + + // Tell the recently joined player about the other player + for (player_id, player) in game_state.players.iter() { + let event = store::GameEvent::PlayerJoined { + player_id: *player_id, + name: player.name.clone(), + }; + server.send_message(client_id, 0, bincode::serialize(&event).unwrap()); + } + + // Add the new player to the game + let event = store::GameEvent::PlayerJoined { + player_id: client_id, + name: name_from_user_data(&user_data), + }; + game_state.consume(&event); + + // Tell all players that a new player has joined + server.broadcast_message(0, bincode::serialize(&event).unwrap()); + + info!("Client {} connected.", id); + // In TicTacTussle the game can begin once two players has joined + if game_state.players.len() == 2 { + let event = store::GameEvent::BeginGame { goes_first: client_id }; + game_state.consume(&event); + server.broadcast_message(0, bincode::serialize(&event).unwrap()); + trace!("The game gas begun"); + } } ServerEvent::ClientDisconnected { client_id, reason } => { - info!("👋 Client {} disconnected", client_id); + // First consume a disconnect event + let event = store::GameEvent::PlayerDisconnected { player_id: client_id }; + game_state.consume(&event); + server.broadcast_message(0, bincode::serialize(&event).unwrap()); + info!("Client {} disconnected", client_id); + + // Then end the game, since tic tac toe can't go on with a single player + let event = store::GameEvent::EndGame { + reason: EndGameReason::PlayerLeft { player_id: client_id }, + }; + game_state.consume(&event); + server.broadcast_message(0, bincode::serialize(&event).unwrap()); + + // NOTE: Since we don't authenticate users we can't do any reconnection attempts. + // We simply have no way to know if the next user is the same as the one that disconnected. } } } - std::thread::sleep(Duration::from_millis(50)); - } + + // Receive GameEvents from clients. Broadcast valid events. + for client_id in server.clients_id().into_iter() { + while let Some(message) = server.receive_message(client_id, 0) { + if let Ok(event) = bincode::deserialize::(&message) { + if game_state.validate(&event) { + game_state.consume(&event); + trace!("Player {} sent:\n\t{:#?}", client_id, event); + server.broadcast_message(0, bincode::serialize(&event).unwrap()); + + // Determine if a player has won the game + if let Some(winner) = game_state.determine_winner() { + let event = store::GameEvent::EndGame { + reason: store::EndGameReason::PlayerWon { winner }, + }; + server.broadcast_message(0, bincode::serialize(&event).unwrap()); + } + } else { + warn!("Player {} sent invalid event:\n\t{:#?}", client_id, event); + } + } + } + } + + server.send_packets().unwrap(); + thread::sleep(Duration::from_millis(50));} } diff --git a/store/Cargo.toml b/store/Cargo.toml index 3d73498..772a5f9 100644 --- a/store/Cargo.toml +++ b/store/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +serde = { version = "1.0", features = ["derive"] } diff --git a/store/src/board.rs b/store/src/board.rs index 17703b5..cd2f80e 100644 --- a/store/src/board.rs +++ b/store/src/board.rs @@ -1,12 +1,10 @@ -use crate::Player; +use crate::player::{Player, Color}; use crate::Error; use serde::{Deserialize, Serialize}; /// Represents the Backgammon board /// -/// A Backgammon board consists of 24 fields, each of which can hold 0 or more checkers. In -/// addition there is a bar to hold checkers that have been hit and an off area to hold checkers -/// that have been removed from the board. +/// A Tric-Trac board consists of 24 fields, each of which can hold 0 or more checkers. /// /// ``` /// # fn foo() {} @@ -16,7 +14,7 @@ use serde::{Deserialize, Serialize}; /// // | X O | | O | +-------+ /// // | X | | O | /// // | X | | O | -/// // | |BAR| | +/// // | | | | /// // | O | | X | /// // | O | | X | /// // | O X | | X | +-------+ @@ -64,7 +62,6 @@ impl Board { BoardDisplay { board, - bar: self.get_bar(), off: self.get_off(), } } @@ -92,32 +89,25 @@ impl Board { return Err(Error::FieldBlocked); } - match player { - Player::Player0 => { + match player.color { + Color::White => { let new = self.raw_board.0.board[field] as i8 + amount; if new < 0 { return Err(Error::MoveInvalid); } self.raw_board.0.board[field] = new as u8; - // in case one opponent's checker is hit, move it to the bar - self.raw_board.1.bar += self.raw_board.1.board[23 - field]; - self.raw_board.1.board[23 - field] = 0; Ok(()) } - Player::Player1 => { + Color::Black => { let new = self.raw_board.1.board[field] as i8 + amount; if new < 0 { return Err(Error::MoveInvalid); } self.raw_board.1.board[field] = new as u8; - // in case one opponent's checker is hit, move it to the bar - self.raw_board.0.bar += self.raw_board.0.board[23 - field]; - self.raw_board.0.board[23 - field] = 0; Ok(()) } - Player::Nobody => Err(Error::PlayerInvalid), } } @@ -127,64 +117,38 @@ impl Board { return Err(Error::FieldInvalid); } - match player { - Player::Player0 => { + match player.color { + Color::White => { if self.raw_board.1.board[23 - field] > 1 { Ok(true) } else { Ok(false) } } - Player::Player1 => { + Color::Black => { if self.raw_board.0.board[23 - field] > 1 { Ok(true) } else { Ok(false) } } - Player::Nobody => Err(Error::PlayerInvalid), - } - } - - /// Set checkers for a player on the bar. This method adds amount to the already existing - /// checkers there. - pub fn set_bar(&mut self, player: Player, amount: i8) -> Result<(), Error> { - match player { - Player::Player0 => { - let new = self.raw_board.0.bar as i8 + amount; - if new < 0 { - return Err(Error::MoveInvalid); - } - self.raw_board.0.bar = new as u8; - Ok(()) - } - Player::Player1 => { - let new = self.raw_board.1.bar as i8 + amount; - if new < 0 { - return Err(Error::MoveInvalid); - } - self.raw_board.1.bar = new as u8; - Ok(()) - } - Player::Nobody => Err(Error::PlayerInvalid), } } /// Set checkers for a player off the board. This method adds amount to the already existing /// checkers there. pub fn set_off(&mut self, player: Player, amount: u8) -> Result<(), Error> { - match player { - Player::Player0 => { + match player.color { + Color::White => { let new = self.raw_board.0.off + amount; self.raw_board.0.off = new; Ok(()) } - Player::Player1 => { + Color::Black => { let new = self.raw_board.1.off + amount; self.raw_board.1.off = new; Ok(()) } - Player::Nobody => Err(Error::PlayerInvalid), } } } @@ -193,7 +157,6 @@ impl Board { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] struct PlayerBoard { board: [u8; 24], - bar: u8, off: u8, } @@ -203,7 +166,6 @@ impl Default for PlayerBoard { board: [ 0, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, ], - bar: 0, off: 0, } } @@ -216,11 +178,6 @@ pub trait Move { where Self: Sized; - /// Move a checker from bar - fn move_checker_from_bar(&mut self, player: Player, dice: u8) -> Result<&mut Self, Error> - where - Self: Sized; - /// Move permitted fn move_permitted(&mut self, player: Player, dice: u8) -> Result<&mut Self, Error> where @@ -243,7 +200,6 @@ mod tests { PlayerBoard::default(), PlayerBoard { board: [0, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,], - bar: 0, off: 0 } ); @@ -258,18 +214,11 @@ mod tests { board: [ -2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, -5, 5, 0, 0, 0, -3, 0, -5, 0, 0, 0, 0, 2, ], - bar: (0, 0), off: (0, 0) } ); } - #[test] - fn get_bar() { - let board = Board::new(); - assert_eq!(board.get_bar(), (0, 0)); - } - #[test] fn get_off() { let board = Board::new(); @@ -279,7 +228,8 @@ mod tests { #[test] fn set_player0() -> Result<(), Error> { let mut board = Board::new(); - board.set(Player::Player0, 1, 1)?; + let player = Player {name: "".into(), color: Color::White}; + board.set(player, 1, 1)?; assert_eq!(board.get().board[1], 1); Ok(()) } @@ -287,31 +237,17 @@ mod tests { #[test] fn set_player1() -> Result<(), Error> { let mut board = Board::new(); - board.set(Player::Player1, 2, 1)?; + let player = Player {name: "".into(), color: Color::Black}; + board.set(player, 2, 1)?; assert_eq!(board.get().board[21], -1); Ok(()) } - #[test] - fn set_player0_bar() -> Result<(), Error> { - let mut board = Board::new(); - board.set_bar(Player::Player0, 1)?; - assert_eq!(board.get().bar.0, 1); - Ok(()) - } - - #[test] - fn set_player1_bar() -> Result<(), Error> { - let mut board = Board::new(); - board.set_bar(Player::Player1, 1)?; - assert_eq!(board.get().bar.1, 1); - Ok(()) - } - #[test] fn set_player0_off() -> Result<(), Error> { let mut board = Board::new(); - board.set_off(Player::Player0, 1)?; + let player = Player {name: "".into(), color: Color::White}; + board.set_off(player, 1)?; assert_eq!(board.get().off.0, 1); Ok(()) } @@ -319,7 +255,8 @@ mod tests { #[test] fn set_player1_off() -> Result<(), Error> { let mut board = Board::new(); - board.set_off(Player::Player1, 1)?; + let player = Player {name: "".into(), color: Color::Black}; + board.set_off(player, 1)?; assert_eq!(board.get().off.1, 1); Ok(()) } @@ -327,108 +264,89 @@ mod tests { #[test] fn set_player1_off1() -> Result<(), Error> { let mut board = Board::new(); - board.set_off(Player::Player1, 1)?; - board.set_off(Player::Player1, 1)?; + let player = Player {name: "".into(), color: Color::Black}; + board.set_off(player, 1)?; + board.set_off(player, 1)?; assert_eq!(board.get().off.1, 2); Ok(()) } - #[test] - fn set_invalid_player() { - let mut board = Board::new(); - assert!(board.set(Player::Nobody, 0, 1).is_err()); - assert!(board.set_bar(Player::Nobody, 1).is_err()); - assert!(board.set_off(Player::Nobody, 1).is_err()); - } - #[test] fn blocked_player0() -> Result<(), Error> { let board = Board::new(); - assert!(board.blocked(Player::Player0, 0)?); + assert!(board.blocked(Player { name:"".into(), color: Color::White }, 0)?); Ok(()) } #[test] fn blocked_player1() -> Result<(), Error> { let board = Board::new(); - assert!(board.blocked(Player::Player1, 0)?); + assert!(board.blocked(Player { name:"".into(), color: Color::Black }, 0)?); Ok(()) } #[test] fn blocked_player0_a() -> Result<(), Error> { let mut board = Board::new(); - board.set(Player::Player1, 1, 2)?; - assert!(board.blocked(Player::Player0, 22)?); + board.set(Player { name:"".into(), color: Color::Black }, 1, 2)?; + assert!(board.blocked(Player { name:"".into(), color: Color::White }, 22)?); Ok(()) } #[test] fn blocked_player1_a() -> Result<(), Error> { let mut board = Board::new(); - board.set(Player::Player0, 1, 2)?; - assert!(board.blocked(Player::Player1, 22)?); + board.set(Player { name:"".into(), color: Color::White }, 1, 2)?; + assert!(board.blocked(Player { name:"".into(), color: Color::Black }, 22)?); Ok(()) } - #[test] - fn blocked_invalid_player() { - let board = Board::new(); - assert!(board.blocked(Player::Nobody, 0).is_err()); - } - #[test] fn blocked_invalid_field() { let board = Board::new(); - assert!(board.blocked(Player::Player0, 24).is_err()); + assert!(board.blocked(Player { name:"".into(), color: Color::White }, 24).is_err()); } #[test] fn set_field_with_1_checker_player0_a() -> Result<(), Error> { let mut board = Board::new(); - board.set(Player::Player0, 1, 1)?; - board.set(Player::Player1, 22, 1)?; + board.set(Player { name:"".into(), color: Color::White }, 1, 1)?; + board.set(Player { name:"".into(), color: Color::Black }, 22, 1)?; assert_eq!(board.get().board[1], -1); - assert_eq!(board.get().bar.0, 1); Ok(()) } #[test] fn set_field_with_1_checker_player0_b() -> Result<(), Error> { let mut board = Board::new(); - board.set(Player::Player0, 1, 1)?; - board.set_bar(Player::Player0, 5)?; - board.set(Player::Player1, 22, 1)?; + board.set(Player { name:"".into(), color: Color::White }, 1, 1)?; + board.set(Player { name:"".into(), color: Color::Black }, 22, 1)?; assert_eq!(board.get().board[1], -1); - assert_eq!(board.get().bar.0, 6); Ok(()) } #[test] fn set_field_with_1_checker_player1_a() -> Result<(), Error> { let mut board = Board::new(); - board.set(Player::Player1, 1, 1)?; - board.set(Player::Player0, 22, 1)?; + board.set(Player { name:"".into(), color: Color::Black }, 1, 1)?; + board.set(Player { name:"".into(), color: Color::White }, 22, 1)?; assert_eq!(board.get().board[22], 1); - assert_eq!(board.get().bar.1, 1); Ok(()) } #[test] fn set_field_with_1_checker_player1_b() -> Result<(), Error> { let mut board = Board::new(); - board.set(Player::Player1, 1, 1)?; - board.set_bar(Player::Player1, 5)?; - board.set(Player::Player0, 22, 1)?; + board.set(Player { name:"".into(), color: Color::Black }, 1, 1)?; + board.set(Player { name:"".into(), color: Color::White }, 22, 1)?; assert_eq!(board.get().board[22], 1); - assert_eq!(board.get().bar.1, 6); Ok(()) } #[test] fn set_field_with_2_checkers_player0_a() -> Result<(), Error> { let mut board = Board::new(); - board.set(Player::Player0, 23, 2)?; + board.set(Player { name:"".into(), color: Color::White }, 23, 2)?; assert_eq!(board.get().board[23], 4); Ok(()) } @@ -436,7 +354,7 @@ mod tests { #[test] fn set_field_with_2_checkers_player0_b() -> Result<(), Error> { let mut board = Board::new(); - board.set(Player::Player0, 23, -1)?; + board.set(Player { name:"".into(), color: Color::White }, 23, -1)?; assert_eq!(board.get().board[23], 1); Ok(()) } @@ -444,24 +362,24 @@ mod tests { #[test] fn set_field_blocked() { let mut board = Board::new(); - assert!(board.set(Player::Player0, 0, 2).is_err()); + assert!(board.set(Player { name:"".into(), color: Color::White }, 0, 2).is_err()); } #[test] fn set_wrong_field1() { let mut board = Board::new(); - assert!(board.set(Player::Player0, 50, 2).is_err()); + assert!(board.set(Player { name:"".into(), color: Color::White }, 50, 2).is_err()); } #[test] fn set_wrong_amount0() { let mut board = Board::new(); - assert!(board.set(Player::Player0, 23, -3).is_err()); + assert!(board.set(Player { name:"".into(), color: Color::White }, 23, -3).is_err()); } #[test] fn set_wrong_amount1() { let mut board = Board::new(); - assert!(board.set(Player::Player1, 23, -3).is_err()); + assert!(board.set(Player { name:"".into(), color: Color::Black }, 23, -3).is_err()); } } diff --git a/store/src/game.rs b/store/src/game.rs index 9b4bca5..601435b 100644 --- a/store/src/game.rs +++ b/store/src/game.rs @@ -1,8 +1,7 @@ //! # Play a TricTrac Game -use crate::CurrentPlayer; -use crate::{Player, PlayerId}; -use crate::{Board, Move}; -use crate::{Dices, Roll}; +use crate::player::{Color, Player, PlayerId}; +use crate::board::{Board, Move}; +use crate::dice::{Dices, Roll}; use crate::Error; use serde::{Deserialize, Serialize}; @@ -36,7 +35,7 @@ impl fmt::Display for GameState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut s = String::new(); s.push_str(&format!("Dices: {:?}\n", self.dices)); - s.push_str(&format!("Who plays: {}\n", self.who_plays)); + s.push_str(&format!("Who plays: {}\n", self.who_plays().map(|player| player.name ).unwrap_or("".to_string()))); s.push_str(&format!("Board: {:?}\n", self.board.get())); write!(f, "{}", s) } @@ -62,6 +61,32 @@ impl GameState { GameState::default() } + pub fn who_plays(&self) -> &Option<&Player> { + &self.players.get(&self.active_player_id) + } + + pub fn switch_active_player(&mut self) { + let other_player_id = self.players.iter() + .filter(|(id, player)| **id != self.active_player_id ) + .map(|(id, player)| *id ) + .next(); + self.active_player_id = other_player_id.unwrap_or(0); + } + + pub fn player_id_by_color(&self, color: Color) -> Option<&PlayerId> { + self.players.iter() + .filter(|(id, player)| player.color == color) + .map(|(id, player)| id ) + .next() + } + + pub fn player_id(&self, player: &Player) -> Option<&PlayerId> { + self.players.iter() + .filter(|(id, candidate)| player.color == candidate.color) + .map(|(id, candidate)| id ) + .next() + } + /// Determines whether an event is valid considering the current GameState pub fn validate(&self, event: &GameEvent) -> bool { use GameEvent::*; @@ -109,7 +134,7 @@ impl GameState { } } - Move { player_id, at } => { + Move { player_id, from, to } => { // Check player exists if !self.players.contains_key(player_id) { return false; @@ -120,7 +145,7 @@ impl GameState { } // Check that the tile index is inside the board - if *at > 23 { + if *to > 23 { return false; } } @@ -141,10 +166,12 @@ impl GameState { } EndGame { reason: _ } => self.stage = Stage::Ended, PlayerJoined { player_id, name } => { + let color = if self.players.len() > 0 { Color::White } else { Color::Black }; self.players.insert( *player_id, Player { name: name.to_string(), + color }, ); } @@ -155,8 +182,8 @@ impl GameState { } Move { player_id, from, to } => { let player = self.players.get(player_id).unwrap(); - self.board.set(player, at, 1); - self.board.set(player, at, 1); + self.board.set(*player, *from, 0 as i8); + self.board.set(*player, *to, 1 as i8); self.active_player_id = self .players .keys() @@ -168,8 +195,13 @@ impl GameState { self.history.push(valid_event.clone()); } -} + /// Determines if someone has won the game + pub fn determine_winner(&self) -> Option { + None + } + +} /// The reasons why a game could end #[derive(Debug, Clone, Copy, Serialize, PartialEq, Deserialize)] @@ -188,11 +220,9 @@ pub enum GameEvent { PlayerJoined { player_id: PlayerId, name: String }, PlayerDisconnected { player_id: PlayerId }, Roll { player_id: PlayerId }, - Move { player_id: PlayerId, at: usize }, + Move { player_id: PlayerId, from: usize, to: usize }, } - - impl Roll for GameState { fn roll(&mut self) -> Result<&mut Self, Error> { if !self.dices.consumed.0 @@ -202,18 +232,12 @@ impl Roll for GameState { } self.dices = self.dices.roll(); - if self.who_plays == Player::Nobody { + if self.who_plays().is_none() { let diff = self.dices.values.0 - self.dices.values.1; - match diff { - 0 => { - self.who_plays = Player::Nobody; - } - _ if diff > 0 => { - self.who_plays = Player::Player0; - } - _ => { - self.who_plays = Player::Player1; - } + let active_color = if diff < 0 { Color::Black } else { Color::White }; + let color_player_id = self.player_id_by_color(active_color); + if color_player_id.is_some(){ + self.active_player_id = *color_player_id.unwrap(); } } Ok(self) @@ -254,36 +278,7 @@ impl Move for GameState { if self.dices.consumed.0 && self.dices.consumed.1 { - self.who_plays = self.who_plays.other(); - self.roll_first = true; - } - - Ok(self) - } - - fn move_checker_from_bar(&mut self, player: Player, dice: u8) -> Result<&mut Self, Error> { - // check if move is permitted - let _ = self.move_permitted(player, dice)?; - - // check if the dice value has been consumed - if (dice == self.dices.values.0 && self.dices.consumed.0) - || (dice == self.dices.values.1 && self.dices.consumed.1) - { - return Err(Error::MoveInvalid); - } - - // set dice value to consumed - if dice == self.dices.values.0 && !self.dices.consumed.0 { - self.dices.consumed.0 = true; - } else if dice == self.dices.values.1 && !self.dices.consumed.1 { - self.dices.consumed.1 = true; - } - - // switch to other player if all dices have been consumed - if self.dices.consumed.0 - && self.dices.consumed.1 - { - self.who_plays = self.who_plays.other(); + self.switch_active_player(); self.roll_first = true; } @@ -292,13 +287,14 @@ impl Move for GameState { /// Implements checks to validate if the player is allowed to move fn move_permitted(&mut self, player: Player, dice: u8) -> Result<&mut Self, Error> { + let maybe_player_id = self.player_id(&player); // check if player is allowed to move - if player != self.who_plays { + if maybe_player_id != Some(&self.active_player_id) { return Err(Error::NotYourTurn); } // if player is nobody, you can not play and have to roll first - if self.who_plays == Player::Nobody { + if maybe_player_id.is_none() { return Err(Error::RollFirst); } @@ -315,19 +311,3 @@ impl Move for GameState { Ok(self) } } - -#[cfg(test)] -mod tests { - use super::*; - - // Test Display trait for Game - #[test] - fn test_display() { - let g = Game::new(); - assert_eq!( - format!("{}", g), - "Rules: Points: 7\nDices: Dices { values: (0, 0), consumed: (false, false, false, false) }\nWho plays: Nobody\nBoard: BoardDisplay { board: [-2, 0, 0, 0, 0, 5, 0, 3, 0, 0, 0, -5, 5, 0, 0, 0, -3, 0, -5, 0, 0, 0, 0, 2], bar: (0, 0), off: (0, 0) }\n" - ); - } - -} diff --git a/store/src/lib.rs b/store/src/lib.rs index bc0bac1..1b97e40 100644 --- a/store/src/lib.rs +++ b/store/src/lib.rs @@ -1,6 +1,10 @@ mod game; -pub use game::Game; mod player; pub use player::Player; +mod error; +pub use error::Error; + +mod board; +mod dice; diff --git a/store/src/player.rs b/store/src/player.rs index efa5fa6..98003df 100644 --- a/store/src/player.rs +++ b/store/src/player.rs @@ -3,8 +3,9 @@ use std::fmt; // This just makes it easier to dissern between a player id and any ol' u64 -type PlayerId = u64; +pub type PlayerId = u64; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Color { White, Black,