Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move game logic to separate library #21

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
[workspace]
members = ["robusta", "trias"]
members = ["robusta", "trias", "chai"]
resolver = "2"

[workspace.dependencies]
axum = { version = "0.6.20", features = ["ws", "tracing", "macros"] }
chrono = "0.4.31"
chrono-tz = "0.8.4"
19 changes: 19 additions & 0 deletions chai/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "chai"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
chrono = "0.4.31"
chrono-tz = "0.8.4"
futures-util = "0.3.28"
trias = { path = "../trias" }
reqwest = "0.11.22"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
specta = { version = "1.0.5", features = ["export"] }
dotenv = "0.15.0"
axum = { workspace = true }
tracing = "0.1.25"
TrueDoctor marked this conversation as resolved.
Show resolved Hide resolved
File renamed without changes.
103 changes: 60 additions & 43 deletions robusta/src/kvv.rs → chai/src/kvv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ use std::collections::HashMap;
use std::sync::OnceLock;
use std::time::Duration;

// mod api;

use crate::point::{interpolate_segment, Point};
use crate::ws_message::Train;

Expand Down Expand Up @@ -136,22 +134,23 @@ static ACCESS_TOKEN: OnceLock<String> = OnceLock::new();
async fn kvv_stops() -> Vec<Stop> {
let access_token = ACCESS_TOKEN.get().unwrap();
let api_endpoint = API_ENDPOINT.get().unwrap();
join_all(STOPS
.iter()
.map(|stop| async move {
let name = stop.1.to_string();
let stops = trias::search_stops(name, access_token.clone(), api_endpoint, 1).await.unwrap();

let first_stop = stops.into_iter().next().unwrap();
let stop_point = first_stop.stop_point;
let position = first_stop.geo_position;
Stop {
name: stop_point.stop_point_name.text,
id: stop_point.stop_point_ref,
lat: position.latitude,
lon: position.longitude,
}
})).await
join_all(STOPS.iter().map(|stop| async move {
let name = stop.1.to_string();
let stops = trias::search_stops(name, access_token.clone(), api_endpoint, 1)
.await
.unwrap();

let first_stop = stops.into_iter().next().unwrap();
let stop_point = first_stop.stop_point;
let position = first_stop.geo_position;
Stop {
name: stop_point.stop_point_name.text,
id: stop_point.stop_point_ref,
lat: position.latitude,
lon: position.longitude,
}
}))
.await
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -182,18 +181,32 @@ type StopRef = String;
pub type LineDepartures = HashMap<JourneyRef, Journey>;

pub fn parse_times(call: &trias::response::CallAtStop) -> Option<Times> {
let arrival = call
.service_arrival
.as_ref()
.map(|service| service.estimated_time.as_ref().unwrap_or(&service.timetabled_time).parse().unwrap());
let departure = call
.service_departure
.as_ref()
.map(|service| service.estimated_time.as_ref().unwrap_or(&service.timetabled_time).parse().unwrap());
let arrival = call.service_arrival.as_ref().map(|service| {
service
.estimated_time
.as_ref()
.unwrap_or(&service.timetabled_time)
.parse()
.unwrap()
});
let departure = call.service_departure.as_ref().map(|service| {
service
.estimated_time
.as_ref()
.unwrap_or(&service.timetabled_time)
.parse()
.unwrap()
});
match (arrival, departure) {
(Some(arrival), Some(departure)) => Some(Times { arrival, departure }),
(Some(arrival), None) => Some(Times { arrival, departure: arrival + DEFAULT_WAIT_TIME }),
(None, Some(departure)) => Some(Times { arrival: departure - DEFAULT_WAIT_TIME, departure }),
(Some(arrival), None) => Some(Times {
arrival,
departure: arrival + DEFAULT_WAIT_TIME,
}),
(None, Some(departure)) => Some(Times {
arrival: departure - DEFAULT_WAIT_TIME,
departure,
}),
(None, None) => {
tracing::warn!("no departure or arrival time");
None
Expand All @@ -205,18 +218,16 @@ pub async fn fetch_departures(stops: &[Stop]) -> LineDepartures {
let access_token = ACCESS_TOKEN.get().unwrap();
let api_endpoint = API_ENDPOINT.get().unwrap();

let stop_results = join_all(stops
.iter()
.map(|stop| {
let name = stop.id.clone();
let access_token = access_token.clone();
async move {
trias::stop_events(name, access_token, 10, api_endpoint)
.await
.unwrap_or_default()
}
})
).await;
let stop_results = join_all(stops.iter().map(|stop| {
let name = stop.id.clone();
let access_token = access_token.clone();
async move {
trias::stop_events(name, access_token, 10, api_endpoint)
.await
.unwrap_or_default()
}
}))
.await;

let mut journeys = HashMap::new();

Expand Down Expand Up @@ -326,7 +337,9 @@ pub fn train_position_per_route(
let segment_duration = next.1.arrival - last.1.departure;
let stop_id = last.0;
let next_stop_id = next.0;
let progress = (current_duration.num_seconds() as f32 / segment_duration.num_seconds() as f32).clamp(0., 1.);
let progress = (current_duration.num_seconds() as f32
/ segment_duration.num_seconds() as f32)
.clamp(0., 1.);
let points = points_on_route(stop_id, next_stop_id, stops);
if let Some(position) = interpolate_segment(&points, progress) {
return Some(Train {
Expand All @@ -346,9 +359,13 @@ pub static KVV_STOPS: OnceLock<Vec<Stop>> = OnceLock::new();

pub async fn init() {
let api_endpoint = dotenv::var("TRIAS_API_ENDPOINT").expect("TRIAS_API_ENDPOINT not set");
API_ENDPOINT.set(api_endpoint).expect("failed to set API_ENDPOINT");
API_ENDPOINT
.set(api_endpoint)
.expect("failed to set API_ENDPOINT");
let access_token = dotenv::var("TRIAS_ACCESS_TOKEN").expect("TRIAS_ACCESS_TOKEN not set");
ACCESS_TOKEN.set(access_token).expect("failed to set ACCESS_TOKEN");
ACCESS_TOKEN
.set(access_token)
.expect("failed to set ACCESS_TOKEN");
let stops = kvv_stops().await;
KVV_STOPS.set(stops).expect("failed to set KVV_STOPS");
}
Expand Down
157 changes: 157 additions & 0 deletions chai/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
use std::io::Write;
use std::{collections::HashMap, fs, ops::ControlFlow};

use kvv::LineDepartures;
use tracing::{info, log::warn};
use ws_message::{GameState, Team};

use crate::ws_message::ClientMessage;

pub mod kvv;
pub mod point;
pub mod unique_id;
pub mod ws_message;

const LOG_FILE: &str = "log.csv";

#[derive(Debug)]
pub enum InputMessage {
Client(String, u32),
Server(ServerMessage),
}

#[derive(Debug)]
pub enum ServerMessage {
Departures(LineDepartures),
ClientDisconnected(u32),
}

#[derive(Debug)]
pub enum ServerResponse {
Broadcast(String),
P2P(String, u32),
}

#[derive(Debug)]
pub struct Connection {
pub id: u32,
pub team_id: u32,
}

#[derive(Debug, Default)]
pub struct AppState {
pub connections: Vec<Connection>,
pub teams: Vec<Team>,
}

impl AppState {
pub fn client_mut(&mut self, id: u32) -> Option<&mut Connection> {
self.connections.iter_mut().find(|x| x.id == id)
}

pub fn client(&self, id: u32) -> Option<&Connection> {
self.connections.iter().find(|x| x.id == id)
}

pub fn team_mut_by_client_id(&mut self, id: u32) -> Option<&mut Team> {
if let Some(team_id) = self.client(id).map(|x| x.team_id) {
self.teams.iter_mut().find(|t| t.id == team_id)
} else {
None
}
}
}

pub fn process_message(
msg: InputMessage,
state: &mut AppState,
departures: &mut HashMap<String, kvv::Journey>,
) -> ControlFlow<()> {
match msg {
InputMessage::Client(msg, id) => {
let msg = serde_json::from_str(&msg).unwrap();
info!("Got message from client {}: {:?}", id, msg);
match msg {
ClientMessage::Position { long, lat } => {
if let Some(team) = state.team_mut_by_client_id(id) {
team.long = (long + team.long) / 2.;
team.lat = (lat + team.lat) / 2.;
}
}
ClientMessage::SetTeamPosition { long, lat, team_id } => {
if let Some(team) = state.teams.iter_mut().find(|t| t.id == team_id) {
team.long = long;
team.lat = lat;
}
}
ClientMessage::Message(msg) => {
info!("Got message: {}", msg);
}
ClientMessage::JoinTeam { team_id } => {
let Some(client) = state.client_mut(id) else {
warn!("Client {} not found", id);
return ControlFlow::Break(());
};
client.team_id = team_id;
}
ClientMessage::EmbarkTrain { train_id } => {
if let Some(team) = state.team_mut_by_client_id(id) {
team.on_train = Some(train_id);
}
}
ClientMessage::DisembarkTrain(_) => {
if let Some(team) = state.team_mut_by_client_id(id) {
team.on_train = None;
}
}
}
}
InputMessage::Server(ServerMessage::Departures(deps)) => {
*departures = deps;
}
InputMessage::Server(ServerMessage::ClientDisconnected(id)) => {
info!("Client {} disconnected", id);
state.connections.retain(|x| x.id != id);
}
}
ControlFlow::Continue(())
}

pub fn generate_response(
departures: &HashMap<String, kvv::Journey>,
state: &mut AppState,
) -> ServerResponse {
let time = chrono::Utc::now();
let mut trains = kvv::train_positions(departures, time);
trains.retain(|x| !x.line_id.contains("bus"));

// TODO: keep a file handle open once we have a proper external state
let mut log_file = fs::OpenOptions::new()
.append(true)
.create(true)
.open(LOG_FILE)
.unwrap();
TrueDoctor marked this conversation as resolved.
Show resolved Hide resolved

// update positions for players on trains
for team in state.teams.iter_mut() {
if let Some(train_id) = &team.on_train {
if let Some(train) = trains.iter().find(|x| &x.line_id == train_id) {
team.long = train.long;
team.lat = train.lat;
}
}
}

let game_state = GameState {
teams: state.teams.clone(),
trains,
};
writeln!(
log_file,
"{}, {}",
time.with_timezone(&chrono_tz::Europe::Berlin).to_rfc3339(),
serde_json::to_string(&game_state).unwrap()
)
.unwrap();
ServerResponse::Broadcast(serde_json::to_string(&game_state).unwrap())
}
5 changes: 4 additions & 1 deletion robusta/src/point.rs → chai/src/point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ pub struct Point {

impl Point {
pub fn distance(self, other: Self) -> f32 {
f32::hypot(other.latitude - self.latitude, other.longitude - self.longitude)
f32::hypot(
other.latitude - self.latitude,
other.longitude - self.longitude,
)
}

/// Linear interpolation.
Expand Down
File renamed without changes.
File renamed without changes.
3 changes: 2 additions & 1 deletion robusta/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
axum = { version = "0.6.20", features = ["ws", "tracing", "macros"] }
axum = { workspace = true, features = ["ws", "tracing", "macros"] }
chrono = "0.4.31"
chrono-tz = "0.8.4"
futures-util = "0.3.28"
chai = { path = "../chai" }
trias = { path = "../trias" }
reqwest = "0.11.22"
serde = { version = "1.0.188", features = ["derive"] }
Expand Down
Loading