diff --git a/src/api/event.rs b/src/api/event.rs index b0e7ec1..8b11482 100644 --- a/src/api/event.rs +++ b/src/api/event.rs @@ -220,7 +220,11 @@ mod test { mod team_game { use super::{ - free_for_all_game::{FreeForAllGame, FreeForAllGameSpec, FreeForAllGameUpdate}, + free_for_all_game::{ + FreeForAllGame, FreeForAllGameSpec, FreeForAllGameUpdate, + FreeForAllGameUpdateParticipants, FreeForAllGameUpdateRanking, + FreeForAllGameUpdateRewards, + }, *, }; @@ -252,9 +256,32 @@ mod team_game { #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[serde(crate = "rocket::serde")] - pub struct TeamGameUpdate { - #[serde(flatten)] - inner: FreeForAllGameUpdate, + pub enum TeamGameUpdateInner { + /// Add or replace a team with the given name and array of members + SetTeam { team: String, members: Vec }, + + /// Remove team with given name + RemoveTeam(String), + } + + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] + #[serde(crate = "rocket::serde")] + #[serde(untagged)] + pub enum TeamGameFfaInheritedUpdate { + /// Change the ranking and scores + Ranking(FreeForAllGameUpdateRanking), + /// Update rewards + Rewards(FreeForAllGameUpdateRewards), + } + + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] + #[serde(crate = "rocket::serde")] + #[serde(untagged)] + pub enum TeamGameUpdate { + /// Team specific updates + Team(TeamGameUpdateInner), + /// Inherited from FreeForAllGame + Ffa(TeamGameFfaInheritedUpdate), } impl EventTrait for TeamGame { @@ -262,15 +289,8 @@ mod team_game { type Update = TeamGameUpdate; fn from_spec(spec: TeamGameSpec) -> Self { - let players: Vec = spec - .teams - .values() - .map(|v| v.iter().cloned()) - .flatten() - .collect(); - let ffa_game_spec = FreeForAllGameSpec { - players, + participants: spec.teams.keys().cloned().collect(), win_rewards: spec.win_rewards, lose_rewards: spec.lose_rewards, }; @@ -282,7 +302,34 @@ mod team_game { } fn apply_update(&mut self, update: Self::Update) -> Result<(), PartyError> { - self.ffa_game.apply_update(update.inner) + match update { + TeamGameUpdate::Ffa(update) => match update { + TeamGameFfaInheritedUpdate::Ranking(u) => { + self.ffa_game.apply_update(FreeForAllGameUpdate::Ranking(u)) + } + TeamGameFfaInheritedUpdate::Rewards(u) => { + self.ffa_game.apply_update(FreeForAllGameUpdate::Rewards(u)) + } + }, + TeamGameUpdate::Team(update) => match update { + TeamGameUpdateInner::SetTeam { team, members } => { + self.ffa_game + .apply_update(FreeForAllGameUpdate::Participants( + FreeForAllGameUpdateParticipants::AddParticipant(team.clone()), + ))?; + self.teams.insert(team, members); + Ok(()) + } + TeamGameUpdateInner::RemoveTeam(team) => { + self.ffa_game + .apply_update(FreeForAllGameUpdate::Participants( + FreeForAllGameUpdateParticipants::RemoveParticipant(team.clone()), + ))?; + self.teams.remove(&team); + Ok(()) + } + }, + } } fn outcome(&self) -> EventOutcome { @@ -305,22 +352,33 @@ mod team_game { } mod free_for_all_game { + use std::collections::HashSet; + use super::*; #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[serde(crate = "rocket::serde")] pub enum FreeForAllGameRanking { - /// Ranking of players by user id (first element is first place, second element is second + /// Ranking of participants by user id or team name (first element is first place, second element is second /// place, etc.) Ranking(Vec), - /// Score based ranking of players by user id + /// Score based ranking of participants/teams Scores(HashMap), } + impl FreeForAllGameRanking { + pub fn is_valid(&self, participants: &HashSet) -> bool { + match self { + Self::Ranking(v) => v.iter().all(|p| participants.contains(p)), + Self::Scores(m) => m.keys().all(|p| participants.contains(p)), + } + } + } + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[serde(crate = "rocket::serde")] pub struct FreeForAllGame { - /// Ranking of players by user id (first element is first place, second element is second + /// Ranking of participants by user id or team name (first element is first place, second element is second /// place, etc.) ranking: Option, @@ -341,7 +399,7 @@ mod free_for_all_game { #[serde(crate = "rocket::serde")] pub struct FreeForAllGameSpec { /// Array of user ids that participate in the game - pub(crate) players: Vec, + pub(crate) participants: HashSet, /// Rewards for winning the game (first element for first place, second element for second /// place, etc.) @@ -355,14 +413,49 @@ mod free_for_all_game { #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[serde(crate = "rocket::serde")] - pub enum FreeForAllGameUpdate { + pub enum FreeForAllGameUpdateRanking { /// Replace the current ranking with the given ranking - Ranking(FreeForAllGameRanking), + SetRanking(FreeForAllGameRanking), /// If the current ranking is of type `Scores`, apply the given score deltas ScoreDelta(HashMap), } + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] + #[serde(crate = "rocket::serde")] + pub enum FreeForAllGameUpdateRewards { + /// Set rewards for winning the game + SetWinRewards(Vec), + + /// Set rewards for losing the game + SetLoseRewards(Vec), + } + + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] + #[serde(crate = "rocket::serde")] + pub enum FreeForAllGameUpdateParticipants { + /// Set list of participants participating in the game + SetParticipants(HashSet), + + /// Add participant by id + AddParticipant(String), + + /// Remove participant by id + RemoveParticipant(String), + } + + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] + #[serde(crate = "rocket::serde")] + #[serde(untagged)] + pub enum FreeForAllGameUpdate { + /// Change the ranking and scores + Ranking(FreeForAllGameUpdateRanking), + /// Update rewards + Rewards(FreeForAllGameUpdateRewards), + /// Update participants + Participants(FreeForAllGameUpdateParticipants), + } + impl EventTrait for FreeForAllGame { type Spec = FreeForAllGameSpec; type Update = FreeForAllGameUpdate; @@ -376,17 +469,61 @@ mod free_for_all_game { fn apply_update(&mut self, update: Self::Update) -> Result<(), PartyError> { match update { - FreeForAllGameUpdate::Ranking(r) => self.ranking = Some(r), - FreeForAllGameUpdate::ScoreDelta(d) => match &mut self.ranking { - Some(FreeForAllGameRanking::Ranking(_)) | None => { - return Err(PartyError::Unknown("cannot apply score delta".into())) + FreeForAllGameUpdate::Ranking(update) => match update { + FreeForAllGameUpdateRanking::SetRanking(r) => { + if !r.is_valid(&self.spec.participants) { + return Err(PartyError::Unknown("invalid ranking, all participants mentioned in ranking must be participating".into())); + } + self.ranking = Some(r) } - Some(FreeForAllGameRanking::Scores(s)) => { - for (player, delta) in d.iter() { - if let Some(value) = s.get(player) { - s.insert(player.clone(), value + delta); + FreeForAllGameUpdateRanking::ScoreDelta(d) => match &mut self.ranking { + Some(FreeForAllGameRanking::Ranking(_)) | None => { + return Err(PartyError::Unknown("cannot apply score delta".into())) + } + Some(FreeForAllGameRanking::Scores(s)) => { + for (participant, delta) in d.iter() { + if let Some(value) = s.get(participant) { + s.insert(participant.clone(), value + delta); + } } } + }, + }, + FreeForAllGameUpdate::Participants(update) => match update { + FreeForAllGameUpdateParticipants::AddParticipant(id) => { + self.spec.participants.insert(id); + } + FreeForAllGameUpdateParticipants::RemoveParticipant(id) => { + self.spec.participants.remove(&id); + + if !self + .ranking + .as_ref() + .map(|r| r.is_valid(&self.spec.participants)) + .unwrap_or(true) + { + self.spec.participants.insert(id); + return Err(PartyError::Unknown("cannot remove participant, all participants mentioned in ranking must be participating".into())); + } + } + FreeForAllGameUpdateParticipants::SetParticipants(participants) => { + if !self + .ranking + .as_ref() + .map(|r| r.is_valid(&participants)) + .unwrap_or(true) + { + return Err(PartyError::Unknown("invalid list of participants, all participants mentioned in ranking must be participating".into())); + } + self.spec.participants = participants; + } + }, + FreeForAllGameUpdate::Rewards(update) => match update { + FreeForAllGameUpdateRewards::SetWinRewards(rewards) => { + self.spec.win_rewards = rewards; + } + FreeForAllGameUpdateRewards::SetLoseRewards(rewards) => { + self.spec.lose_rewards = rewards; } }, } @@ -406,14 +543,14 @@ mod free_for_all_game { let mut points = HashMap::new(); - for (player, reward) in ranking.iter().zip(self.spec.win_rewards.iter()) { - let score = points.get(player).unwrap_or(&0); - points.insert(player.clone(), score + reward); + for (participant, reward) in ranking.iter().zip(self.spec.win_rewards.iter()) { + let score = points.get(participant).unwrap_or(&0); + points.insert(participant.clone(), score + reward); } - for (player, reward) in ranking.iter().rev().zip(self.spec.lose_rewards.iter()) { - let score = points.get(player).unwrap_or(&0); - points.insert(player.clone(), score + reward); + for (participant, reward) in ranking.iter().rev().zip(self.spec.lose_rewards.iter()) { + let score = points.get(participant).unwrap_or(&0); + points.insert(participant.clone(), score + reward); } EventOutcome { points }