init
This commit is contained in:
commit
458f217c30
8 changed files with 213 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/bin
|
||||
/deno.lock
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"deno.enable": true
|
||||
}
|
43
README.md
Normal file
43
README.md
Normal 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
6
build.sh
Executable 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
6
deno.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"fmt": {
|
||||
"useTabs": true,
|
||||
"lineWidth": 120
|
||||
}
|
||||
}
|
17
example/demo
Normal file
17
example/demo
Normal 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
57
example/minecraft
Normal 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
79
src/main.ts
Executable 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();
|
Loading…
Add table
Add a link
Reference in a new issue