/* 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 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") 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( bake(combine(pizza_dough, tomato_juice, cut(cheese))).tr(PL), 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, patty, leek)).tr(PL), ) // Bowl // edible(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( 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( combine(PL, cut(tomato), cut(lettuce)), combine(PL, cut(lettuce)), ) // Burger edible( 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( combine(PL, noodle, tomato_juice, cut(cheese)), // combine(PL, noodle, tomato_juice, cut(cheese), sear(patty)), ) // Soup edible( cook(combine(POT, tomato_juice)).as("tomato-soup").tr(PL), cook(combine(POT, cut(mushroom))).as("mushroom-soup").tr(PL), cook(combine(POT, cheese, cut(leek))).as("cheese-leek-soup").tr(PL), // cook(combine(PL, patty, cut(potato), cut(leek))).as("stew").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(curry_with_rice) auto_trash() auto_burn() finish()