From 4e109e806c3c177f10b842865a21e689c0f5a10d Mon Sep 17 00:00:00 2001 From: metamuffin Date: Mon, 30 Sep 2024 19:13:48 +0200 Subject: recipe generation with javascript for portability to nodejs which we use for flatpak --- Cargo.lock | 8 - Cargo.toml | 1 - data/makefile | 11 +- data/recipes/Cargo.toml | 12 - data/recipes/default.js | 289 +++++++++++++++++++++ data/recipes/default.rs | 648 ------------------------------------------------ data/recipes/default.ts | 288 --------------------- data/recipes/none.ts | 18 -- 8 files changed, 297 insertions(+), 978 deletions(-) delete mode 100644 data/recipes/Cargo.toml create mode 100644 data/recipes/default.js delete mode 100644 data/recipes/default.rs delete mode 100644 data/recipes/default.ts delete mode 100644 data/recipes/none.ts diff --git a/Cargo.lock b/Cargo.lock index df34c0a9..40952269 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2297,14 +2297,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "recipe-generator" -version = "0.1.0" -dependencies = [ - "serde", - "serde_yml", -] - [[package]] name = "redox_syscall" version = "0.5.3" diff --git a/Cargo.toml b/Cargo.toml index 89a4e650..cbe90e84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ members = [ "server/replaytool", "server/registry", "server/discover", - "data/recipes", "pixel-client", "pixel-client/tools", "locale/tools", diff --git a/data/makefile b/data/makefile index af215a6c..a13f9095 100644 --- a/data/makefile +++ b/data/makefile @@ -13,11 +13,16 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # -all: $(patsubst %.ts,%.yaml,$(wildcard recipes/*.ts)) +SETS = default none +all: $(patsubst %,recipes/%.yaml,$(SETS)) graphs: recipes/default.svg -recipes/%.yaml: recipes/%.ts - DENO_NO_UPDATE_CHECK=1 deno run $< > $@ +JSR = deno run + +recipes/none.yaml: + echo > $@ +recipes/default.yaml: recipes/default.js + DENO_NO_UPDATE_CHECK=1 $(JSR) $< > $@ recipes/%.gv.txt: recipes/%.yaml { cd .. && cargo +nightly run --release --bin graph $(patsubst recipes/%.yaml,%,$<); } > $@ diff --git a/data/recipes/Cargo.toml b/data/recipes/Cargo.toml deleted file mode 100644 index 272918d6..00000000 --- a/data/recipes/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[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.js b/data/recipes/default.js new file mode 100644 index 00000000..c1c72714 --- /dev/null +++ b/data/recipes/default.js @@ -0,0 +1,289 @@ +/* + 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 . + +*/ + +//? Is this a good idea? Probably not. + +// export interface Recipe { +// tile?: string, +// inputs: (Item | null | undefined)[], +// outputs: (Item | null | undefined)[], +// action: "instant" | "passive" | "active" | "demand" | "demand" +// duration?: number +// revert_duration?: number, +// warn?: boolean, +// points?: number, +// } + +const all_items = new Set() +const all_recipes = new Set() +export function out(r) { + r.inputs.forEach(i => i ? all_items.add(i) : void 0) + r.outputs.forEach(i => i ? all_items.add(i) : void 0) + r.inputs = r.inputs.filter(e => e) + r.outputs = r.outputs.filter(e => e) + all_recipes.add(r); +} +export function finish() { + const k = new Set() + for (const r of all_recipes) { + let s = `- action: !${r.action}\n` + s += ` inputs: [${r.inputs.map(e => JSON.stringify(e.toString())).join(",")}]\n` + s += ` outputs: [${r.outputs.map(e => JSON.stringify(e.toString())).join(",")}]\n` + if (r.warn) s += ` warn: true\n` + if (r.duration) s += ` duration: ${r.duration}\n` + if (r.revert_duration) s += ` revert_duration: ${r.revert_duration}\n` + if (r.points) s += ` points: ${r.points}\n` + if (r.tile) s += ` tile: ${r.tile}\n` + k.add(s) + } + for (const r of k) + console.log(r); +} +function auto_trash() { + for (const i of [...all_items]) { + if (i instanceof Container) continue + if (!i.container) out({ action: "instant", inputs: [i], outputs: [], tile: "trash" }) + else { + out({ + action: "instant", + inputs: [i], + outputs: [i.container.dispose ?? i.container], + tile: i.container.dispose_tile ?? "trash" + }) + } + } +} +function auto_burn() { + for (const i of [...all_items]) { + if (i instanceof Container) continue + if (i.container || i.name == "burned") continue + else { + out({ + action: "passive", + inputs: [i], + outputs: [new Item("burned")], + duration: 1.5, + revert_duration: 1.5, + warn: true, + tile: "stove" + }) + } + } +} + +class Item { + constructor(name, container) { this.name = name; this.container = container } + as(s) { this.name = s; return this } + tr(container) { + const o = new Item(this.name, container) + if (this.container == container) return o + if (this.container) out({ action: "instant", inputs: [this, container], outputs: [this.container, o] }) + else out({ action: "instant", inputs: [container, this], outputs: [o] }) + return o + } + toString() { + return (this.container ? this.container + ":" : "") + this.name + } +} + +class Container extends Item { + constructor(name, dispose, dispose_tile) { + super(name); this.dispose = dispose; this.dispose_tile = dispose_tile; + } +} +const FP = new Container("foodprocessor") +const POT = new Container("pot") +const PAN = new Container("pan") +const PL = new Container("plate", new Container("dirty-plate")) +const GL = new Container("glass", undefined, "sink") + +function crate(s) { + const item = new Item(s); + out({ action: "instant", inputs: [], outputs: [item], tile: `${s}-crate`, points: -1 }) + out({ action: "instant", inputs: [item], outputs: [], tile: `${s}-crate`, points: 1 }) + return item +} +function cut(s, two) { + const o = new Item(`sliced-${s.name}`, s.container) + out({ action: "active", inputs: [s], outputs: [o, two ? o : null], tile: "cuttingboard", duration: 2 }) + return o +} +function cook(s, duration = 20) { + const o = new Item(`cooked-${s.name}`, s.container) + out({ action: "passive", duration, revert_duration: duration * 2, tile: "stove", inputs: [s], outputs: [o] }) + out({ action: "passive", duration: duration / 3, revert_duration: duration / 2, tile: "stove", inputs: [o], outputs: [new Item("burned", POT)], warn: true }) + return o +} +function sear(s, duration = 15) { + s = s.tr(PAN) + const o = new Item(`seared-${s.name}`, s.container) + out({ action: "passive", duration, revert_duration: duration * 2, tile: "stove", inputs: [s], outputs: [o] }) + out({ action: "passive", duration: duration / 3, revert_duration: duration / 3, tile: "stove", inputs: [o], outputs: [new Item("burned", PAN)], warn: true }) + return o +} +function bake(s, duration = 25) { + const o = new Item(`sliced-${s.name}`, s.container) + out({ action: "passive", duration, revert_duration: duration * 2, tile: "oven", inputs: [s], outputs: [o] }) + out({ action: "passive", duration: duration / 2, revert_duration: duration / 4, tile: "oven", inputs: [o], outputs: [new Item("burned")], warn: true }) + return o +} +function freeze(s) { + const o = new Item(`frozen-${s.name}`, s.container) + out({ action: "passive", duration: 25, tile: "freezer", inputs: [s], outputs: [o] }) + return o +} +function process(s) { + const o = new Item(`processed-${s.name}`, s.container) + out({ action: "passive", duration: 5, inputs: [s], outputs: [o] }) + return o +} +function container_add(base, add) { + const o = new Item("!!!", base.container) + out({ action: "instant", inputs: [base, add], outputs: [o, add.container] }) + return o +} +function combine(c, ...items) { + if (items.length == 1) return items[0].tr(c) + const open = items.map(i => (i.tr(c), [i.name])) + const seen = new Set() + let result + while (1) { + const cur = open.pop() + if (!cur) break; + for (const new_item of items) { + if (cur.includes(new_item.name)) continue + const rkey = cur.join(",") + "#" + new_item + + if (seen.has(rkey)) continue + seen.add(rkey) + + const parts = [...cur, new_item.name] + parts.sort() + open.push(parts) + const i = new Item(cur.join(","), c) + const o = new Item(parts.join(","), c) + if (parts.length == items.length) result = o + out({ + action: "instant", + inputs: [i, new_item], + outputs: [o, new_item.container] + }) + } + } + return result +} +function edible(...items) { + for (const i of items) { + out({ action: "demand", inputs: [i], outputs: [i.container?.dispose ?? i.container], duration: 10 }) + } +} +function either(a, b) { + if (a.name != b.name) throw new Error("either options are named differently"); + if (a.container != b.container) throw new Error("either options are contained differently"); + return a +} +function sink_fill(c) { + const o = new Item("water", c) + out({ action: "active", inputs: [c], outputs: [o], tile: "sink", duration: 1 }) + return o +} + +out({ action: "active", duration: 2, tile: "sink", inputs: [new Container("dirty-plate")], outputs: [PL] }) + +const tomato = crate("tomato") +const steak = crate("steak") +const flour = crate("flour") +const leek = crate("leek") +const rice = crate("rice") +const fish = crate("fish") +const coconut = crate("coconut") +const strawberry = crate("strawberry") +const cheese = crate("cheese") +const lettuce = crate("lettuce") + +// Buns +const dough = process(flour.tr(FP)).as("dough").tr() +const bun = bake(dough).as("bun") +edible(bun.tr(PL)) + +// Steak +const seared_steak = sear(steak) +edible( + combine(PL, seared_steak, bun), + combine(PL, seared_steak), +) + +// Salad +edible( + combine(PL, cut(tomato), cut(lettuce)), + combine(PL, cut(lettuce)), +) + +// Burger +const b_patty = sear(cut(steak).as("patty")) +edible( + combine(PL, cut(bun), b_patty, cut(cheese)), + combine(PL, cut(bun), b_patty, cut(cheese), cut(lettuce)), + combine(PL, cut(bun), b_patty, cut(tomato), cut(lettuce)), + combine(PL, cut(bun), b_patty, cut(cheese), cut(tomato)), + combine(PL, cut(bun), cut(cheese), cut(lettuce), cut(tomato)), + combine(PL, cut(bun), cut(lettuce), cut(tomato)), +) + +// Soup +const tomato_juice = process(tomato.tr(FP)).as("tomato-juice") +const leek_tj_pot = combine(POT, leek, tomato_juice) +const tomato_soup_plate = cook(leek_tj_pot).as("tomato-soup").tr(PL) +edible(tomato_soup_plate) + +// Rice and nigiri +const nigiri = container_add(cut(fish), cook(rice.tr(POT))).as("nigiri").tr(PL) +edible(nigiri) + +// coconut milk and strawberry puree +const strawberry_puree = process(strawberry.tr(FP)).as("strawberry-puree") +const milk = process(coconut.tr(FP)).as("milk") +const strawberry_shake = either( + process(container_add(milk, strawberry).as("milk,strawberry")).as("strawberry-shake"), + process(container_add(strawberry_puree, coconut).as("coconut,strawberry-puree")).as("strawberry-shake") +) + +// Icecream +edible(freeze(strawberry_shake).as("strawberry-icecream").tr(PL)) + +// Mochi +const rice_flour = process(rice.tr(FP)).as("rice-flour") +const mochi_dough = cook(rice_flour.tr(POT), 5).as("mochi-dough") +const strawberry_mochi = container_add(strawberry, mochi_dough).as("strawberry-mochi") +edible(strawberry_mochi) + +// Drinks +edible( + strawberry_shake.tr(GL), + tomato_juice.tr(GL), + sink_fill(GL) +) + +// Curry +const curry_with_rice = combine(PL, cook(rice.tr(POT)), cook(combine(POT, milk, tomato, leek)).as("curry")) +edible(curry_with_rice) + +auto_trash() +auto_burn() +finish() + diff --git a/data/recipes/default.rs b/data/recipes/default.rs deleted file mode 100644 index 34ae53f8..00000000 --- a/data/recipes/default.rs +++ /dev/null @@ -1,648 +0,0 @@ -/* - 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 . -*/ - -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>, - name: String, - container_dispose_tile: Option, -} - -#[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 { - #[serde(default)] tile: Option, - #[serde(default)] inputs: Vec, - #[serde(default)] outputs: Vec, - #[serde(default)] action: RecipeAction, - #[serde(default)] warn: bool, - #[serde(default)] revert_duration: Option>, - #[serde(default)] duration: Option>, - #[serde(default)] points: Option, -} - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)] -#[repr(transparent)] -#[serde(transparent)] -struct OrdAnyway(T); -impl Ord for OrdAnyway { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.partial_cmp(other).unwrap() - } -} -impl Eq for OrdAnyway {} - -static ALL_ITEMS: Mutex> = Mutex::new(BTreeSet::new()); -static ALL_RECIPES: Mutex>> = Mutex::new(BTreeSet::new()); - -fn out(r: Recipe) { - 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::>() - ) - .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>) -> 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> = LazyLock::new(move || { - Box::new(Item { - container: None, - name: "foodprocessor".to_owned(), - container_dispose_tile: Some("trash".to_string()), - }) -}); -static POT: LazyLock> = LazyLock::new(move || { - Box::new(Item { - container: None, - name: "pot".to_owned(), - container_dispose_tile: Some("trash".to_string()), - ..Default::default() - }) -}); -static PAN: LazyLock> = LazyLock::new(move || { - Box::new(Item { - container: None, - name: "pan".to_owned(), - container_dispose_tile: Some("trash".to_string()), - ..Default::default() - }) -}); -static PL: LazyLock> = { - LazyLock::new(move || { - Box::new(Item { - container: None, - name: "plate".to_owned(), - container_dispose_tile: Some("trash".to_string()), - ..Default::default() - }) - }) -}; -static GL: LazyLock> = { - 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(); -} diff --git a/data/recipes/default.ts b/data/recipes/default.ts deleted file mode 100644 index 7e5942aa..00000000 --- a/data/recipes/default.ts +++ /dev/null @@ -1,288 +0,0 @@ -/* - 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 . - -*/ - -//? Is this a good idea? Probably not. - -export interface Recipe { - tile?: string, - inputs: (Item | null | undefined)[], - outputs: (Item | null | undefined)[], - action: "instant" | "passive" | "active" | "demand" | "demand" - duration?: number - revert_duration?: number, - warn?: boolean, - points?: number, -} - -const all_items = new Set() -const all_recipes = new Set() -export function out(r: Recipe) { - r.inputs.forEach(i => i ? all_items.add(i) : void 0) - r.outputs.forEach(i => i ? all_items.add(i) : void 0) - r.inputs = r.inputs.filter(e => e) - r.outputs = r.outputs.filter(e => e) - all_recipes.add(r); -} -export function finish() { - const k = new Set() - for (const r of all_recipes) { - let s = `- action: !${r.action}\n` - s += ` inputs: [${r.inputs.map(e => JSON.stringify(e!.toString())).join(",")}]\n` - s += ` outputs: [${r.outputs.map(e => JSON.stringify(e!.toString())).join(",")}]\n` - if (r.warn) s += ` warn: true\n` - if (r.duration) s += ` duration: ${r.duration}\n` - if (r.revert_duration) s += ` revert_duration: ${r.revert_duration}\n` - if (r.points) s += ` points: ${r.points}\n` - if (r.tile) s += ` tile: ${r.tile}\n` - k.add(s) - } - for (const r of k) - console.log(r); -} -function auto_trash() { - for (const i of [...all_items]) { - if (i instanceof Container) continue - if (!i.container) out({ action: "instant", inputs: [i], outputs: [], tile: "trash" }) - else { - out({ - action: "instant", - inputs: [i], - outputs: [i.container.dispose ?? i.container], - tile: i.container.dispose_tile ?? "trash" - }) - } - } -} -function auto_burn() { - for (const i of [...all_items]) { - if (i instanceof Container) continue - if (i.container || i.name == "burned") continue - else { - out({ - action: "passive", - inputs: [i], - outputs: [new Item("burned")], - duration: 1.5, - revert_duration: 1.5, - warn: true, - tile: "stove" - }) - } - } -} - -class Item { - constructor( - public name: string, - public container?: Container - ) { } - as(s: string) { this.name = s; return this } - tr(container?: Container) { - const o = new Item(this.name, container) - if (this.container == container) return o - if (this.container) out({ action: "instant", inputs: [this, container], outputs: [this.container, o] }) - else out({ action: "instant", inputs: [container, this], outputs: [o] }) - return o - } - toString() { - return (this.container ? this.container + ":" : "") + this.name - } -} - -class Container extends Item { constructor(name: string, public dispose?: Item, public dispose_tile?: string) { super(name) } } -const FP = new Container("foodprocessor") -const POT = new Container("pot") -const PAN = new Container("pan") -const PL = new Container("plate", new Container("dirty-plate")) -const GL = new Container("glass", undefined, "sink") - -function crate(s: string): Item { - const item = new Item(s); - out({ action: "instant", inputs: [], outputs: [item], tile: `${s}-crate`, points: -1 }) - out({ action: "instant", inputs: [item], outputs: [], tile: `${s}-crate`, points: 1 }) - return item -} -function cut(s: Item, two?: boolean): Item { - const o = new Item(`sliced-${s.name}`, s.container) - out({ action: "active", inputs: [s], outputs: [o, two ? o : null], tile: "cuttingboard", duration: 2 }) - return o -} -function cook(s: Item, duration = 20): Item { - const o = new Item(`cooked-${s.name}`, s.container) - out({ action: "passive", duration, revert_duration: duration * 2, tile: "stove", inputs: [s], outputs: [o] }) - out({ action: "passive", duration: duration / 3, revert_duration: duration / 2, tile: "stove", inputs: [o], outputs: [new Item("burned", POT)], warn: true }) - return o -} -function sear(s: Item, duration = 15): Item { - s = s.tr(PAN) - const o = new Item(`seared-${s.name}`, s.container) - out({ action: "passive", duration, revert_duration: duration * 2, tile: "stove", inputs: [s], outputs: [o] }) - out({ action: "passive", duration: duration / 3, revert_duration: duration / 3, tile: "stove", inputs: [o], outputs: [new Item("burned", PAN)], warn: true }) - return o -} -function bake(s: Item, duration = 25): Item { - const o = new Item(`sliced-${s.name}`, s.container) - out({ action: "passive", duration, revert_duration: duration * 2, tile: "oven", inputs: [s], outputs: [o] }) - out({ action: "passive", duration: duration / 2, revert_duration: duration / 4, tile: "oven", inputs: [o], outputs: [new Item("burned")], warn: true }) - return o -} -function freeze(s: Item): Item { - const o = new Item(`frozen-${s.name}`, s.container) - out({ action: "passive", duration: 25, tile: "freezer", inputs: [s], outputs: [o] }) - return o -} -function process(s: Item): Item { - const o = new Item(`processed-${s.name}`, s.container) - out({ action: "passive", duration: 5, inputs: [s], outputs: [o] }) - return o -} -function container_add(base: Item, add: Item): Item { - const o = new Item("!!!", base.container) - out({ action: "instant", inputs: [base, add], outputs: [o, add.container] }) - return o -} -function combine(c: Container, ...items: Item[]): Item { - if (items.length == 1) return items[0].tr(c) - const open = items.map(i => (i.tr(c), [i.name])) - const seen = new Set() - let result - while (1) { - const cur = open.pop() - if (!cur) break; - for (const new_item of items) { - if (cur.includes(new_item.name)) continue - const rkey = cur.join(",") + "#" + new_item - - if (seen.has(rkey)) continue - seen.add(rkey) - - const parts = [...cur, new_item.name] - parts.sort() - open.push(parts) - const i = new Item(cur.join(","), c) - const o = new Item(parts.join(","), c) - if (parts.length == items.length) result = o - out({ - action: "instant", - inputs: [i, new_item], - outputs: [o, new_item.container] - }) - } - } - return result! -} -function edible(...items: Item[]) { - for (const i of items) { - out({ action: "demand", inputs: [i], outputs: [i.container?.dispose ?? i.container], duration: 10 }) - } -} -function either(a: Item, b: Item) { - if (a.name != b.name) throw new Error("either options are named differently"); - if (a.container != b.container) throw new Error("either options are contained differently"); - return a -} -function sink_fill(c: Container) { - const o = new Item("water", c) - out({ action: "active", inputs: [c], outputs: [o], tile: "sink", duration: 1 }) - return o -} - -out({ action: "active", duration: 2, tile: "sink", inputs: [new Container("dirty-plate")], outputs: [PL] }) - -const tomato = crate("tomato") -const steak = crate("steak") -const flour = crate("flour") -const leek = crate("leek") -const rice = crate("rice") -const fish = crate("fish") -const coconut = crate("coconut") -const strawberry = crate("strawberry") -const cheese = crate("cheese") -const lettuce = crate("lettuce") - -// Buns -const dough = process(flour.tr(FP)).as("dough").tr() -const bun = bake(dough).as("bun") -edible(bun.tr(PL)) - -// Steak -const seared_steak = sear(steak) -edible( - combine(PL, seared_steak, bun), - combine(PL, seared_steak), -) - -// Salad -edible( - combine(PL, cut(tomato), cut(lettuce)), - combine(PL, cut(lettuce)), -) - -// Burger -const b_patty = sear(cut(steak).as("patty")) -edible( - combine(PL, cut(bun), b_patty, cut(cheese)), - combine(PL, cut(bun), b_patty, cut(cheese), cut(lettuce)), - combine(PL, cut(bun), b_patty, cut(tomato), cut(lettuce)), - combine(PL, cut(bun), b_patty, cut(cheese), cut(tomato)), - combine(PL, cut(bun), cut(cheese), cut(lettuce), cut(tomato)), - combine(PL, cut(bun), cut(lettuce), cut(tomato)), -) - -// Soup -const tomato_juice = process(tomato.tr(FP)).as("tomato-juice") -const leek_tj_pot = combine(POT, leek, tomato_juice) -const tomato_soup_plate = cook(leek_tj_pot).as("tomato-soup").tr(PL) -edible(tomato_soup_plate) - -// Rice and nigiri -const nigiri = container_add(cut(fish), cook(rice.tr(POT))).as("nigiri").tr(PL) -edible(nigiri) - -// coconut milk and strawberry puree -const strawberry_puree = process(strawberry.tr(FP)).as("strawberry-puree") -const milk = process(coconut.tr(FP)).as("milk") -const strawberry_shake = either( - process(container_add(milk, strawberry).as("milk,strawberry")).as("strawberry-shake"), - process(container_add(strawberry_puree, coconut).as("coconut,strawberry-puree")).as("strawberry-shake") -) - -// Icecream -edible(freeze(strawberry_shake).as("strawberry-icecream").tr(PL)) - -// Mochi -const rice_flour = process(rice.tr(FP)).as("rice-flour") -const mochi_dough = cook(rice_flour.tr(POT), 5).as("mochi-dough") -const strawberry_mochi = container_add(strawberry, mochi_dough).as("strawberry-mochi") -edible(strawberry_mochi) - -// Drinks -edible( - strawberry_shake.tr(GL), - tomato_juice.tr(GL), - sink_fill(GL) -) - -// Curry -const curry_with_rice = combine(PL, cook(rice.tr(POT)), cook(combine(POT, milk, tomato, leek)).as("curry")) -edible(curry_with_rice) - -auto_trash() -auto_burn() -finish() - diff --git a/data/recipes/none.ts b/data/recipes/none.ts deleted file mode 100644 index ba2b48c4..00000000 --- a/data/recipes/none.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - 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 . - -*/ -// This scripts generates no recipes. Interesting, isn't it? -- cgit v1.2.3-70-g09d2