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 { | 
