From: Geoffrey Allott Date: Mon, 29 Mar 2021 21:15:01 +0000 (+0100) Subject: add game status flags and filter by completed: false X-Git-Url: https://git.pointlesshacks.com/?a=commitdiff_plain;h=390dc2bc5d2698344864651248b4bdc286a4650a;p=pokerwave.git add game status flags and filter by completed: false --- diff --git a/site/modules/mainmenu.js b/site/modules/mainmenu.js index 019f666..f65ea23 100644 --- a/site/modules/mainmenu.js +++ b/site/modules/mainmenu.js @@ -16,7 +16,7 @@ export class MainMenu { texas_hold_em_text.innerText = "Texas Hold 'em"; texas_hold_em.append(texas_hold_em_img); texas_hold_em.append(texas_hold_em_text); - texas_hold_em.onclick = () => this.send({type: "JoinLobby", filter: "format: TexasHoldEm"}); + texas_hold_em.onclick = () => this.send({type: "JoinLobby", filter: "format: TexasHoldEm and completed: false"}); menu_container.append(texas_hold_em); const knock_out_whist = document.createElement("div"); @@ -27,7 +27,7 @@ export class MainMenu { knock_out_whist_text.innerText = "Knock-Out Whist"; knock_out_whist.append(knock_out_whist_img); knock_out_whist.append(knock_out_whist_text); - knock_out_whist.onclick = () => this.send({type: "JoinLobby", filter: "format: KnockOutWhist"}); + knock_out_whist.onclick = () => this.send({type: "JoinLobby", filter: "format: KnockOutWhist and completed: false"}); menu_container.append(knock_out_whist); const chatroom = document.createElement("div"); diff --git a/src/dealer.rs b/src/dealer.rs index 44a96c6..2fe00bf 100644 --- a/src/dealer.rs +++ b/src/dealer.rs @@ -125,7 +125,10 @@ impl Dealer { return Ok(Termination::Continue); } DealerAction::WaitForPlayer => return Ok(Termination::Continue), - DealerAction::Leave => return Ok(Termination::Break), + DealerAction::Leave => { + self.server.set_game_completed(id, true).await?; + return Ok(Termination::Break); + } } } } diff --git a/src/game/filter.rs b/src/game/filter.rs index 699f51f..813a316 100644 --- a/src/game/filter.rs +++ b/src/game/filter.rs @@ -2,7 +2,7 @@ use nom::{ branch::alt, bytes::complete::{tag, take_until}, character::complete::{alpha1, multispace0}, - combinator::map, + combinator::{map, value}, error::ParseError, multi::{many0_count, separated_list0, separated_list1}, sequence::{delimited, preceded, tuple}, @@ -26,6 +26,7 @@ where enum Field { Format(String), Title(String), + Completed(bool), Parens(Box), } @@ -38,6 +39,7 @@ impl Filter for Field { GameSettings::TexasHoldEm(_) => format.eq_ignore_ascii_case("TexasHoldEm"), }, Field::Title(title) => summary.settings.title().to_lowercase().contains(&title.to_lowercase()), + Field::Completed(completed) => summary.status.completed == *completed, Field::Parens(filter) => filter.matches(summary), } } @@ -45,10 +47,12 @@ impl Filter for Field { fn parse_field(input: &str) -> IResult<&str, Field> { let string = || map(alt((alpha1, delimited(tag("\""), take_until("\""), tag("\"")))), String::from); + let boolean = || alt((value(true, tag("true")), value(false, tag("false")))); let format = map(preceded(tuple((tag("format"), ws(tag(":")))), string()), Field::Format); let title = map(preceded(tuple((tag("title"), ws(tag(":")))), string()), Field::Title); + let completed = map(preceded(tuple((tag("completed"), ws(tag(":")))), boolean()), Field::Completed); let parens = delimited(tag("("), map(map(ws(parse_or), Box::new), Field::Parens), tag(")")); - alt((format, title, parens))(input) + alt((format, title, completed, parens))(input) } #[derive(Clone, Debug, PartialEq, Eq)] @@ -143,11 +147,11 @@ mod tests { let filter = parse_filter("format: KnockOutWhist and (title:Game or title:Play)").unwrap(); let games: Vec = serde_json::from_str( r#"[ - {"id": 0, "settings": {"format":"KnockOutWhist","title":"Game 0","max_players":2}}, - {"id": 1, "settings": {"format":"KnockOutWhist","title":"game 1","max_players":2}}, - {"id": 2, "settings": {"format":"Chatroom","title":"Game 2"}}, - {"id": 3, "settings": {"format":"KnockOutWhist","title":"Invalid 3","max_players":2}}, - {"id": 4, "settings": {"format":"KnockOutWhist","title":"play 4","max_players":2}} + {"id": 0, "status":{"created":0,"completed":false,"players":[]}, "settings": {"format":"KnockOutWhist","title":"Game 0","max_players":2}}, + {"id": 1, "status":{"created":0,"completed":false,"players":[]}, "settings": {"format":"KnockOutWhist","title":"game 1","max_players":2}}, + {"id": 2, "status":{"created":0,"completed":false,"players":[]}, "settings": {"format":"Chatroom","title":"Game 2"}}, + {"id": 3, "status":{"created":0,"completed":false,"players":[]}, "settings": {"format":"KnockOutWhist","title":"Invalid 3","max_players":2}}, + {"id": 4, "status":{"created":0,"completed":false,"players":[]}, "settings": {"format":"KnockOutWhist","title":"play 4","max_players":2}} ]"#, ) .unwrap(); @@ -163,11 +167,11 @@ mod tests { let filter = parse_filter(" ").unwrap(); let games: Vec = serde_json::from_str( r#"[ - {"id": 0, "settings": {"format":"KnockOutWhist","title":"Game 0","max_players":2}}, - {"id": 1, "settings": {"format":"KnockOutWhist","title":"game 1","max_players":2}}, - {"id": 2, "settings": {"format":"Chatroom","title":"Game 2"}}, - {"id": 3, "settings": {"format":"KnockOutWhist","title":"Invalid 3","max_players":2}}, - {"id": 4, "settings": {"format":"KnockOutWhist","title":"play 4","max_players":2}} + {"id": 0, "status":{"created":0,"completed":false,"players":[]}, "settings": {"format":"KnockOutWhist","title":"Game 0","max_players":2}}, + {"id": 1, "status":{"created":0,"completed":false,"players":[]}, "settings": {"format":"KnockOutWhist","title":"game 1","max_players":2}}, + {"id": 2, "status":{"created":0,"completed":false,"players":[]}, "settings": {"format":"Chatroom","title":"Game 2"}}, + {"id": 3, "status":{"created":0,"completed":false,"players":[]}, "settings": {"format":"KnockOutWhist","title":"Invalid 3","max_players":2}}, + {"id": 4, "status":{"created":0,"completed":false,"players":[]}, "settings": {"format":"KnockOutWhist","title":"play 4","max_players":2}} ]"#, ) .unwrap(); diff --git a/src/game/mod.rs b/src/game/mod.rs index 552e0f2..cc3e6db 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -44,7 +44,7 @@ impl Clone for Box { } impl dyn Game { - pub fn new(GameSummary { id, settings }: GameSummary, seed: Seed) -> Box { + pub fn new(GameSummary { id, settings, .. }: GameSummary, seed: Seed) -> Box { match settings { GameSettings::Chatroom(settings) => Box::new(Chatroom::new(id, settings)), GameSettings::KnockOutWhist(settings) => Box::new(KnockOutWhist::new(id, settings, seed)), @@ -100,15 +100,35 @@ impl GameList { } } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GameStatus { + pub created: Timestamp, + pub completed: bool, + pub players: HashSet, + pub winner: Option, +} + +impl GameStatus { + pub fn new(created: Timestamp) -> Self { + Self { + created, + completed: false, + players: HashSet::new(), + winner: None, + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GameSummary { id: i64, settings: GameSettings, + status: GameStatus, } impl GameSummary { - pub fn new(id: i64, settings: GameSettings) -> Self { - Self { id, settings } + pub fn new(id: i64, settings: GameSettings, status: GameStatus) -> Self { + GameSummary { id, settings, status } } pub fn id(&self) -> i64 { diff --git a/src/server.rs b/src/server.rs index 6e567cc..9549b5f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -10,7 +10,7 @@ use scrypt::password_hash::HasherError; use serde::{Deserialize, Serialize}; use crate::auth::{Auth, CreateAuth}; -use crate::game::{GameSettings, GameSummary, ValidatedUserAction}; +use crate::game::{GameSettings, GameStatus, GameSummary, ValidatedUserAction}; use crate::pubsub::{client_interest_channel, ClientInterest, ClientInterestReceiver, ClientInterestSender}; use crate::rng::Seed; use crate::username::Username; @@ -64,6 +64,10 @@ fn game_settings_key(id: i64) -> String { format!("game:{}:settings", id) } +fn game_status_key(id: i64) -> String { + format!("game:{}:status", id) +} + fn game_seed_key(id: i64) -> String { format!("game:{}:seed", id) } @@ -115,13 +119,24 @@ impl ServerState { } pub async fn create_game(&mut self, settings: GameSettings) -> RedisResult { + let now = self.now().await?; let id = self.redis.incr("game:next_id", 1).await?; let () = self.redis.set(game_settings_key(id), AsJson(settings)).await?; + let GameStatus { created, completed, players, winner } = GameStatus::new(now); + let status_key = game_status_key(id); + let () = self.redis.hset(&status_key, "created", AsJson(created)).await?; + let () = self.redis.hset(&status_key, "completed", AsJson(completed)).await?; + let () = self.redis.hset(&status_key, "players", AsJson(players)).await?; + let () = self.redis.hset(&status_key, "winner", AsJson(winner)).await?; let () = self.redis.set(game_seed_key(id), AsJson(Seed::cha_cha_20_from_entropy())).await?; let () = self.redis.rpush("game:list", id).await?; Ok(id) } + pub async fn set_game_completed(&mut self, id: i64, completed: bool) -> RedisResult<()> { + self.redis.hset(game_status_key(id), "completed", AsJson(completed)).await + } + pub async fn game_list(&mut self, from: usize) -> RedisResult> { debug!("game_list(from: {})", from); let games: Vec = self.redis.lrange("game:list", from as isize, -1).await?; @@ -142,9 +157,17 @@ impl ServerState { } pub async fn game_summary(&mut self, id: i64) -> RedisResult { - let key = game_settings_key(id); - info!("Getting summary from key: {}", key); - self.redis.get(key).await.map(AsJson::get).map(|settings| GameSummary::new(id, settings)) + let settings_key = game_settings_key(id); + let status_key = game_status_key(id); + info!("Getting settings from key: {}", settings_key); + let settings = self.redis.get(settings_key).await.map(AsJson::get)?; + info!("Getting status from key: {}", status_key); + let created = self.redis.hget(&status_key, "created").await.map(AsJson::get)?; + let completed = self.redis.hget(&status_key, "completed").await.map(AsJson::get)?; + let players = self.redis.hget(&status_key, "players").await.map(AsJson::get)?; + let winner = self.redis.hget(&status_key, "winner").await.map(AsJson::get)?; + let status = GameStatus { created, completed, players, winner }; + Ok(GameSummary::new(id, settings, status)) } pub async fn game_seed(&mut self, id: i64) -> RedisResult {