/* 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 => e!.toString()).join(",")}]\n` s += ` outputs: [${r.outputs.map(e => 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: "trash" }) } } } 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) out({ action: "instant", inputs: [this, container], outputs: [this.container, o] }) else out({ action: "instant", inputs: [container, this], outputs: [o] }) return o } toString() { return this.name + (this.container ? "-" + this.container : "") } } class Container extends Item { constructor(name: string, public dispose?: Item) { super(name) } } const FP = new Container("foodprocessor") const POT = new Container("pot") const PL = new Container("plate", new Container("dirty-plate")) const GL = new Container("glass") function crate(s: string): Item { const item = new Item(s); out({ action: "instant", inputs: [], outputs: [item], 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, tile: "stove", inputs: [s], outputs: [o] }) out({ action: "passive", duration: duration / 3, revert_duration: 20, tile: "stove", inputs: [o], outputs: [new Item("burned", POT)], warn: true }) return o } function bake(s: Item, duration = 25): Item { const o = new Item(`sliced-${s.name}`, s.container) out({ action: "passive", duration, tile: "oven", inputs: [s], outputs: [o] }) out({ action: "passive", duration: duration / 2, revert_duration: 20, 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 { 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: "instant", inputs: [c], outputs: [o], tile: "sink" }) return o } out({ action: "active", duration: 2, tile: "sink", inputs: [new Container("dirty-plate")], outputs: [PL] }) const tomato = crate("tomato") const raw_steak = crate("raw-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") // Bread const dough = process(flour.tr(FP)).as("dough").tr() const bread = bake(dough).as("bread") const bread_slice = cut(bread, true).as("bread-slice") edible(bread) // Burger const steak_pot = cook(raw_steak.tr(POT)).as("steak") const sliced_tomato = cut(tomato).as("sliced-tomato") const burger = combine(PL, steak_pot, sliced_tomato, bread_slice) const tomato_toast = combine(PL, sliced_tomato, bread_slice) edible(burger, tomato_toast) // 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("coconut-strawberry-puree")).as("strawberry-shake"), process(container_add(strawberry_puree, coconut).as("milk-strawberry")).as("strawberry-shake") ) // Icecream const strawberry_icecream = freeze(strawberry_shake).as("strawberry-icecream").tr(PL) edible(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) // Drinks edible( strawberry_shake.tr(GL), sink_fill(GL) ) const curry_with_rice = combine(PL, cook(rice.tr(POT)), cook(combine(POT, milk, tomato, leek))).as("curry-with-rice") edible(curry_with_rice) auto_trash() finish()