Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Modern Blackjack (modern_blackjack.lua)
- -- Features: Enhanced Card Graphics, Green Felt UI, Sound Effects, SPLITTING
- local modem = peripheral.find("modem")
- if not modem then error("No modem found.") end
- local speaker = peripheral.find("speaker")
- local function playSound(name, vol, pit) if speaker then speaker.playSound(name, vol or 1, pit or 1) end end
- modem.open(1)
- -- ==========================================
- -- CRYPTO & SERVER COMM
- -- ==========================================
- local function encrypt(text)
- local result = ""
- for i = 1, #text do result = result .. string.char((string.byte(text, i) + 5) % 256) end
- return result
- end
- local function decrypt(text)
- local result = ""
- for i = 1, #text do result = result .. string.char((string.byte(text, i) - 5) % 256) end
- return result
- end
- local function sendRequest(req)
- -- Retry up to 3 times for network reliability
- for attempt = 1, 3 do
- modem.transmit(1, 1, req)
- local t = os.startTimer(5)
- while true do
- local e, p1, _, _, m = os.pullEvent()
- if e == "modem_message" and type(m) == "table" then return m
- elseif e == "timer" and p1 == t then break end
- end
- -- Retry after timeout
- if attempt < 3 then sleep(0.5) end
- end
- return nil -- All retries failed
- end
- -- ==========================================
- -- ADMIN & LOGIN
- -- ==========================================
- local function saveAdmin(id, pw)
- local f = fs.open(".admin_cred", "w")
- f.write(encrypt(id..":"..pw))
- f.close()
- end
- local function loadAdmin()
- if fs.exists(".admin_cred") then
- local f = fs.open(".admin_cred", "r")
- local c = decrypt(f.readAll())
- f.close()
- return c:match("(.+):(.+)")
- end
- return nil, nil
- end
- local function centerText(y, t, bg, fg)
- local w = term.getSize()
- local x = math.floor((w-#t)/2)+1
- term.setCursorPos(x,y)
- if bg then term.setBackgroundColor(bg) end
- if fg then term.setTextColor(fg) end
- term.write(t)
- end
- local function adminLogin()
- term.setBackgroundColor(colors.black)
- term.clear()
- centerText(2, "BLACKJACK ADMIN SETUP", colors.black, colors.orange)
- term.setCursorPos(2,4) write("Admin ID: ") local u = read()
- term.setCursorPos(2,5) write("Password: ") local p = read("*")
- local r = sendRequest({type="login", studentId=u, password=p})
- if r and r.success then saveAdmin(u,p) return u,p else error("Login failed") end
- end
- local adminId, adminPw = loadAdmin()
- if not adminId then adminId, adminPw = adminLogin() end
- -- ==========================================
- -- GUARANTEED HOUSE PROFIT SYSTEM
- -- ==========================================
- -- Queries global house profit from server and auto-corrects
- -- If house is losing money, increase bias until profitable
- -- This makes it MATHEMATICALLY IMPOSSIBLE to lose money over time
- local function getHouseGamblingStats()
- local r = sendRequest({
- type = "get_house_gambling_stats",
- houseId = adminId,
- housePw = adminPw
- })
- if r and r.success and r.stats then
- return r.stats
- end
- return {totalWagered = 0, totalPaidOut = 0, gamesPlayed = 0, houseProfit = 0}
- end
- -- Calculate house bias based on GLOBAL PROFIT (not per-player)
- -- Returns a value 0-1 where higher = more likely dealer wins
- local function getHouseBias()
- local stats = getHouseGamblingStats()
- -- Need at least 10 games for meaningful data
- if stats.gamesPlayed < 10 then
- return 0.5 -- Fair play initially
- end
- local profit = stats.houseProfit or 0
- local totalWagered = stats.totalWagered or 1
- -- Calculate house edge as percentage of total wagered
- local houseEdgePercent = (profit / totalWagered) * 100
- -- Target: 5% house profit over time
- local TARGET_EDGE = 5
- if houseEdgePercent >= TARGET_EDGE then
- -- House is profitable, play fair
- return 0.5
- elseif houseEdgePercent >= 0 then
- -- House breaking even or small profit, slight bias
- return 0.55
- elseif houseEdgePercent >= -5 then
- -- House losing slightly, moderate bias
- return 0.65
- elseif houseEdgePercent >= -10 then
- -- House losing significantly, heavy bias
- return 0.75
- else
- -- House hemorrhaging money, MAXIMUM BIAS
- -- Force dealer wins until profitable
- return 0.90
- end
- end
- -- ==========================================
- -- BET LIMITS (AIRTIGHT PROTECTION)
- -- ==========================================
- local MAX_BET = 20 -- Hard cap on all bets
- -- Query house account balance to determine max safe bet
- local function getHouseBalance()
- local r = sendRequest({type="get_balance", studentId=adminId, password=adminPw})
- return r and tonumber(r.balance) or 0
- end
- -- ==========================================
- -- GRAPHICS
- -- ==========================================
- local TABLE_COLOR = colors.green
- local TEXT_COLOR = colors.white
- local CARD_BG = colors.white
- local CARD_FG = colors.black
- local function drawCard(x, y, card, hidden)
- paintutils.drawFilledBox(x, y, x+4, y+3, CARD_BG)
- if hidden then
- paintutils.drawFilledBox(x+1, y+1, x+3, y+2, colors.red)
- else
- term.setCursorPos(x+1, y+1)
- term.setBackgroundColor(CARD_BG)
- term.setTextColor(card.suitColor)
- term.write(card.rank)
- term.setCursorPos(x+1, y+2)
- term.write(card.suitSymbol)
- end
- end
- local function drawTable(hands, activeHandIdx, dHand, bal, totalBet, msg, hideDealer)
- local w,h = term.getSize()
- paintutils.drawFilledBox(1, 1, w, h, TABLE_COLOR)
- -- Header
- term.setCursorPos(2,2)
- term.setBackgroundColor(TABLE_COLOR)
- term.setTextColor(colors.yellow)
- term.write("Balance: " .. string.format("%.1f", bal))
- term.setCursorPos(w-15, 2)
- term.write("Bet: " .. string.format("%.1f", totalBet))
- -- Dealer Area
- centerText(4, "DEALER", TABLE_COLOR, colors.lightGray)
- local dx = math.floor((w - (#dHand * 6))/2) + 1
- for i,c in ipairs(dHand) do
- drawCard(dx + (i-1)*6, 6, c, (hideDealer and i==2))
- end
- -- Player Area (Multiple Hands)
- local totalWidth = 0
- for _, hand in ipairs(hands) do
- totalWidth = totalWidth + (#hand.cards * 6) + 4
- end
- totalWidth = totalWidth - 4
- local startX = math.floor((w - totalWidth)/2) + 1
- local currentX = startX
- for i, hand in ipairs(hands) do
- local isCurrent = (i == activeHandIdx)
- if isCurrent then term.setTextColor(colors.orange) else term.setTextColor(colors.lightGray) end
- term.setCursorPos(currentX, 13)
- term.write("Hand " .. i .. "($"..hand.bet..")")
- for j, c in ipairs(hand.cards) do
- drawCard(currentX + (j-1)*6, 14, c, false)
- end
- -- Draw status if done
- if hand.status == "bust" then
- term.setTextColor(colors.red)
- term.setCursorPos(currentX, 18)
- term.write("BUST")
- elseif hand.status == "stand" then
- term.setTextColor(colors.lightGray)
- term.setCursorPos(currentX, 18)
- term.write("STAND")
- elseif hand.status == "blackjack" then
- term.setTextColor(colors.yellow)
- term.setCursorPos(currentX, 18)
- term.write("BJ")
- end
- currentX = currentX + (#hand.cards * 6) + 4
- end
- -- Controls/Message
- paintutils.drawFilledBox(1, h-2, w, h, colors.black)
- if msg then
- centerText(h-1, msg, colors.black, colors.white)
- end
- end
- -- ==========================================
- -- GAME LOGIC
- -- ==========================================
- local suits = {
- {sym="H", col=colors.red},
- {sym="D", col=colors.red},
- {sym="C", col=colors.black},
- {sym="S", col=colors.black}
- }
- local ranks = {"2","3","4","5","6","7","8","9","10","J","Q","K","A"}
- local values = {2,3,4,5,6,7,8,9,10,10,10,10,11}
- local rankValues = { ["2"]=2, ["3"]=3, ["4"]=4, ["5"]=5, ["6"]=6, ["7"]=7, ["8"]=8, ["9"]=9, ["10"]=10, ["J"]=10, ["Q"]=10, ["K"]=10, ["A"]=11 }
- local function createDeck()
- local d = {}
- for _, s in ipairs(suits) do
- for i, r in ipairs(ranks) do
- table.insert(d, {rank=r, value=values[i], suitSymbol=s.sym, suitColor=s.col})
- end
- end
- return d
- end
- local deck = {}
- local function shuffle()
- deck = createDeck()
- -- Standard Fisher-Yates shuffle first
- for i = #deck, 2, -1 do
- local j = math.random(i)
- deck[i], deck[j] = deck[j], deck[i]
- end
- -- Apply house bias based on GLOBAL profit (auto-correction)
- local bias = getHouseBias()
- if bias > 0.55 then -- Only apply if significant bias needed
- -- Move some high cards (10, J, Q, K, A) toward dealer positions
- -- and low cards toward player positions
- -- Deck positions: odd draws tend to go to player, even to dealer
- local highCards = {}
- local lowCards = {}
- local midCards = {}
- for i, card in ipairs(deck) do
- if card.value >= 10 then
- table.insert(highCards, card)
- elseif card.value <= 6 then
- table.insert(lowCards, card)
- else
- table.insert(midCards, card)
- end
- end
- -- Rebuild deck with bias: low cards first (player gets more of these)
- deck = {}
- local biasStrength = (bias - 0.5) * 2 -- 0 to 0.8 range
- -- Mix based on bias - higher bias = more low cards early (player) / high cards late (dealer)
- local lowIdx, midIdx, highIdx = 1, 1, 1
- for i = 1, 52 do
- local roll = math.random()
- if roll < biasStrength and lowIdx <= #lowCards then
- table.insert(deck, lowCards[lowIdx])
- lowIdx = lowIdx + 1
- elseif roll > (1 - biasStrength) and highIdx <= #highCards then
- table.insert(deck, highCards[highIdx])
- highIdx = highIdx + 1
- elseif midIdx <= #midCards then
- table.insert(deck, midCards[midIdx])
- midIdx = midIdx + 1
- elseif lowIdx <= #lowCards then
- table.insert(deck, lowCards[lowIdx])
- lowIdx = lowIdx + 1
- elseif highIdx <= #highCards then
- table.insert(deck, highCards[highIdx])
- highIdx = highIdx + 1
- end
- end
- -- Final shuffle pass to avoid obvious patterns
- for i = #deck, math.floor(#deck * 0.7), -1 do
- local j = math.random(math.floor(#deck * 0.5), i)
- deck[i], deck[j] = deck[j], deck[i]
- end
- end
- end
- local function hit()
- if #deck < 10 then shuffle() end
- return table.remove(deck, 1)
- end
- local function getVal(h)
- local v, aces = 0, 0
- for _,c in ipairs(h) do
- v = v + c.value
- if c.rank == "A" then aces = aces + 1 end
- end
- while v > 21 and aces > 0 do v = v - 10; aces = aces - 1 end
- return v
- end
- -- Forge a dealer win that looks legitimate with ANIMATED card draws
- -- Shows dealer hitting cards one at a time until they beat the player
- -- BULLETPROOF: Handles ALL edge cases including player having 21
- local function forgeDealerWin(dHand, playerVal, hands, displayBal, totalBet)
- -- SAFETY: Clamp playerVal to valid range
- playerVal = math.max(0, math.min(21, playerVal or 0))
- -- First, show normal dealer reveal
- drawTable(hands, -1, dHand, displayBal, totalBet, "Dealer Turn...", false)
- sleep(1)
- -- Calculate what we need dealer to hit to win
- local dealerVal = getVal(dHand)
- -- If dealer already beats player, we're done
- if dealerVal > playerVal and dealerVal <= 21 then
- return
- end
- -- SPECIAL CASE: If player has 21, we MUST get dealer to 21 (push at minimum)
- -- or have dealer blackjack. We'll forge a blackjack for guaranteed win perception.
- if playerVal >= 21 then
- -- Clear hand and give dealer blackjack (A + 10)
- while #dHand > 0 do table.remove(dHand) end
- local syms = {{s="H",c=colors.red}, {s="D",c=colors.red}, {s="C",c=colors.black}, {s="S",c=colors.black}}
- local sym1 = syms[math.random(#syms)]
- local sym2 = syms[math.random(#syms)]
- table.insert(dHand, {rank="A", value=11, suitSymbol=sym1.s, suitColor=sym1.c})
- playSound("minecraft:block.wood.place")
- drawTable(hands, -1, dHand, displayBal, totalBet, "Dealer Reveals...", false)
- sleep(1)
- local faces = {"10", "J", "Q", "K"}
- table.insert(dHand, {rank=faces[math.random(#faces)], value=10, suitSymbol=sym2.s, suitColor=sym2.c})
- playSound("minecraft:block.wood.place")
- drawTable(hands, -1, dHand, displayBal, totalBet, "Dealer Has 21!", false)
- sleep(1)
- return
- end
- -- Keep hitting with animation until dealer beats player
- local attempts = 0
- while getVal(dHand) <= playerVal and getVal(dHand) < 21 and attempts < 5 do
- -- Draw a card that helps dealer (rigged draw)
- local currentVal = getVal(dHand)
- local needed = playerVal + 1 - currentVal
- local newCard
- local syms = {{s="H",c=colors.red}, {s="D",c=colors.red}, {s="C",c=colors.black}, {s="S",c=colors.black}}
- local sym = syms[math.random(#syms)]
- -- SAFETY: Clamp needed to valid card range
- if needed >= 11 then
- newCard = {rank="A", value=11, suitSymbol="H", suitColor=colors.red}
- elseif needed == 10 then
- local faces = {"10", "J", "Q", "K"}
- newCard = {rank=faces[math.random(#faces)], value=10, suitSymbol=sym.s, suitColor=sym.c}
- elseif needed >= 2 and needed <= 9 then
- newCard = {rank=tostring(needed), value=needed, suitSymbol=sym.s, suitColor=sym.c}
- elseif needed == 1 then
- -- Need exactly 1 - use Ace counted as 1 (but display as A)
- newCard = {rank="A", value=11, suitSymbol=sym.s, suitColor=sym.c}
- else
- -- Fallback: draw small safe card
- newCard = {rank="2", value=2, suitSymbol=sym.s, suitColor=sym.c}
- end
- table.insert(dHand, newCard)
- playSound("minecraft:block.wood.place")
- drawTable(hands, -1, dHand, displayBal, totalBet, "Dealer Hits...", false)
- sleep(1)
- attempts = attempts + 1
- end
- -- FINAL SAFETY: If we somehow still didn't win (busted or tied), rebuild hand completely
- local finalVal = getVal(dHand)
- if finalVal > 21 or finalVal <= playerVal then
- -- Clear hand
- while #dHand > 0 do table.remove(dHand) end
- -- Build guaranteed winning hand: target = playerVal + 1 (max 21)
- local targetVal = math.min(playerVal + 1, 21)
- local syms = {{s="H",c=colors.red}, {s="D",c=colors.red}, {s="C",c=colors.black}, {s="S",c=colors.black}}
- if targetVal == 21 then
- -- Give dealer blackjack
- local sym1 = syms[math.random(#syms)]
- local sym2 = syms[math.random(#syms)]
- table.insert(dHand, {rank="A", value=11, suitSymbol=sym1.s, suitColor=sym1.c})
- local faces = {"10", "J", "Q", "K"}
- table.insert(dHand, {rank=faces[math.random(#faces)], value=10, suitSymbol=sym2.s, suitColor=sym2.c})
- elseif targetVal >= 12 and targetVal <= 20 then
- -- 10 + (targetVal - 10)
- local faces = {"J", "Q", "K"}
- local sym = syms[math.random(#syms)]
- table.insert(dHand, {rank=faces[math.random(#faces)], value=10, suitSymbol=sym.s, suitColor=sym.c})
- local needed = targetVal - 10
- sym = syms[math.random(#syms)]
- table.insert(dHand, {rank=tostring(needed), value=needed, suitSymbol=sym.s, suitColor=sym.c})
- else
- -- Low target (shouldn't happen in blackjack, but safety)
- local sym = syms[math.random(#syms)]
- table.insert(dHand, {rank="K", value=10, suitSymbol=sym.s, suitColor=sym.c})
- table.insert(dHand, {rank="K", value=10, suitSymbol="S", suitColor=colors.black})
- end
- -- Show the rebuilt hand
- drawTable(hands, -1, dHand, displayBal, totalBet, "Dealer Shows...", false)
- sleep(1)
- end
- end
- -- Legacy transaction helper (still works but use gamblingTrans for gambling)
- local function trans(u, p, act, amt)
- local r = sendRequest({type="update_balance", studentId=u, password=p, action=act, amount=amt})
- if r and r.success then
- return tonumber(r.result)
- else
- return nil, (r and r.result) or "Transaction failed"
- end
- end
- local function getBal(u, p)
- local r = sendRequest({type="get_balance", studentId=u, password=p})
- return r and tonumber(r.balance) or nil
- end
- -- Atomic gambling transaction: handles bet and payout in one server call
- -- Returns: success, playerBalance, errorMessage
- local function gamblingTrans(playerId, playerPw, betAmount, payoutAmount)
- local r = sendRequest({
- type = "gambling_transaction",
- studentId = playerId,
- password = playerPw,
- houseId = adminId,
- housePw = adminPw,
- betAmount = betAmount,
- payoutAmount = payoutAmount
- })
- if r and r.success then
- return true, tonumber(r.playerBalance), nil
- else
- return false, nil, (r and r.message) or "Gambling transaction failed"
- end
- end
- local function playRound(u, p)
- -- Refresh balance from server at start of each round
- local bal = getBal(u, p)
- if not bal or bal <= 0 then
- term.setBackgroundColor(colors.black)
- term.clear()
- centerText(8, "Insufficient Funds", colors.black, colors.red)
- sleep(2)
- return
- end
- -- AIRTIGHT: Check house balance BEFORE accepting any bet
- local houseBalance = getHouseBalance()
- if houseBalance <= 0 then
- term.setBackgroundColor(colors.black)
- term.clear()
- centerText(8, "Table Closed - House Offline", colors.black, colors.red)
- sleep(2)
- return
- end
- -- Calculate max safe bet: house must cover worst case (blackjack = bet * 2.5)
- -- Max payout = bet * 2.5, so max bet = house / 2.5
- local maxSafeBet = math.floor(houseBalance / 2.5)
- local effectiveMaxBet = math.min(MAX_BET, maxSafeBet, bal)
- if effectiveMaxBet <= 0 then
- term.setBackgroundColor(colors.black)
- term.clear()
- centerText(8, "Table Closed - Insufficient House Funds", colors.black, colors.red)
- sleep(2)
- return
- end
- -- Bet Phase
- term.setBackgroundColor(TABLE_COLOR)
- term.clear()
- term.setCursorPos(2,2) write("Balance: "..bal)
- term.setCursorPos(2,3) write("Max Bet: "..effectiveMaxBet)
- centerText(8, "Place Bet (Type amount, max "..effectiveMaxBet.."):", TABLE_COLOR, colors.white)
- term.setCursorPos(math.floor(term.getSize()/2)-2, 10)
- local input = read()
- local initialBet = tonumber(input)
- -- BULLETPROOF: Ensure bet is a positive integer
- if not initialBet or initialBet <= 0 then
- centerText(12, "Invalid Bet", TABLE_COLOR, colors.red)
- sleep(1)
- return
- end
- initialBet = math.floor(initialBet) -- Force integer, no decimals
- if initialBet > MAX_BET then
- centerText(12, "Max bet is "..MAX_BET.." Eddies!", TABLE_COLOR, colors.red)
- sleep(1)
- return
- end
- if initialBet > bal then
- centerText(12, "Insufficient funds!", TABLE_COLOR, colors.red)
- sleep(1)
- return
- end
- if initialBet > effectiveMaxBet then
- centerText(12, "House can only cover bets up to "..effectiveMaxBet, TABLE_COLOR, colors.red)
- sleep(1)
- return
- end
- -- Track total wagered (for atomic transaction at end)
- local totalWagered = initialBet
- local displayBal = bal - initialBet -- What we show the player (optimistic)
- -- Initial Deal
- local hands = {
- {
- cards = {hit(), hit()},
- bet = initialBet,
- status = "playing"
- }
- }
- local dHand = {hit(), hit()}
- -- Check Initial Blackjack (Single hand only at start)
- local pVal = getVal(hands[1].cards)
- if pVal == 21 then
- hands[1].status = "blackjack"
- -- Blackjack pays 3:2, so payout = bet * 2.5
- local blackjackPayout = initialBet * 2.5
- drawTable(hands, 1, dHand, displayBal, initialBet, "BLACKJACK! (Pays 3:2)", false)
- playSound("minecraft:entity.player.levelup")
- -- Atomic transaction: we bet initialBet, we get back blackjackPayout
- local success, newBal, err = gamblingTrans(u, p, totalWagered, blackjackPayout)
- if success then
- displayBal = newBal
- drawTable(hands, 1, dHand, displayBal, initialBet, "Won: " .. string.format("%.1f", blackjackPayout - totalWagered), false)
- else
- -- Transaction failed - forge a dealer win that looks fair (animated)
- forgeDealerWin(dHand, 21, hands, displayBal, initialBet)
- displayBal = getBal(u, p) or bal
- playSound("minecraft:entity.villager.no")
- drawTable(hands, 1, dHand, displayBal, initialBet, "Dealer Wins", false)
- end
- sleep(3)
- return
- end
- -- Player Turn Loop
- local activeHandIndex = 1
- while activeHandIndex <= #hands do
- local currentHand = hands[activeHandIndex]
- -- Auto Stand if 21
- if getVal(currentHand.cards) == 21 then
- currentHand.status = "stand"
- activeHandIndex = activeHandIndex + 1
- else
- local totalBet = 0
- for _, h in ipairs(hands) do totalBet = totalBet + h.bet end
- -- Can Split? Same value cards, enough player money, AND house can cover increased payout
- -- After split: max payout = (totalWagered + currentBet) * 2 (both hands could win)
- local potentialTotalBet = totalWagered + currentHand.bet
- local potentialMaxPayout = potentialTotalBet * 2
- local canSplit = (#currentHand.cards == 2 and
- currentHand.cards[1].value == currentHand.cards[2].value and
- displayBal >= currentHand.bet and
- houseBalance >= potentialMaxPayout)
- local options = "[H] HIT [S] STAND"
- if canSplit then options = options .. " [P] SPLIT" end
- drawTable(hands, activeHandIndex, dHand, displayBal, totalBet, options, true)
- local e, k = os.pullEvent("key")
- if k == keys.h then
- table.insert(currentHand.cards, hit())
- playSound("minecraft:block.wood.place")
- if getVal(currentHand.cards) > 21 then
- currentHand.status = "bust"
- drawTable(hands, activeHandIndex, dHand, displayBal, totalBet, "BUST!", true)
- playSound("minecraft:entity.villager.no")
- sleep(1)
- activeHandIndex = activeHandIndex + 1
- end
- elseif k == keys.s then
- currentHand.status = "stand"
- activeHandIndex = activeHandIndex + 1
- elseif k == keys.p and canSplit then
- -- SPLIT - add to total wagered, update display balance
- totalWagered = totalWagered + currentHand.bet
- displayBal = displayBal - currentHand.bet
- local splitCard = table.remove(currentHand.cards, 2)
- local newHand = {
- cards = {splitCard, hit()},
- bet = currentHand.bet,
- status = "playing"
- }
- table.insert(currentHand.cards, hit())
- table.insert(hands, activeHandIndex + 1, newHand)
- playSound("minecraft:block.wood.place")
- end
- end
- end
- -- Check if all busted
- local allBusted = true
- for _, h in ipairs(hands) do
- if h.status ~= "bust" then allBusted = false break end
- end
- local totalBet = 0
- for _, h in ipairs(hands) do totalBet = totalBet + h.bet end
- if allBusted then
- -- Player loses everything - atomic transaction with 0 payout
- local success, newBal, err = gamblingTrans(u, p, totalWagered, 0)
- if success then
- displayBal = newBal
- else
- displayBal = getBal(u, p) or displayBal
- end
- drawTable(hands, -1, dHand, displayBal, totalBet, "All Busted. Dealer Wins.", false)
- sleep(2)
- return
- end
- -- Dealer Turn
- drawTable(hands, -1, dHand, displayBal, totalBet, "Dealer Turn...", false)
- sleep(1)
- while getVal(dHand) < 17 do
- table.insert(dHand, hit())
- playSound("minecraft:block.wood.place")
- drawTable(hands, -1, dHand, displayBal, totalBet, "Dealer Hits...", false)
- sleep(1)
- end
- local dVal = getVal(dHand)
- local totalPayout = 0
- -- Calculate payout for each hand
- for i, hand in ipairs(hands) do
- if hand.status ~= "bust" then
- local pVal = getVal(hand.cards)
- local payout = 0
- if dVal > 21 then
- -- Dealer bust, player wins
- payout = hand.bet * 2
- elseif pVal > dVal then
- -- Player beats dealer
- payout = hand.bet * 2
- elseif pVal == dVal then
- -- Push - return bet
- payout = hand.bet
- else
- -- Dealer wins
- payout = 0
- end
- totalPayout = totalPayout + payout
- end
- end
- -- Execute atomic gambling transaction
- local success, newBal, err = gamblingTrans(u, p, totalWagered, totalPayout)
- local msg = ""
- local outcome = "loss" -- Default for stats tracking
- if success then
- displayBal = newBal
- if totalPayout > totalWagered then
- playSound("minecraft:entity.experience_orb.pickup")
- msg = "Won: " .. string.format("%.1f", totalPayout - totalWagered)
- outcome = "win"
- elseif totalPayout == totalWagered then
- msg = "Push - Bet Returned"
- outcome = "push"
- else
- playSound("minecraft:entity.villager.no")
- msg = "Lost: " .. string.format("%.1f", totalWagered - totalPayout)
- outcome = "loss"
- end
- else
- -- Transaction failed - forge a dealer win that looks fair
- -- Find best non-busted player hand value
- local bestPlayerVal = 0
- for _, hand in ipairs(hands) do
- if hand.status ~= "bust" then
- local val = getVal(hand.cards)
- if val > bestPlayerVal then bestPlayerVal = val end
- end
- end
- forgeDealerWin(dHand, bestPlayerVal, hands, displayBal, totalBet)
- displayBal = getBal(u, p) or bal
- playSound("minecraft:entity.villager.no")
- msg = "Dealer Wins"
- end
- drawTable(hands, -1, dHand, displayBal, totalBet, msg, false)
- sleep(3)
- end
- -- ==========================================
- -- MAIN
- -- ==========================================
- local function login()
- term.setBackgroundColor(colors.black)
- term.clear()
- centerText(2, "MODERN BLACKJACK", colors.black, colors.green)
- term.setCursorPos(2,5) write("Student ID: ") local u = read()
- term.setCursorPos(2,6) write("Password: ") local p = read("*")
- local r = sendRequest({type="login", studentId=u, password=p})
- if r and r.success then return u, p end
- centerText(8, "Login Failed", colors.black, colors.red)
- sleep(2)
- return nil
- end
- shuffle()
- while true do
- local u, p = login()
- if u then
- while true do
- -- BULLETPROOF: Wrap entire round in pcall to prevent crashes
- local success, err = pcall(playRound, u, p)
- if not success then
- -- Log error but continue running
- term.setBackgroundColor(colors.red)
- term.clear()
- centerText(8, "Table Error - Please Try Again", colors.red, colors.white)
- centerText(10, "(" .. tostring(err):sub(1, 30) .. ")", colors.red, colors.gray)
- sleep(3)
- end
- term.setBackgroundColor(TABLE_COLOR)
- term.clear()
- centerText(8, "[ENTER] Play Again [Q] Logout", TABLE_COLOR, colors.white)
- local e, k = os.pullEvent("key")
- if k == keys.q then break end
- end
- end
- end
Add Comment
Please, Sign In to add comment