This commit is contained in:
JOLIMAITRE Matthieu 2024-03-24 05:16:08 +01:00
commit 458f217c30
8 changed files with 213 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/bin
/deno.lock

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"deno.enable": true
}

43
README.md Normal file
View file

@ -0,0 +1,43 @@
# reciper
A script to calculate recipe costs from concise recipe definition file.
## Usage
```sh
Usage:
reciper <recipe_file>
```
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
#
```

6
build.sh Executable file
View file

@ -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

6
deno.json Normal file
View file

@ -0,0 +1,6 @@
{
"fmt": {
"useTabs": true,
"lineWidth": 120
}
}

17
example/demo Normal file
View file

@ -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 } ]

57
example/minecraft Normal file
View file

@ -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

79
src/main.ts Executable file
View file

@ -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 <recipe_file>") 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<string, number>();
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();