542 lines
19 KiB
Lua
542 lines
19 KiB
Lua
-- Roulette Machine — circular wheel, top-down view
|
||
-- Tom's Peripherals GPU + screen wall (any size).
|
||
--
|
||
-- Authentic European pocket order (37 pockets, 0–36).
|
||
--
|
||
-- Physics:
|
||
-- Rotor : spins CW, decelerates under friction (heavy wheel, slow).
|
||
-- Ball : orbits outer track CCW at higher speed, decelerates faster.
|
||
-- When angular speed drops below DROP_SPEED the ball loses
|
||
-- centripetal support, gains inward radial velocity, and a
|
||
-- small random deflector-pin kick is applied.
|
||
-- Ball decelerates in the pocket ring until stopped.
|
||
-- Result : nearest pocket by angle (ball vs rotor) at rest.
|
||
--
|
||
-- Rendering strategy:
|
||
-- The full wheel (37 wedges) is expensive to rasterise, so it is only
|
||
-- redrawn when the rotor has rotated more than ROTOR_REDRAW_THRESH rad
|
||
-- since the last draw. Between redraws only the ball is erased/repainted
|
||
-- over the static background — keeping the frame rate smooth.
|
||
|
||
----------------------------------------------------------------------
|
||
-- GPU discovery
|
||
----------------------------------------------------------------------
|
||
|
||
local function findGPU()
|
||
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
|
||
|
||
----------------------------------------------------------------------
|
||
-- Constants
|
||
----------------------------------------------------------------------
|
||
|
||
local FRAME_DELAY = 0.05 -- ~20 fps (keeps CC happy)
|
||
local TWO_PI = math.pi * 2
|
||
|
||
-- Rotor is only redrawn when it has moved this many radians since last draw.
|
||
-- At R_OUTER ~200px, 0.02 rad ≈ 4px of arc — imperceptible until it accumulates.
|
||
local ROTOR_REDRAW_THRESH = 0.025 -- rad
|
||
|
||
-- European wheel order
|
||
local WHEEL_ORDER = {
|
||
0, 32, 15, 19, 4, 21, 2, 25, 17, 34, 6, 27, 13, 36,
|
||
11, 30, 8, 23, 10, 5, 24, 16, 33, 1, 20, 14, 31, 9,
|
||
22, 18, 29, 7, 28, 12, 35, 3, 26
|
||
}
|
||
local NUM_POCKETS = #WHEEL_ORDER -- 37
|
||
|
||
local RED_SET = {}
|
||
for _, n in ipairs({1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36}) do
|
||
RED_SET[n] = true
|
||
end
|
||
|
||
-- Geometry (computed in start())
|
||
local CX, CY
|
||
local R_OUTER, R_POCKET_OUT, R_POCKET_IN, R_HUB
|
||
|
||
-- Colours
|
||
local COL_BG = 0x050505
|
||
local COL_RIM = 0x8B6914
|
||
local COL_TRACK = 0x1A1A1A
|
||
local COL_RED = 0xC62828
|
||
local COL_BLACK = 0x1C1C1C
|
||
local COL_GREEN = 0x1B5E20
|
||
local COL_SEP = 0xB8860B
|
||
local COL_HUB = 0x2C2C2C
|
||
local COL_HUB_RING = 0x8B6914
|
||
local COL_WHITE = 0xFFFFFF
|
||
local COL_BALL = 0xF0F0F0
|
||
local COL_BALL_SHD = 0x444444
|
||
|
||
-- Physics tunables
|
||
local ROTOR_SPEED_MIN = 1.2 -- rad/s
|
||
local ROTOR_SPEED_MAX = 2.0
|
||
local ROTOR_FRICTION = 0.06 -- rad/s²
|
||
|
||
local BALL_SPEED_MIN = 7.0 -- rad/s (CCW → negative)
|
||
local BALL_SPEED_MAX = 11.0
|
||
local TRACK_FRICTION = 0.38 -- rad/s²
|
||
|
||
-- Radial bounce: ball oscillates between the outer wall and an inner
|
||
-- wall (the pocket-ring outer edge) while on the track.
|
||
local BALL_VR_INIT = 55.0 -- px/s initial inward radial speed
|
||
local WALL_RESTITUTION = 0.55 -- fraction of radial speed kept on bounce
|
||
-- The "pyramid tip" deflector sits at this fraction of the track width
|
||
-- inward from the outer wall. Ball can bounce off it before dropping.
|
||
local DEFLECTOR_FRAC = 0.62 -- 0 = outer wall, 1 = pocket-ring edge
|
||
|
||
local DROP_SPEED = 1.4 -- rad/s — ball angular speed at which it finally
|
||
-- drops into the pocket ring
|
||
local DEFLECT_MAX = 0.22 -- rad — max random angular kick on final drop
|
||
|
||
local POCKET_FRICTION = 1.4 -- rad/s² — higher friction in pocket ring
|
||
|
||
local BALL_RADIUS = 8 -- px
|
||
|
||
----------------------------------------------------------------------
|
||
-- GPU / pixel primitives
|
||
----------------------------------------------------------------------
|
||
|
||
local gpu
|
||
local PW, PH
|
||
|
||
local function px_rect(x, y, w, h, col)
|
||
x = math.floor(x); y = math.floor(y)
|
||
w = math.floor(w); h = math.floor(h)
|
||
if x < 1 then w = w + x - 1; x = 1 end
|
||
if y < 1 then h = h + y - 1; y = 1 end
|
||
if x + w - 1 > PW then w = PW - x + 1 end
|
||
if y + h - 1 > PH then h = PH - y + 1 end
|
||
if w < 1 or h < 1 then return end
|
||
gpu.filledRectangle(x, y, w, h, col)
|
||
end
|
||
|
||
local function px_circle(cx, cy, r, col)
|
||
cx = math.floor(cx); cy = math.floor(cy); r = math.floor(r)
|
||
for dy = -r, r do
|
||
local half = math.floor(math.sqrt(r*r - dy*dy) + 0.5)
|
||
px_rect(cx - half, cy + dy, half*2 + 1, 1, col)
|
||
end
|
||
end
|
||
|
||
local function px_annulus(cx, cy, r1, r2, col)
|
||
cx = math.floor(cx); cy = math.floor(cy)
|
||
r1 = math.floor(r1); r2 = math.floor(r2)
|
||
for dy = -r2, r2 do
|
||
local ho = math.floor(math.sqrt(math.max(0, r2*r2 - dy*dy)) + 0.5)
|
||
local hi = math.floor(math.sqrt(math.max(0, r1*r1 - dy*dy)) + 0.5)
|
||
if ho > hi then
|
||
px_rect(cx - ho, cy + dy, ho - hi, 1, col)
|
||
px_rect(cx + hi, cy + dy, ho - hi + 1, 1, col)
|
||
elseif hi == 0 then
|
||
px_rect(cx - ho, cy + dy, ho*2 + 1, 1, col)
|
||
end
|
||
end
|
||
end
|
||
|
||
local function px_spoke(cx, cy, r1, r2, angle, col)
|
||
local ca, sa = math.cos(angle), math.sin(angle)
|
||
for i = 0, r2 - r1 do
|
||
local r = r1 + i
|
||
px_rect(math.floor(cx + ca*r + 0.5), math.floor(cy + sa*r + 0.5), 1, 1, col)
|
||
end
|
||
end
|
||
|
||
local function px_text(str, x, y, fg, bg, size)
|
||
pcall(gpu.drawText, math.floor(x), math.floor(y), str, fg, bg, size or 1, 0)
|
||
end
|
||
|
||
----------------------------------------------------------------------
|
||
-- Wedge rasteriser (used at startup and for glow flashes only)
|
||
----------------------------------------------------------------------
|
||
|
||
local function pocketColor(num)
|
||
if num == 0 then return COL_GREEN end
|
||
if RED_SET[num] then return COL_RED end
|
||
return COL_BLACK
|
||
end
|
||
|
||
local function drawWedge(slotIdx, rotorAngle, glowing)
|
||
local halfArc = math.pi / NUM_POCKETS
|
||
local midAngle = rotorAngle + (slotIdx - 1) * TWO_PI / NUM_POCKETS
|
||
local a0 = midAngle - halfArc
|
||
local a1 = midAngle + halfArc
|
||
|
||
local num = WHEEL_ORDER[slotIdx]
|
||
local col = pocketColor(num)
|
||
if glowing then
|
||
local r = math.min(255, math.floor(col / 0x10000) + 60)
|
||
local g = math.min(255, math.floor((col % 0x10000) / 0x100) + 60)
|
||
local b = math.min(255, col % 0x100 + 60)
|
||
col = r * 0x10000 + g * 0x100 + b
|
||
end
|
||
|
||
local ri, ro = R_POCKET_IN, R_POCKET_OUT
|
||
local bx0 = math.floor(CX - ro) - 1
|
||
local bx1 = math.ceil (CX + ro) + 1
|
||
local by0 = math.floor(CY - ro) - 1
|
||
local by1 = math.ceil (CY + ro) + 1
|
||
local arc = (a1 - a0) % TWO_PI
|
||
|
||
for sy = by0, by1 do
|
||
local runStart = nil
|
||
for sx = bx0, bx1 do
|
||
local dx = sx - CX
|
||
local dy = sy - CY
|
||
local dist = math.sqrt(dx*dx + dy*dy)
|
||
local inRing = dist >= ri and dist <= ro
|
||
local rel = (math.atan2(dy, dx) - a0) % TWO_PI
|
||
local inWedge = rel <= arc
|
||
|
||
if inRing and inWedge then
|
||
if not runStart then runStart = sx end
|
||
else
|
||
if runStart then
|
||
px_rect(runStart, sy, sx - runStart, 1, col)
|
||
runStart = nil
|
||
end
|
||
end
|
||
end
|
||
if runStart then
|
||
px_rect(runStart, sy, bx1 - runStart + 1, 1, col)
|
||
end
|
||
end
|
||
|
||
px_spoke(CX, CY, ri, ro, a0, COL_SEP)
|
||
|
||
local labelR = (ri + ro) / 2
|
||
local lx = CX + math.cos(midAngle) * labelR
|
||
local ly = CY + math.sin(midAngle) * labelR
|
||
local label = tostring(num)
|
||
px_text(label, lx - (#label * 4), ly - 4, COL_WHITE, col, 1)
|
||
end
|
||
|
||
-- Draw ALL wedges then overlay static chrome. Yields between wedges
|
||
-- so CC doesn't timeout; only called when the wheel needs a full repaint.
|
||
local function drawAllWedges(rotorAngle, glowSlot)
|
||
for i = 1, NUM_POCKETS do
|
||
drawWedge(i, rotorAngle, i == glowSlot)
|
||
sleep(0) -- yield once per wedge (37 yields, not thousands)
|
||
end
|
||
end
|
||
|
||
local function drawChrome()
|
||
-- outer gold rim
|
||
px_annulus(CX, CY, R_OUTER - 6, R_OUTER, COL_RIM)
|
||
-- ball track channel
|
||
px_annulus(CX, CY, R_POCKET_OUT + 2, R_OUTER - 6, COL_TRACK)
|
||
-- inner/outer pocket borders
|
||
px_annulus(CX, CY, R_POCKET_OUT, R_POCKET_OUT + 2, COL_RIM)
|
||
px_annulus(CX, CY, R_POCKET_IN - 2, R_POCKET_IN, COL_RIM)
|
||
-- hub
|
||
px_circle(CX, CY, R_HUB, COL_HUB)
|
||
px_annulus(CX, CY, R_HUB - 4, R_HUB, COL_HUB_RING)
|
||
px_circle(CX, CY, 6, COL_HUB_RING)
|
||
px_circle(CX, CY, 3, COL_HUB)
|
||
end
|
||
|
||
local function drawWheelFull(rotorAngle, glowSlot)
|
||
px_circle(CX, CY, R_OUTER, COL_BG)
|
||
drawAllWedges(rotorAngle, glowSlot)
|
||
drawChrome()
|
||
end
|
||
|
||
----------------------------------------------------------------------
|
||
-- Ball helpers
|
||
----------------------------------------------------------------------
|
||
|
||
local ballX, ballY = 0, 0
|
||
|
||
local function bgColorAt(r)
|
||
-- What colour is behind the ball at radius r?
|
||
if r > R_POCKET_OUT + 2 then return COL_TRACK end
|
||
if r > R_POCKET_IN - 2 then return COL_BLACK end -- approximate — wedge redraws handle exact colour
|
||
return COL_HUB
|
||
end
|
||
|
||
local function eraseBall(bx, by, r)
|
||
-- Repaint the annulus region the ball touched.
|
||
-- Use COL_TRACK for track zone, COL_BLACK for pocket zone (close enough between full redraws).
|
||
local dist = math.sqrt((bx - CX)^2 + (by - CY)^2)
|
||
px_circle(math.floor(bx), math.floor(by), r + 2, bgColorAt(dist))
|
||
end
|
||
|
||
local function drawBall(bx, by)
|
||
ballX = math.floor(bx)
|
||
ballY = math.floor(by)
|
||
px_circle(ballX + 2, ballY + 2, BALL_RADIUS, COL_BALL_SHD)
|
||
px_circle(ballX, ballY, BALL_RADIUS, COL_BALL)
|
||
px_circle(ballX - 2, ballY - 2, 2, COL_WHITE)
|
||
end
|
||
|
||
local function ballPosAt(radius, angle)
|
||
return CX + math.cos(angle) * radius,
|
||
CY + math.sin(angle) * radius
|
||
end
|
||
|
||
----------------------------------------------------------------------
|
||
-- Center text overlay
|
||
----------------------------------------------------------------------
|
||
|
||
local function drawCenterText(lines, textSize)
|
||
textSize = textSize or 2
|
||
local r = R_HUB - 8
|
||
px_circle(CX, CY, r, COL_HUB)
|
||
local lineH = 13 * textSize
|
||
local totalH = #lines * lineH
|
||
local startY = CY - math.floor(totalH / 2)
|
||
for i, line in ipairs(lines) do
|
||
local lx = CX - math.floor(#line * 6 * textSize / 2)
|
||
px_text(line, lx, startY + (i-1) * lineH, COL_WHITE, COL_HUB, textSize)
|
||
end
|
||
px_annulus(CX, CY, R_HUB - 4, R_HUB, COL_HUB_RING)
|
||
gpu.sync()
|
||
end
|
||
|
||
----------------------------------------------------------------------
|
||
-- Physics spin
|
||
--
|
||
-- Phases:
|
||
-- TRACK : ball on outer track, both rotor+ball decelerating.
|
||
-- DROP : ball's centripetal support gone; gains inward radial velocity
|
||
-- + small random deflector-pin kick.
|
||
-- POCKET : ball in pocket ring, decelerates to rest.
|
||
--
|
||
-- The wheel is only fully redrawn when the rotor has moved
|
||
-- ROTOR_REDRAW_THRESH radians. Between redraws only the ball moves.
|
||
----------------------------------------------------------------------
|
||
|
||
local rotorAngle = 0
|
||
local rotorSpeed = 0
|
||
local lastDrawnRotor = 0 -- rotorAngle at last full wheel redraw
|
||
|
||
local function spin()
|
||
local dt = FRAME_DELAY
|
||
|
||
rotorSpeed = ROTOR_SPEED_MIN + math.random() * (ROTOR_SPEED_MAX - ROTOR_SPEED_MIN)
|
||
local ballSpeed = -(BALL_SPEED_MIN + math.random() * (BALL_SPEED_MAX - BALL_SPEED_MIN))
|
||
local ballAngle = math.random() * TWO_PI
|
||
|
||
-- Track radii
|
||
local R_WALL_OUT = R_OUTER - 6 -- inner face of outer gold rim
|
||
local R_WALL_IN = R_POCKET_OUT + 2 -- outer face of pocket ring (inner track wall)
|
||
local R_DEFLECTOR = R_WALL_OUT - (R_WALL_OUT - R_WALL_IN) * DEFLECTOR_FRAC
|
||
local R_SETTLE = (R_POCKET_IN + R_POCKET_OUT) / 2
|
||
|
||
-- Ball starts pressed against the outer wall with a small inward nudge.
|
||
local ballR = R_WALL_OUT - BALL_RADIUS
|
||
local ballVr = BALL_VR_INIT -- positive = moving inward
|
||
|
||
local phase = "TRACK" -- "TRACK" | "POCKET"
|
||
|
||
-- Initial full draw
|
||
drawWheelFull(rotorAngle, nil)
|
||
lastDrawnRotor = rotorAngle
|
||
local bx0, by0 = ballPosAt(ballR, ballAngle)
|
||
drawBall(bx0, by0)
|
||
gpu.sync()
|
||
|
||
while true do
|
||
-- ── Rotor ──────────────────────────────────────────────────
|
||
if rotorSpeed > 0 then
|
||
rotorSpeed = math.max(0, rotorSpeed - ROTOR_FRICTION * dt)
|
||
end
|
||
rotorAngle = (rotorAngle + rotorSpeed * dt) % TWO_PI
|
||
|
||
-- ── Ball angular motion ─────────────────────────────────────
|
||
local angFriction = (phase == "POCKET") and POCKET_FRICTION or TRACK_FRICTION
|
||
if ballSpeed < 0 then
|
||
ballSpeed = math.min(0, ballSpeed + angFriction * dt)
|
||
else
|
||
ballSpeed = math.max(0, ballSpeed - angFriction * dt)
|
||
end
|
||
ballAngle = (ballAngle + ballSpeed * dt) % TWO_PI
|
||
|
||
-- ── Radial motion (bounce in track channel) ─────────────────
|
||
if phase == "TRACK" then
|
||
ballR = ballR + ballVr * dt
|
||
|
||
-- Bounce off outer wall
|
||
if ballR <= R_WALL_OUT - BALL_RADIUS then
|
||
ballR = R_WALL_OUT - BALL_RADIUS
|
||
ballVr = math.abs(ballVr) * WALL_RESTITUTION
|
||
end
|
||
|
||
-- Bounce off deflector tip (inner pyramid tip) — only while fast enough
|
||
local angSpd = math.abs(ballSpeed)
|
||
if ballR >= R_DEFLECTOR and angSpd > DROP_SPEED then
|
||
ballR = R_DEFLECTOR
|
||
ballVr = -math.abs(ballVr) * WALL_RESTITUTION
|
||
-- Small random angular kick from the deflector tip
|
||
local kick = (math.random() * 2 - 1) * (DEFLECT_MAX * 0.4)
|
||
ballAngle = (ballAngle + kick) % TWO_PI
|
||
end
|
||
|
||
-- Once angular speed is slow enough the ball can no longer
|
||
-- hold centripetal orbit — it falls past the deflector tip
|
||
-- into the pocket ring.
|
||
if angSpd <= DROP_SPEED and ballR >= R_DEFLECTOR then
|
||
phase = "POCKET"
|
||
ballVr = math.abs(ballVr) + 30 -- extra inward push
|
||
-- Final random deflector kick
|
||
local kick = (math.random() * 2 - 1) * DEFLECT_MAX
|
||
ballAngle = (ballAngle + kick) % TWO_PI
|
||
ballSpeed = ballSpeed * 0.55
|
||
end
|
||
else
|
||
-- POCKET phase: slide inward to R_SETTLE, then stop.
|
||
ballR = ballR + ballVr * dt
|
||
ballVr = ballVr * (1 - 5 * dt)
|
||
if ballR >= R_SETTLE then
|
||
ballR = R_SETTLE
|
||
ballVr = 0
|
||
end
|
||
end
|
||
|
||
-- ── Redraw wheel if rotor has moved enough ──────────────────
|
||
local rotorDelta = math.abs(rotorAngle - lastDrawnRotor)
|
||
if rotorDelta > math.pi then rotorDelta = TWO_PI - rotorDelta end
|
||
|
||
if rotorDelta >= ROTOR_REDRAW_THRESH then
|
||
eraseBall(ballX, ballY, BALL_RADIUS)
|
||
drawAllWedges(rotorAngle, nil)
|
||
drawChrome()
|
||
lastDrawnRotor = rotorAngle
|
||
end
|
||
|
||
-- ── Ball render ─────────────────────────────────────────────
|
||
eraseBall(ballX, ballY, BALL_RADIUS)
|
||
local bx, by = ballPosAt(ballR, ballAngle)
|
||
drawBall(bx, by)
|
||
gpu.sync()
|
||
sleep(dt)
|
||
|
||
-- ── Stop condition ──────────────────────────────────────────
|
||
if phase == "POCKET" and ballSpeed == 0 and ballVr == 0 then break end
|
||
end
|
||
|
||
-- Determine winning pocket
|
||
local relAngle = (ballAngle - rotorAngle) % TWO_PI
|
||
local bestSlot, bestDist = 1, math.huge
|
||
for i = 1, NUM_POCKETS do
|
||
local sa = ((i - 1) * TWO_PI / NUM_POCKETS) % TWO_PI
|
||
local diff = math.abs(sa - relAngle)
|
||
if diff > math.pi then diff = TWO_PI - diff end
|
||
if diff < bestDist then bestDist = diff; bestSlot = i end
|
||
end
|
||
|
||
-- Snap ball to pocket centre
|
||
local snapAngle = rotorAngle + (bestSlot - 1) * TWO_PI / NUM_POCKETS
|
||
local sx, sy = ballPosAt(R_SETTLE, snapAngle)
|
||
eraseBall(ballX, ballY, BALL_RADIUS)
|
||
drawBall(sx, sy)
|
||
gpu.sync()
|
||
|
||
return WHEEL_ORDER[bestSlot], bestSlot
|
||
end
|
||
|
||
----------------------------------------------------------------------
|
||
-- Glow animation
|
||
----------------------------------------------------------------------
|
||
|
||
local function glowAnimation(slotIdx)
|
||
local R_SETTLE = (R_POCKET_IN + R_POCKET_OUT) / 2
|
||
local sa = rotorAngle + (slotIdx - 1) * TWO_PI / NUM_POCKETS
|
||
local bx, by = ballPosAt(R_SETTLE, sa)
|
||
for flash = 1, 6 do
|
||
drawWedge(slotIdx, rotorAngle, flash % 2 == 1)
|
||
drawBall(bx, by)
|
||
gpu.sync()
|
||
sleep(0.18)
|
||
end
|
||
drawWedge(slotIdx, rotorAngle, true)
|
||
drawBall(bx, by)
|
||
gpu.sync()
|
||
end
|
||
|
||
----------------------------------------------------------------------
|
||
-- Redstone helper
|
||
----------------------------------------------------------------------
|
||
|
||
local function waitForRedstonePulse()
|
||
while true do
|
||
os.pullEvent("redstone")
|
||
for _, side in ipairs(redstone.getSides()) do
|
||
if redstone.getInput(side) then return end
|
||
end
|
||
end
|
||
end
|
||
|
||
----------------------------------------------------------------------
|
||
-- Lifecycle
|
||
----------------------------------------------------------------------
|
||
|
||
local function start()
|
||
math.randomseed(os.epoch("utc"))
|
||
|
||
gpu = findGPU()
|
||
if not gpu then error("No GPU peripheral found.") end
|
||
|
||
gpu.refreshSize()
|
||
sleep(0)
|
||
gpu.setSize(64)
|
||
|
||
PW, PH = gpu.getSize()
|
||
print(("[roulette] GPU: %dx%d px"):format(PW, PH))
|
||
if not PW or PW < 128 or PH < 128 then
|
||
error(("GPU pixel size %dx%d too small."):format(PW or 0, PH or 0))
|
||
end
|
||
|
||
CX = math.floor(PW / 2)
|
||
CY = math.floor(PH / 2)
|
||
local R_MAX = math.floor(math.min(PW, PH) / 2) - 4
|
||
R_OUTER = R_MAX
|
||
R_POCKET_OUT = math.floor(R_MAX * 0.82)
|
||
R_POCKET_IN = math.floor(R_MAX * 0.58)
|
||
R_HUB = math.floor(R_MAX * 0.38)
|
||
|
||
gpu.fill(COL_BG)
|
||
drawWheelFull(rotorAngle, nil)
|
||
drawCenterText({ "ROULETTE", "Pull lever" })
|
||
end
|
||
|
||
local function stop()
|
||
if gpu then gpu.fill(COL_BG); gpu.sync() end
|
||
end
|
||
|
||
local function main()
|
||
while true do
|
||
waitForRedstonePulse()
|
||
|
||
drawCenterText({ "SPINNING..." })
|
||
sleep(0.2)
|
||
|
||
local num, slotIdx = spin()
|
||
|
||
glowAnimation(slotIdx)
|
||
|
||
local name = "GREEN"
|
||
if num ~= 0 then
|
||
name = RED_SET[num] and "RED" or "BLACK"
|
||
end
|
||
drawCenterText({ "WINNER!", name, tostring(num) })
|
||
|
||
sleep(5)
|
||
|
||
drawWheelFull(rotorAngle, nil)
|
||
lastDrawnRotor = rotorAngle
|
||
drawCenterText({ "ROULETTE", "Pull lever" })
|
||
end
|
||
end
|
||
|
||
return { start = start, stop = stop, main = main }
|