updated
This commit is contained in:
@@ -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({})
|
||||||
|
|||||||
@@ -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
238
programs/roulette.lua
Normal 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 }
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user