this.state = "LoggedIn";
break;
case "JoinGameSuccess":
- this.game = message.game;
+ this.game = {summary: message.summary, actions: message.actions};
this.create_game_display();
this.state = "InGame";
break;
break;
case "Leave":
this.game.seats.delete(user_action.username);
- this.game.hands.delete(user_action.username);
this.chatroom_chat.append(this.leave_element(user_action.username));
if (!initialising && user_action.username === this.auth.username) {
this.send({type: "JoinLobby", filter: ""});
redraw_chatroom() {
this.chatroom_chat.textContent = "";
- for (const user_action of this.game.state.actions) {
+ for (const user_action of this.game.actions) {
this.add_action(user_action, true);
}
}
redraw_knock_out_whist() {
- for (const user_action of this.game.state.actions) {
+ for (const user_action of this.game.actions) {
this.add_action(user_action, true);
}
}
--- /dev/null
+use std::fmt::{Debug, Display, Formatter};
+
+use crate::card::{Card, Suit};
+use crate::username::Username;
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct UserAction {
+ pub username: Username,
+ pub action: Action,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(transparent)]
+pub struct ValidatedUserAction(pub UserAction);
+
+impl ValidatedUserAction {
+ pub fn view_for(&self, username: Username) -> UserAction {
+ UserAction {
+ username: self.0.username.clone(),
+ action: if username == self.0.username { self.0.action.clone() } else { self.0.action.anonymise() },
+ }
+ }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(tag = "action")]
+pub enum Action {
+ Join { seat: u32, chips: u64 },
+ AddOn { chips: u64 },
+ NextToDeal,
+ CommunityCard { card: Card },
+ ReceiveCard { card: Option<Card> },
+ EndDeal,
+ RevealCard { card: Card },
+ PlayCard { card: Card },
+ ChooseTrumps { suit: Suit },
+ Fold,
+ Bet { chips: u64 },
+ WinTrick,
+ WinHand { chips: u64 },
+ WinGame,
+ Message { message: String },
+ Leave,
+ KnockedOut,
+}
+
+impl Action {
+ pub fn anonymise(&self) -> Self {
+ match self {
+ Action::ReceiveCard{..} => Action::ReceiveCard{card: None},
+ action => action.clone(),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Serialize)]
+pub enum ActionError {
+ NotAuthorised,
+ AlreadyJoined,
+ GameHasStarted,
+ SeatNotAvailable,
+ NoSeatAvailable,
+ OutOfTurn,
+ CardNotPlayable,
+ InvalidActionForGameType,
+}
+
+impl Display for ActionError {
+ fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
+ match self {
+ ActionError::NotAuthorised => f.write_str("NotAuthorised"),
+ ActionError::AlreadyJoined => f.write_str("AlreadyJoined"),
+ ActionError::GameHasStarted => f.write_str("GameHasStarted"),
+ ActionError::SeatNotAvailable => f.write_str("SeatNotAvailable"),
+ ActionError::NoSeatAvailable => f.write_str("NoSeatAvailable"),
+ ActionError::OutOfTurn => f.write_str("OutOfTurn"),
+ ActionError::CardNotPlayable => f.write_str("CardNotPlayable"),
+ ActionError::InvalidActionForGameType => f.write_str("InvalidActionForGameType"),
+ }
+ }
+}
Leave,
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatroomSettings {
title: String,
}
-pub mod chatroom;
+mod action;
+mod chatroom;
+mod whist;
use std::collections::HashSet;
use std::fmt::{Debug, Display, Formatter};
-use crate::card::{Card, Suit};
use crate::username::Username;
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(transparent)]
-pub struct ValidatedUserAction(UserAction);
+use self::chatroom::{Chatroom, ChatroomSettings};
+use self::whist::{KnockOutWhist, KnockOutWhistSettings};
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct UserAction {
- pub username: Username,
- pub action: Action,
-}
-
-impl ValidatedUserAction {
- pub fn view_for(&self, username: Username) -> UserAction {
- UserAction {
- username: self.0.username.clone(),
- action: if username == self.0.username { self.0.action.clone() } else { self.0.action.anonymise() },
- }
- }
-}
+pub use self::action::{Action, ActionError, UserAction, ValidatedUserAction};
pub trait Game : Debug + CloneBox + Send + Sync {
fn id(&self) -> u32;
fn next_dealer_action(&self) -> Option<ValidatedUserAction>;
}
-pub trait CloneBox {
+trait CloneBox {
fn clone_box(&self) -> Box<dyn Game>;
}
}
impl dyn Game {
- pub fn new(summary: GameSummary) -> Box<Self> {
- todo!()
+ pub fn new(GameSummary{id, settings}: GameSummary) -> Box<Self> {
+ match settings {
+ GameSettings::Chatroom(settings) => Box::new(Chatroom::new(id, settings)),
+ _ => todo!()
+ }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "format")]
pub enum GameSettings {
- Chatroom {
- title: String,
- max_players: u32,
- },
- KnockOutWhist {
- title: String,
- max_players: u32,
- },
+ Chatroom(ChatroomSettings),
+ KnockOutWhist(KnockOutWhistSettings),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
}
}
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(tag = "action")]
-pub enum Action {
- Join { seat: u32, chips: u64 },
- AddOn { chips: u64 },
- NextToDeal,
- CommunityCard { card: Card },
- ReceiveCard { card: Option<Card> },
- EndDeal,
- RevealCard { card: Card },
- PlayCard { card: Card },
- ChooseTrumps { suit: Suit },
- Fold,
- Bet { chips: u64 },
- WinTrick,
- WinHand { chips: u64 },
- WinGame,
- Message { message: String },
- Leave,
- KnockedOut,
-}
-
-impl Action {
- pub fn anonymise(&self) -> Self {
- match self {
- Action::ReceiveCard{..} => Action::ReceiveCard{card: None},
- action => action.clone(),
- }
- }
-}
-
-#[derive(Debug, Clone, Serialize)]
-pub enum ActionError {
- NotAuthorised,
- AlreadyJoined,
- GameHasStarted,
- SeatNotAvailable,
- NoSeatAvailable,
- OutOfTurn,
- CardNotPlayable,
- InvalidActionForGameType,
-}
-
-impl Display for ActionError {
- fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
- match self {
- ActionError::NotAuthorised => f.write_str("NotAuthorised"),
- ActionError::AlreadyJoined => f.write_str("AlreadyJoined"),
- ActionError::GameHasStarted => f.write_str("GameHasStarted"),
- ActionError::SeatNotAvailable => f.write_str("SeatNotAvailable"),
- ActionError::NoSeatAvailable => f.write_str("NoSeatAvailable"),
- ActionError::OutOfTurn => f.write_str("OutOfTurn"),
- ActionError::CardNotPlayable => f.write_str("CardNotPlayable"),
- ActionError::InvalidActionForGameType => f.write_str("InvalidActionForGameType"),
- }
- }
-}
-
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GameSummary {
id: u32,
-pub enum KnockOutWhist {
- NotYetStarted {
- seats: Seats,
+use std::collections::{HashMap, HashSet};
+
+use crate::card::Card;
+use crate::seats::Seats;
+use crate::username::Username;
+
+use super::{Action, ActionError, Game, UserAction, ValidatedUserAction};
+
+#[derive(Copy, Clone, Debug)]
+enum KnockOutWhistState {
+ NotYetStarted,
+ Dealing,
+ ChoosingTrumps,
+ Playing,
+ CutForCall,
+ Completed,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct KnockOutWhistSettings {
+ title: String,
+}
+
+#[derive(Clone, Debug)]
+pub struct KnockOutWhist {
+ id: u32,
+ actions_len: usize,
+ settings: KnockOutWhistSettings,
+ state: KnockOutWhistState,
+ seats: Seats,
+ dealer: Username,
+ call: Username,
+ deck: HashSet<Card>,
+ hands: HashMap<Username, HashSet<Card>>,
+ tricks_won: HashMap<Username, u32>,
+ winners: HashSet<Username>,
+ trump_card: Option<Card>,
+ cards_to_deal: u32,
+}
+
+impl Game for KnockOutWhist {
+ fn id(&self) -> u32 {
+ self.id
}
- Dealing {
- dealer: Username,
- call: Username,
- deck: HashSet<Card>,
- hands: HashMap<Username, HashSet<Card>>,
- trump_card: Option<Card>,
- seats: Seats,
- cards_to_deal: u32,
+
+ fn players(&self) -> HashSet<Username> {
+ self.seats.player_set()
}
- ChoosingTrumps {
- dealer: Username,
- call: Username,
- hands: HashMap<Username, HashSet<Card>>,
- seats: Seats,
+
+ fn actions_len(&self) -> usize {
+ self.actions_len
}
- Playing {
- turn: Username,
- trumps: Suit,
- trick: HashMap<Username, Card>,
- hands: HashMap<Username, HashSet<Card>>,
- seats: Seats,
- tricks_won: HashMap<Username, u32>,
+
+ fn validate_action(&self, action: UserAction) -> Result<ValidatedUserAction, ActionError> {
+ todo!()
}
- CutForCall {
- winners: HashSet<Username>,
- cards: HashMap<Username, Card>,
- seats: Seats,
+
+ fn take_action(&mut self, action: ValidatedUserAction) -> Result<(), ActionError> {
+ todo!()
}
- Completed {
- winner: Username,
+
+ fn next_dealer_action(&self) -> Option<ValidatedUserAction> {
+ todo!()
}
}
use std::collections::{BTreeMap, HashSet};
+use crate::username::Username;
+
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Seats {
- players: BTreeMap<u32, String>,
+ players: BTreeMap<u32, Username>,
}
impl Seats {
}
}
- pub fn add_player(&mut self, seat: u32, username: &str) {
- self.players.insert(seat, username.to_owned());
+ pub fn add_player(&mut self, seat: u32, username: Username) {
+ self.players.insert(seat, username);
}
- pub fn remove_player(&mut self, username: &str) {
- if let Some(seat) = self.players.iter().find(|(_, player)| &**player == username).map(|(&seat, _)| seat) {
+ pub fn remove_player(&mut self, username: Username) {
+ if let Some(seat) = self.players.iter().find(|(_, &player)| player == username).map(|(&seat, _)| seat) {
self.players.remove(&seat);
}
}
- fn player_after_seat(&self, seat: u32) -> Option<String> {
- if let Some((_, name)) = self.players.range(seat+1..).next() {
- Some(name.to_owned())
- } else if let Some((_, name)) = self.players.range(..seat).next() {
- Some(name.to_owned())
+ fn player_after_seat(&self, seat: u32) -> Option<Username> {
+ if let Some((_, &name)) = self.players.range(seat+1..).next() {
+ Some(name)
+ } else if let Some((_, &name)) = self.players.range(..seat).next() {
+ Some(name)
} else {
None
}
}
- pub fn player_after(&self, username: &str) -> Option<String> {
- for (&seat, player) in &self.players {
+ pub fn player_after(&self, username: Username) -> Option<Username> {
+ for (&seat, &player) in &self.players {
if player == username {
return self.player_after_seat(seat);
}
None
}
- pub fn contains_player(&self, username: &str) -> bool {
- for (_, player) in &self.players {
+ pub fn contains_player(&self, username: Username) -> bool {
+ for (_, &player) in &self.players {
if player == username {
return true;
}
false
}
- pub fn player_set(&self) -> HashSet<&str> {
- self.players.iter().map(|(_, player)| &**player).collect()
+ pub fn player_set(&self) -> HashSet<Username> {
+ self.players.iter().map(|(_, player)| *player).collect()
}
pub fn seat_is_available(&self, seat: u32) -> bool {
#[test]
fn player_after_absent_player_is_none() {
let mut seats = Seats::new();
- seats.add_player(1, "Player 1");
- seats.add_player(2, "Player 2");
- seats.add_player(3, "Player 3");
- assert_eq!(None, seats.player_after("Player 4"));
+ seats.add_player(1, "Player_1".parse().unwrap());
+ seats.add_player(2, "Player_2".parse().unwrap());
+ seats.add_player(3, "Player_3".parse().unwrap());
+ assert_eq!(None, seats.player_after("Player_4".parse().unwrap()));
assert!(seats.seat_is_available(0));
assert!(!seats.seat_is_available(1));
assert!(!seats.seat_is_available(2));
#[test]
fn player_after_single_player_is_none() {
let mut seats = Seats::new();
- seats.add_player(1, "Player 1");
- assert_eq!(None, seats.player_after("Player 1"));
+ seats.add_player(1, "Player_1".parse().unwrap());
+ assert_eq!(None, seats.player_after("Player_1".parse().unwrap()));
assert!(seats.seat_is_available(0));
assert!(!seats.seat_is_available(1));
assert!(seats.seat_is_available(2));
#[test]
fn player_after_simple() {
+ let player_1 = "Player_1".parse().unwrap();
+ let player_2 = "Player_2".parse().unwrap();
+ let player_3 = "Player_3".parse().unwrap();
let mut seats = Seats::new();
- seats.add_player(1, "Player 1");
- seats.add_player(2, "Player 2");
- seats.add_player(3, "Player 3");
- assert_eq!("Player 2", seats.player_after("Player 1").unwrap());
- assert_eq!("Player 3", seats.player_after("Player 2").unwrap());
+ seats.add_player(1, player_1);
+ seats.add_player(2, player_2);
+ seats.add_player(3, player_3);
+ assert_eq!(player_2, seats.player_after(player_1).unwrap());
+ assert_eq!(player_3, seats.player_after(player_2).unwrap());
}
#[test]
fn player_after_wrap_around() {
+ let player_1 = "Player_1".parse().unwrap();
+ let player_5 = "Player_5".parse().unwrap();
+ let player_8 = "Player_8".parse().unwrap();
let mut seats = Seats::new();
- seats.add_player(1, "Player 1");
- seats.add_player(5, "Player 5");
- seats.add_player(8, "Player 8");
- assert_eq!("Player 1", seats.player_after("Player 8").unwrap());
+ seats.add_player(1, player_1);
+ seats.add_player(5, player_5);
+ seats.add_player(8, player_8);
+ assert_eq!(player_1, seats.player_after(player_8).unwrap());
}
#[test]
fn player_after_two_player() {
+ let player_1 = "Player_1".parse().unwrap();
+ let player_4 = "Player_4".parse().unwrap();
let mut seats = Seats::new();
- seats.add_player(1, "Player 1");
- seats.add_player(4, "Player 4");
- assert_eq!("Player 4", seats.player_after("Player 1").unwrap());
- assert_eq!("Player 1", seats.player_after("Player 4").unwrap());
+ seats.add_player(1, player_1);
+ seats.add_player(4, player_4);
+ assert_eq!(player_4, seats.player_after(player_1).unwrap());
+ assert_eq!(player_1, seats.player_after(player_4).unwrap());
}
#[test]
fn remove_player() {
+ let player_1 = "Player_1".parse().unwrap();
+ let player_2 = "Player_2".parse().unwrap();
+ let player_3 = "Player_3".parse().unwrap();
let mut seats = Seats::new();
- seats.add_player(1, "Player 1");
- seats.add_player(2, "Player 2");
- seats.add_player(3, "Player 3");
- seats.remove_player("Player 2");
- assert_eq!("Player 3", seats.player_after("Player 1").unwrap());
- assert_eq!(None, seats.player_after("Player 2"));
- assert_eq!("Player 1", seats.player_after("Player 3").unwrap());
+ seats.add_player(1, player_1);
+ seats.add_player(2, player_2);
+ seats.add_player(3, player_3);
+ seats.remove_player(player_2);
+ assert_eq!(player_3, seats.player_after(player_1).unwrap());
+ assert_eq!(None, seats.player_after(player_2));
+ assert_eq!(player_1, seats.player_after(player_3).unwrap());
assert!(seats.seat_is_available(0));
assert!(!seats.seat_is_available(1));
assert!(seats.seat_is_available(2));
-use std::fmt::{self, Display, Formatter};
+use std::fmt::{self, Debug, Display, Formatter};
use std::str::{self, FromStr, Utf8Error};
use serde::{Serialize, Deserialize, Serializer, Deserializer, ser::Error, de::{self, Visitor}};
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(align(8))]
pub struct Username {
username: [u8; Self::MAX_LENGTH],
}
}
+impl Debug for Username {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ match self.as_str() {
+ Ok(str) => Debug::fmt(str, f),
+ Err(_) => Debug::fmt(String::from_utf8_lossy(&self.username).trim_end_matches('\0'), f),
+ }
+ }
+}
+
impl FromStr for Username {
type Err = &'static str;