109 lines
4 KiB
Org Mode
109 lines
4 KiB
Org Mode
|
#+title: Factorio Recipe Analyzer
|
||
|
#+author: Phil Bajsicki
|
||
|
#+PROPERTY: header-args :tangle fra.raku
|
||
|
* Intro
|
||
|
This is a simple Raku script which generates an analysis of each recipe into its component parts, and delivers insight into the balance and progression of a mod.
|
||
|
|
||
|
The .csv files used as input are generated in Factorio, by loading a new game with only ~base~ and your chosen mod enabled, and running the following Lua code:
|
||
|
|
||
|
#+begin_src lua :tangle no
|
||
|
/c
|
||
|
local whitelist = {}
|
||
|
for _, k in pairs({
|
||
|
"iron-plate",
|
||
|
"copper-plate",
|
||
|
"steel-plate",
|
||
|
"copper-wire",
|
||
|
"iron-gear-wheel",
|
||
|
"iron-stick",
|
||
|
"pipe",
|
||
|
}) do whitelist[k] = true end
|
||
|
|
||
|
local parts = {}
|
||
|
for name, recipe in pairs(game.recipe_prototypes) do
|
||
|
local history = script.get_prototype_history("recipe", name)
|
||
|
if history.created == "base" then
|
||
|
local add = false
|
||
|
local ingredients = {}
|
||
|
for _, ingredient in pairs(recipe.ingredients) do
|
||
|
if whitelist[ingredient.name] then add = true end
|
||
|
ingredients[#ingredients+1] = ingredient.name .. "," .. ingredient.amount
|
||
|
end
|
||
|
if add then
|
||
|
parts[#parts+1] = name .. "," .. table.concat(ingredients, ",")
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
game.write_file("recipes.csv", table.concat(parts, "\n"), false)
|
||
|
#+end_src
|
||
|
|
||
|
* This script
|
||
|
|
||
|
This is a literate script. The source code is embedded in these code blocks, and tangled into the Raku script using org-babel. This allows me to write a description of what I want to do, and comment on it without resorting to ~// /* */~ ugly comments.
|
||
|
|
||
|
|
||
|
|
||
|
* License
|
||
|
I don't own the source csv files generated by Factorio, nor the mods the script is pulling from. The Raku script itself is GPLv3.
|
||
|
** TODO: include GPLv3 in the repo
|
||
|
** Credits:
|
||
|
The following are mods from which .csv files have been generated. The recipe .csv files are included in the ~mod-recipes~ directory.
|
||
|
|
||
|
* The Code
|
||
|
** Setup
|
||
|
** Open csv file
|
||
|
** Open (create if needed) output org file
|
||
|
/I like org-mode, sue me. Also pseudocode is in Lisp./
|
||
|
|
||
|
For this, we'll likely want to include some metadata, like creation date, mod name, number of ingredients, maybe the total amount of raw mats needed to make one of everything?
|
||
|
** Make a list of products
|
||
|
That's the first column in the .csv file.
|
||
|
Read first column of the csv file and insert it into the .org (output) file.
|
||
|
*** For each product, create a templated section
|
||
|
Ideally we'd end up with Something like:
|
||
|
|
||
|
#+begin_example
|
||
|
,* product
|
||
|
,** direct inputs
|
||
|
- input and amount
|
||
|
- input and amount
|
||
|
,** raw ingredients
|
||
|
+ raw ingredient and amount
|
||
|
+ raw ingredient and amount
|
||
|
#+end_example
|
||
|
|
||
|
|
||
|
**** Parse the csv file:
|
||
|
For each line:
|
||
|
1. First column becomes the top header
|
||
|
2. Insert second header
|
||
|
3. Insert each ingredient and its amount as a separate item
|
||
|
**** Parse the output.org file, filling it out recursively
|
||
|
1. Open .org (output file)
|
||
|
2. Loop over output.org:
|
||
|
1. Find *product section.*
|
||
|
2. Find (next) ingredient lines in this product section.
|
||
|
3. Pass the product and each direct input item and its number to ~raw-ingredients~. /(We can distinguish direct inputs from raw ingredients easily because org-mode supports multiple characters for defining lists. So we can just look for lines beginning with ~-~ and not really think about anything else.)/
|
||
|
|
||
|
This would look something like:
|
||
|
|
||
|
#+begin_example
|
||
|
(raw-ingredients iron-gear-wheel iron-plate 2)
|
||
|
(raw-ingredients yellow-belt iron-plate 1)
|
||
|
#+end_example
|
||
|
|
||
|
3. ~(raw-ingredients (product item number))~:
|
||
|
1. Store in variables for clarity:
|
||
|
- product
|
||
|
- item
|
||
|
- number
|
||
|
2. Read .csv file:
|
||
|
1. If a recipe for this item exists in the csv file:
|
||
|
1. Go to the line with the recipe (first column.
|
||
|
2. For each ~(item number)~ pair, call ~(raw-ingredients (product new-item (* new-number old-number)))~.
|
||
|
3. If a recipe does not exist:
|
||
|
1. Find ~* product~ section in the .org file.
|
||
|
2. If the ingredient item already exists:
|
||
|
1. Add new number we just got to the existing number.
|
||
|
3. Else: write new raw ingredient line and number in this section.
|