diff options
author | metamuffin <metamuffin@disroot.org> | 2024-09-29 17:14:47 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-09-29 17:14:47 +0200 |
commit | 824771fd53fc9cff2388d6f3baae3380debad81f (patch) | |
tree | cbf324e768d68dd0d107d889b3dc6352364eaec4 /data | |
parent | 6fbc2a8f507b43cadc8c3d954e57552204f5dbee (diff) | |
download | hurrycurry-824771fd53fc9cff2388d6f3baae3380debad81f.tar hurrycurry-824771fd53fc9cff2388d6f3baae3380debad81f.tar.bz2 hurrycurry-824771fd53fc9cff2388d6f3baae3380debad81f.tar.zst |
attempt to rewrite recipes in rust
Diffstat (limited to 'data')
-rw-r--r-- | data/recipes/Cargo.toml | 12 | ||||
-rw-r--r-- | data/recipes/default.rs | 648 |
2 files changed, 660 insertions, 0 deletions
diff --git a/data/recipes/Cargo.toml b/data/recipes/Cargo.toml new file mode 100644 index 00000000..272918d6 --- /dev/null +++ b/data/recipes/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "recipe-generator" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0.210", features = ["derive"] } +serde_yml = "0.0.12" + +[[bin]] +path = "default.rs" +name = "recipes-default" 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(); +} |