This commit is contained in:
2026-05-05 17:41:22 -04:00
parent 57f83bd56b
commit 875edb891a
4 changed files with 254 additions and 13 deletions

View File

@@ -1,5 +1,5 @@
local diskDrive = peripheral.find("drive") local diskDrive = peripheral.find("drive")
local monitor = require("monitor-driver")
local function getFilePath() local function getFilePath()
if diskDrive and diskDrive.isDiskPresent() then if diskDrive and diskDrive.isDiskPresent() then
local path = diskDrive.getMountPath() local path = diskDrive.getMountPath()
@@ -75,9 +75,10 @@ local function writeValue(key, value)
end end
local function initialize() local function initialize()
monitor.writeLine("Initializing Data Storage...") local monitor = peripheral.find("monitor")
if monitor then monitor.write("Initializing Data Storage...") end
if diskDrive and diskDrive.isDiskPresent() then if diskDrive and diskDrive.isDiskPresent() then
monitor.writeLine("Disk detected. Initializing data storage.") if monitor then monitor.write("Disk detected. Initializing data storage.") end
local path = getFilePath() local path = getFilePath()
if path and not fs.exists(path) then if path and not fs.exists(path) then
saveData({}) saveData({})

View File

@@ -55,6 +55,7 @@ addDriver("task-manager")
addDriver("monitor-driver") addDriver("monitor-driver")
addDriver("disk-driver") addDriver("disk-driver")
addDriver("speaker-driver") addDriver("speaker-driver")
addSound("notification")
return { addDriver = addDriver, addFolderDriver = addFolderDriver, addProgram = addProgram, addSound = addSound, addServerHandler = addServerHandler } return { addDriver = addDriver, addFolderDriver = addFolderDriver, addProgram = addProgram, addSound = addSound, addServerHandler = addServerHandler }

238
programs/roulette.lua Normal file
View File

@@ -0,0 +1,238 @@
-- Roulette Machine
-- Listens for a redstone signal on any side, spins a "ball" around the
-- perimeter of every connected monitor, and lands on a random pocket.
--
-- Setup:
-- * 1 advanced computer
-- * 1+ monitors (vanilla or Tom's wired together as a single logical monitor)
-- * Redstone input on any side to start a spin
local mon -- the active monitor peripheral
local W, H -- monitor character size
local perimeter -- ordered list of {x, y, color} cells around the edge
local ballIndex -- current position in perimeter
local lastBallIndex -- previous position (so we can repaint the pocket)
local SPIN_MIN_TIME = 6 -- seconds
local SPIN_MAX_TIME = 12
local START_DELAY = 0.03 -- seconds between ball moves at full speed
local END_DELAY = 0.45 -- seconds between ball moves just before stopping
local BALL_COLOR = colors.white
local ZERO_COLOR = colors.green
local TEXT_COLOR = colors.white
----------------------------------------------------------------------
-- Monitor helpers
----------------------------------------------------------------------
local function pickMonitor()
-- peripheral.find returns the first match; with Tom's Peripherals
-- a multi-monitor block exposes itself as a single "monitor".
mon = peripheral.find("monitor")
if not mon then
error("No monitor attached / found on the network")
end
mon.setTextScale(0.5)
W, H = mon.getSize()
end
local function setPixel(x, y, bg)
mon.setBackgroundColor(bg)
mon.setCursorPos(x, y)
mon.write(" ")
end
local function setLabel(x, y, bg, fg, ch)
mon.setBackgroundColor(bg)
mon.setTextColor(fg)
mon.setCursorPos(x, y)
mon.write(ch)
end
----------------------------------------------------------------------
-- Build the wheel perimeter
----------------------------------------------------------------------
-- Walks the edge clockwise starting at top-left, returning a list of
-- {x = , y = , color = , label = } pockets. Colors alternate red/black
-- with a single green "0" pocket at the start.
local function buildPerimeter()
perimeter = {}
local function add(x, y)
table.insert(perimeter, { x = x, y = y })
end
-- top edge: left -> right
for x = 1, W do add(x, 1) end
-- right edge: top+1 -> bottom
for y = 2, H do add(W, y) end
-- bottom edge: right-1 -> left
for x = W - 1, 1, -1 do add(x, H) end
-- left edge: bottom-1 -> top+1
for y = H - 1, 2, -1 do add(1, y) end
-- Assign colors: 0 = green, then alternating red/black around the wheel.
for i, cell in ipairs(perimeter) do
if i == 1 then
cell.color = ZERO_COLOR
cell.label = "0"
else
cell.color = (i % 2 == 0) and colors.red or colors.black
cell.label = tostring(i - 1)
end
end
end
----------------------------------------------------------------------
-- Drawing
----------------------------------------------------------------------
local function drawWheel()
mon.setBackgroundColor(colors.black)
mon.clear()
for _, cell in ipairs(perimeter) do
setPixel(cell.x, cell.y, cell.color)
end
end
local function drawCenter(lines)
-- Clear interior
mon.setBackgroundColor(colors.black)
for y = 2, H - 1 do
for x = 2, W - 1 do
setPixel(x, y, colors.black)
end
end
mon.setTextColor(TEXT_COLOR)
local startY = math.floor(H / 2) - math.floor(#lines / 2)
for i, line in ipairs(lines) do
local x = math.max(2, math.floor((W - #line) / 2) + 1)
mon.setCursorPos(x, startY + i - 1)
mon.setBackgroundColor(colors.black)
mon.write(line)
end
end
local function repaintPocket(idx)
local c = perimeter[idx]
setPixel(c.x, c.y, c.color)
end
local function drawBall(idx)
local c = perimeter[idx]
setPixel(c.x, c.y, BALL_COLOR)
end
----------------------------------------------------------------------
-- Spin logic
----------------------------------------------------------------------
local function moveBall(newIdx)
if lastBallIndex then repaintPocket(lastBallIndex) end
drawBall(newIdx)
lastBallIndex = newIdx
ballIndex = newIdx
end
-- ease-out cubic from 0..1
local function easeOut(t)
local inv = 1 - t
return 1 - inv * inv * inv
end
local function spin()
local n = #perimeter
local spinTime = SPIN_MIN_TIME + math.random() * (SPIN_MAX_TIME - SPIN_MIN_TIME)
local elapsed = 0
drawCenter({ "SPINNING..." })
-- Make sure the centered text doesn't hide the ball trail; redraw shortly
sleep(0.6)
drawCenter({ "" })
-- During the fast portion the ball jumps several pockets per tick to look frantic.
while elapsed < spinTime do
local t = elapsed / spinTime -- 0 .. 1
local eased = easeOut(t) -- 0 .. 1, slows over time
local delay = START_DELAY + (END_DELAY - START_DELAY) * eased
local jump = math.max(1, math.floor((1 - eased) * 4 + 0.5))
local nextIdx = ((ballIndex - 1 + jump) % n) + 1
moveBall(nextIdx)
sleep(delay)
elapsed = elapsed + delay
end
-- Final settle: a few slow ticks then stop on a uniformly random pocket.
local finalIdx = math.random(1, n)
-- Walk forward to the final pocket so the stop looks natural.
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)
sleep(END_DELAY + i * 0.04)
end
return perimeter[finalIdx]
end
local function announce(pocket)
local colorName =
(pocket.color == colors.red) and "RED" or
(pocket.color == colors.green) and "GREEN" or "BLACK"
drawCenter({
"WINNER",
colorName .. " " .. pocket.label,
})
end
----------------------------------------------------------------------
-- Lifecycle
----------------------------------------------------------------------
local function start()
math.randomseed(os.epoch("utc"))
pickMonitor()
buildPerimeter()
ballIndex = 1
lastBallIndex = nil
drawWheel()
drawBall(ballIndex)
drawCenter({ "ROULETTE", "Pull lever to spin" })
end
local function stop()
if mon then
mon.setBackgroundColor(colors.black)
mon.setTextColor(colors.white)
mon.clear()
mon.setCursorPos(1, 1)
end
end
local function waitForRedstonePulse()
-- Wait for a rising edge on any side.
while true do
os.pullEvent("redstone")
for _, side in ipairs(redstone.getSides()) do
if redstone.getInput(side) then
return side
end
end
end
end
local function main()
while true do
waitForRedstonePulse()
local pocket = spin()
announce(pocket)
-- Small cooldown so a held lever doesn't immediately re-trigger.
sleep(3)
-- Drain any redstone events during cooldown by redrawing idle screen.
drawCenter({ "ROULETTE", "Pull lever to spin" })
end
end
return { start = start, stop = stop, main = main }

View File

@@ -1,15 +1,11 @@
local speaker = peripheral.find("speaker") local speaker = peripheral.find("speaker")
local dfpwm = require("cc.audio.dfpwm") local dfpwm = require("cc.audio.dfpwm")
local encoder = dfpwm.make_encoder()
local decoder = dfpwm.make_decoder()
local sounds = { local sounds = {
{name = "notification", file = "notification.dfpwm"} {name = "notification", file = "notification.dfpwm"}
} }
local function initialize() local function initialize()
kernel = require("kernel")
kernel.addSound("notification")
speaker = peripheral.find("speaker") speaker = peripheral.find("speaker")
if not speaker then if not speaker then
print("Warning: No speaker attached.") print("Warning: No speaker attached.")
@@ -39,7 +35,6 @@ local function play(name)
return false return false
end end
-- Create a new decoder for this playback to reset state
local decoder = dfpwm.make_decoder() local decoder = dfpwm.make_decoder()
for chunk in io.lines(filePath, 16 * 1024) do for chunk in io.lines(filePath, 16 * 1024) do
@@ -50,7 +45,6 @@ local function play(name)
end end
return true return true
end end
----------------
local function getFileName(name) local function getFileName(name)
local extension = ".dfpwm" local extension = ".dfpwm"
@@ -68,6 +62,7 @@ end
local function playSound(fileName) local function playSound(fileName)
local fileStream = getFileName(fileName) local fileStream = getFileName(fileName)
local decoder = dfpwm.make_decoder()
local values = io.lines(fileStream, 16 * 1024) local values = io.lines(fileStream, 16 * 1024)
for input in values do for input in values do
local decoded = decoder(input) local decoded = decoder(input)
@@ -77,20 +72,26 @@ local function playSound(fileName)
end end
end end
local function playTTSFile(value) local function playTTSFile(value)
local message = textutils.urlEncode(value) local message = textutils.urlEncode(value)
local url = "http://api.astrocore.space/api/TextToSpeech?message=" .. message local url = "http://api.astrocore.space/api/TextToSpeech?message=" .. message
local response, err = http.get { url = url, binary = true } local response, err = http.get { url = url, binary = true }
if not response then
print("TTS request failed: " .. (err or "unknown error"))
return false
end
local name = randomFileName() local name = randomFileName()
local fileName = name .. ".dfpwm" local fileName = name .. ".dfpwm"
local fileData = response.readAll() local fileData = response.readAll()
local file = fs.open(fileName,"w") response.close()
local file = fs.open(fileName, "wb")
file.write(fileData) file.write(fileData)
file.close() file.close()
response.close()
playSound(name) playSound(name)
shell.execute("rm", fileName) shell.execute("rm", fileName)
end end