Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- controller_startup.lua
- -- Two-display controller: LEFT 4x4 list + MAP on the other monitor.
- -- Wireless Ender modem required. Supports pocket RPC and goto().
- -- Map shows controller, GPS nodes, and turtles with high-contrast colors.
- -- ================= CONFIG =================
- local CONFIG = {
- repl = false, -- set true to enable inline REPL
- controllerPos = { x = -732, y = 66, z = -322 },
- gpsNodes = {
- {x=-715,y=68,z=-327},
- {x=-732,y=67,z=-344},
- {x=-762,y=69,z=-329},
- {x=-745,y=66,z=-312},
- {x=-725,y=72,z=-300},
- },
- map = {
- origin = { x = -769, z = -351 }, -- top-left world X/Z at map cell (1,1)
- scale = 1, -- blocks per pixel
- follow = false,
- }
- }
- -- ==========================================
- local PROTOCOL = "minesys:v1" -- miners: status/acks/cmds
- local CTL_PROTO = "minesys:ctl" -- controller RPC (goto, clear, etc.)
- -- ---------- Peripherals ----------
- local modem = peripheral.find("modem", function(_,p) return p.isWireless() end)
- if not modem then error("No wireless modem") end
- rednet.open(peripheral.getName(modem))
- pcall(function() rednet.host(CTL_PROTO, "miner_controller") end)
- -- find two monitors; use the 4x4 merged as LIST, the other as MAP
- local mons = { peripheral.find("monitor") }
- if #mons < 2 then error("Need two monitors attached") end
- local function pickMonitors()
- local listMon, mapMon
- for _,m in ipairs(mons) do
- m.setTextScale(0.5)
- local w,h = m.getSize()
- if w==4 and h==4 then listMon = m else mapMon = m end
- end
- if not listMon then
- table.sort(mons, function(a,b) local aw,ah=a.getSize(); local bw,bh=b.getSize(); return aw*ah < bw*bh end)
- listMon, mapMon = mons[1], mons[2]
- end
- return listMon, mapMon
- end
- local monList, monMap = pickMonitors()
- -- ---------- State ----------
- local function now() return os.epoch("utc") end
- local turtles = {} -- id-> {id,label,pos,fuel,facing,status,freeSlots,freePct,tSeen}
- local function count(t) local n=0 for _ in pairs(t) do n=n+1 end return n end
- local function upsertStatus(m)
- local key = m.id or m.label
- if not key then return end
- local t = turtles[key] or {}
- t.id = m.id or t.id
- t.label = m.label or t.label or ("t"..tostring(m.id or ""))
- t.pos = m.pos or t.pos
- t.fuel = m.fuel or t.fuel
- t.facing = m.facing or t.facing
- t.status = m.status or t.status or "?"
- if m.extra and m.extra.inv then
- t.freeSlots = m.extra.inv.freeSlots
- t.freePct = m.extra.inv.freePct
- end
- t.tSeen = now()
- turtles[key] = t
- end
- -- ---------- Commands to miners ----------
- local function sendCmd(target, tbl)
- tbl.op="cmd"; tbl.ref=math.random(1,1e9)
- local tgtID=nil
- for _,t in pairs(turtles) do
- if tostring(t.id)==tostring(target) or t.label==target then tgtID=t.id break end
- end
- if not tgtID then return false,"unknown target" end
- rednet.send(tgtID, tbl, PROTOCOL)
- local tm=os.startTimer(3)
- while true do
- local ev,a,b,c = os.pullEvent()
- if ev=="rednet_message" then
- local sid,msg,proto=a,b,c
- if proto==PROTOCOL and type(msg)=="table" and msg.op=="ack" and msg.ref==tbl.ref then
- return msg.ok, msg.err
- end
- elseif ev=="timer" and a==tm then return false,"ack timeout" end
- end
- end
- local function face(lbl,dir) return sendCmd(lbl,{act="face",dir=dir}) end
- local function forward(lbl,n) return sendCmd(lbl,{act="forward",n=n}) end
- local function up(lbl,n) return sendCmd(lbl,{act="up",n=n}) end
- local function down(lbl,n) return sendCmd(lbl,{act="down",n=n}) end
- local function clearArea(lbl,L,W,H,dir) return sendCmd(lbl,{act="clear_area",length=L,width=W,height=H,dir=dir}) end
- local function setFacingTag(lbl,dir) return sendCmd(lbl,{act="set_facing_tag",dir=dir}) end
- -- Move along one axis only
- local function moveAxis(lbl, axis, delta)
- if delta==0 then return true end
- if axis=="x" then
- if delta>0 then assert(face(lbl,"E")) else assert(face(lbl,"W")) end
- return forward(lbl, math.abs(delta))
- elseif axis=="z" then
- if delta>0 then assert(face(lbl,"S")) else assert(face(lbl,"N")) end
- return forward(lbl, math.abs(delta))
- elseif axis=="y" then
- if delta>0 then return up(lbl, math.abs(delta)) else return down(lbl, math.abs(-delta)) end
- else
- return false,"bad axis"
- end
- end
- -- goto composed of axis-only moves in selected order
- local function gotoXYZ(tgtLabel, tx, ty, tz, order)
- local t = nil
- for _,v in pairs(turtles) do if v.label==tgtLabel then t=v break end end
- if not t or not t.pos then return false,"unknown turtle or position" end
- local cx, cy, cz = t.pos.x, t.pos.y, t.pos.z
- local dx, dy, dz = tx-cx, ty-cy, tz-cz
- order = order or {"y","x","z"}
- local map = {x=dx,y=dy,z=dz}
- for _,ax in ipairs(order) do
- local ok,err = moveAxis(tgtLabel, ax, map[ax])
- if not ok then return false,err end
- end
- return true
- end
- -- ---------- LIST (left 4x4) ----------
- local function drawList()
- local m = monList
- m.setBackgroundColor(colors.black); m.setTextColor(colors.white); m.clear()
- m.setCursorPos(1,1); m.write("T:"..count(turtles))
- -- Very compact 4x4: each turtle uses one row: Lbl St Fuel %Free
- -- Position is shown briefly in row 4 as the most recent turtle seen.
- local y = 2
- local lastPosTxt = nil
- for _,t in pairs(turtles) do
- if y>3 then break end
- m.setCursorPos(1,y)
- local lab = (t.label or "?"):sub(1,4)
- local st = (t.status or "?"):sub(1,4)
- local fu = t.fuel and tostring(t.fuel) or "?"
- local fp = t.freePct and string.format("%2d", math.floor(t.freePct+0.5)) or "--"
- local line = string.format("%-4s%-4s", lab, st)
- m.write(line)
- local w,_ = m.getSize()
- m.setCursorPos(w-3,y); m.write(string.format("%3s", fu)) -- fuel right
- m.setCursorPos(w-1,y); m.write(fp) -- % free
- if t.pos then lastPosTxt = ("x% d z% d"):format(t.pos.x, t.pos.z) end
- y = y + 1
- end
- if lastPosTxt then
- m.setCursorPos(1,4); m.write(lastPosTxt:sub(1,4)) -- hint of position due to tight space
- end
- end
- -- ---------- MAP (top-down XZ) ----------
- local function worldToMap(x,z)
- local w,h = monMap.getSize()
- local ox, oz = CONFIG.map.origin.x, CONFIG.map.origin.z
- local s = CONFIG.map.scale
- local col = math.floor((x - ox)/s) + 1
- local row = math.floor((z - oz)/s) + 1
- if col < 1 or row < 1 or col > w or row > h then return nil,nil end
- return col,row
- end
- local function maybeFollow()
- if not CONFIG.map.follow then return end
- for _,t in pairs(turtles) do
- if t.pos then
- CONFIG.map.origin.x = t.pos.x - 10*CONFIG.map.scale
- CONFIG.map.origin.z = t.pos.z - 6*CONFIG.map.scale
- return
- end
- end
- end
- local function drawMap()
- maybeFollow()
- local m = monMap
- m.setBackgroundColor(colors.black); m.clear()
- local w,h = m.getSize()
- -- High-contrast border and grid
- m.setTextColor(colors.lightGray)
- for x=1,w do m.setCursorPos(x,1); m.write("─"); m.setCursorPos(x,h); m.write("─") end
- for y=1,h do m.setCursorPos(1,y); m.write("│"); m.setCursorPos(w,y); m.write("│") end
- m.setCursorPos(1,1); m.write("┌"); m.setCursorPos(w,1); m.write("┐")
- m.setCursorPos(1,h); m.write("└"); m.setCursorPos(w,h); m.write("┘")
- -- faint grid points every 4 cells
- for r=2,h-1 do
- for c=2,w-1 do
- if ((c-2) % 4 == 0) and ((r-2) % 4 == 0) then
- m.setCursorPos(c,r); m.setTextColor(colors.gray); m.write(".")
- end
- end
- end
- -- Legend
- m.setCursorPos(3,1); m.setTextColor(colors.white); m.write("MAP C/G/T")
- -- Controller
- local cx,cz = CONFIG.controllerPos.x, CONFIG.controllerPos.z
- local mc, mr = worldToMap(cx, cz)
- if mc then m.setCursorPos(mc,mr); m.setTextColor(colors.cyan); m.write("C") end
- -- GPS nodes
- for _,g in ipairs(CONFIG.gpsNodes) do
- local gc,gr = worldToMap(g.x, g.z)
- if gc then m.setCursorPos(gc,gr); m.setTextColor(colors.magenta); m.write("G") end
- end
- -- Turtles (lime good, yellow low space, red near full)
- for _,t in pairs(turtles) do
- if t.pos then
- local tc,tr = worldToMap(t.pos.x, t.pos.z)
- if tc then
- local col = colors.lime
- if t.freePct then
- if t.freePct < 10 then col = colors.red
- elseif t.freePct < 30 then col = colors.yellow
- else col = colors.lime end
- end
- m.setCursorPos(tc,tr); m.setTextColor(col); m.write("T")
- end
- end
- end
- end
- -- ---------- Controller RPC server (for pocket 'goto', etc.) ----------
- local function ctlServer()
- while true do
- local sid, msg, proto = rednet.receive(CTL_PROTO)
- if proto==CTL_PROTO and type(msg)=="table" and msg.op=="req" then
- local ok, err
- if msg.act=="goto" then
- ok, err = gotoXYZ(msg.label, msg.x, msg.y, msg.z, msg.order)
- elseif msg.act=="clear" then
- ok, err = clearArea(msg.label, msg.L, msg.W, msg.H, msg.dir)
- elseif msg.act=="face" then
- ok, err = face(msg.label, msg.dir)
- elseif msg.act=="fwd" then
- ok, err = forward(msg.label, msg.n)
- elseif msg.act=="up" then
- ok, err = up(msg.label, msg.n)
- elseif msg.act=="dn" then
- ok, err = down(msg.label, msg.n)
- else
- ok, err = false, "unknown act"
- end
- rednet.send(sid, {op="resp", ref=msg.ref, ok=ok, err=err}, CTL_PROTO)
- end
- end
- end
- -- ---------- Networking + UI ----------
- local function netUiThread()
- while true do
- local id,msg,proto = rednet.receive(PROTOCOL, 0.2)
- if id and proto==PROTOCOL and type(msg)=="table" and msg.op=="status" then
- upsertStatus(msg)
- end
- drawList()
- drawMap()
- end
- end
- -- Optional inline REPL for manual driving
- local function replThread()
- term.setCursorPos(1,1); print("Controller REPL. help for cmds.")
- while true do
- io.write("> ")
- local line = read()
- local a,b,c,d,e,f = line:match("^(%S+)%s*(%S*)%s*(%S*)%s*(%S*)%s*(%S*)%s*(%S*)")
- if a=="help" then
- print("fac <label> <N|E|S|W>")
- print("fwd <label> <n>")
- print("up <label> <n>")
- print("dn <label> <n>")
- print("clear <label> <L> <W> <H> <N|E|S|W>")
- print("tagface <label> <N|E|S|W>")
- print("goto <label> <x> <y> <z> [order yxz|xyz]")
- elseif a=="fac" then print(face(b,c))
- elseif a=="fwd" then print(forward(b,tonumber(c)))
- elseif a=="up" then print(up(b,tonumber(c)))
- elseif a=="dn" then print(down(b,tonumber(c)))
- elseif a=="clear" then print(clearArea(b,tonumber(c),tonumber(d),tonumber(e),f))
- elseif a=="tagface" then print(setFacingTag(b,c))
- elseif a=="goto" then
- local order = (f and #f==3) and {f:sub(1,1),f:sub(2,2),f:sub(3,3)} or nil
- print(gotoXYZ(b, tonumber(c), tonumber(d), tonumber(e), order))
- else print("unknown") end
- end
- end
- if CONFIG.repl then
- parallel.waitForAny(netUiThread, ctlServer, replThread)
- else
- parallel.waitForAny(netUiThread, ctlServer)
- end
Advertisement
Add Comment
Please, Sign In to add comment