cc-stuff/mess/restock.lua

435 lines
15 KiB
Lua
Raw Normal View History

local function draw_bar(y, c1, c2, p, fmt, ...)
local str = string.format(fmt, ...)
local tw = term.getSize()
local w1 = math.ceil(p * tw)
local w2 = tw - w1
local old_bg = term.getBackgroundColor()
term.setCursorPos(1, y)
term.setBackgroundColor(c1)
term.write(str:sub(1, w1))
local rem = w1 - #str
if rem > 0 then
term.write(string.rep(" ", rem))
end
term.setBackgroundColor(c2)
term.write(str:sub(w1 + 1, w1 + w2))
rem = math.min(tw - #str, w2)
if rem > 0 then
term.write(string.rep(" ", rem))
end
term.setBackgroundColor(old_bg)
end
function table.deepcopy(obj)
if type(obj) ~= 'table' then return obj end
local res = {}
for k, v in pairs(obj) do res[table.deepcopy(k)] = table.deepcopy(v) end
return res
end
function spairs(t, order)
-- collect the tbl_keys
local tbl_keys = {}
for k in pairs(t) do tbl_keys[#tbl_keys+1] = k end
-- if order function given, sort by it by passing the table and tbl_keys a, b,
-- otherwise just sort the tbl_keys
if order then
table.sort(tbl_keys, function(a,b) return order(t, a, b) end)
else
table.sort(tbl_keys)
end
-- return the iterator function
local i = 0
return function()
i = i + 1
if tbl_keys[i] then
return tbl_keys[i], t[tbl_keys[i]]
end
end
end
function string.split(inputstr, sep)
sep = sep or "%s"
local t = {}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
table.insert(t, str)
end
return table.unpack(t)
end
function map(tbl, fn)
local out = {}
for k, v in pairs(tbl) do
out[k] = fn(k, v, tbl)
end
return out
end
function filter(tbl, fil)
local out = {}
for k, v in pairs(tbl) do
if fil(k, v, tbl) then
out[k] = v
end
end
return out
end
function pop(tbl)
local out = { k = nil, v = nil }
for k, v in pairs(tbl) do
out.k = k
out.v = v
table.remove(tbl, k)
break
end
return out.k, out.v
end
function groupby(tbl, fn)
local out = {}
for k, v in pairs(tbl) do
local group = fn(k, v, tbl)
out[group] = out[group] or {}
table.insert(out[group], { k = k, v = v })
end
return out
end
function reduce(tbl, operator, initial)
local accumulator = initial
for k, v in pairs(tbl) do
accumulator = operator(k, v, accumulator, tbl)
end
return accumulator
end
function pipe(input)
local function chain(fn)
return function(self, ...)
fn(self, ...)
return self
end
end
return {
_value = table.deepcopy(input),
groupby = chain(function(self, fn) self._value = groupby(self._value, fn) end),
filter = chain(function(self, fn) self._value = filter(self._value, fn) end),
map = chain(function(self, fn) self._value = map(self._value, fn) end),
reduce = chain(function(self, operator, initial) self._value = reduce(self._value, operator, initial) end),
sort = chain(function(self, key)
local out = {}
for k, v in spairs(self._value, key) do
table.insert(out, { k = k, v = v })
end
self._value = out
end),
get = function(self) return self._value end
}
end
local keyboard = {
"qwertyuiop",
"asdfghjkl\x1b",
"zxcvbnm \xd7",
}
local config = { stock = {}, trash = {}, take = {}, storage = {} }
local item_state = { stock = {}, storage = {}, junk = {} }
local storage_sizes = { stock = {}, storage = {}, junk = {} }
local sort_mode, sort_inverse = "count", false
local search_open, search_query = false, ""
local mon = peripheral.find("monitor")
mon.setTextScale(0.5)
mon.clear()
mon.setPaletteColor(colors.blue, 0x131326) -- odd lines
mon.setPaletteColor(colors.purple, 0x261326) -- even lines
mon.setPaletteColor(colors.magenta, 0xaa55ff) -- search query
mon.setPaletteColor(colors.cyan, 0x55aaff) -- keyboard
mon.setPaletteColor(colors.brown, 0x262626) -- keyboard bg
parallel.waitForAll(function()
while true do
local fp = assert(io.open("/restock.json", "r"))
config = textutils.unserializeJSON(fp:read("a"))
fp:close()
os.sleep(1)
end
end, function()
while true do
local new_state = {}
local new_sizes = {}
map(config.storage, function(i, inv)
new_sizes[inv] = peripheral.call(inv, "size")
for slot, item in pairs(peripheral.call(inv, "list")) do
new_state[string.format("%s#%d", inv, slot)] = item
end
end)
item_state.storage = new_state
storage_sizes.storage = new_sizes
os.sleep(0)
end
end, function()
while true do
local new_state = {}
local new_sizes = {}
map(
groupby(config.stock, function(location)
local storage = string.split(location, "#")
return storage
end
), function(inv)
new_sizes[inv] = peripheral.call(inv, "size")
for slot, item in pairs(peripheral.call(inv, "list")) do
new_state[string.format("%s#%d", inv, slot)] = item
end
end)
item_state.stock = new_state
storage_sizes.stock = new_sizes
os.sleep(0)
end
end, function() -- IMPORT
while true do
map(
groupby(config.stock, function(location) return ({ string.split(location, "#") })[1] end),
function(stock_inv)
local size = storage_sizes.stock[stock_inv] or 0
for i = 1, size do
local slot = string.format("%s#%d", stock_inv, i)
local slot_stocked, slot_expected = item_state.stock[slot], config.stock[slot]
if (slot_stocked and slot_expected and slot_stocked.name ~= slot_expected.name) or (slot_stocked ~= nil and slot_expected == nil) then
for _, output in ipairs(config.storage) do
if peripheral.call(stock_inv, "pushItems", output, i) ~= 0 then
print("MOVE", slot, slot_stocked.name)
break
end
end
end
end
end)
os.sleep(0)
end
end, function() -- STOCK
while true do
local new_junk = {}
local item_locations = groupby(item_state.storage, function(location, item)
new_junk[item.name] = (new_junk[item.name] or 0) + item.count
return item.name
end)
local tw, th = term.getSize()
map(config.stock, function(destination, item)
local dst_container, dst_slot = string.split(destination, "#")
new_junk[item.name] = nil
if item_locations[item.name] == nil then return end
local dst_item = item_state.stock[destination]
local dst_count = dst_item and dst_item.count or 0
if dst_count >= item.count then return end
local missing = item.count - dst_count
local _, take_from = pop(filter(item_locations[item.name], function(_, src_item)
local src_container, src_slot = string.split(src_item.k, "#")
return src_container ~= dst_container
end))
if not take_from then return end
local src_container, src_slot = string.split(take_from.k, "#")
local t = os.clock()
local out = peripheral.call(dst_container, "pullItems", src_container, tonumber(src_slot), missing, tonumber(dst_slot))
print(string.format("PUT %s*%d/%d to %s", item.name, out, missing, destination))
-- print(string.format("%s -> %s (%s*%d) took %.2f", take_from.k, destination, item.name, item.count, os.clock() - t))
end)
item_state.junk = new_junk
os.sleep(0)
end
end, function() -- JUNK
while true do
local y = 2
for name, count in pairs(item_state.junk) do
map(filter(item_state.storage, function(location, item)
return item.name == name
end), function(location, item)
local junk_container, junk_slot = string.split(location, "#")
for _, junk_output in ipairs(config.junk) do
if peripheral.call(junk_container, "pushItems", junk_output, tonumber(junk_slot)) ~= 0 then
print("JUNK OUT", item.name, "->", junk_container)
break
end
end
end)
y = y + 1
end
os.sleep(0)
end
end, function() -- USER INPUT
while true do
local ev = { os.pullEvent() }
if ev[1] == "char" and search_open then
search_query = search_query .. ev[2]
mon.setBackgroundColor(colors.gray)
mon.setTextColor(colors.magenta)
mon.setCursorPos(tw - 14, th - 20)
mon.write("> " .. search_query)
for i, line in ipairs(keyboard) do
mon.setCursorPos(tw - 14, th - 20 + i)
mon.setBackgroundColor(colors.brown)
mon.setTextColor(colors.cyan)
mon.write(line)
end
elseif ev[1] == "key" then
if ev[2] == keys.enter then
search_open = not search_open
elseif ev[2] == keys.backspace and search_open then
search_query = search_query:sub(1, #search_query - 1)
mon.setBackgroundColor(colors.gray)
mon.setTextColor(colors.magenta)
mon.setCursorPos(tw - 14, th - 20)
mon.write("> " .. search_query)
for i, line in ipairs(keyboard) do
mon.setCursorPos(tw - 14, th - 20 + i)
mon.setBackgroundColor(colors.brown)
mon.setTextColor(colors.cyan)
mon.write(line)
end
end
elseif ev[1] == "monitor_touch" then
local tw, th = mon.getSize()
local _, _, x, y = table.unpack(ev)
if y == 3 or y == th then
local new_mode
if x >= 1 and x <= 8 then new_mode = "count"
elseif x >= 10 and x <= (tw - 1) then new_mode = "name"
elseif x == tw then
search_open = not search_open
end
if new_mode ~= nil and new_mode == sort_mode then
sort_inverse = not sort_inverse
elseif new_mode ~= nil then
sort_mode = new_mode
sort_inverse = false
end
elseif search_open and x >= (tw - 14) and x < (tw - 4) and y > (th - 20) and y <= (th - 17) then
mon.setBackgroundColor(colors.gray)
mon.setTextColor(colors.magenta)
mon.setCursorPos(tw - 14, th - 20)
mon.write("> " .. search_query)
for i, line in ipairs(keyboard) do
mon.setCursorPos(tw - 14, th - 20 + i)
mon.setBackgroundColor(colors.brown)
mon.setTextColor(colors.cyan)
mon.write(line)
end
local char = keyboard[20 - th + y]:sub(15 - tw + x, 15 - tw + x)
if char >= "a" and char <= "z" then
os.queueEvent("key", keys[char], false)
os.queueEvent("char", char)
os.queueEvent("key_up", keys[char])
else
-- 27 backspace (ev=259)
-- 215 enter (ev=257)
local code = nil
if char == "\x1b" then code = keys.backspace
elseif char == "\xd7" then code = keys.enter
end
if code then
os.queueEvent("key", code, false)
os.queueEvent("key_up", code)
end
end
end
end
end
end, function() -- STATUS
while true do
local back_term = term.redirect(mon)
local tw, th = term.getSize()
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
local usage = pipe(config.storage)
:map(function(_, storage)
local used, total = 0, storage_sizes.storage[storage] or 0
for slot = 1, total do
if item_state.storage[string.format("%s#%d", storage, slot)] then
used = used + 1
end
end
return { used = used, total = total }
end)
:reduce(function(_, info, acc)
return { used = info.used + acc.used, total = info.total + acc.total }
end, { used = 0, total = 0 }):get()
draw_bar(1, colors.lightGray, colors.gray, usage.used / usage.total, "Storage utilization: %7.3f%%", 100 * usage.used / usage.total)
term.setCursorPos(1, 3)
term.setBackgroundColor(colors.gray)
term.clearLine()
local infoline = string.format("Count %s Name %s",
(sort_mode == "count" and (sort_inverse and "\x1e" or "\x1f") or " "),
(sort_mode == "name" and (sort_inverse and "\x1e" or "\x1f") or " "))
local fg = sort_mode == "count" and "55555551f88888881" or "88888881f55555551"
term.blit(infoline, fg, "77777777777777777")
term.setCursorPos(tw, 3)
term.write("\x0c")
term.setCursorPos(1, th)
term.clearLine()
term.blit(infoline, fg, "77777777777777777")
term.setCursorPos(tw, th)
term.write("\x0c")
pipe(item_state.storage)
:groupby(function(slot, item) return item.name end)
:filter(function(name, slots)
if not search_open then return true end
return string.match(name, search_query)
end)
:map(function(name, slots) return reduce(slots, function(_, slot, acc) return slot.v.count + acc end, 0) end)
:sort(function(t, a, b)
local swap = false
if sort_mode == "count" then swap = t[a] > t[b] end
if sort_mode == "name" then swap = a > b end
return swap ~= sort_inverse
end)
:map(function(i, kv)
if i >= (th - 4) then return end
term.setCursorPos(1, 3 + i)
term.setBackgroundColor((i % 2) == 0 and colors.blue or colors.purple)
term.clearLine()
term.write(string.format("%8d %s", kv.v, kv.k))
end)
if search_open then
mon.setBackgroundColor(colors.gray)
mon.setTextColor(colors.magenta)
term.setCursorPos(tw - 14, th - 20)
term.write("> " .. search_query)
for i, line in ipairs(keyboard) do
term.setCursorPos(tw - 14, th - 20 + i)
term.setBackgroundColor(colors.brown)
term.setTextColor(colors.cyan)
term.write(line)
end
end
term.redirect(back_term)
os.sleep(1)
end
end)