diff --git a/compacting-storage.lua b/compacting-storage.lua index a271252..019ed84 100644 --- a/compacting-storage.lua +++ b/compacting-storage.lua @@ -8,164 +8,164 @@ local INVENTORY = "left" local SLEEP_TIME = 30 -local AMOUNT_USED = 63 -- the amount of item used per request -local AMOUNT_RETURNED = 7 -- the amount of item returned per request -local REDUCTION_FACTOR = 0.75 -- when draining, what percent to do at a time +local NUGGET_RATIO = 63 -- amount of nuggets per craft +local BLOCK_RATIO = 7 -- amount of blocks per craft local ITEM_TYPES = { "iron", "copper", "zinc", "gold", "electrum" } -local PRIORITY = { "ingot", "block", "nugget" } -function stacks (num) - return 64 * num -end - --- TODO map targets to specific relays -local target = {} -target.iron = peripheral.wrap"redstone_relay_0" -target.copper = peripheral.wrap"redstone_relay_1" -target.zinc = peripheral.wrap"redstone_relay_2" -target.gold = peripheral.wrap"redstone_relay_3" -target.electrum = peripheral.wrap"redstone_relay_4" +local STACKS = 64 +local NUGGETS = 1 +local INGOTS = 9 +local BLOCKS = 64 -- Levels -local limits = {} -for _, type in ipairs(ITEM_TYPES) do - limits[type] = { - nugget = { stacks(4), stacks(8) }, - ingot = { stacks(4), stacks(8) }, - block = { stacks(1), stacks(1000) } - } +local MIN_NUM = (4 * STACKS * NUGGETS) + (4 * STACKS * INGOTS) +local MAX_NUM = (8 * STACKS * NUGGETS) + (8 * STACKS * INGOTS) + +-- TODO map targets to specific relays and faces +-- Key - 1: nuggets_to_ingots, 2: ingots_to_blocks, 3: blocks_to_ingots, +-- 4: ingots_to_nuggets, 5: enable/disable production +local target = {} +target.iron = {} +target.iron[1] = "0:front" +target.iron[2] = "0:top" +target.iron[3] = "0:back" +target.iron[4] = "16:front" +target.iron[5] = "17:back" +target.copper = {} +target.copper[1] = "1:front" +target.copper[2] = "1:top" +target.copper[3] = "1:back" +target.copper[4] = "16:top" +target.copper[5] = "18:front" +target.zinc = {} +target.zinc[1] = "2:front" +target.zinc[2] = "2:top" +target.zinc[3] = "2:back" +target.zinc[4] = "16:back" +target.zinc[5] = "18:top" +target.gold = {} +target.gold[1] = "3:front" +target.gold[2] = "3:top" +target.gold[3] = "3:back" +target.gold[4] = "17:front" +target.gold[5] = "18:back" +target.electrum = {} +target.electrum[1] = "4:front" +target.electrum[2] = "4:top" +target.electrum[3] = "4:back" +target.electrum[4] = "17:top" + +-- basic actions +function craft (item_type, conversion_type) + -- TODO double check this + local relay_num, face = string.match(target[item_type][conversion_type], + "(d+):(%l+)" + ) + local periph = string.format("redstone_relay_%d", relay_num) + peripheral.call(periph, "setOutput", face, true) + os.sleep(0.1) + peripheral.call(periph, "setOutput", face, false) end --- Map types of conversions to specific faces -local convert = {} -convert.nugget_to_ingot = "front" -convert.ingot_to_block = "top" -convert.block_to_ingot = "back" +function craft_multiple (item_type, conversion_type, count) + if count < 1 then + return + else + craft(item_type, conversion_type) + os.sleep(1) + return craft_multiple(item_type, conversion_type, count - 1) + end +end -function sum_items (inv) - -- organize list of item, count into item_type: (count_N, count_I, count_B) - -- item name will be in form `{mod}:{item_type}_{form}` - local result = {} - for _, type in ipairs(ITEM_TYPES) do - result[type] = {} - for _, form in ipairs(PRIORITY) do - result[type][form] = 0 +function execute_crafts (item_type, crafts) + for type, num_crafts in pairs(crafts) do + if type == "nugget" then + if num_crafts > 0 then + craft_multiple(item_type, 4, num_crafts) + elseif num_crafts < 0 then + craft_multiple(item_type, 1, num_crafts) + end + elseif type == "block" then + if num_crafts > 0 then + craft_multiple(item_type, 2, num_crafts) + elseif num_crafts < 0 then + craft_multiple(item_type, 3, num_crafts) + end end end - for slot, item in pairs(inv.list()) do - local type, form = item.name:match".-:(%l-)_(%l*)" - if result[type] ~= nil then - result[type][form] = result[type][form] + item.count - end +end + +function set_production (item_type, produce) + local target_string = target[item_type][5] + if target_string == nil then return end + local relay_num, face = string.match(target_string, + "(d+):(%l+)" + ) + local periph = string.format("redstone_relay_%d", relay_num) + -- ON is disable production + peripheral.call(periph, "setOutput", face, not produce) +end + +-- logic + +function sum_items (inv) + local result = {} + for _, item in pairs(inv.list()) do + if result[item.name] == nil then result[item.name] = 0 end + result[item.name] = result[item.name] + item.count end return result end --- basic actions -function request (item_type, conversion_type) - target[item_type].setOutput(convert[conversion_type], true) - os.sleep(0.1) - target[item_type].setOutput(convert[conversion_type], false) +function dist_to_num (dist) + return dist.nuggets + dist.ingots * INGOTS + dist.blocks * BLOCKS end -function request_multiple (item_type, conversion_type, count) - if count < 1 then - return - else - request(item_type, conversion_type) - os.sleep(1) - return request_multiple(item_type, conversion_type, count - 1) - end -end - --- fundamental actions -function drain (type, form, amount) - local conversion = nil - if form == "nugget" then - conversion = "nugget_to_ingot" - elseif form == "ingot" then - conversion = "ingot_to_block" - end - local num_requests = math.ceil(amount / AMOUNT_USED) - print(("Draining %d %s %ss"):format(amount, type, form)) - request_multiple(type, conversion, num_requests) -end - -function fill (type, src, dest, amount) - local conversion = nil - local num_requests = math.ceil(amount / AMOUNT_USED) - if dest == "block" then - conversion = "ingot_to_block" - elseif dest == "ingot" then - if src == "nugget" then - conversion = "nugget_to_ingot" - elseif src == "block" then - conversion = "block_to_ingot" - num_requests = math.ceil(amount / AMOUNT_RETURNED) +function get_dist (item_type, items) + local dist = { nuggets: 0, ingots: 0, blocks: 0 } + for _, item in pairs(items) do + local type, form = item.name:match".-:(%l-)_(%l*)" + if type == item_type and dist[form] ~= nil then + dist[form] = dist[form] + item.count end end - if conversion == nil then return end - print(("Converting %d %s %ss to %ss"):format(amount, type, src, dest)) - request_multiple(type, conversion, num_requests) + return dist end --- making the decision what to do -function should_drain (type, counts) - for _, form in ipairs(PRIORITY) do - local diff = counts[form] - limits[type][form][2] - if diff > 0 then - return form, diff * REDUCTION_FACTOR - end - end +function get_diff (dist1, dist2) + return { + nuggets: dist1.nuggets - dist2.nuggets, + ingots: dist1.ingots - dist2.ingots, + blocks: dist1.blocks - dist2.blocks + } end -function should_fill (type, counts) - local fill_dest, fill_src, amount_needed, amount_spare = nil, nil, 0, 0 - for _, form in ipairs(PRIORITY) do - local diff = limits[type][form][1] - counts[form] - if diff > 0 then - if fill_dest == nil then - fill_dest = form - amount_needed = diff - end - else - -- if the source is higher priority and using it would put it under - -- the fill limit, don't use it as a source - if fill_dest == nil and diff > -AMOUNT_USED then - -- do nothing - else - fill_src = form - amount_spare = counts[form] - end - end - if fill_src ~= nil and fill_dest ~= nil then break end +function filter_clamp (diff, current_dist) + -- reduce diff numbers to what can be crafted using current resources + local new_diff = { nuggets: 0, ingots: 0, blocks: 0 } + if diff.blocks > 0 then + new_diff.blocks = math.min( + diff.blocks, + math.floor(current_dist.ingots / 9) + ) end - local amount_to_use = nil - local ratio = AMOUNT_USED / AMOUNT_RETURNED - local amount_needed_src = nil - if fill_src == "block" then - amount_needed_src = amount_needed / ratio - else - amount_needed_src = amount_needed * ratio + if diff.nuggets > 0 then + new_diff.nuggets = math.min( + diff.nuggets, + current_dist.ingots * 9 + ) end - if amount_needed_src < amount_spare then - amount_to_use = amount_needed_src - else - amount_to_use = amount_spare - end - if fill_src == "nugget" and fill_dest == "block" then return end - return fill_dest, fill_src, amount_to_use * REDUCTION_FACTOR + new_diff.ingots = (new_diff.nuggets / 9) + (new_diff.blocks * 9) end -function decide_action (type, counts) - -- using the levels of each form of the item, decide what to do - local drain_form, drain_amt = should_drain(type, counts) - local fill_dest, fill_src, fill_amt = should_fill(type, counts) - if drain_form ~= nil then - drain(type, drain_form, drain_amt) - elseif fill_dest ~= nil and fill_src ~= nil then - fill(type, fill_src, fill_dest, fill_amt) - end +function diff_to_crafts (diff) + -- TODO double-check this one + -- go from one end e.g. nuggets to the other e.g. blocks, removing + -- from the diff to create crafts until the diff is empty + local crafts = {} + crafts.nuggets = math.floor(diff.nuggets / NUGGET_RATIO) + crafts.blocks = math.floor(diff.blocks / BLOCK_RATIO) + return crafts end function print_counts (items) @@ -181,9 +181,20 @@ while true do local items = sum_items(peripheral.wrap(INVENTORY)) -- print_counts(items) - -- 2. take one action for each item type per cycle - for item_type, counts in pairs(items) do - decide_action(item_type, counts) + -- 2. either work towards a desired distribution or create more resources + for _, item_type in ipairs(ITEM_TYPES) do + local current_dist = get_dist(item_type, items) + local num = dist_to_num(current_dist) + if num > MAX_NUM then set_production(item_type, false) + if num > MIN_NUM then + local desired_dist = decide_dist(num) + local diff = get_diff(current_dist, desired_dist) + diff = filter_clamp(diff, current_dist) + local crafts = diff_to_crafts(dist) + execute_crafts(item_type, crafts) + else + set_production(item_type, true) + end end -- sleep until next cycle