/* Hurry Curry! - a game about cooking Copyright (C) 2025 Hurry Curry! Contributors 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. // 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() 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); } 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 roll(s, two) { const o = new Item(`rolled-${s.name}`, s.container) out({ action: "active", inputs: [s], outputs: [o, two ? o : null], tile: "rollingboard", 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(`baked-${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 merge(...items) { return combine(null, ...items) } function edible(...items) { for (const i of items) { out({ action: "demand", inputs: [i], outputs: [i.container?.dispose ?? i.container], duration: 10 }) } } function edible_hot(...items) { edible(...items) edible(...items.map(i => { return combine(i.container, i, chili) })) } 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 chili = crate("chili") 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") const mushroom = crate("mushroom") const potato = crate("potato") // Common renamed const tomato_juice = process(tomato.tr(FP)).as("tomato-juice") const patty = cut(steak).as("patty") const dough = process(flour.tr(FP)).as("dough").tr() // Pizza const pizza_dough = roll(dough) edible_hot( bake(combine(pizza_dough, tomato_juice, cut(cheese), cut(mushroom))).tr(PL), bake(combine(pizza_dough, patty, cut(cheese))).tr(PL), bake(combine(pizza_dough, tomato_juice, cut(cheese))).tr(PL), bake(combine(pizza_dough, patty, leek)).tr(PL), ) // Bowl edible_hot(combine(PL, cook(rice.tr(POT)), cut(mushroom), cut(tomato)).as("rice-bowl")) // Steak const french_fries = sear(cut(potato)).as("french-fries"); const bun = bake(dough).as("bun") edible_hot( bun.tr(PL), french_fries.tr(PL), combine(PL, sear(steak), bun), combine(PL, sear(steak), french_fries), combine(PL, sear(steak), cook(potato.tr(POT))), combine(PL, sear(steak), sear(cut(mushroom))), combine(PL, sear(steak), sear(cut(mushroom)), french_fries), combine(PL, sear(steak), sear(cut(mushroom)), cook(potato.tr(POT))), ) // Salad edible_hot( combine(PL, cut(tomato), cut(lettuce)), combine(PL, cut(lettuce)), ) // Burger edible_hot( combine(PL, cut(bun), sear(patty), cut(cheese)), combine(PL, cut(bun), sear(patty), cut(cheese), cut(lettuce)), combine(PL, cut(bun), sear(patty), cut(tomato), cut(lettuce)), combine(PL, cut(bun), sear(patty), cut(cheese), cut(tomato)), combine(PL, cut(bun), cut(cheese), cut(lettuce), cut(tomato)), combine(PL, cut(bun), cut(lettuce), cut(tomato)), combine(PL, cut(bun), cut(fish)) ) // Noodles const noodle = cook(cut(roll(dough)).as("noodles").tr(POT)) edible_hot( combine(PL, noodle, tomato_juice, cut(cheese)), combine(PL, noodle, tomato_juice, cut(cheese), sear(patty)), ) // Soup edible_hot( cook(combine(POT, tomato_juice)).as("tomato-soup").tr(PL), cook(combine(POT, cut(mushroom))).as("mushroom-soup").tr(PL), cook(combine(POT, cheese, leek)).as("cheese-leek-soup").tr(PL), cook(combine(PL, patty, cut(potato), leek)).tr(PL) ) // Rice and nigiri edible(container_add(cut(fish), cook(rice.tr(POT))).as("nigiri").tr(PL)) // 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.tr(GL)).as("strawberry-icecream")) // 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.tr(PL)) // 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_hot(curry_with_rice) auto_trash() auto_burn() finish()