diff --git a/programs/roulette.lua b/programs/roulette.lua index 849a7f3..03fdfc6 100644 --- a/programs/roulette.lua +++ b/programs/roulette.lua @@ -1,158 +1,71 @@ --- 3D rotating cube (temporary test - roulette code commented out below) -print("Scanning peripherals...") -for _, name in ipairs(peripheral.getNames()) do - print(name .. " = " .. peripheral.getType(name)) -end - -local tris = { - -- SOUTH - { 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0 }, - { 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0 }, - -- NORTH - { 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0 }, - { 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0 }, - -- EAST - { 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0 }, - { 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0 }, - -- WEST - { 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0 }, - { 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0 }, - -- TOP - { 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0 }, - { 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0 }, - -- BOTTOM - { 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0 }, - { 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0 }, -} - -local gpu -for _, name in ipairs(peripheral.getNames()) do - local t = peripheral.getType(name) - if t and t:find("gpu") then - gpu = peripheral.wrap(name) - print("Found GPU: " .. name) - break - end -end -if not gpu then error("No GPU peripheral found.") end -gpu.refreshSize() -gpu.setSize(64) -local pw, ph, bx, by, sb = gpu.getSize() -print(("GPU pixel size: %sx%s blocks: %sx%s per-block: %s"):format(tostring(pw), tostring(ph), tostring(bx), tostring(by), tostring(sb))) -local gl = gpu.createWindow3D(1, 1, pw, ph) -gl.glFrustum(90, 0.1, 1000) -gl.glDirLight(0, 0, -1) -local rot = 0 -while true do - gl.clear() - gl.glDisable(0xDE1) - gl.glTranslate(0, 1, 3) - gl.glRotate(rot, 0, 1, 0) - gl.glRotate(rot, 0, 0, 1) - rot = rot + 3 - gl.glBegin() - for k, v in pairs(tris) do - local ci = math.floor((k - 1) / 2) - gl.glVertex(v[1], v[2], v[3]) - local cv = 255 - if ci % 2 == 0 then cv = 127 end - if math.floor(ci / 2) == 0 then - gl.glColor(cv, 0, 0) - elseif math.floor(ci / 2) == 1 then - gl.glColor(0, cv, 0) - else - gl.glColor(0, 0, cv) - end - gl.glVertex(v[4], v[5], v[6]) - gl.glVertex(v[7], v[8], v[9]) - end - gl.glEnd() - gl.render() - gl.sync() - gpu.sync() - sleep(0.01) -end - --- ============================================================================= --- ROULETTE CODE (commented out) --- ============================================================================= ---[[ - -- Roulette Machine -- Designed for Tom's Peripherals GPU + screen blocks (multi-screen wall). --- Falls back to vanilla CC:Tweaked monitor if no GPU is attached. -- -- Behaviour: --- * On boot, sizes itself to the full screen surface. +-- * On boot, discovers the GPU peripheral by scanning all attached peripherals. +-- * Calls refreshSize() + setSize(64) to bind the full monitor wall. -- * Draws a red/black/green pocket ring around the perimeter. -- * Waits for a redstone signal on any side, then spins the "ball" -- around the ring with random duration and ease-out deceleration, -- finally landing on a uniformly random pocket. ---------------------------------------------------------------------- --- Backend abstraction (GPU vs vanilla monitor) +-- GPU discovery ---------------------------------------------------------------------- --- All drawing goes through a small `gfx` table with these methods: --- gfx.size() -> width, height in cells/pixels --- gfx.fillRect(x, y, w, h, col) -- col is a backend-specific color --- gfx.text(x, y, str, fg, bg) --- gfx.clear(col) --- gfx.sync() -- present frame (no-op for monitor) --- gfx.colors -- table with .red .black .green .white .bg --- gfx.cellSize -- pixel size of one "pocket cell" - -local gfx local function findGPU() - -- Tom's Peripherals registers GPUs as tm_gpu_0, tm_gpu_1, ... so - -- scan all peripherals for any type that starts with "tm_gpu" or - -- exposes the GPU API surface. - for _, side in ipairs(peripheral.getNames()) do - local t = peripheral.getType(side) - if t and t:sub(1, 6) == "tm_gpu" then - return peripheral.wrap(side), t - end - end - for _, side in ipairs(peripheral.getNames()) do - local p = peripheral.wrap(side) - if p and p.refreshSize and p.filledRectangle and p.sync then - return p, peripheral.getType(side) + print("[roulette] Scanning peripherals...") + for _, name in ipairs(peripheral.getNames()) do + local t = peripheral.getType(name) + print(" " .. name .. " = " .. tostring(t)) + if t and t:find("gpu") then + print("[roulette] Using GPU: " .. name) + return peripheral.wrap(name) end end return nil end -local function makeGpuBackend(gpu) - -- refreshSize() schedules a server-thread monitor scan asynchronously. - -- sleep(0) yields back to the server, giving it a tick to finish. +---------------------------------------------------------------------- +-- Backend +---------------------------------------------------------------------- + +local gfx + +local function initBackend() + local gpu = findGPU() + if not gpu then + error("No GPU peripheral found. Attach a Tom's Peripherals GPU adjacent to the monitor wall.") + end + + -- Let the server tick resolve the monitor wall geometry before setSize. gpu.refreshSize() sleep(0) gpu.setSize(64) + -- getSize() -> pixelW, pixelH, blocksX, blocksY, sizePerBlock local pw, ph, bx, by, sb = gpu.getSize() - print(("[roulette] GPU pixel size: %sx%s blocks: %sx%s per-block: %s") - :format(tostring(pw), tostring(ph), tostring(bx), tostring(by), tostring(sb))) + print(("[roulette] GPU: %dx%d px | %dx%d blocks | %d px/block") + :format(pw, ph, bx or 0, by or 0, sb or 0)) - if not pw or not ph or pw < 8 or ph < 8 then - error(("GPU reports unusable pixel size %sx%s.") - :format(tostring(pw), tostring(ph))) + if not pw or pw < 8 or ph < 8 then + error(("GPU pixel size %dx%d is too small. Is the monitor wall placed and facing correctly?") + :format(pw or 0, ph or 0)) end - -- Pick a cell size that gives at least 16x16 cells, capped at 16 px. + -- Cell size: target at least 16 cells on the short axis, max 16 px each. local cell = math.max(2, math.min(16, math.floor(math.min(pw, ph) / 16))) - print(("[roulette] Using cell size: %d px -> %dx%d cells"):format( - cell, math.floor(pw / cell), math.floor(ph / cell))) + local cw = math.floor(pw / cell) + local ch = math.floor(ph / cell) + print(("[roulette] Cell size: %d px -> grid: %dx%d cells"):format(cell, cw, ch)) return { - kind = "gpu", + kind = "gpu", cellSize = cell, - pixelW = pw, - pixelH = ph, - size = function() - return math.floor(pw / cell), math.floor(ph / cell) - end, + pixelW = pw, + pixelH = ph, + size = function() return cw, ch end, fillRect = function(x, y, w, h, col) - -- 1-indexed pixel coords, per gpuDraw.lua example. local px = (x - 1) * cell + 1 local py = (y - 1) * cell + 1 gpu.filledRectangle(px, py, w * cell, h * cell, col) @@ -160,15 +73,12 @@ local function makeGpuBackend(gpu) text = function(x, y, str, fg, bg) local px = (x - 1) * cell + 1 local py = (y - 1) * cell + 1 - -- drawText(x, y, text, textColor, bgColor, size, padding) - pcall(gpu.drawText, px, py, str, fg, bg, 1, 0) + pcall(gpu.drawText, px, py, str, fg or 0xFFFFFF, bg or 0x000000, 1, 0) end, clear = function(col) if col then gpu.fill(col) else gpu.fill() end end, - sync = function() - gpu.sync() - end, + sync = function() gpu.sync() end, colors = { red = 0xE53935, black = 0x101010, @@ -179,58 +89,12 @@ local function makeGpuBackend(gpu) } end -local function makeMonitorBackend() - local mon = peripheral.find("monitor") - if not mon then return nil end - mon.setTextScale(0.5) - return { - kind = "monitor", - cellSize = 1, - size = function() return mon.getSize() end, - fillRect = function(x, y, w, h, col) - mon.setBackgroundColor(col) - for dy = 0, h - 1 do - mon.setCursorPos(x, y + dy) - mon.write(string.rep(" ", w)) - end - end, - text = function(x, y, str, fg, bg) - mon.setBackgroundColor(bg or colors.black) - mon.setTextColor(fg or colors.white) - mon.setCursorPos(x, y) - mon.write(str) - end, - clear = function(col) - mon.setBackgroundColor(col) - mon.clear() - end, - sync = function() end, - colors = { - red = colors.red, - black = colors.black, - green = colors.green, - white = colors.white, - bg = colors.black, - }, - } -end - -local function initBackend() - local gpu = findGPU() - if gpu then - return makeGpuBackend(gpu) - end - local mon = makeMonitorBackend() - if mon then return mon end - error("No GPU or monitor attached. Connect a Tom's Peripherals GPU + screens, or a CC monitor.") -end - ---------------------------------------------------------------------- -- Wheel state ---------------------------------------------------------------------- -local W, H -- size in cells -local perimeter -- ordered list of {x, y, color, label} +local W, H +local perimeter local ballIndex local lastBallIndex @@ -243,10 +107,10 @@ local function buildPerimeter() perimeter = {} local function add(x, y) table.insert(perimeter, { x = x, y = y }) end - for x = 1, W do add(x, 1) end -- top - for y = 2, H do add(W, y) end -- right - for x = W - 1, 1, -1 do add(x, H) end -- bottom - for y = H - 1, 2, -1 do add(1, y) end -- left + for x = 1, W do add(x, 1) end -- top L->R + for y = 2, H do add(W, y) end -- right T->B + for x = W - 1, 1, -1 do add(x, H) end -- bottom R->L + for y = H - 1, 2, -1 do add(1, y) end -- left B->T for i, c in ipairs(perimeter) do if i == 1 then @@ -271,7 +135,6 @@ local function drawWheel() end local function drawCenter(lines) - -- Clear interior if W > 2 and H > 2 then gfx.fillRect(2, 2, W - 2, H - 2, gfx.colors.bg) end @@ -296,7 +159,7 @@ local function moveBall(newIdx) if lastBallIndex then repaintPocket(lastBallIndex) end drawBall(newIdx) lastBallIndex = newIdx - ballIndex = newIdx + ballIndex = newIdx gfx.sync() end @@ -310,9 +173,9 @@ local function easeOut(t) end local function spin() - local n = #perimeter + local n = #perimeter local spinTime = SPIN_MIN_TIME + math.random() * (SPIN_MAX_TIME - SPIN_MIN_TIME) - local elapsed = 0 + local elapsed = 0 drawCenter({ "SPINNING" }) gfx.sync() @@ -320,24 +183,22 @@ local function spin() drawCenter({ "" }) while elapsed < spinTime do - local t = elapsed / spinTime + local t = elapsed / spinTime local eased = easeOut(t) local delay = START_DELAY + (END_DELAY - START_DELAY) * eased - local jump = math.max(1, math.floor((1 - eased) * 4 + 0.5)) + local jump = math.max(1, math.floor((1 - eased) * 4 + 0.5)) - local nextIdx = ((ballIndex - 1 + jump) % n) + 1 - moveBall(nextIdx) + moveBall(((ballIndex - 1 + jump) % n) + 1) sleep(delay) elapsed = elapsed + delay end - -- Final settle: pick a uniformly random pocket and crawl to it. + -- Crawl to a uniformly random final pocket. local finalIdx = math.random(1, n) - local steps = ((finalIdx - ballIndex) % n) + local steps = (finalIdx - ballIndex) % n if steps == 0 then steps = n end for i = 1, steps do - local nextIdx = ((ballIndex - 1 + 1) % n) + 1 - moveBall(nextIdx) + moveBall(((ballIndex - 1 + 1) % n) + 1) sleep(END_DELAY + i * 0.04) end @@ -346,8 +207,8 @@ end local function announce(pocket) local name = "BLACK" - if pocket.color == gfx.colors.red then name = "RED" - elseif pocket.color == gfx.colors.green then name = "GREEN" end + if pocket.color == gfx.colors.red then name = "RED" end + if pocket.color == gfx.colors.green then name = "GREEN" end drawCenter({ "WINNER", name .. " " .. pocket.label }) gfx.sync() end @@ -365,7 +226,7 @@ local function start() error(("Screen too small: %dx%d cells. Add more screen blocks."):format(W, H)) end buildPerimeter() - ballIndex = 1 + ballIndex = 1 lastBallIndex = nil drawWheel() drawBall(ballIndex) @@ -401,5 +262,3 @@ local function main() end return { start = start, stop = stop, main = main } - ---]]