diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..70f9eae --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[registries.crates-io] +protocol = "sparse" diff --git a/flake.lock b/flake.lock index 88b5380..04252dc 100644 --- a/flake.lock +++ b/flake.lock @@ -6,11 +6,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1685254813, - "narHash": "sha256-Pod+U90fDJJml5cwoOvx/KKBF4HmWtK9Cttql5sfwFQ=", + "lastModified": 1685514167, + "narHash": "sha256-urRxF0ZGSNeZjM4kALNg3wTh7fBscbqQmS6S/HU7Wms=", "owner": "nix-community", "repo": "fenix", - "rev": "2804d7ee704057959d831b038dea0e6845b18658", + "rev": "3abfea51663583186f687c49a157eab1639349ca", "type": "github" }, "original": { @@ -24,11 +24,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1681202837, - "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "lastModified": 1685518550, + "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", "owner": "numtide", "repo": "flake-utils", - "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", "type": "github" }, "original": { @@ -38,11 +38,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1685168767, - "narHash": "sha256-wQgnxz0PdqbyKKpsWl/RU8T8QhJQcHfeC6lh1xRUTfk=", + "lastModified": 1685383865, + "narHash": "sha256-3uQytfnotO6QJv3r04ajSXbEFMII0dUtw0uqYlZ4dbk=", "owner": "nixos", "repo": "nixpkgs", - "rev": "e10802309bf9ae351eb27002c85cfdeb1be3b262", + "rev": "5e871d8aa6f57cc8e0dc087d1c5013f6e212b4ce", "type": "github" }, "original": { @@ -54,11 +54,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1685325175, - "narHash": "sha256-QAKsxoePrMUoHRYpfbxBeVXDKoRJ8S864/UMjML+JiU=", + "lastModified": 1685555835, + "narHash": "sha256-P2BQb6kdLfinxkJALnqI0GdlEjBBK9BuGAHpCtNYjl8=", "owner": "nixos", "repo": "nixpkgs", - "rev": "4295779fbaacb7158d5f32c4aa2740ef7b56d91b", + "rev": "254264957ed99b982312504783c2e6036822f826", "type": "github" }, "original": { @@ -77,11 +77,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1685177170, - "narHash": "sha256-bRURsRZZmBZtQo8OHD/PRslGQC04wed6lWroQaAPSPg=", + "lastModified": 1685465261, + "narHash": "sha256-aJ2nUinUrNcFi+pb47bS5IIAeSiUEEPLJY8W4Q8Pcjk=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "f6e3a87bf9478574f8c64ac2efec125bc19b1c64", + "rev": "d2b3caa5b5694125fad04a9699e919444439f6a2", "type": "github" }, "original": { diff --git a/src/main.rs b/src/main.rs index c41772e..f8d4022 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,8 @@ async fn main() -> Result<()> { let web_app_handle = tokio::spawn(web_app()); - let (game_result, web_result) = tokio::join!(game_loop_handle, web_app_handle); + let (game_result, web_result) = + tokio::join!(game_loop_handle, web_app_handle); game_result??; web_result??; diff --git a/src/map.rs b/src/map.rs index 29b592c..6accb5c 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,15 +1,34 @@ -use std::{collections::HashSet, path::Path}; +use std::{collections::HashSet, ops::Deref, path::Path, sync::Arc}; use anyhow::Result; use image::{Rgb, RgbImage}; use imageproc::{ - drawing::{draw_filled_circle_mut, draw_filled_rect_mut, draw_line_segment_mut}, + drawing::{ + draw_filled_circle_mut, draw_filled_rect_mut, draw_line_segment_mut, + }, rect::Rect, }; use itertools::Itertools; use nalgebra::Vector2; use rand::Rng; +#[derive(Clone)] +pub struct MapRef(Arc); + +impl MapRef { + pub fn new(map: Map) -> Self { + Self(Arc::new(map)) + } +} + +impl Deref for MapRef { + type Target = Map; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + pub struct Map { points: Vec>, @@ -80,7 +99,10 @@ impl Map { |(top_left, bottom_right), point| { ( Vector2::new(top_left.x.min(point.x), top_left.y.min(point.y)), - Vector2::new(bottom_right.x.max(point.x), bottom_right.y.max(point.y)), + Vector2::new( + bottom_right.x.max(point.x), + bottom_right.y.max(point.y), + ), ) }, ); @@ -105,16 +127,18 @@ impl Map { ); let to_image_space_f32 = |p: &Vector2| { - let image_x = ((p.x - top_left.x) * image_width as f64 / space_width) as f32; - let image_y = - image_height as f32 - ((p.y - bottom_right.y) * image_height as f64 / space_height) as f32; + let image_x = + ((p.x - top_left.x) * image_width as f64 / space_width) as f32; + let image_y = image_height as f32 + - ((p.y - bottom_right.y) * image_height as f64 / space_height) as f32; (image_x, image_y) }; let to_image_space_i32 = |p: &Vector2| { - let image_x = ((p.x - top_left.x) * image_width as f64 / space_width) as i32; - let image_y = - image_height as i32 - ((p.y - bottom_right.y) * image_height as f64 / space_height) as i32; + let image_x = + ((p.x - top_left.x) * image_width as f64 / space_width) as i32; + let image_y = image_height as i32 + - ((p.y - bottom_right.y) * image_height as f64 / space_height) as i32; (image_x, image_y) }; diff --git a/src/state.rs b/src/state.rs index 76816a8..1914a52 100644 --- a/src/state.rs +++ b/src/state.rs @@ -3,8 +3,11 @@ use std::{ time::{Duration, Instant}, }; +use anyhow::Result; use rand::Rng; +use crate::map::{Map, MapOptionsBuilder, MapRef}; + /// The entire game state at a particular point in time (keyframe) pub struct GameState { /// When the game started @@ -16,68 +19,141 @@ pub struct GameState { /// How long it's been since the beginning of the game elapsed_time: Duration, - empires: HashMap, + galaxy_state: GalaxyState, } impl GameState { - pub fn new() -> GameState { - GameState { + pub fn new() -> Result { + let rng = rand::thread_rng(); + let map = Map::generate(MapOptionsBuilder::default().build()?, rng); + + Ok(GameState { start_time: Instant::now(), elapsed_time: Duration::new(0, 0), - empires: HashMap::default(), - } + galaxy_state: GalaxyState::new(map), + }) } /// Compute when the next keyframe would be based on the current knowledge of /// the game, and what the state would look like at that keyframe. /// - /// This process must be _entirely_ deterministic. - pub fn next_keyframe(&self) -> PendingGameState { + /// This process must be _entirely_ atomic and deterministic. + pub fn next_keyframe(&self) -> NextKeyframe { todo!() } /// Interpolate two frames to find out what the frame at the given timestamp /// would look like. /// - /// This process must be _entirely_ deterministic. - pub fn interpolate(from: &GameState, to: &GameState, at: Duration) -> GameState { - debug_assert_eq!(from.start_time, to.start_time); - + /// This process must be _entirely_ atomic and deterministic. + pub fn evaluate(from: &Self, at: Duration) -> GameState { GameState { start_time: from.start_time, elapsed_time: at, - empires: todo!(), + galaxy_state: GalaxyState::evaluate(&from.galaxy_state, at), } } } -/// A game state that is pending several non-deterministic operations to be applied -pub struct PendingGameState { +/// A game state that is pending several non-deterministic operations to be +/// applied +pub struct NextKeyframe { inner: GameState, pending_operations: HashSet, + + intermediate_events: HashSet, } +/// Mostly notifications and stuff +pub enum IntermediateEvent {} + pub enum GameStateChange { /// A new empire joins the galaxy CreateEmpire, } -impl PendingGameState { +impl NextKeyframe { /// Resolve all the pending operations into a keyframe pub fn resolve(self, _rand: impl Rng) -> GameState { todo!() } } +#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct EmpireId(usize); - pub struct ResourceId(usize); - pub struct MetricId(usize); +pub struct FleetId(usize); +pub struct StarSystemId(usize); -pub struct EmpireState { - resources: HashMap, +pub struct GalaxyState { + empires: HashMap, + map: MapRef, } -pub struct ResourceInfo {} +impl GalaxyState { + pub fn new(map: Map) -> Self { + GalaxyState { + empires: HashMap::default(), + map: MapRef::new(map), + } + } + + /// Interpolate two GalaxyStates + pub fn evaluate(from: &Self, at: Duration) -> Self { + // Interpolate each empire separately + let empire_keys = { + let mut keys = from.empires.keys().collect::>(); + keys.sort(); + keys + }; + + let new_empires = empire_keys + .into_iter() + .map(|empire_id| { + let from_empire = from.empires.get(&empire_id).expect("must exist"); + let new_empire = EmpireState::evaluate(from_empire, at); + (*empire_id, new_empire) + }) + .collect(); + + GalaxyState { + empires: new_empires, + map: from.map.clone(), + } + } +} + +pub struct EmpireState { + id: EmpireId, + + resources: HashMap, + + fleets: HashMap, +} + +impl EmpireState { + /// Interpolate empire states + pub fn evaluate(from: &Self, at: Duration) -> Self { + // Increase resources using the specified equation + let new_resources = from + .resources + .iter() + .map(|(resource_id, resource_state)| {}); + + todo!() + } +} + +pub struct ResourceState {} + +pub struct FleetState { + owned_by_empire: EmpireId, + command_queue: Vec, +} + +pub enum FleetCommand { + GoToSystem(StarSystemId), + AttackFleet(FleetId), +} diff --git a/src/web_app.rs b/src/web_app.rs index 7df62a0..152960a 100644 --- a/src/web_app.rs +++ b/src/web_app.rs @@ -1,11 +1,15 @@ +use std::{net::SocketAddr, str::FromStr}; + use anyhow::Result; use axum::{routing::get, Router, Server}; pub async fn web_app() -> Result<()> { let app = Router::new().route("/", get(|| async { "Hello, World!" })); - // run it with hyper on localhost:3000 - Server::bind(&"0.0.0.0:3000".parse().unwrap()) + // run it with hyper on localhost + let addr = SocketAddr::from_str("0.0.0.0:8392")?; + println!("Running a web server on {addr:?}"); + Server::bind(&addr) .serve(app.into_make_service()) .await?;