From 646edb041187a262995da39b8c7618f6aac1f671 Mon Sep 17 00:00:00 2001 From: Erlend <49862976+Erb3@users.noreply.github.com> Date: Fri, 21 Jun 2024 09:55:39 +0200 Subject: [PATCH] feat: configurable time (#86) fixes #19 --- README.md | 1 + frontend/game.js | 10 +++--- src/cli.rs | 9 +++++ src/game.rs | 89 +++++++++++++++++++++++++++--------------------- src/main.rs | 6 ++-- src/packets.rs | 6 ++++ src/server.rs | 2 +- src/state.rs | 5 ++- 8 files changed, 82 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index e508a38..9972736 100644 --- a/README.md +++ b/README.md @@ -62,3 +62,4 @@ Here are some resources to get you started with Socketioxide: - [Posio](https://github.com/abrenaut/posio) by [Abrenaut](https://github.com/abrenaut) - [JSON of cities](https://github.com/abrenaut/posio/blob/master/game/data/cities.json) by [Abrenaut](https://github.com/abrenaut) - [Leaflet.js](https://leafletjs.com/) +- [Carto maps](https://carto.com/) diff --git a/frontend/game.js b/frontend/game.js index 0eb7eeb..fc3935c 100644 --- a/frontend/game.js +++ b/frontend/game.js @@ -53,6 +53,7 @@ const distanceLine = L.polyline([], { color: "red" }); const distancePopup = L.popup(); const otherPlayerMarkers = []; let canMoveMarker = false; +let guessingTime = 5; map.on("click", (e) => { if (!canMoveMarker) return; @@ -77,7 +78,7 @@ socket.on("newTarget", (data) => { targetElement.innerHTML = `${data.name}, ${data.country}`; progressElement.style.width = "100%"; - progressElement.style.transitionDuration = "5s"; + progressElement.style.transitionDuration = guessingTime + "s"; canMoveMarker = true; mapElement.classList.remove("cursor-grab"); map.setZoom(3); @@ -125,12 +126,13 @@ socket.on("solution", (data) => { mapElement.classList.add("cursor-grab"); }); -socket.on("join-response", () => { - console.log("Connected!"); +socket.on("game-metadata", (data) => { + console.log("Connected to game!", data); + guessingTime = data.guess_time; }); socket.on("kick", (data) => { - console.log(data); + console.log("Kicked!", data); location.href = "/?message=" + data.message; }); diff --git a/src/cli.rs b/src/cli.rs index 6ac8d5b..ddc6ae0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -33,6 +33,15 @@ pub struct Cli { /// Optional logging level to use. Default is info #[arg(short, long, env = "SVEIO_LOGGING_LEVEL")] pub logging: Option, + + /// Optional amount of seconds to allow guessing. Default is 7s + #[arg(long, env = "SVEIO_GUESS_TIME")] + pub guess_time: Option, + + /// Optional amount of seconds where players can see where the others + /// guessed. Default is 3s + #[arg(long, env = "SVEIO_SHOWCASE_TIME")] + pub showcase_time: Option, } pub fn get_settings() -> Cli { diff --git a/src/game.rs b/src/game.rs index 9a3ad75..897ccda 100644 --- a/src/game.rs +++ b/src/game.rs @@ -6,11 +6,12 @@ use socketioxide::extract::{Data, SocketRef, State}; use socketioxide::SocketIo; use std::sync::Arc; use std::time::Duration; -use tokio::time; use tracing::{debug, info}; +#[derive(Clone)] pub struct GameOptions { - pub datasource: datasource::Datasource, + pub guess_time: u64, + pub showcase_time: u64, } pub fn on_connect(socket: SocketRef) { @@ -53,7 +54,16 @@ pub fn on_connect(socket: SocketRef) { .insert_player(socket.id, state::Player::new(data.username.clone())) .await; - socket.emit("join-response", "").unwrap(); + socket + .emit( + "game-metadata", + packets::GameMetadataMessage { + guess_time: state.options.guess_time, + showcase_time: state.options.showcase_time, + }, + ) + .unwrap(); + socket.join("PRIMARY").unwrap(); info!( @@ -79,45 +89,16 @@ pub fn on_connect(socket: SocketRef) { } pub async fn game_loop(opts: GameOptions, io: Arc, state: state::GameState) { - let mut interval = time::interval(Duration::from_secs(5)); - let mut last_city: Option = None; + let guessing_time = Duration::from_secs(opts.guess_time); + let showcase_time = Duration::from_secs(opts.showcase_time); + let mut last_city: Option; let mut index = 0; + let datasource = datasource::new().await; loop { - interval.tick().await; - - if let Some(city) = last_city { - let target = Location::new(city.latitude, city.longitude); - - for guess in state.get_guesses().await { - let packet = guess.1; - let distance = - target.distance_to(&geoutils::Location::new(packet.lat, packet.long)); - let points = utils::calculate_score(distance.unwrap().meters() / 1000.0); - - if let Some(existing_player) = state.get_player(guess.0).await { - let mut p = existing_player.to_owned(); - p.score += points; - state.insert_player(guess.0, p).await; - } - } - - let solution = packets::SolutionPacket { - location: city, - guesses: state.get_guesses().await, - leaderboard: state.get_players().await, - }; - - io.to("PRIMARY") - .emit("solution", solution) - .expect("Unable to broadcast solution"); - } - - interval.tick().await; - - let city: &datasource::City = opts.datasource.cities.get(index).unwrap(); + let city: &datasource::City = datasource.cities.get(index).unwrap(); index += 1; - if index == opts.datasource.cities.len() - 1 { + if index == datasource.cities.len() - 1 { index = 0; } @@ -145,5 +126,37 @@ pub async fn game_loop(opts: GameOptions, io: Arc, state: state::GameS } } } + + tokio::time::sleep(guessing_time).await; + + if let Some(city) = last_city { + debug!("Announcing solutions"); + let target = Location::new(city.latitude, city.longitude); + + for guess in state.get_guesses().await { + let packet = guess.1; + let distance = + target.distance_to(&geoutils::Location::new(packet.lat, packet.long)); + let points = utils::calculate_score(distance.unwrap().meters() / 1000.0); + + if let Some(existing_player) = state.get_player(guess.0).await { + let mut p = existing_player.to_owned(); + p.score += points; + state.insert_player(guess.0, p).await; + } + } + + let solution = packets::SolutionPacket { + location: city, + guesses: state.get_guesses().await, + leaderboard: state.get_players().await, + }; + + io.to("PRIMARY") + .emit("solution", solution) + .expect("Unable to broadcast solution"); + } + + tokio::time::sleep(showcase_time).await; } } diff --git a/src/main.rs b/src/main.rs index 4f12c08..a356403 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,8 @@ async fn main() -> Result<(), Box> { server::create_server(server::ServerOptions { game: game::GameOptions { - datasource: datasource::new().await, + guess_time: settings.guess_time.unwrap_or(7), + showcase_time: settings.showcase_time.unwrap_or(3), }, port: Some(settings.port.unwrap_or(8085)), }) @@ -40,7 +41,8 @@ async fn main() -> shuttle_axum::ShuttleAxum { Ok(server::create_server(server::ServerOptions { game: game::GameOptions { - datasource: datasource::new().await, + guess_time: 7, + showcase_time: 3, }, port: None, }) diff --git a/src/packets.rs b/src/packets.rs index 90ae092..6b494c7 100644 --- a/src/packets.rs +++ b/src/packets.rs @@ -20,6 +20,12 @@ pub struct JoinMessage { pub username: String, } +#[derive(Serialize, Debug)] +pub struct GameMetadataMessage { + pub guess_time: u64, + pub showcase_time: u64, +} + #[derive(Serialize, Debug)] pub struct DisconnectPacket { pub message: String, diff --git a/src/server.rs b/src/server.rs index e6811b7..ec06a34 100644 --- a/src/server.rs +++ b/src/server.rs @@ -16,7 +16,7 @@ pub struct ServerOptions { } pub async fn create_server(opts: ServerOptions) -> Option { - let socketio_state = state::GameState::new(); + let socketio_state = state::GameState::new(opts.game.clone()); let (socketio_layer, io) = SocketIoBuilder::new() .with_state(socketio_state.clone()) diff --git a/src/state.rs b/src/state.rs index a793df8..e612b7c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,3 +1,4 @@ +use crate::game::GameOptions; use crate::packets; use chrono::Utc; use serde::{Deserialize, Serialize}; @@ -36,13 +37,15 @@ pub type PlayerMap = HashMap; pub struct GameState { guesses: Arc>, players: Arc>, + pub options: GameOptions, } impl GameState { - pub fn new() -> GameState { + pub fn new(options: GameOptions) -> GameState { GameState { guesses: Arc::new(RwLock::new(GuessMap::new())), players: Arc::new(RwLock::new(PlayerMap::new())), + options, } }