aboutsummaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2024-07-13 23:27:18 +0200
committermetamuffin <metamuffin@disroot.org>2024-07-13 23:27:18 +0200
commit0c77e0938de43a970e03c6dcef019c87745f0ee4 (patch)
tree76212988078d18edd59330061eeecb38b6ab7fc5 /server
parentcd3a3989901d877426cbd64622b93367eaf81d4a (diff)
downloadhurrycurry-0c77e0938de43a970e03c6dcef019c87745f0ee4.tar
hurrycurry-0c77e0938de43a970e03c6dcef019c87745f0ee4.tar.bz2
hurrycurry-0c77e0938de43a970e03c6dcef019c87745f0ee4.tar.zst
automatically generate demands from map and recipes. added icecream and sushi.
Diffstat (limited to 'server')
-rw-r--r--server/src/bin/graph.rs2
-rw-r--r--server/src/data/demands.rs94
-rw-r--r--server/src/data/mod.rs (renamed from server/src/data.rs)49
-rw-r--r--server/src/state.rs14
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 {