diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index c12f9fb..b5c09e6 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -55,6 +55,8 @@ function coordinator.configure_monitors(num_units) local monitors = { primary = nil, primary_name = "", + flow = nil, + flow_name = "", unit_displays = {}, unit_name_map = {} } @@ -69,8 +71,8 @@ function coordinator.configure_monitors(num_units) table.insert(available, iface) end - -- we need a certain number of monitors (1 per unit + 1 primary display) - local num_displays_needed = num_units + 1 + -- we need a certain number of monitors (1 per unit + 1 primary display + 1 flow display) + local num_displays_needed = num_units + 2 if #names < num_displays_needed then local message = "not enough monitors connected (need " .. num_displays_needed .. ")" println(message) @@ -83,10 +85,12 @@ function coordinator.configure_monitors(num_units) log.warning("configure_monitors(): failed to load coordinator settings file (may not exist yet)") else local _primary = settings.get("PRIMARY_DISPLAY") + local _flow = settings.get("FLOW_DISPLAY") local _unitd = settings.get("UNIT_DISPLAYS") -- filter out already assigned monitors util.filter_table(available, function (x) return x ~= _primary end) + util.filter_table(available, function (x) return x ~= _flow end) if type(_unitd) == "table" then util.filter_table(available, function (x) return not util.table_contains(_unitd, x) end) end @@ -106,7 +110,6 @@ function coordinator.configure_monitors(num_units) end while iface_primary_display == nil and #available > 0 do - -- lets get a monitor iface_primary_display = ask_monitor(available) end @@ -118,6 +121,31 @@ function coordinator.configure_monitors(num_units) monitors.primary = ppm.get_periph(iface_primary_display) monitors.primary_name = iface_primary_display + -------------------------- + -- FLOW MONITOR DISPLAY -- + -------------------------- + + local iface_flow_display = settings.get("FLOW_DISPLAY") ---@type boolean|string|nil + + if not util.table_contains(names, iface_flow_display) then + println("flow monitor display is not connected") + local response = dialog.ask_y_n("would you like to change it", true) + if response == false then return false end + iface_flow_display = nil + end + + while iface_flow_display == nil and #available > 0 do + iface_flow_display = ask_monitor(available) + end + + if type(iface_flow_display) ~= "string" then return false end + + settings.set("FLOW_DISPLAY", iface_flow_display) + util.filter_table(available, function (x) return x ~= iface_flow_display end) + + monitors.flow = ppm.get_periph(iface_flow_display) + monitors.flow_name = iface_flow_display + ------------------- -- UNIT DISPLAYS -- ------------------- @@ -130,7 +158,6 @@ function coordinator.configure_monitors(num_units) local display = nil while display == nil and #available > 0 do - -- lets get a monitor println("please select monitor for unit #" .. i) display = ask_monitor(available) end @@ -152,7 +179,6 @@ function coordinator.configure_monitors(num_units) end while display == nil and #available > 0 do - -- lets get a monitor display = ask_monitor(available) end diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 5a6a605..b4709b6 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -10,6 +10,7 @@ local iocontrol = require("coordinator.iocontrol") local style = require("coordinator.ui.style") local pgi = require("coordinator.ui.pgi") +local flow_view = require("coordinator.ui.layout.flow_view") local panel_view = require("coordinator.ui.layout.front_panel") local main_view = require("coordinator.ui.layout.main_view") local unit_view = require("coordinator.ui.layout.unit_view") @@ -29,6 +30,7 @@ local engine = { ui = { front_panel = nil, ---@type graphics_element|nil main_display = nil, ---@type graphics_element|nil + flow_display = nil, ---@type graphics_element|nil unit_displays = {} } } @@ -60,8 +62,9 @@ end -- init all displays in use by the renderer function renderer.init_displays() - -- init primary monitor + -- init primary and flow monitors _init_display(engine.monitors.primary) + _init_display(engine.monitors.flow) -- init unit displays for _, monitor in ipairs(engine.monitors.unit_displays) do @@ -169,6 +172,12 @@ function renderer.start_ui() main_view(engine.ui.main_display) end + -- show flow view on flow monitor + if engine.monitors.flow ~= nil then + engine.ui.flow_display = DisplayBox{window=engine.monitors.flow,fg_bg=style.root} + flow_view(engine.ui.flow_display) + end + -- show unit views on unit displays for idx, display in pairs(engine.monitors.unit_displays) do engine.ui.unit_displays[idx] = DisplayBox{window=display,fg_bg=style.root} @@ -192,6 +201,7 @@ function renderer.close_ui() -- delete element trees if engine.ui.main_display ~= nil then engine.ui.main_display.delete() end + if engine.ui.flow_display ~= nil then engine.ui.flow_display.delete() end for _, display in pairs(engine.ui.unit_displays) do display.delete() end -- report ui as not ready @@ -199,6 +209,7 @@ function renderer.close_ui() -- clear root UI elements engine.ui.main_display = nil + engine.ui.flow_display = nil engine.ui.unit_displays = {} -- clear unit monitors @@ -317,6 +328,8 @@ function renderer.handle_mouse(event) elseif engine.ui_ready then if event.monitor == engine.monitors.primary_name then engine.ui.main_display.handle_mouse(event) + elseif event.monitor == engine.monitors.flow_name then + engine.ui.flow_display.handle_mouse(event) else for id, monitor in ipairs(engine.monitors.unit_name_map) do if event.monitor == monitor then diff --git a/coordinator/startup.lua b/coordinator/startup.lua index a3cf6fa..9951d20 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -22,7 +22,7 @@ local sounder = require("coordinator.sounder") local apisessions = require("coordinator.session.apisessions") -local COORDINATOR_VERSION = "v0.21.2" +local COORDINATOR_VERSION = "v1.0.0" local println = util.println local println_ts = util.println_ts diff --git a/coordinator/ui/components/flow_overview.lua b/coordinator/ui/components/flow_overview.lua new file mode 100644 index 0000000..6116667 --- /dev/null +++ b/coordinator/ui/components/flow_overview.lua @@ -0,0 +1,159 @@ +-- +-- Basic Unit Flow Overview +-- + +local util = require("scada-common.util") + +local core = require("graphics.core") + +local style = require("coordinator.ui.style") + +local reactor_view = require("coordinator.ui.components.reactor") +local boiler_view = require("coordinator.ui.components.boiler") +local turbine_view = require("coordinator.ui.components.turbine") + +local Div = require("graphics.elements.div") +local PipeNetwork = require("graphics.elements.pipenet") +local TextBox = require("graphics.elements.textbox") + +local Rectangle = require("graphics.elements.rectangle") + +local DataIndicator = require("graphics.elements.indicators.data") +local HorizontalBar = require("graphics.elements.indicators.hbar") +local StateIndicator = require("graphics.elements.indicators.state") + +local IndicatorLight = require("graphics.elements.indicators.light") +local TriIndicatorLight = require("graphics.elements.indicators.trilight") +local VerticalBar = require("graphics.elements.indicators.vbar") + +local cpair = core.cpair +local border = core.border + +local TEXT_ALIGN = core.TEXT_ALIGN + +local pipe = core.pipe + +-- make a new unit overview window +---@param parent graphics_element parent +---@param x integer top left x +---@param y integer top left y +---@param unit ioctl_unit unit database entry +local function make(parent, x, y, unit) + local height = 16 + + local v_start = 1 + ((unit.unit_id - 1) * 4) + local v_names = { + util.sprintf("PV%02d-PU", v_start), + util.sprintf("PV%02d-PO", v_start + 1), + util.sprintf("PV%02d-PL", v_start + 2), + util.sprintf("PV%02d-AM", v_start + 3) + } + + assert(parent.get_height() >= (y + height), "flow display not of sufficient vertical resolution (add an additional row of monitors) " .. y .. "," .. parent.get_height()) + + -- bounding box div + local root = Div{parent=parent,x=x,y=y,width=114,height=height} + + local text_fg_bg = cpair(colors.black, colors.white) + local lu_col = cpair(colors.gray, colors.gray) + + ------------- + -- REACTOR -- + ------------- + + local reactor = Rectangle{parent=root,x=1,y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=cpair(colors.white,colors.gray)} + TextBox{parent=reactor,y=1,text="FISSION REACTOR",alignment=TEXT_ALIGN.CENTER,height=1} + TextBox{parent=reactor,y=3,text="UNIT #"..unit.unit_id,alignment=TEXT_ALIGN.CENTER,height=1} + TextBox{parent=root,x=19,y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=cpair(colors.lightGray,colors.gray)} + TextBox{parent=root,x=4,y=5,text="\x19",width=1,height=1,fg_bg=cpair(colors.lightGray,colors.gray)} + + local rc_pipes = {} + + table.insert(rc_pipes, pipe(0, 1, 19, 1, colors.lightBlue, true)) + table.insert(rc_pipes, pipe(0, 3, 19, 3, colors.orange, true)) + table.insert(rc_pipes, pipe(39, 1, 58, 1, colors.blue, true)) + table.insert(rc_pipes, pipe(39, 3, 58, 3, colors.white, true)) + + table.insert(rc_pipes, pipe(78, 0, 83, 0, colors.white, true)) + table.insert(rc_pipes, pipe(78, 2, 83, 2, colors.white, true)) + table.insert(rc_pipes, pipe(78, 4, 83, 4, colors.white, true)) + + PipeNetwork{parent=root,x=20,y=1,pipes=rc_pipes,bg=colors.lightGray} + + local hc_rate = DataIndicator{parent=root,x=22,y=3,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=text_fg_bg} + local cc_rate = DataIndicator{parent=root,x=22,y=5,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=text_fg_bg} + + local boiler = Rectangle{parent=root,x=40,y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=cpair(colors.white,colors.gray)} + TextBox{parent=boiler,y=1,text="THERMO-ELECTRIC",alignment=TEXT_ALIGN.CENTER,height=1} + TextBox{parent=boiler,y=3,text="BOILERS",alignment=TEXT_ALIGN.CENTER,height=1} + TextBox{parent=root,x=40,y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=cpair(colors.lightGray,colors.gray)} + TextBox{parent=root,x=58,y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=cpair(colors.lightGray,colors.gray)} + + local wt_rate = DataIndicator{parent=root,x=61,y=3,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=text_fg_bg} + local st_rate = DataIndicator{parent=root,x=61,y=5,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=text_fg_bg} + + local turbine = Rectangle{parent=root,x=79,y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=cpair(colors.white,colors.gray)} + TextBox{parent=turbine,y=1,text="STEAM TURBINE",alignment=TEXT_ALIGN.CENTER,height=1} + TextBox{parent=turbine,y=3,text="GENERATORS",alignment=TEXT_ALIGN.CENTER,height=1} + TextBox{parent=root,x=79,y=2,text="\x1a \x80 \x1b",width=1,height=3,fg_bg=cpair(colors.lightGray,colors.gray)} + + TextBox{parent=root,x=101,y=3,text="\x10\x11",fg_bg=cpair(colors.black,colors.lightGray),width=2,height=1} + TextBox{parent=root,x=103,y=3,text="\x7f",fg_bg=cpair(colors.white,colors.lightGray),width=1,height=1} + local conn = TriIndicatorLight{parent=root,x=106,y=1,label="PRV01",c1=colors.gray,c2=colors.yellow,c3=colors.red} + local conn = TriIndicatorLight{parent=root,x=106,y=3,label="PRV02",c1=colors.gray,c2=colors.yellow,c3=colors.red} + local conn = TriIndicatorLight{parent=root,x=106,y=5,label="PRV03",c1=colors.gray,c2=colors.yellow,c3=colors.red} + + local waste = Div{parent=root,x=3,y=6} + + local waste_pipes = { + pipe(0, 0, 16, 1, colors.brown, true), + pipe(12, 1, 16, 5, colors.brown, true), + pipe(18, 1, 44, 1, colors.brown, true), + pipe(18, 5, 23, 5, colors.brown, true), + pipe(52, 1, 80, 1, colors.green, true), + pipe(42, 4, 60, 4, colors.cyan, true), + pipe(56, 4, 60, 8, colors.cyan, true), + pipe(62, 4, 80, 4, colors.cyan, true), + pipe(62, 8, 110, 8, colors.cyan, true), + pipe(93, 1, 94, 3, colors.black, true, true), + pipe(93, 4, 109, 6, colors.black, true, true), + pipe(109, 6, 107, 6, colors.black, true, true) + } + + PipeNetwork{parent=waste,x=2,y=1,pipes=waste_pipes,bg=colors.lightGray} + + local function _valve(vx, vy, n) + TextBox{parent=waste,x=vx,y=vy,text="\x10\x11",fg_bg=cpair(colors.black,colors.lightGray),width=2,height=1} + local conn = IndicatorLight{parent=waste,x=vx-3,y=vy+1,label=v_names[n],colors=cpair(colors.green,colors.gray)} + local state = IndicatorLight{parent=waste,x=vx-3,y=vy+2,label="STATE",colors=cpair(colors.white,colors.white)} + end + + local function _machine(mx, my, name) + local l = string.len(name) + 2 + TextBox{parent=waste,x=mx,y=my,text=util.strrep("\x8f",l),alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.lightGray,colors.gray),width=l,height=1} + TextBox{parent=waste,x=mx,y=my+1,text=name,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.white,colors.gray),width=l,height=1} + end + + local waste_rate = DataIndicator{parent=waste,x=1,y=3,lu_colors=lu_col,label="",unit="mB/t",format="%7.2f",value=1234.56,width=12,fg_bg=text_fg_bg} + local pu_rate = DataIndicator{parent=waste,x=70,y=3,lu_colors=lu_col,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=text_fg_bg} + local po_rate = DataIndicator{parent=waste,x=45,y=6,lu_colors=lu_col,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=text_fg_bg} + local popl_rate = DataIndicator{parent=waste,x=70,y=6,lu_colors=lu_col,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=text_fg_bg} + local poam_rate = DataIndicator{parent=waste,x=70,y=10,lu_colors=lu_col,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=text_fg_bg} + local spent_rate = DataIndicator{parent=waste,x=99,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=text_fg_bg} + + _valve(18, 2, 1); _valve(18, 6, 2); _valve(62, 5, 3); _valve(62, 9, 4) + _machine(45, 1, "CENTRIFUGE \x1a"); _machine(83, 1, "PRC [Pu] \x1a"); _machine(83, 4, "PRC [Po] \x1a"); _machine(94, 6, "SPENT WASTE \x1b") + + + TextBox{parent=waste,x=25,y=3,text="SNAs [Po]",alignment=TEXT_ALIGN.CENTER,width=19,height=1,fg_bg=cpair(colors.white,colors.gray)} + local sna_po = Rectangle{parent=waste,x=25,y=4,border=border(1, colors.gray, true),width=19,height=7,thin=true,fg_bg=cpair(colors.black,colors.white)} + local sna_act = IndicatorLight{parent=sna_po,label="ACTIVE",colors=cpair(colors.green,colors.red)} + local sna_cnt = DataIndicator{parent=sna_po,x=12,y=1,lu_colors=lu_col,label="CNT",unit="",format="%2d",value=99,width=7,fg_bg=text_fg_bg} + local sna_pk = DataIndicator{parent=sna_po,y=3,lu_colors=lu_col,label="PEAK",unit="mB/t",format="%7.2f",value=1000,width=17,fg_bg=text_fg_bg} + local sna_max = DataIndicator{parent=sna_po,lu_colors=lu_col,label="MAX ",unit="mB/t",format="%7.2f",value=1000,width=17,fg_bg=text_fg_bg} + local sna_in = DataIndicator{parent=sna_po,lu_colors=lu_col,label="IN ",unit="mB/t",format="%7.2f",value=1000,width=17,fg_bg=text_fg_bg} + + return root +end + +return make diff --git a/coordinator/ui/layout/flow_view.lua b/coordinator/ui/layout/flow_view.lua new file mode 100644 index 0000000..2e865c7 --- /dev/null +++ b/coordinator/ui/layout/flow_view.lua @@ -0,0 +1,41 @@ +-- +-- Flow Monitor GUI +-- + +local util = require("scada-common.util") + +local iocontrol = require("coordinator.iocontrol") + +local style = require("coordinator.ui.style") + +local flow_overview = require("coordinator.ui.components.flow_overview") + +local core = require("graphics.core") + +local TextBox = require("graphics.elements.textbox") + +local DataIndicator = require("graphics.elements.indicators.data") + +local TEXT_ALIGN = core.TEXT_ALIGN + +local cpair = core.cpair + +-- create new flow view +---@param main graphics_element main displaybox +local function init(main) + local facility = iocontrol.get_db().facility + local units = iocontrol.get_db().units + + -- window header message + local header = TextBox{parent=main,y=1,text="Facility Coolant and Waste Flow Monitor",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + -- max length example: "01:23:45 AM - Wednesday, September 28 2022" + local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=TEXT_ALIGN.RIGHT,width=42,height=1,fg_bg=style.header} + + datetime.register(facility.ps, "date_time", datetime.set_value) + + for i = 1, 4 do + flow_overview(main, 25, 5 + ((i - 1) * 20), units[i]) + end +end + +return init diff --git a/graphics/core.lua b/graphics/core.lua index 534d4e2..5abc28d 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -7,7 +7,7 @@ local flasher = require("graphics.flasher") local core = {} -core.version = "1.0.2" +core.version = "1.0.3" core.flasher = flasher core.events = events diff --git a/graphics/elements/pipenet.lua b/graphics/elements/pipenet.lua index efe6179..5319e68 100644 --- a/graphics/elements/pipenet.lua +++ b/graphics/elements/pipenet.lua @@ -1,6 +1,7 @@ -- Pipe Graphics Element local util = require("scada-common.util") +local log = require("scada-common.log") local core = require("graphics.core") local element = require("graphics.element") @@ -14,6 +15,12 @@ local element = require("graphics.element") ---@field y? integer auto incremented if omitted ---@field hidden? boolean true to hide on initial draw +---@class _pipe_map_entry +---@field atr boolean align top right (or bottom left for false) +---@field thin boolean thin pipe or not +---@field fg string foreground blit +---@field bg string background blit + -- new pipe network ---@param args pipenet_args ---@return graphics_element element, element_id id @@ -44,102 +51,266 @@ local function pipenet(args) -- create new graphics element base object local e = element.new(args) - -- draw all pipes + -- determine if there are any thin pipes involved + local any_thin = false for p = 1, #args.pipes do - local pipe = args.pipes[p] ---@type pipe + any_thin = args.pipes[p].thin + if any_thin then break end + end - local x = 1 + pipe.x1 - local y = 1 + pipe.y1 + if not any_thin then + -- draw all pipes + for p = 1, #args.pipes do + local pipe = args.pipes[p] ---@type pipe - local x_step = util.trinary(pipe.x1 >= pipe.x2, -1, 1) - local y_step = util.trinary(pipe.y1 >= pipe.y2, -1, 1) + local x = 1 + pipe.x1 + local y = 1 + pipe.y1 - e.window.setCursorPos(x, y) + local x_step = util.trinary(pipe.x1 >= pipe.x2, -1, 1) + local y_step = util.trinary(pipe.y1 >= pipe.y2, -1, 1) - local c = core.cpair(pipe.color, e.fg_bg.bkg) + if pipe.thin then + x_step = util.trinary(pipe.x1 == pipe.x2, 0, x_step) + y_step = util.trinary(pipe.y1 == pipe.y2, 0, y_step) + end - if pipe.align_tr then - -- cross width then height - for i = 1, pipe.w do - if pipe.thin then - if i == pipe.w then - -- corner - if y_step > 0 then - e.window.blit("\x93", c.blit_bkg, c.blit_fgd) + e.window.setCursorPos(x, y) + + local c = core.cpair(pipe.color, e.fg_bg.bkg) + + if pipe.align_tr then + -- cross width then height + for i = 1, pipe.w do + if pipe.thin then + if i == pipe.w then + -- corner + if y_step > 0 then + e.window.blit("\x93", c.blit_bkg, c.blit_fgd) + else + e.window.blit("\x8e", c.blit_fgd, c.blit_bkg) + end else - e.window.blit("\x8e", c.blit_fgd, c.blit_bkg) + e.window.blit("\x8c", c.blit_fgd, c.blit_bkg) end else + if i == pipe.w and y_step > 0 then + -- corner + e.window.blit(" ", c.blit_bkg, c.blit_fgd) + else + e.window.blit("\x8f", c.blit_fgd, c.blit_bkg) + end + end + + x = x + x_step + e.window.setCursorPos(x, y) + end + + -- back up one + x = x - x_step + + for _ = 1, pipe.h - 1 do + y = y + y_step + e.window.setCursorPos(x, y) + + if pipe.thin then + e.window.blit("\x95", c.blit_bkg, c.blit_fgd) + else + e.window.blit(" ", c.blit_bkg, c.blit_fgd) + end + end + else + -- cross height then width + for i = 1, pipe.h do + if pipe.thin then + if i == pipe.h then + -- corner + if y_step < 0 then + e.window.blit("\x97", c.blit_bkg, c.blit_fgd) + elseif y_step > 0 then + e.window.blit("\x8d", c.blit_fgd, c.blit_bkg) + else + e.window.blit("\x8c", c.blit_fgd, c.blit_bkg) + end + else + e.window.blit("\x95", c.blit_fgd, c.blit_bkg) + end + else + if i == pipe.h and y_step < 0 then + -- corner + e.window.blit("\x83", c.blit_bkg, c.blit_fgd) + else + e.window.blit(" ", c.blit_bkg, c.blit_fgd) + end + end + + y = y + y_step + e.window.setCursorPos(x, y) + end + + -- back up one + y = y - y_step + + for _ = 1, pipe.w - 1 do + x = x + x_step + e.window.setCursorPos(x, y) + + if pipe.thin then e.window.blit("\x8c", c.blit_fgd, c.blit_bkg) - end - else - if i == pipe.w and y_step > 0 then - -- corner - e.window.blit(" ", c.blit_bkg, c.blit_fgd) else - e.window.blit("\x8f", c.blit_fgd, c.blit_bkg) - end - end - - x = x + x_step - e.window.setCursorPos(x, y) - end - - -- back up one - x = x - x_step - - for _ = 1, pipe.h - 1 do - y = y + y_step - e.window.setCursorPos(x, y) - - if pipe.thin then - e.window.blit("\x95", c.blit_bkg, c.blit_fgd) - else - e.window.blit(" ", c.blit_bkg, c.blit_fgd) - end - end - else - -- cross height then width - for i = 1, pipe.h do - if pipe.thin then - if i == pipe.h then - -- corner - if y_step < 0 then - e.window.blit("\x97", c.blit_bkg, c.blit_fgd) - else - e.window.blit("\x8d", c.blit_fgd, c.blit_bkg) - end - else - e.window.blit("\x95", c.blit_fgd, c.blit_bkg) - end - else - if i == pipe.h and y_step < 0 then - -- corner e.window.blit("\x83", c.blit_bkg, c.blit_fgd) - else - e.window.blit(" ", c.blit_bkg, c.blit_fgd) end end - - y = y + y_step - e.window.setCursorPos(x, y) end + end + else + -- build map if using thin pipes, easist way to check adjacent blocks (cannot 'cheat' like with standard width) + local map = {} - -- back up one - y = y - y_step + -- allocate map + for x = 1, args.width do + table.insert(map, {}) + for _ = 1, args.height do table.insert(map[x], false) end + end - for _ = 1, pipe.w - 1 do - x = x + x_step - e.window.setCursorPos(x, y) + -- build map + for p = 1, #args.pipes do + local pipe = args.pipes[p] ---@type pipe - if pipe.thin then - e.window.blit("\x8c", c.blit_fgd, c.blit_bkg) - else - e.window.blit("\x83", c.blit_bkg, c.blit_fgd) + local x = 1 + pipe.x1 + local y = 1 + pipe.y1 + + local x_step = util.trinary(pipe.x1 >= pipe.x2, -1, 1) + local y_step = util.trinary(pipe.y1 >= pipe.y2, -1, 1) + + local entry = { atr = pipe.align_tr, thin = pipe.thin, fg = colors.toBlit(pipe.color), bg = e.fg_bg.blit_bkg } + + if pipe.align_tr then + -- cross width then height + for _ = 1, pipe.w do + map[x][y] = entry + x = x + x_step + end + + x = x - x_step -- back up one + + for _ = 1, pipe.h do + map[x][y] = entry + y = y + y_step + end + else + -- cross height then width + for _ = 1, pipe.h do + map[x][y] = entry + y = y + y_step + end + + y = y - y_step -- back up one + + for _ = 1, pipe.w do + map[x][y] = entry + x = x + x_step end end end + -- for x = 1, args.width do + -- for y = 1, args.height do + -- local entry = map[x][y] ---@type _pipe_map_entry|false + -- if entry == false then + -- e.window.setCursorPos(x, y) + -- e.window.blit("x", "f", "e") + -- end + -- end + -- end + + -- render + for x = 1, args.width do + for y = 1, args.height do + local entry = map[x][y] ---@type _pipe_map_entry|false + local char = "" + local invert = false + + if entry ~= false then + local function check(cx, cy) + return (map[cx] ~= nil) and (map[cx][cy] ~= nil) and (map[cx][cy] ~= false) and (map[cx][cy].fg == entry.fg) + end + + if entry.thin then + if check(x - 1, y) then -- if left + if check(x, y - 1) then -- if above + if check(x + 1, y) then -- if right + if check(x, y + 1) then -- if below + char = util.trinary(entry.atr, "\x91", "\x9d") + invert = entry.atr + else -- not below + char = util.trinary(entry.atr, "\x8e", "\x8d") + end + else -- not right + if check(x, y + 1) then -- if below + char = util.trinary(entry.atr, "\x91", "\x95") + invert = entry.atr + else -- not below + char = util.trinary(entry.atr, "\x8e", "\x85") + end + end + elseif check(x, y + 1) then-- not above, if below + if check(x + 1, y) then -- if right + char = util.trinary(entry.atr, "\x93", "\x9c") + invert = entry.atr + else -- not right + char = util.trinary(entry.atr, "\x93", "\x94") + invert = entry.atr + end + else -- not above, not below + char = "\x8c" + end + elseif check(x + 1, y) then -- not left, if right + if check(x, y - 1) then -- if above + if check(x, y + 1) then -- if below + char = util.trinary(entry.atr, "\x95", "\x9d") + invert = entry.atr + else -- not below + char = util.trinary(entry.atr, "\x8a", "\x8d") + end + else -- not above + if check(x, y + 1) then -- if below + char = util.trinary(entry.atr, "\x97", "\x9c") + invert = entry.atr + else -- not below + char = "\x8c" + end + end + else -- not left, not right + char = "\x95" + invert = entry.atr + end + else + if check(x, y - 1) then -- above + -- not below and (if left or right) + if (not check(x, y + 1)) and (check(x - 1, y) or check(x + 1, y)) then + char = util.trinary(entry.atr, "\x8f", "\x83") + invert = not entry.atr + else -- not above w/ sides only + char = " " + invert = true + end + elseif check(x, y + 1) then -- not above, if below + char = util.trinary(entry.atr, "\x8f", "\x83") + invert = not entry.atr + else -- not above, not below + end + end + + e.window.setCursorPos(x, y) + + if invert then + e.window.blit(char, entry.bg, entry.fg) + else + e.window.blit(char, entry.fg, entry.bg) + end + end + end + end end return e.complete()