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");
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");
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);
+ }
}
}
}
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},
enum Field {
Format(String),
Title(String),
+ Completed(bool),
Parens(Box<Or>),
}
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),
}
}
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)]
let filter = parse_filter("format: KnockOutWhist and (title:Game or title:Play)").unwrap();
let games: Vec<GameSummary> = 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();
let filter = parse_filter(" ").unwrap();
let games: Vec<GameSummary> = 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();
}
impl dyn Game {
- pub fn new(GameSummary { id, settings }: GameSummary, seed: Seed) -> Box<Self> {
+ pub fn new(GameSummary { id, settings, .. }: GameSummary, seed: Seed) -> Box<Self> {
match settings {
GameSettings::Chatroom(settings) => Box::new(Chatroom::new(id, settings)),
GameSettings::KnockOutWhist(settings) => Box::new(KnockOutWhist::new(id, settings, seed)),
}
}
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct GameStatus {
+ pub created: Timestamp,
+ pub completed: bool,
+ pub players: HashSet<Username>,
+ pub winner: Option<Username>,
+}
+
+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 {
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;
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)
}
}
pub async fn create_game(&mut self, settings: GameSettings) -> RedisResult<i64> {
+ 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<Vec<GameSummary>> {
debug!("game_list(from: {})", from);
let games: Vec<i64> = self.redis.lrange("game:list", from as isize, -1).await?;
}
pub async fn game_summary(&mut self, id: i64) -> RedisResult<GameSummary> {
- 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<Seed> {