diff options
Diffstat (limited to 'server/src')
-rw-r--r-- | server/src/bin/graph.rs | 2 | ||||
-rw-r--r-- | server/src/data/demands.rs | 94 | ||||
-rw-r--r-- | server/src/data/mod.rs (renamed from server/src/data.rs) | 49 | ||||
-rw-r--r-- | server/src/state.rs | 14 |
4 files changed, 130 insertions, 29 deletions
diff --git a/server/src/bin/graph.rs b/server/src/bin/graph.rs index 888119aa..49ad4716 100644 --- a/server/src/bin/graph.rs +++ b/server/src/bin/graph.rs @@ -33,7 +33,7 @@ async fn main() -> Result<()> { .nth(1) .ok_or(anyhow!("first arg should be recipe set name"))?; - let data = index.generate(format!("lobby-default-{rn}")).await?; + let data = index.generate(format!("sushibar-{rn}")).await?; for i in 0..data.item_names.len() { println!("i{i} [label=\"{}\"]", data.item_name(ItemIndex(i))) diff --git a/server/src/data/demands.rs b/server/src/data/demands.rs new file mode 100644 index 00000000..2501e225 --- /dev/null +++ b/server/src/data/demands.rs @@ -0,0 +1,94 @@ +/* + Hurry Curry! - a game about cooking + Copyright 2024 metamuffin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, version 3 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +*/ +use super::Demand; +use crate::interaction::Recipe; +use hurrycurry_protocol::{ItemIndex, TileIndex}; +use std::collections::{HashMap, HashSet}; + +pub fn generate_demands( + tiles: HashSet<TileIndex>, + items: HashSet<ItemIndex>, + raw_demands: &[(ItemIndex, Option<ItemIndex>, f32)], + recipes: &[Recipe], +) -> Vec<Demand> { + let recipes = recipes + .iter() + .filter(|r| r.tile().map(|t| tiles.contains(&t)).unwrap_or(true)) + .collect::<Vec<_>>(); + + let mut producable = HashMap::new(); + + for i in &items { + producable.insert(*i, 0.0); + } + + loop { + let prod_count = producable.len(); + + for r in &recipes { + let output_count = r.outputs().iter().filter(|o| !items.contains(&o)).count(); + let Some(ingred_cost) = r + .inputs() + .iter() + .map(|i| producable.get(i).copied()) + .reduce(|a, b| { + if let (Some(a), Some(b)) = (a, b) { + Some(a + b) + } else { + None + } + }) + .unwrap_or(Some(0.)) + else { + continue; + }; + + let base_cost = match r { + Recipe::Passive { duration, .. } => 2. + duration * 0.1, + Recipe::Active { duration, .. } => 2. + duration, + Recipe::Instant { .. } => 1., + }; + + let output_cost = (ingred_cost + base_cost) / output_count as f32; + for o in r.outputs() { + let cost = producable.entry(o).or_insert(f32::INFINITY); + *cost = cost.min(output_cost); + } + } + + if prod_count == producable.len() { + break; + } + } + + raw_demands + .iter() + .filter_map(|(i, o, d)| { + if let Some(cost) = producable.get(i) { + Some(Demand { + from: *i, + to: *o, + duration: *d, + points: *cost as i64, + }) + } else { + None + } + }) + .collect() +} diff --git a/server/src/data.rs b/server/src/data/mod.rs index 24e8e232..6df60535 100644 --- a/server/src/data.rs +++ b/server/src/data/mod.rs @@ -21,6 +21,7 @@ use crate::{ interaction::Recipe, }; use anyhow::{anyhow, bail, Result}; +use demands::generate_demands; use hurrycurry_protocol::{ glam::{IVec2, Vec2}, DemandIndex, ItemIndex, MapMetadata, RecipeIndex, TileIndex, @@ -35,6 +36,8 @@ use std::{ }; use tokio::fs::read_to_string; +pub mod demands; + #[derive(Debug, Deserialize, Serialize, Clone, Copy, Default)] #[serde(rename_all = "snake_case")] pub enum Action { @@ -43,6 +46,7 @@ pub enum Action { Passive, Active, Instant, + Demand, } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -159,30 +163,23 @@ impl DataIndex { } pub async fn generate(&self, spec: String) -> Result<Gamedata> { - let (map, rest) = spec.split_once("-").unwrap_or((spec.as_str(), "default")); - let (demands, recipes) = rest.split_once("-").unwrap_or((rest, "default")); + let (map, recipes) = spec.split_once("-").unwrap_or((spec.as_str(), "default")); let map_in = serde_yaml::from_str(&self.read_map(map).await?)?; - let demands_in = serde_yaml::from_str(&self.read_demands(demands).await?)?; let recipes_in = serde_yaml::from_str(&self.read_recipes(recipes).await?)?; - let mut gd = Gamedata::build(spec, map_in, demands_in, recipes_in)?; + let mut gd = Gamedata::build(spec, map_in, recipes_in)?; gd.map = self.maps.clone(); Ok(gd) } } impl Gamedata { - pub fn build( - spec: String, - map_in: InitialMap, - demands_in: Vec<DemandDecl>, - recipes_in: Vec<RecipeDecl>, - ) -> Result<Self> { + pub fn build(spec: String, map_in: InitialMap, recipes_in: Vec<RecipeDecl>) -> Result<Self> { let reg = ItemTileRegistry::default(); let mut recipes = Vec::new(); - let mut demands = Vec::new(); let mut entities = Vec::new(); + let mut raw_demands = Vec::new(); for mut r in recipes_in { let r2 = r.clone(); @@ -217,24 +214,32 @@ impl Gamedata { outputs: [outputs.next(), outputs.next()], }); } + Action::Demand => raw_demands.push(( + inputs.next().ok_or(anyhow!("demand needs inputs"))?, + outputs.next(), + r.duration.unwrap_or(10.), + )), } assert_eq!(inputs.next(), None, "{r2:?} inputs left over"); assert_eq!(outputs.next(), None, "{r2:?} outputs left over"); assert_eq!(r.points, None, "points specified where not possible") } - for d in demands_in { - demands.push(Demand { - from: reg.register_item(d.from), - to: d.to.map(|to| reg.register_item(to)), - duration: d.duration, - points: d.points, - }) - } + // TODO + // for d in demands_in { + // demands.push(Demand { + // from: reg.register_item(d.from), + // to: d.to.map(|to| reg.register_item(to)), + // duration: d.duration, + // points: d.points, + // }) + // } let mut chef_spawn = Vec2::new(0., 0.); let mut customer_spawn = Vec2::new(0., 0.); let mut initial_map = HashMap::new(); + let mut tiles_used = HashSet::new(); + let mut items_used = HashSet::new(); for (y, line) in map_in.map.iter().enumerate() { for (x, tile) in line.trim().chars().enumerate() { let pos = IVec2::new(x as i32, y as i32); @@ -255,6 +260,10 @@ impl Gamedata { let itemname = map_in.items.get(&tile).cloned(); let tile = reg.register_tile(tilename); let item = itemname.map(|i| reg.register_item(i)); + tiles_used.insert(tile); + if let Some(i) = item { + items_used.insert(i); + }; initial_map.insert(pos, (tile, item)); } } @@ -267,6 +276,8 @@ impl Gamedata { .try_collect::<Vec<_>>()?, ); + let demands = generate_demands(tiles_used, items_used, &raw_demands, &recipes); + let item_names = reg.items.into_inner().unwrap(); let tile_names = reg.tiles.into_inner().unwrap(); let tile_collide = tile_names diff --git a/server/src/state.rs b/server/src/state.rs index e9cb1722..215f9a1b 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -76,7 +76,7 @@ impl State { index.reload()?; let mut game = Game::new(); - game.load(index.generate("lobby-none-none".to_string()).await?, None); + game.load(index.generate("lobby-none".to_string()).await?, None); Ok(Self { game, index, tx }) } @@ -88,10 +88,8 @@ impl State { text: format!("Game finished. You reached {} points.", self.game.points), }) .ok(); - self.game.load( - self.index.generate("lobby-none-none".to_string()).await?, - None, - ); + self.game + .load(self.index.generate("lobby-none".to_string()).await?, None); } while let Some(p) = self.game.packet_out() { if matches!(p, PacketC::UpdateMap { .. } | PacketC::Position { .. }) { @@ -156,10 +154,8 @@ impl State { ), }) .ok(); - self.game.load( - self.index.generate("lobby-none-none".to_string()).await?, - None, - ); + self.game + .load(self.index.generate("lobby-none".to_string()).await?, None); } Command::Reload => { if self.game.count_chefs() > 1 { |