-- Plinko Turtle Casino -- ComputerCraft / CC:Tweaked style program -- Designed for Advanced Monitor wall, 3 tall x 7 wide -- Uses a turtle as the diamond bank/payout machine -- Uses speaker if available -------------------------------------------------- -- CONFIG -------------------------------------------------- local DIAMOND_ID = "minecraft:diamond" local TEXT_SCALE = 0.5 local MIN_BET = 1 local MAX_BET_AMOUNT = 999 local CASINO_MENU_PROGRAM = "casino" local CASINO_BANK_FILE = "casino_bank_base.txt" local PLINKO_ROWS = 8 local DROP_DELAY = 0.28 -- Total payout multipliers. -- Example: bet 10 on 2x pays 20 total. -- 0x loses. local SLOT_MULTIPLIERS = { 5, 2, 1, 1, 0, 1, 1, 2, 5 } -------------------------------------------------- -- PERIPHERALS -------------------------------------------------- local mon = peripheral.find("monitor") if not mon then error("No monitor found. Attach/wrap an advanced monitor wall.") end local speaker = nil for _, name in ipairs(peripheral.getNames()) do local p = peripheral.wrap(name) if p then if type(p.playNote) == "function" or type(p.playSound) == "function" then speaker = p break end end end mon.setTextScale(TEXT_SCALE) mon.setBackgroundColor(colors.green) mon.setTextColor(colors.white) mon.clear() local W, H = mon.getSize() -------------------------------------------------- -- UTILS -------------------------------------------------- local function clamp(v, a, b) if v < a then return a end if v > b then return b end return v end local function writeAt(x, y, text, fg, bg) if bg then mon.setBackgroundColor(bg) end if fg then mon.setTextColor(fg) end mon.setCursorPos(x, y) mon.write(text) end local function fillBox(x, y, w, h, bg) if w <= 0 or h <= 0 then return end mon.setBackgroundColor(bg) for yy = y, y + h - 1 do if yy >= 1 and yy <= H then mon.setCursorPos(x, yy) mon.write(string.rep(" ", math.max(0, math.min(w, W - x + 1)))) end end end local function centerText(x, y, w, text, fg, bg) text = tostring(text) if #text > w then text = string.sub(text, 1, w) end local pad = math.floor((w - #text) / 2) writeAt(x + pad, y, text, fg, bg) end local function drawButton(x, y, w, h, label, bg, fg) fillBox(x, y, w, h, bg) local cy = y + math.floor(h / 2) centerText(x, cy, w, label, fg or colors.white, bg) end local function inBox(px, py, box) return px >= box.x and px < box.x + box.w and py >= box.y and py < box.y + box.h end local function money(n) return tostring(math.floor(n or 0)) end local function formatMultiplier(m) if m == 0.5 then return "0.5x" end return tostring(m) .. "x" end -------------------------------------------------- -- DIAMOND BANKING -------------------------------------------------- local function countDiamonds() local total = 0 for slot = 1, 16 do local d = turtle.getItemDetail(slot) if d and d.name == DIAMOND_ID then total = total + d.count end end return total end local function selectDiamondSlot() for slot = 1, 16 do local d = turtle.getItemDetail(slot) if d and d.name == DIAMOND_ID and d.count > 0 then turtle.select(slot) return true end end return false end local function pullDiamondsFromAboveUntil(amountNeeded) local tries = 0 while countDiamonds() < amountNeeded and tries < 64 do tries = tries + 1 local pulled = turtle.suckUp(64) if not pulled then break end sleep(0.05) end return countDiamonds() >= amountNeeded end local function dropDiamondsDown(amount) local remaining = amount while remaining > 0 do if not selectDiamondSlot() then return false, amount - remaining end local d = turtle.getItemDetail() if not d or d.name ~= DIAMOND_ID then return false, amount - remaining end local toDrop = math.min(remaining, d.count) local ok = turtle.dropDown(toDrop) if not ok then return false, amount - remaining end remaining = remaining - toDrop sleep(0.03) end return true, amount end local function loadBankBase() if fs.exists(CASINO_BANK_FILE) then local f = fs.open(CASINO_BANK_FILE, "r") if f then local raw = f.readAll() f.close() local n = tonumber(raw) if n and n >= 0 then return n end end end return countDiamonds() end local bankBase = loadBankBase() local function saveBankBase() local f = fs.open(CASINO_BANK_FILE, "w") if f then f.write(tostring(math.floor(bankBase))) f.close() end end saveBankBase() -------------------------------------------------- -- SOUND -------------------------------------------------- local function playNoteSafe(instrument, volume, pitch) if not speaker then return end instrument = instrument or "harp" volume = volume or 1 pitch = pitch or 12 if type(speaker.playNote) == "function" then local ok = pcall(function() speaker.playNote(instrument, volume, pitch) end) if ok then return end end if type(speaker.playSound) == "function" then pcall(function() speaker.playSound("note.harp", volume, 1) end) end end local function soundClick() playNoteSafe("hat", 0.8, 12) end local function soundTick() playNoteSafe("hat", 0.7, math.random(8, 18)) end local function soundWin() playNoteSafe("bell", 1, 16) sleep(0.08) playNoteSafe("bell", 1, 20) sleep(0.08) playNoteSafe("bell", 1, 24) end local function soundBigWin() playNoteSafe("bell", 1, 16) sleep(0.06) playNoteSafe("bell", 1, 20) sleep(0.06) playNoteSafe("bell", 1, 24) sleep(0.06) playNoteSafe("bell", 1, 28) end local function soundLoss() playNoteSafe("bass", 1, 6) sleep(0.12) playNoteSafe("bass", 1, 4) end -------------------------------------------------- -- GAME STATE -------------------------------------------------- math.randomseed(os.epoch and os.epoch("utc") or os.clock() * 100000) local hitBoxes = {} local betAmount = 1 local currentBet = 0 local dropping = false local chipRow = 0 local chipSlot = 5 local lastSlot = nil local lastMultiplier = nil local lastPayout = nil local message = "Set bet amount, then press DROP CHIP." local confirmMode = nil local drawAll local function availableCredits() local current = countDiamonds() local credits = current - bankBase - currentBet if credits < 0 then credits = 0 end return credits end -------------------------------------------------- -- BET CONTROLS -------------------------------------------------- local function changeBetAmount(delta) if dropping or confirmMode then message = "Cannot change bet while chip is dropping." soundLoss() return end betAmount = betAmount + delta betAmount = clamp(betAmount, MIN_BET, MAX_BET_AMOUNT) message = "Bet amount set to " .. betAmount .. " diamond(s)." soundClick() end local function resetBetAmount() if dropping or confirmMode then message = "Cannot reset bet while chip is dropping." soundLoss() return end betAmount = 1 message = "Bet amount reset to 1 diamond." soundClick() end -------------------------------------------------- -- CASH OUT / MENU -------------------------------------------------- local function resetRoundForExitOrCashout() currentBet = 0 dropping = false chipRow = 0 chipSlot = 5 end local function cashOutCredits() if dropping then message = "Cannot cash out while chip is dropping." soundLoss() return end local amount = availableCredits() if amount <= 0 then message = "No unbet credits to cash out." soundLoss() return end local ok, dropped = dropDiamondsDown(amount) if ok then message = "Cashed out " .. dropped .. " diamond(s) below." soundWin() else message = "Cash out blocked! Dropped " .. dropped .. "/" .. amount .. "." soundLoss() end saveBankBase() end local function requestCashOut() if dropping or currentBet > 0 then confirmMode = "cashout" message = "Confirm cash out. Active drop/bet will be cleared." soundLoss() return end cashOutCredits() end local function exitToMenuNow() resetRoundForExitOrCashout() confirmMode = nil saveBankBase() mon.setBackgroundColor(colors.black) mon.setTextColor(colors.white) mon.clear() if shell then if fs.exists(CASINO_MENU_PROGRAM) then shell.run(CASINO_MENU_PROGRAM) elseif fs.exists(CASINO_MENU_PROGRAM .. ".lua") then shell.run(CASINO_MENU_PROGRAM .. ".lua") else print("Casino menu not found.") print("Expected: " .. CASINO_MENU_PROGRAM) end end error("Exited to casino menu", 0) end local function requestExit() if dropping or currentBet > 0 then confirmMode = "exit" message = "Confirm menu. Active drop/bet will be cleared." soundLoss() return end exitToMenuNow() end -------------------------------------------------- -- PAYOUT -------------------------------------------------- local function payPlayer(amount, outcomeMessage, soundFunc) local unbetCredits = availableCredits() if amount <= 0 then message = outcomeMessage or "No payout." if soundFunc then soundFunc() end return end message = outcomeMessage .. " Paying " .. amount .. " diamonds." if soundFunc then soundFunc() end local enough = pullDiamondsFromAboveUntil(amount) if enough then local ok, dropped = dropDiamondsDown(amount) if ok then message = outcomeMessage .. " Paid " .. dropped .. " diamonds below." else message = "Payout blocked! Dropped " .. dropped .. "/" .. amount end else local available = countDiamonds() local _, dropped = dropDiamondsDown(available) message = "HOUSE EMPTY! Paid only " .. dropped .. "/" .. amount .. "." end local after = countDiamonds() bankBase = after - unbetCredits if bankBase < 0 then bankBase = 0 end saveBankBase() end local function houseKeepsBet() bankBase = bankBase + currentBet saveBankBase() end -------------------------------------------------- -- PLINKO LOGIC -------------------------------------------------- local function calculatePayout(bet, multiplier) return math.floor(bet * multiplier) end local function startDrop() if dropping then return end if betAmount < MIN_BET then message = "Bet amount must be at least " .. MIN_BET .. "." soundLoss() return end if availableCredits() < betAmount then message = "Not enough credits. Need " .. betAmount .. " diamond(s)." soundLoss() return end currentBet = betAmount dropping = true chipRow = 0 chipSlot = 5 lastSlot = nil lastMultiplier = nil lastPayout = nil message = "Dropping chip..." drawAll() sleep(0.2) for row = 1, PLINKO_ROWS do chipRow = row local moveRight = math.random(0, 1) == 1 if moveRight then chipSlot = chipSlot + 1 else chipSlot = chipSlot - 1 end chipSlot = clamp(chipSlot, 1, #SLOT_MULTIPLIERS) soundTick() drawAll() sleep(DROP_DELAY) end dropping = false local multiplier = SLOT_MULTIPLIERS[chipSlot] or 0 local payout = calculatePayout(currentBet, multiplier) lastSlot = chipSlot lastMultiplier = multiplier lastPayout = payout if payout > 0 then local msg = "Landed on " .. formatMultiplier(multiplier) .. "!" if multiplier >= 5 then payPlayer(payout, msg, soundBigWin) else payPlayer(payout, msg, soundWin) end else houseKeepsBet() message = "Landed on 0x. You lose." soundLoss() end currentBet = 0 drawAll() end -------------------------------------------------- -- DRAWING -------------------------------------------------- local function resetHitBoxes() hitBoxes = {} end local function addHit(id, x, y, w, h) table.insert(hitBoxes, { id = id, x = x, y = y, w = w, h = h }) end local function drawFrame() fillBox(1, 1, W, H, colors.green) fillBox(1, 1, W, 1, colors.black) centerText(1, 1, W, "PLINKO | DIAMOND CASINO", colors.lime, colors.black) end local function drawLeftInfoPanel() local x = 2 local y = 3 local w = 38 local h = 15 fillBox(x, y, w, h, colors.black) centerText(x, y, w, "GAME INFO", colors.yellow, colors.black) writeAt(x + 2, y + 2, "Credits: " .. money(availableCredits()), colors.lime, colors.black) writeAt(x + 2, y + 3, "Current Bet: " .. money(currentBet), colors.yellow, colors.black) writeAt(x + 2, y + 4, "Bet Amount: " .. money(betAmount), colors.orange, colors.black) writeAt(x + 2, y + 5, "Diamonds In Turtle: " .. money(countDiamonds()), colors.white, colors.black) writeAt(x + 2, y + 6, "House Bank Base: " .. money(bankBase), colors.lightGray, colors.black) if lastMultiplier then writeAt(x + 2, y + 8, "Last Slot: " .. tostring(lastSlot), colors.lightGray, colors.black) writeAt(x + 2, y + 9, "Last Multi: " .. formatMultiplier(lastMultiplier), colors.lightGray, colors.black) writeAt(x + 2, y + 10, "Last Payout: " .. money(lastPayout), colors.lightGray, colors.black) else writeAt(x + 2, y + 8, "Last Slot: --", colors.lightGray, colors.black) writeAt(x + 2, y + 9, "Last Multi: --", colors.lightGray, colors.black) writeAt(x + 2, y + 10, "Last Payout: --", colors.lightGray, colors.black) end fillBox(x + 1, y + 12, w - 2, 2, colors.gray) writeAt(x + 2, y + 12, string.sub(message, 1, w - 4), colors.white, colors.gray) end local function boardBounds() local leftEdge = 43 local rightEdge = W - 2 if leftEdge > rightEdge then leftEdge = 2 rightEdge = W - 2 end return leftEdge, rightEdge, rightEdge - leftEdge + 1 end local function slotColor(multiplier) if multiplier == 0 then return colors.red, colors.white elseif multiplier == 1 then return colors.gray, colors.white elseif multiplier == 2 then return colors.yellow, colors.black elseif multiplier == 5 then return colors.lime, colors.black else return colors.orange, colors.black end end local function drawPlinkoBoard() local left, _, areaW = boardBounds() local boardW = math.min(areaW, 88) local boardX = left + math.floor((areaW - boardW) / 2) local boardY = 4 local boardH = H - 18 if boardH < 18 then boardH = 18 end fillBox(boardX, boardY, boardW, boardH, colors.black) centerText(boardX, boardY, boardW, "DROP ZONE", colors.yellow, colors.black) local centerX = boardX + math.floor(boardW / 2) local pegStartY = boardY + 3 local rowGapY = 2 local pegGapX = 6 for row = 1, PLINKO_ROWS do local pegs = row + 1 local totalPegW = (pegs - 1) * pegGapX local startX = centerX - math.floor(totalPegW / 2) local y = pegStartY + (row - 1) * rowGapY for i = 1, pegs do local x = startX + (i - 1) * pegGapX writeAt(x, y, "*", colors.lightGray, colors.black) end end if dropping then local y = pegStartY + math.max(0, chipRow - 1) * rowGapY - 1 local slotCount = #SLOT_MULTIPLIERS local slotAreaW = math.min(boardW - 4, slotCount * 9) local slotStartX = boardX + math.floor((boardW - slotAreaW) / 2) local slotW = math.floor(slotAreaW / slotCount) local x = slotStartX + (chipSlot - 1) * slotW + math.floor(slotW / 2) writeAt(x, y, "O", colors.yellow, colors.black) end local slotCount = #SLOT_MULTIPLIERS local slotAreaW = math.min(boardW - 4, slotCount * 9) local slotStartX = boardX + math.floor((boardW - slotAreaW) / 2) local slotW = math.floor(slotAreaW / slotCount) local slotY = boardY + boardH - 4 centerText(boardX, slotY - 2, boardW, "MULTIPLIERS", colors.white, colors.black) for i, mult in ipairs(SLOT_MULTIPLIERS) do local x = slotStartX + (i - 1) * slotW local bg, fg = slotColor(mult) if lastSlot == i then bg = colors.purple fg = colors.white end drawButton(x, slotY, slotW - 1, 3, formatMultiplier(mult), bg, fg) end end local function drawActionRow() local y = H - 13 local h = 3 local gap = 2 local btnW = 22 local buttons = { {"DROP", "DROP CHIP", colors.lime, colors.black}, {"EXIT_MENU", "MENU", colors.gray, colors.white} } local totalW = #buttons * btnW + (#buttons - 1) * gap local x = math.floor((W - totalW) / 2) for i, b in ipairs(buttons) do local id, label, bg, fg = b[1], b[2], b[3], b[4] local bx = x + (i - 1) * (btnW + gap) drawButton(bx, y, btnW, h, label, bg, fg) addHit(id, bx, y, btnW, h) end end local function drawBottomBetControls() local panelW = math.min(W - 6, 124) local x = math.floor((W - panelW) / 2) local y = H - 9 local panelH = 8 fillBox(x, y, panelW, panelH, colors.black) centerText(x, y, panelW, "BET CONTROLS | CURRENT BET AMOUNT: " .. betAmount, colors.yellow, colors.black) local gap = 1 local smallW = math.floor((panelW - 4 - gap * 2) / 3) drawButton(x + 1, y + 1, smallW, 2, "+1", colors.lime, colors.black) addHit("BETAMT:+1", x + 1, y + 1, smallW, 2) drawButton(x + 1 + smallW + gap, y + 1, smallW, 2, "+5", colors.lime, colors.black) addHit("BETAMT:+5", x + 1 + smallW + gap, y + 1, smallW, 2) drawButton(x + 1 + (smallW + gap) * 2, y + 1, smallW, 2, "+10", colors.lime, colors.black) addHit("BETAMT:+10", x + 1 + (smallW + gap) * 2, y + 1, smallW, 2) drawButton(x + 1, y + 3, smallW, 2, "-1", colors.orange, colors.black) addHit("BETAMT:-1", x + 1, y + 3, smallW, 2) drawButton(x + 1 + smallW + gap, y + 3, smallW, 2, "-5", colors.orange, colors.black) addHit("BETAMT:-5", x + 1 + smallW + gap, y + 3, smallW, 2) drawButton(x + 1 + (smallW + gap) * 2, y + 3, smallW, 2, "-10", colors.orange, colors.black) addHit("BETAMT:-10", x + 1 + (smallW + gap) * 2, y + 3, smallW, 2) local halfW = math.floor((panelW - 3) / 2) drawButton(x + 1, y + 5, halfW, 3, "RESET AMT", colors.gray, colors.white) addHit("BETAMT:RESET", x + 1, y + 5, halfW, 3) drawButton(x + 2 + halfW, y + 5, halfW, 3, "CASH OUT", colors.yellow, colors.black) addHit("ACTION:CASHOUT", x + 2 + halfW, y + 5, halfW, 3) end local function drawConfirmScreen() resetHitBoxes() fillBox(1, 1, W, H, colors.black) if confirmMode == "exit" then centerText(1, 4, W, "CONFIRM RETURN TO GAME MENU", colors.yellow, colors.black) if dropping then centerText(1, 7, W, "A chip is currently dropping.", colors.white, colors.black) centerText(1, 9, W, "Leaving will cancel the drop and clear the bet back to credits.", colors.lightGray, colors.black) elseif currentBet > 0 then centerText(1, 7, W, "You have a " .. currentBet .. " diamond bet staged.", colors.white, colors.black) centerText(1, 9, W, "Leaving will clear that bet back into your credits.", colors.lightGray, colors.black) end centerText(1, 11, W, "No diamonds will be paid out or removed.", colors.lightGray, colors.black) elseif confirmMode == "cashout" then centerText(1, 4, W, "CONFIRM CASH OUT", colors.yellow, colors.black) if dropping then centerText(1, 7, W, "A chip is currently dropping.", colors.white, colors.black) centerText(1, 9, W, "Cash out will cancel the drop and clear the bet back to credits.", colors.lightGray, colors.black) elseif currentBet > 0 then centerText(1, 7, W, "You have a " .. currentBet .. " diamond bet staged.", colors.white, colors.black) centerText(1, 9, W, "Cash out will clear the bet back into credits first.", colors.lightGray, colors.black) end centerText(1, 11, W, "Then all player credits will drop below the turtle.", colors.lightGray, colors.black) end local btnW = 32 local btnH = 5 local gap = 6 local totalW = btnW * 2 + gap local startX = math.floor((W - totalW) / 2) local y = 16 drawButton(startX, y, btnW, btnH, "CANCEL", colors.lime, colors.black) addHit("CONFIRM_CANCEL", startX, y, btnW, btnH) if confirmMode == "exit" then drawButton(startX + btnW + gap, y, btnW, btnH, "MENU - CLEAR BET", colors.red, colors.white) addHit("CONFIRM_YES", startX + btnW + gap, y, btnW, btnH) elseif confirmMode == "cashout" then drawButton(startX + btnW + gap, y, btnW, btnH, "CASH OUT ALL", colors.yellow, colors.black) addHit("CONFIRM_YES", startX + btnW + gap, y, btnW, btnH) end end drawAll = function() if confirmMode then drawConfirmScreen() return end resetHitBoxes() drawFrame() drawLeftInfoPanel() drawPlinkoBoard() drawActionRow() drawBottomBetControls() end -------------------------------------------------- -- INPUT -------------------------------------------------- local function handleConfirmTouch(x, y) for _, box in ipairs(hitBoxes) do if inBox(x, y, box) then if box.id == "CONFIRM_CANCEL" then confirmMode = nil message = "Action cancelled." soundClick() drawAll() return true elseif box.id == "CONFIRM_YES" then if confirmMode == "exit" then resetRoundForExitOrCashout() confirmMode = nil saveBankBase() exitToMenuNow() return true elseif confirmMode == "cashout" then resetRoundForExitOrCashout() confirmMode = nil saveBankBase() cashOutCredits() drawAll() return true end end end end return false end local function handleTouch(x, y) if confirmMode then handleConfirmTouch(x, y) return end if dropping then return end for _, box in ipairs(hitBoxes) do if inBox(x, y, box) then local id = box.id if id == "BETAMT:+1" then changeBetAmount(1) elseif id == "BETAMT:+5" then changeBetAmount(5) elseif id == "BETAMT:+10" then changeBetAmount(10) elseif id == "BETAMT:-1" then changeBetAmount(-1) elseif id == "BETAMT:-5" then changeBetAmount(-5) elseif id == "BETAMT:-10" then changeBetAmount(-10) elseif id == "BETAMT:RESET" then resetBetAmount() elseif id == "DROP" then startDrop() elseif id == "ACTION:CASHOUT" then requestCashOut() elseif id == "EXIT_MENU" then requestExit() end drawAll() return end end end -------------------------------------------------- -- STARTUP -------------------------------------------------- drawAll() while true do local e, side, x, y = os.pullEvent() if e == "monitor_touch" then handleTouch(x, y) elseif e == "peripheral" or e == "peripheral_detach" then drawAll() end end