From af6733110d211fc776246423650753c069b81f02 Mon Sep 17 00:00:00 2001 From: konsumlamm Date: Thu, 23 May 2024 16:29:32 +0200 Subject: [PATCH] Implement Mr. X position messages --- liberica/src/lib/bindings.ts | 20 ++++++++-------- robusta/src/kvv.rs | 17 ++++++++++++-- robusta/src/main.rs | 44 ++++++++++++++++++++++++------------ robusta/src/ws_message.rs | 9 ++++++++ 4 files changed, 64 insertions(+), 26 deletions(-) diff --git a/liberica/src/lib/bindings.ts b/liberica/src/lib/bindings.ts index d967234..26d181b 100644 --- a/liberica/src/lib/bindings.ts +++ b/liberica/src/lib/bindings.ts @@ -1,29 +1,31 @@ // This file has been generated by Specta. DO NOT EDIT. -export type CreateTeam = { name: string; color: string; kind: TeamKind } - /** * Information about a tram station. */ export type Stop = { name: string; id: string; lat: number; lon: number } -export type ClientResponse = { GameState: GameState } | { MrXGadget: MrXGadget } | { DetectiveGadget: DetectiveGadget } - export type MrXGadget = { AlternativeFacts: { stop_id: string } } | { Midjourney: { image: number[] } } | "NotFound" | "Teleport" | "Shifter" -export type GameState = { teams: TeamState[]; trains: Train[]; position_cooldown?: number | null; detective_gadget_cooldown?: number | null; mr_x_gadget_cooldown?: number | null } - export type DetectiveGadget = { Stop: { stop_id: string } } | "OutOfOrder" | "Shackles" -export type CreateTeamError = "InvalidName" | "NameAlreadyExists" +export type TeamState = { team: Team; long: number; lat: number; on_train: string | null } + +export type ClientResponse = { GameState: GameState } | { MrXGadget: MrXGadget } | { DetectiveGadget: DetectiveGadget } | { MrXPosition: MrXPosition } + +export type GameState = { teams: TeamState[]; trains: Train[]; position_cooldown?: number | null; detective_gadget_cooldown?: number | null; mr_x_gadget_cooldown?: number | null } export type Train = { id: number; long: number; lat: number; line_id: string; line_name: string; direction: string } +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 Team = { id: number; name: string; color: string; kind: TeamKind } -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 CreateTeam = { name: string; color: string; kind: TeamKind } -export type TeamState = { team: Team; long: number; lat: number; on_train: string | null } +export type CreateTeamError = "InvalidName" | "NameAlreadyExists" export type TeamKind = "MrX" | "Detective" | "Observer" diff --git a/robusta/src/kvv.rs b/robusta/src/kvv.rs index 87cff18..2460aaf 100644 --- a/robusta/src/kvv.rs +++ b/robusta/src/kvv.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, Utc}; use futures_util::future::join_all; use lazy_static::lazy_static; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::OnceLock; @@ -16,7 +16,7 @@ use crate::ws_message::Train; const DEFAULT_WAIT_TIME: Duration = Duration::from_secs(30); /// Information about a tram station. -#[derive(Debug, Serialize, specta::Type, PartialEq)] +#[derive(Debug, Serialize, Deserialize, specta::Type, Clone, PartialEq)] pub struct Stop { /// human readable stop name pub name: String, @@ -342,3 +342,16 @@ pub fn train_positions(departures_per_line: &LineDepartures, render_time: DateTi .flat_map(|(journey_ref, departures)| train_position_per_route(render_time, journey_ref, departures, stops)) .collect() } + +pub fn nearest_stop(pos: Point) -> &'static Stop { + let stops = KVV_STOPS.get().expect("KVV_STOPS not initialized"); + stops + .iter() + .min_by_key(|stop| { + pos.distance(Point { + latitude: stop.lat as f32, + longitude: stop.lon as f32, + }) as u64 + }) + .expect("no stops") +} diff --git a/robusta/src/main.rs b/robusta/src/main.rs index 8a4c5df..5a74248 100644 --- a/robusta/src/main.rs +++ b/robusta/src/main.rs @@ -16,6 +16,7 @@ use axum::{ Json, Router, }; use futures_util::SinkExt; +use point::Point; use reqwest::StatusCode; use tokio::sync::mpsc::{Receiver, Sender}; use tokio::time::Instant; @@ -26,6 +27,7 @@ use tower_http::{ }; use tracing::{error, info, warn, Level}; use tracing_appender::rolling::{self, Rotation}; +use ws_message::MrXPosition; use crate::gadgets::{DetectiveGadget, GadgetState, MrXGadget}; use crate::kvv::LineDepartures; @@ -388,7 +390,7 @@ async fn run_game_loop(mut recv: Receiver, state: SharedState) { let mut interval = tokio::time::interval(Duration::from_millis(500)); let running_state = Arc::new(tokio::sync::Mutex::new(RunningState::new())); - start_game(Arc::clone(&running_state)).await; + start_game(Arc::clone(&state), Arc::clone(&running_state)).await; loop { interval.tick().await; @@ -603,7 +605,7 @@ impl RunningState { } } -async fn start_game(state: Arc>) { +async fn start_game(state: SharedState, running_state: Arc>) { tokio::spawn(async move { let mut warmup = true; @@ -611,42 +613,54 @@ async fn start_game(state: Arc>) { let mut interval = tokio::time::interval(Duration::from_millis(100)); loop { interval.tick().await; - let mut state = state.lock().await; + let mut running_state = running_state.lock().await; let old_time = time; time = Instant::now(); let delta = (time - old_time).as_secs_f32(); - if let Some(cooldown) = state.position_cooldown.as_mut() { + if let Some(cooldown) = running_state.position_cooldown.as_mut() { *cooldown -= delta; if *cooldown < 0.0 { - state.position_cooldown = Some(COOLDOWN); + running_state.position_cooldown = Some(COOLDOWN); if warmup { // TODO: broadcast Detective start warmup = false; - state.mr_x_gadgets.allow_use(); - state.detective_gadgets.allow_use(); + running_state.mr_x_gadgets.allow_use(); + running_state.detective_gadgets.allow_use(); } else { // broadcast Mr. X position - match &state.special_pos { + let mut state = state.lock().await; + let position = match running_state.special_pos.take() { Some(SpecialPos::Stop(stop_id)) => { - // TODO: broadcast stop id + MrXPosition::Stop(stop_id) } Some(SpecialPos::Image(image)) => { - // TODO: broadcast image + MrXPosition::Image(image) } Some(SpecialPos::NotFound) => { - // TODO: broadcast 404 + MrXPosition::NotFound } None => { - // TODO: 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 }); + MrXPosition::Stop(stop.id.clone()) + } + }; + for connection in state.connections.iter_mut() { + if let Err(err) = connection + .send + .send(ClientResponse::MrXPosition(position.clone())) + .await + { + error!("failed to send Mr. X position to client {}: {}", connection.id, err); + continue; } } - state.special_pos = None; } } } - state.mr_x_gadgets.update_time(delta); - state.detective_gadgets.update_time(delta); + running_state.mr_x_gadgets.update_time(delta); + running_state.detective_gadgets.update_time(delta); } }); } diff --git a/robusta/src/ws_message.rs b/robusta/src/ws_message.rs index 75b5db8..257f310 100644 --- a/robusta/src/ws_message.rs +++ b/robusta/src/ws_message.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use crate::gadgets::{DetectiveGadget, MrXGadget}; +use crate::kvv::Stop; #[derive(specta::Type, Clone, Deserialize, Debug)] pub enum ClientMessage { @@ -19,6 +20,14 @@ pub enum ClientResponse { GameState(GameState), MrXGadget(MrXGadget), DetectiveGadget(DetectiveGadget), + MrXPosition(MrXPosition), +} + +#[derive(specta::Type, Clone, Serialize, Deserialize, Debug)] +pub enum MrXPosition { + Stop(String), + Image(Vec), + NotFound, } #[derive(specta::Type, Default, Clone, Serialize, Deserialize, Debug)]