/*
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")
edible(
combine(PL, steak_pot, sliced_tomato, bread_slice),
combine(PL, sliced_tomato, bread_slice),
combine(PL, steak_pot, bread_slice),
combine(PL, sliced_tomato, steak_pot),
combine(PL, sliced_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("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)))
edible(curry_with_rice)
auto_trash()
finish()