From 458f217c301f3aecc894372ef0a2ee87d8ce29ad Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Sun, 24 Mar 2024 05:16:08 +0100 Subject: [PATCH] init --- .gitignore | 2 ++ .vscode/settings.json | 3 ++ README.md | 43 +++++++++++++++++++++++ build.sh | 6 ++++ deno.json | 6 ++++ example/demo | 17 ++++++++++ example/minecraft | 57 +++++++++++++++++++++++++++++++ src/main.ts | 79 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 213 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100755 build.sh create mode 100644 deno.json create mode 100644 example/demo create mode 100644 example/minecraft create mode 100755 src/main.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7c70c79 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/bin +/deno.lock \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b943dbc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "deno.enable": true +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b3e5a3 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# reciper + +A script to calculate recipe costs from concise recipe definition file. + +## Usage + +```sh +Usage: +reciper +``` + +With the file `example/demo` as input : + +```sh + +# main targets +3 a +2 b + +# definition for a +a +2 c +3 b + +# definition for b +b +1 c + +# no definition for c +``` + +The output is : + +```sh +$ ./bin/reciper example/demo +# To produce : +# - 3 a +# - 2 b +# +# Get : +# - 17 c +# +``` diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..5bd7e7b --- /dev/null +++ b/build.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -e +cd "$(dirname "$(realpath "$0")")" + +mkdir -p bin +deno compile --output=bin/reciper --allow-read src/main.ts diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..38d5831 --- /dev/null +++ b/deno.json @@ -0,0 +1,6 @@ +{ + "fmt": { + "useTabs": true, + "lineWidth": 120 + } +} \ No newline at end of file diff --git a/example/demo b/example/demo new file mode 100644 index 0000000..6c817c2 --- /dev/null +++ b/example/demo @@ -0,0 +1,17 @@ +# main targets +3 a +2 b + +# definition for a +a +2 c +3 b + +# definition for b +b +1 c + +# no definition for c + +# gives : +# to produce [ { item: "a", count: 3 }, { item: "b", count: 2 } ] get [ { item: "c", count: 17 } ] diff --git a/example/minecraft b/example/minecraft new file mode 100644 index 0000000..d31a366 --- /dev/null +++ b/example/minecraft @@ -0,0 +1,57 @@ +# target declaration +1 digital miner + +# all applicable recipes +digital miner +2 atomic alloy +1 basic control circuit +2 logistical sorter +1 robit +2 teleportation core +1 steel casing + +atomic alloy +1 reinforced alloy +1 enriched obsidian + +reinforced alloy +1 infused alloy +1 enriched diamond + +infused alloy +1 iron ingot +1 enriched redstrone + +logistical sorter +7 iron ingot +1 piston +1 basic control circuit + +robit +1 steel ingot +1 atomic alloy +2 energy tablet +1 refined obsidian ingot +1 personal chest + +energy tablet +4 redstrone +3 gold ingot +2 infused alloy + +personal chest +5 steel ingot +2 chest +1 glass +1 basic control circuit + +teleportation core +2 atomic alloy +4 lapis +2 gold +1 diamond + +basic control circuit +1 osmium ingot +1 enriched redstrone + diff --git a/src/main.ts b/src/main.ts new file mode 100755 index 0000000..7bceb42 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,79 @@ +#!/bin/env -S deno run --allow-read + +async function main() { + const { path } = parse_args(Deno.args); + const content = await Deno.readTextFile(path); + const { targets, definitions } = parse_file(content); + const ingredients = calculate_ingredients(targets, definitions); + const merged = merge_ingredients(ingredients); + console.log("To produce :\n", ...format_ingredients(targets), "\nGet :\n", ...format_ingredients(merged)); +} + +function parse_args(args: string[]) { + const invalid_usage = () => Deno.exit(console.log("Usage:\nreciper ") as undefined); + if (args.length !== 1) invalid_usage(); + const [path] = args; + if (["-h", "--help"].includes(path)) invalid_usage(); + return { path }; +} + +function parse_file(content: string) { + const lines = content.trim().split("\n"); + const definitions = [] as Definition[]; + const main = { target: "main", ingredients: [] } as Definition; + let current_block = null as Definition | null; + for (const line of lines) { + if (line.trim().startsWith("#")) continue; + if (line.trim() === "") { + if (current_block !== null) definitions.push(current_block); + current_block = null; + continue; + } + const [count_str, ...words] = line.split(" "); + const count = parseFloat(count_str); + if (isNaN(count)) { + if (current_block !== null) definitions.push(current_block); + current_block = null; + current_block = { target: line, ingredients: [] }; + } else { + const item = words.join(" "); + if (current_block === null) main.ingredients.push({ item, count }); + else current_block.ingredients.push({ item, count }); + } + } + if (current_block !== null) definitions.push(current_block); + const targets = main.ingredients; + return { targets, definitions }; +} + +type Ingredient = { item: string; count: number }; +type Definition = { target: string; ingredients: Ingredient[] }; + +function calculate_ingredients(targets: Ingredient[], definitions: Definition[]) { + const result = [] as Ingredient[]; + for (const target of targets) { + const recipe = definitions.find((recipe) => recipe.target === target.item); + if (recipe === undefined) { + result.push(target); + continue; + } + const defs = definitions.filter((def) => def.target !== target.item); + for (const ingredient of calculate_ingredients(recipe.ingredients, defs)) { + const count = ingredient.count * target.count; + result.push({ item: ingredient.item, count }); + } + } + return result; +} + +function merge_ingredients(ingredients: Ingredient[]): Ingredient[] { + const set = new Map(); + for (const { count, item } of ingredients) set.set(item, count + (set.get(item) ?? 0)); + return Array.from(set.entries()).map(([item, count]) => ({ item, count })); +} + +function* format_ingredients(ingredients: Ingredient[]) { + for (const { count, item } of ingredients) yield* ["-", count, item, "\n"]; +} + +if (import.meta.main) await main();