/*
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()