From f3b9c06eb5343d12b149526a16eb757356c0f737 Mon Sep 17 00:00:00 2001 From: konsumlamm Date: Thu, 23 May 2024 17:15:29 +0200 Subject: [PATCH] Add `broadcast` function & implement detective gadgets --- liberica/src/lib/bindings.ts | 18 +++++----- robusta/src/main.rs | 69 ++++++++++++++++++------------------ robusta/src/ws_message.rs | 2 ++ 3 files changed, 45 insertions(+), 44 deletions(-) diff --git a/liberica/src/lib/bindings.ts b/liberica/src/lib/bindings.ts index 26d181b..778940d 100644 --- a/liberica/src/lib/bindings.ts +++ b/liberica/src/lib/bindings.ts @@ -9,23 +9,23 @@ export type MrXGadget = { AlternativeFacts: { stop_id: string } } | { Midjourney export type DetectiveGadget = { Stop: { stop_id: string } } | "OutOfOrder" | "Shackles" -export type TeamState = { team: Team; long: number; lat: number; on_train: string | null } +export type CreateTeamError = "InvalidName" | "NameAlreadyExists" -export type ClientResponse = { GameState: GameState } | { MrXGadget: MrXGadget } | { DetectiveGadget: DetectiveGadget } | { MrXPosition: MrXPosition } +export type CreateTeam = { name: string; color: string; kind: TeamKind } -export type GameState = { teams: TeamState[]; trains: Train[]; position_cooldown?: number | null; detective_gadget_cooldown?: number | null; mr_x_gadget_cooldown?: number | null } +export type ClientResponse = { GameState: GameState } | { MrXGadget: MrXGadget } | { DetectiveGadget: DetectiveGadget } | { MrXPosition: MrXPosition } -export type Train = { id: number; long: number; lat: number; line_id: string; line_name: string; direction: string } +export type GameState = { teams: TeamState[]; trains: Train[]; position_cooldown?: number | null; detective_gadget_cooldown?: number | null; mr_x_gadget_cooldown?: number | null; blocked_stop?: string | null } export type ClientMessage = { Position: { long: number; lat: number } } | { SetTeamPosition: { long: number; lat: number } } | { JoinTeam: { team_id: number } } | { EmbarkTrain: { train_id: string } } | "DisembarkTrain" | { MrXGadget: MrXGadget } | { DetectiveGadget: DetectiveGadget } | { Message: string } -export type MrXPosition = { Stop: string } | { Image: number[] } | "NotFound" +export type TeamKind = "MrX" | "Detective" | "Observer" -export type Team = { id: number; name: string; color: string; kind: TeamKind } +export type MrXPosition = { Stop: string } | { Image: number[] } | "NotFound" -export type CreateTeam = { name: string; color: string; kind: TeamKind } +export type Train = { id: number; long: number; lat: number; line_id: string; line_name: string; direction: string } -export type CreateTeamError = "InvalidName" | "NameAlreadyExists" +export type TeamState = { team: Team; long: number; lat: number; on_train: string | null } -export type TeamKind = "MrX" | "Detective" | "Observer" +export type Team = { id: number; name: string; color: string; kind: TeamKind } diff --git a/robusta/src/main.rs b/robusta/src/main.rs index 5a74248..1dc6e29 100644 --- a/robusta/src/main.rs +++ b/robusta/src/main.rs @@ -466,16 +466,7 @@ async fn run_game_loop(mut recv: Receiver, state: SharedState) { Teleport => {} Shifter => {} } - for connection in state.connections.iter_mut() { - if connection - .send - .send(ClientResponse::MrXGadget(gadget.clone())) - .await - .is_err() - { - continue; - } - } + broadcast(&state.connections, ClientResponse::MrXGadget(gadget)).await; } ClientMessage::DetectiveGadget(gadget) => { use DetectiveGadget::*; @@ -488,31 +479,30 @@ async fn run_game_loop(mut recv: Receiver, state: SharedState) { warn!("Client {} tried to use Detective Gadget, but is not Detective", id); continue; } + let running_state_arc = &running_state; let mut running_state = running_state.lock().await; if !running_state.detective_gadgets.try_use(&gadget, COOLDOWN) { warn!("Client {} tried to use Detective Gadget, but is not allowed to", id); continue; } - match &gadget { + broadcast(&state.connections, ClientResponse::DetectiveGadget(gadget.clone())).await; + match gadget { Stop { stop_id } => { - // TODO: mark stop as blocked for the next 20 mins + running_state.blocked_stop = Some(stop_id); + let running_state = Arc::clone(&running_state_arc); + tokio::spawn(async move { + tokio::time::sleep(Duration::from_secs_f32(2.0 * COOLDOWN)).await; + running_state.lock().await.blocked_stop = None; + }); } OutOfOrder => { - // TODO: immediately broadcast Mr. X position + let mr_x = state.teams.iter().find(|ts| ts.team.kind == TeamKind::MrX).expect("no Mr. X"); + let stop = kvv::nearest_stop(Point { latitude: mr_x.lat, longitude: mr_x.long }); + broadcast(&state.connections, ClientResponse::MrXPosition(MrXPosition::Stop(stop.id.clone()))).await; } Shackles => {} } - for connection in state.connections.iter_mut() { - if connection - .send - .send(ClientResponse::DetectiveGadget(gadget.clone())) - .await - .is_err() - { - continue; - } - } } } } @@ -550,6 +540,7 @@ async fn run_game_loop(mut recv: Receiver, state: SharedState) { position_cooldown: running_state.position_cooldown, mr_x_gadget_cooldown: running_state.mr_x_gadgets.remaining(), detective_gadget_cooldown: running_state.detective_gadgets.remaining(), + blocked_stop: running_state.blocked_stop.clone(), } }; writeln!( @@ -574,10 +565,11 @@ async fn run_game_loop(mut recv: Receiver, state: SharedState) { position_cooldown: game_state.position_cooldown, mr_x_gadget_cooldown: game_state.mr_x_gadget_cooldown, detective_gadget_cooldown: game_state.detective_gadget_cooldown, + blocked_stop: game_state.blocked_stop.clone(), }; if let Err(err) = connection .send - .send(ClientResponse::GameState(game_state.clone())) + .send(ClientResponse::GameState(game_state)) .await { error!("failed to send game state to client {}: {}", connection.id, err); @@ -587,11 +579,26 @@ async fn run_game_loop(mut recv: Receiver, state: SharedState) { } } +async fn broadcast(connections: &[ClientConnection], message: ClientResponse) { + for connection in connections.iter() { + if let Err(err) = connection.send.send(message.clone()).await { + let message_type = match message { + ClientResponse::GameState(_) => "game state", + ClientResponse::MrXPosition(_) => "Mr. X position", + ClientResponse::MrXGadget(_) => "Mr. X gadget", + ClientResponse::DetectiveGadget(_) => "Detective gadget", + }; + error!("failed to send {} to client {}: {}", message_type, connection.id, err); + } + } +} + struct RunningState { position_cooldown: Option, mr_x_gadgets: GadgetState, detective_gadgets: GadgetState, special_pos: Option, + blocked_stop: Option, } impl RunningState { @@ -601,6 +608,7 @@ impl RunningState { mr_x_gadgets: GadgetState::new(), detective_gadgets: GadgetState::new(), special_pos: None, + blocked_stop: None, } } } @@ -629,7 +637,7 @@ async fn start_game(state: SharedState, running_state: Arc { MrXPosition::Stop(stop_id) @@ -646,16 +654,7 @@ async fn start_game(state: SharedState, running_state: Arc, + #[specta(optional)] + pub blocked_stop: Option, } #[derive(specta::Type, Default, Clone, Serialize, Deserialize, Debug)]