summaryrefslogtreecommitdiff
path: root/data/recipes/default.rs
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2024-09-29 17:14:47 +0200
committermetamuffin <metamuffin@disroot.org>2024-09-29 17:14:47 +0200
commit824771fd53fc9cff2388d6f3baae3380debad81f (patch)
treecbf324e768d68dd0d107d889b3dc6352364eaec4 /data/recipes/default.rs
parent6fbc2a8f507b43cadc8c3d954e57552204f5dbee (diff)
downloadhurrycurry-824771fd53fc9cff2388d6f3baae3380debad81f.tar
hurrycurry-824771fd53fc9cff2388d6f3baae3380debad81f.tar.bz2
hurrycurry-824771fd53fc9cff2388d6f3baae3380debad81f.tar.zst
attempt to rewrite recipes in rust
Diffstat (limited to 'data/recipes/default.rs')
-rw-r--r--data/recipes/default.rs648
1 files changed, 648 insertions, 0 deletions
diff --git a/data/recipes/default.rs b/data/recipes/default.rs
new file mode 100644
index 00000000..34ae53f8
--- /dev/null
+++ b/data/recipes/default.rs
@@ -0,0 +1,648 @@
+/*
+ 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 serde::{Deserialize, Serialize};
+use std::{
+ collections::BTreeSet,
+ fmt::Display,
+ sync::{LazyLock, Mutex},
+};
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
+pub struct Item {
+ container: Option<Box<Item>>,
+ name: String,
+ container_dispose_tile: Option<String>,
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
+#[serde(rename_all = "snake_case")]
+pub enum RecipeAction {
+ #[default]
+ Never,
+ Passive,
+ Active,
+ Instant,
+ Demand,
+}
+
+#[rustfmt::skip]
+#[derive(Debug, Clone, Deserialize,Default, Serialize,PartialEq, Eq, PartialOrd, Ord)]
+pub struct Recipe<T> {
+ #[serde(default)] tile: Option<String>,
+ #[serde(default)] inputs: Vec<T>,
+ #[serde(default)] outputs: Vec<T>,
+ #[serde(default)] action: RecipeAction,
+ #[serde(default)] warn: bool,
+ #[serde(default)] revert_duration: Option<OrdAnyway<f32>>,
+ #[serde(default)] duration: Option<OrdAnyway<f32>>,
+ #[serde(default)] points: Option<i64>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
+#[repr(transparent)]
+#[serde(transparent)]
+struct OrdAnyway<T>(T);
+impl<T: PartialOrd> Ord for OrdAnyway<T> {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.partial_cmp(other).unwrap()
+ }
+}
+impl<T: PartialEq> Eq for OrdAnyway<T> {}
+
+static ALL_ITEMS: Mutex<BTreeSet<Item>> = Mutex::new(BTreeSet::new());
+static ALL_RECIPES: Mutex<BTreeSet<Recipe<Item>>> = Mutex::new(BTreeSet::new());
+
+fn out(r: Recipe<Item>) {
+ for i in r.inputs.clone() {
+ ALL_ITEMS.lock().unwrap().insert(i.clone());
+ }
+ for o in r.outputs.clone() {
+ ALL_ITEMS.lock().unwrap().insert(o.clone());
+ }
+ ALL_RECIPES.lock().unwrap().insert(r);
+}
+fn finish() {
+ println!(
+ "{}",
+ serde_yml::to_string(
+ &ALL_RECIPES
+ .lock()
+ .unwrap()
+ .iter()
+ .map(|r| Recipe {
+ action: r.action,
+ inputs: r.inputs.iter().map(|i| i.to_string()).collect(),
+ outputs: r.outputs.iter().map(|o| o.to_string()).collect(),
+ points: r.points,
+ tile: r.tile.clone(),
+ warn: r.warn,
+ duration: r.duration,
+ revert_duration: r.revert_duration,
+ })
+ .collect::<Vec<_>>()
+ )
+ .unwrap()
+ );
+}
+fn auto_trash() {
+ let all_items = ALL_ITEMS.lock().unwrap().clone();
+ for i in all_items {
+ if i.container_dispose_tile.is_some() {
+ continue;
+ }
+ if let Some(c) = &i.container {
+ let cdt = c.container_dispose_tile.as_ref().unwrap();
+ out(Recipe {
+ action: RecipeAction::Instant,
+ inputs: vec![i.clone()],
+ outputs: i.container.clone().into_iter().map(|i| *i).collect(),
+ tile: Some(cdt.to_owned()),
+ ..Default::default()
+ });
+ } else {
+ out(Recipe {
+ action: RecipeAction::Instant,
+ inputs: vec![i],
+ outputs: vec![],
+ tile: Some("trash".to_owned()),
+ ..Default::default()
+ });
+ }
+ }
+}
+fn auto_burn() {
+ let all_items = ALL_ITEMS.lock().unwrap().clone();
+ for i in all_items {
+ if i.container_dispose_tile.is_some() {
+ continue;
+ }
+ if i.container.is_some() || i.name == "burned" {
+ continue;
+ } else {
+ out(Recipe {
+ action: RecipeAction::Passive,
+ inputs: vec![i],
+ outputs: vec![Item {
+ name: "burned".to_owned(),
+ ..Default::default()
+ }],
+ duration: Some(OrdAnyway(1.5)),
+ revert_duration: Some(OrdAnyway(1.5)),
+ warn: true,
+ tile: Some("stove".to_owned()),
+ ..Default::default()
+ })
+ }
+ }
+}
+
+impl Item {
+ fn r#as(mut self, s: &str) -> Item {
+ self.name = s.to_owned();
+ self
+ }
+ fn tr(&self, container: Option<Box<Item>>) -> Item {
+ let o = Item {
+ name: self.name.to_owned(),
+ container,
+ ..Default::default()
+ };
+ if self.container == o.container {
+ return o;
+ }
+ match (self.container.clone(), o.container.clone()) {
+ (None, None) => (),
+ (Some(old_c), Some(new_c)) => out(Recipe {
+ action: RecipeAction::Instant,
+ inputs: vec![self.clone(), *new_c],
+ outputs: vec![*old_c, o.clone()],
+ ..Default::default()
+ }),
+ (None, Some(new_c)) => out(Recipe {
+ action: RecipeAction::Instant,
+ inputs: vec![*new_c, self.clone()],
+ outputs: vec![o.clone()],
+ ..Default::default()
+ }),
+ (Some(old_c), None) => out(Recipe {
+ action: RecipeAction::Instant,
+ inputs: vec![self.clone()],
+ outputs: vec![o.clone(), *old_c],
+ ..Default::default()
+ }),
+ }
+ return o;
+ }
+}
+impl Display for Item {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ if let Some(c) = &self.container {
+ write!(f, "{c}:")?;
+ }
+ write!(f, "{}", self.name)
+ }
+}
+
+static FP: LazyLock<Box<Item>> = LazyLock::new(move || {
+ Box::new(Item {
+ container: None,
+ name: "foodprocessor".to_owned(),
+ container_dispose_tile: Some("trash".to_string()),
+ })
+});
+static POT: LazyLock<Box<Item>> = LazyLock::new(move || {
+ Box::new(Item {
+ container: None,
+ name: "pot".to_owned(),
+ container_dispose_tile: Some("trash".to_string()),
+ ..Default::default()
+ })
+});
+static PAN: LazyLock<Box<Item>> = LazyLock::new(move || {
+ Box::new(Item {
+ container: None,
+ name: "pan".to_owned(),
+ container_dispose_tile: Some("trash".to_string()),
+ ..Default::default()
+ })
+});
+static PL: LazyLock<Box<Item>> = {
+ LazyLock::new(move || {
+ Box::new(Item {
+ container: None,
+ name: "plate".to_owned(),
+ container_dispose_tile: Some("trash".to_string()),
+ ..Default::default()
+ })
+ })
+};
+static GL: LazyLock<Box<Item>> = {
+ LazyLock::new(move || {
+ Box::new(Item {
+ container: None,
+ name: "glass".to_owned(),
+ container_dispose_tile: Some("sink".to_string()),
+ ..Default::default()
+ })
+ })
+};
+
+fn from_crate(s: &str) -> Item {
+ let item = Item {
+ name: s.to_owned(),
+ ..Default::default()
+ };
+ out(Recipe {
+ action: RecipeAction::Instant,
+ inputs: vec![],
+ outputs: vec![item.clone()],
+ tile: Some(format!("{s}-crate")),
+ points: Some(-1),
+ ..Default::default()
+ });
+ out(Recipe {
+ action: RecipeAction::Instant,
+ inputs: vec![item.clone()],
+ outputs: vec![],
+ tile: Some(format!("{s}-crate")),
+ points: Some(1),
+ ..Default::default()
+ });
+ return item;
+}
+fn cut(s: &Item) -> Item {
+ cut_two(s, false)
+}
+fn cut_two(s: &Item, two: bool) -> Item {
+ let o = Item {
+ name: format!("sliced-{}", s.name),
+ container: s.container.clone(),
+ ..Default::default()
+ };
+ out(Recipe {
+ action: RecipeAction::Active,
+ inputs: vec![s.clone()],
+ outputs: if two {
+ vec![o.clone(), o.clone()]
+ } else {
+ vec![o.clone()]
+ },
+ tile: Some("cuttingboard".to_owned()),
+ duration: Some(OrdAnyway(2.)),
+ ..Default::default()
+ });
+ o
+}
+
+const COOK_D: f32 = 20.;
+fn cook(s: &Item, d: f32) -> Item {
+ let o = Item {
+ name: format!("cooked-{}", s.name),
+ container: s.container.clone(),
+ ..Default::default()
+ };
+ out(Recipe {
+ action: RecipeAction::Passive,
+ duration: Some(OrdAnyway(d)),
+ revert_duration: Some(OrdAnyway(d * 2.)),
+ tile: Some("stove".to_owned()),
+ inputs: vec![s.clone()],
+ outputs: vec![o.clone()],
+ ..Default::default()
+ });
+ out(Recipe {
+ action: RecipeAction::Passive,
+ duration: Some(OrdAnyway(d / 3.)),
+ revert_duration: Some(OrdAnyway(d / 2.)),
+ tile: Some("stove".to_owned()),
+ inputs: vec![o.clone()],
+ outputs: vec![Item {
+ name: "burned".to_owned(),
+ container: Some(POT.clone()),
+ ..Default::default()
+ }],
+ warn: true,
+ ..Default::default()
+ });
+ return o;
+}
+const SEAR_D: f32 = 15.;
+fn sear(s: &Item, d: f32) -> Item {
+ let s = s.tr(Some(PAN.clone()));
+ let o = Item {
+ name: format!("seared-{}", s.name),
+ container: s.container.clone(),
+ ..Default::default()
+ };
+ out(Recipe {
+ action: RecipeAction::Passive,
+ duration: Some(OrdAnyway(d)),
+ revert_duration: Some(OrdAnyway(d * 2.)),
+ tile: Some("stove".to_owned()),
+ inputs: vec![s.clone()],
+ outputs: vec![o.clone()],
+ ..Default::default()
+ });
+ out(Recipe {
+ action: RecipeAction::Passive,
+ duration: Some(OrdAnyway(d / 3.)),
+ revert_duration: Some(OrdAnyway(d / 3.)),
+ tile: Some("stove".to_owned()),
+ inputs: vec![o.clone()],
+ outputs: vec![Item {
+ name: "burned".to_owned(),
+ container: Some(PAN.clone()),
+ ..Default::default()
+ }],
+ warn: true,
+ ..Default::default()
+ });
+ return o;
+}
+const BAKE_D: f32 = 25.;
+fn bake(s: &Item, d: f32) -> Item {
+ let o = Item {
+ name: format!("sliced-{}", s.name),
+ container: s.container.clone(),
+ ..Default::default()
+ };
+ out(Recipe {
+ action: RecipeAction::Passive,
+ duration: Some(OrdAnyway(d)),
+ revert_duration: Some(OrdAnyway(d * 2.)),
+ tile: Some("oven".to_owned()),
+ inputs: vec![s.clone()],
+ outputs: vec![o.clone()],
+ ..Default::default()
+ });
+ out(Recipe {
+ action: RecipeAction::Passive,
+ duration: Some(OrdAnyway(d / 2.)),
+ revert_duration: Some(OrdAnyway(d / 4.)),
+ tile: Some("oven".to_owned()),
+ inputs: vec![o.clone()],
+ outputs: vec![Item {
+ name: "burned".to_owned(),
+ ..Default::default()
+ }],
+ warn: true,
+ ..Default::default()
+ });
+ return o;
+}
+fn freeze(s: &Item) -> Item {
+ let o = Item {
+ name: format!("frozen-{}", s.name),
+ container: s.container.clone(),
+ ..Default::default()
+ };
+ out(Recipe {
+ action: RecipeAction::Passive,
+ duration: Some(OrdAnyway(25.)),
+ tile: Some("freezer".to_owned()),
+ inputs: vec![s.clone()],
+ outputs: vec![o.clone()],
+ ..Default::default()
+ });
+ return o;
+}
+fn process(s: &Item) -> Item {
+ let o = Item {
+ name: format!("processed-{}", s.name),
+ container: s.container.clone(),
+ ..Default::default()
+ };
+ out(Recipe {
+ action: RecipeAction::Passive,
+ duration: Some(OrdAnyway(5.)),
+ inputs: vec![s.clone()],
+ outputs: vec![o.clone()],
+ ..Default::default()
+ });
+ return o;
+}
+fn container_add(base: &Item, add: &Item) -> Item {
+ let o = Item {
+ name: "!!!".to_string(),
+ container: base.container.clone(),
+ ..Default::default()
+ };
+ out(Recipe {
+ action: RecipeAction::Instant,
+ inputs: vec![base.clone(), add.clone()],
+ outputs: if let Some(c) = add.container.clone() {
+ vec![o.clone(), *c]
+ } else {
+ vec![o.clone()]
+ },
+ ..Default::default()
+ });
+ return o;
+}
+fn combine(c: Item, items: Vec<&Item>) -> Item {
+ if items.len() == 1 {
+ return items[0].tr(Some(Box::new(c.clone())));
+ }
+ let mut open = BTreeSet::from_iter(items.iter().map(|i| {
+ i.tr(Some(Box::new(c.clone())));
+ vec![i.name.clone()]
+ }));
+ let mut seen = BTreeSet::new();
+ let mut result = None;
+ while let Some(cur) = open.pop_first() {
+ for &new_item in &items {
+ if cur.contains(&new_item.name) {
+ continue;
+ }
+ let rkey = format!("{}#{}", cur.join(","), new_item);
+
+ if seen.contains(&rkey) {
+ continue;
+ }
+ seen.insert(rkey);
+
+ let mut parts = cur.clone();
+ parts.push(new_item.name.clone());
+ parts.sort();
+ open.insert(parts.clone());
+ let i = Item {
+ name: cur.join(","),
+ container: Some(Box::new(c.clone())),
+ ..Default::default()
+ };
+ let o = Item {
+ name: parts.join(","),
+ container: Some(Box::new(c.clone())),
+ ..Default::default()
+ };
+ if parts.len() == items.len() {
+ result = Some(o.clone())
+ };
+ out(Recipe {
+ action: RecipeAction::Instant,
+ inputs: vec![i, new_item.clone()],
+ outputs: if let Some(c) = new_item.container.clone() {
+ vec![o, *c]
+ } else {
+ vec![o]
+ },
+ ..Default::default()
+ })
+ }
+ }
+ return result.unwrap();
+}
+fn edible(i: Item) {
+ out(Recipe {
+ action: RecipeAction::Demand,
+ inputs: vec![i],
+ outputs: vec![], // TODO
+ duration: Some(OrdAnyway(10.)),
+ ..Default::default()
+ })
+}
+fn either(a: Item, b: Item) -> Item {
+ if a.name != b.name {
+ panic!("either options are named differently");
+ }
+ if a.container != b.container {
+ panic!("either options are contained differently");
+ }
+ return a;
+}
+fn sink_fill() -> Item {
+ let o = Item {
+ name: "water".to_owned(),
+ container: Some(GL.clone()),
+ ..Default::default()
+ };
+ out(Recipe {
+ action: RecipeAction::Active,
+ inputs: vec![*GL.clone()],
+ outputs: vec![o.clone()],
+ tile: Some("sink".to_owned()),
+ duration: Some(OrdAnyway(1.)),
+ ..Default::default()
+ });
+ return o;
+}
+
+fn main() {
+ out(Recipe {
+ action: RecipeAction::Active,
+ duration: Some(OrdAnyway(2.)),
+ tile: Some("sink".to_owned()),
+ inputs: vec![Item {
+ name: "dirty-plate".to_owned(),
+ ..Default::default()
+ }],
+ outputs: vec![*PL.clone()],
+ ..Default::default()
+ });
+
+ let tomato = from_crate("tomato");
+ let steak = from_crate("steak");
+ let flour = from_crate("flour");
+ let leek = from_crate("leek");
+ let rice = from_crate("rice");
+ let fish = from_crate("fish");
+ let coconut = from_crate("coconut");
+ let strawberry = from_crate("strawberry");
+ let cheese = from_crate("cheese");
+ let lettuce = from_crate("lettuce");
+
+ // // Buns
+ let dough = process(&flour.tr(Some(FP.clone()))).r#as("dough").tr(None);
+ let bun = bake(&dough, BAKE_D).r#as("bun");
+ edible(bun.tr(Some(PL.clone())));
+
+ // Steak
+ let seared_steak = sear(&steak, SEAR_D);
+
+ edible(combine(*PL.clone(), vec![&seared_steak, &bun]));
+ edible(combine(*PL.clone(), vec![&seared_steak]));
+
+ // Salad
+ edible(combine(*PL.clone(), vec![&cut(&tomato), &cut(&lettuce)]));
+ edible(combine(*PL.clone(), vec![&cut(&lettuce)]));
+
+ // Burger
+ let b_patty = sear(&cut(&steak).r#as("patty"), SEAR_D);
+
+ edible(combine(
+ *PL.clone(),
+ vec![&cut(&bun), &b_patty, &cut(&cheese)],
+ ));
+ edible(combine(
+ *PL.clone(),
+ vec![&cut(&bun), &b_patty, &cut(&cheese), &cut(&lettuce)],
+ ));
+ edible(combine(
+ *PL.clone(),
+ vec![&cut(&bun), &b_patty, &cut(&tomato), &cut(&lettuce)],
+ ));
+ edible(combine(
+ *PL.clone(),
+ vec![&cut(&bun), &b_patty, &cut(&cheese), &cut(&tomato)],
+ ));
+ edible(combine(
+ *PL.clone(),
+ vec![&cut(&bun), &cut(&cheese), &cut(&lettuce), &cut(&tomato)],
+ ));
+ edible(combine(
+ *PL.clone(),
+ vec![&cut(&bun), &cut(&lettuce), &cut(&tomato)],
+ ));
+
+ // Soup
+ let tomato_juice = process(&tomato.tr(Some(FP.clone()))).r#as("tomato-juice");
+ let leek_tj_pot = combine(*POT.clone(), vec![&leek, &tomato_juice]);
+ let tomato_soup_plate = cook(&leek_tj_pot, COOK_D)
+ .r#as("tomato-soup")
+ .tr(Some(PL.clone()));
+ edible(tomato_soup_plate);
+
+ // Rice and nigiri
+ let nigiri = container_add(&cut(&fish), &cook(&rice.tr(Some(POT.clone())), COOK_D))
+ .r#as("nigiri")
+ .tr(Some(PL.clone()));
+ edible(nigiri);
+
+ // coconut milk and strawberry puree
+ let strawberry_puree = process(&strawberry.tr(Some(FP.clone()))).r#as("strawberry-puree");
+ let milk = process(&coconut.tr(Some(FP.clone()))).r#as("milk");
+ let strawberry_shake = either(
+ process(&container_add(&milk, &strawberry).r#as("milk,strawberry"))
+ .r#as("strawberry-shake"),
+ process(&container_add(&strawberry_puree, &coconut).r#as("coconut,strawberry-puree"))
+ .r#as("strawberry-shake"),
+ );
+
+ // Icecream
+ edible(
+ freeze(&strawberry_shake)
+ .r#as("strawberry-icecream")
+ .tr(Some(PL.clone())),
+ );
+
+ // Mochi
+ let rice_flour = process(&rice.tr(Some(FP.clone()))).r#as("rice-flour");
+ let mochi_dough = cook(&rice_flour.tr(Some(POT.clone())), 5.).r#as("mochi-dough");
+ let strawberry_mochi = container_add(&strawberry, &mochi_dough).r#as("strawberry-mochi");
+ edible(strawberry_mochi);
+
+ // Drinks
+
+ edible(strawberry_shake.tr(Some(GL.clone())));
+ edible(tomato_juice.tr(Some(GL.clone())));
+ edible(sink_fill());
+
+ // Curry
+ let curry_with_rice = combine(
+ *PL.clone(),
+ vec![
+ &cook(&&rice.tr(Some(POT.clone())), COOK_D),
+ &cook(&combine(*POT.clone(), vec![&milk, &tomato, &leek]), COOK_D).r#as("curry"),
+ ],
+ );
+ edible(curry_with_rice);
+
+ auto_trash();
+ auto_burn();
+ finish();
+}