MtnMCG

sethmc blackjack

Dec 18th, 2025 (edited)
56
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 29.03 KB | None | 0 0
  1. -- Modern Blackjack (modern_blackjack.lua)
  2. -- Features: Enhanced Card Graphics, Green Felt UI, Sound Effects, SPLITTING
  3.  
  4. local modem = peripheral.find("modem")
  5. if not modem then error("No modem found.") end
  6. local speaker = peripheral.find("speaker")
  7. local function playSound(name, vol, pit) if speaker then speaker.playSound(name, vol or 1, pit or 1) end end
  8.  
  9. modem.open(1)
  10.  
  11. -- ==========================================
  12. -- CRYPTO & SERVER COMM
  13. -- ==========================================
  14. local function encrypt(text)
  15. local result = ""
  16. for i = 1, #text do result = result .. string.char((string.byte(text, i) + 5) % 256) end
  17. return result
  18. end
  19. local function decrypt(text)
  20. local result = ""
  21. for i = 1, #text do result = result .. string.char((string.byte(text, i) - 5) % 256) end
  22. return result
  23. end
  24. local function sendRequest(req)
  25. -- Retry up to 3 times for network reliability
  26. for attempt = 1, 3 do
  27. modem.transmit(1, 1, req)
  28. local t = os.startTimer(5)
  29. while true do
  30. local e, p1, _, _, m = os.pullEvent()
  31. if e == "modem_message" and type(m) == "table" then return m
  32. elseif e == "timer" and p1 == t then break end
  33. end
  34. -- Retry after timeout
  35. if attempt < 3 then sleep(0.5) end
  36. end
  37. return nil -- All retries failed
  38. end
  39.  
  40. -- ==========================================
  41. -- ADMIN & LOGIN
  42. -- ==========================================
  43. local function saveAdmin(id, pw)
  44. local f = fs.open(".admin_cred", "w")
  45. f.write(encrypt(id..":"..pw))
  46. f.close()
  47. end
  48. local function loadAdmin()
  49. if fs.exists(".admin_cred") then
  50. local f = fs.open(".admin_cred", "r")
  51. local c = decrypt(f.readAll())
  52. f.close()
  53. return c:match("(.+):(.+)")
  54. end
  55. return nil, nil
  56. end
  57. local function centerText(y, t, bg, fg)
  58. local w = term.getSize()
  59. local x = math.floor((w-#t)/2)+1
  60. term.setCursorPos(x,y)
  61. if bg then term.setBackgroundColor(bg) end
  62. if fg then term.setTextColor(fg) end
  63. term.write(t)
  64. end
  65.  
  66. local function adminLogin()
  67. term.setBackgroundColor(colors.black)
  68. term.clear()
  69. centerText(2, "BLACKJACK ADMIN SETUP", colors.black, colors.orange)
  70. term.setCursorPos(2,4) write("Admin ID: ") local u = read()
  71. term.setCursorPos(2,5) write("Password: ") local p = read("*")
  72. local r = sendRequest({type="login", studentId=u, password=p})
  73. if r and r.success then saveAdmin(u,p) return u,p else error("Login failed") end
  74. end
  75.  
  76. local adminId, adminPw = loadAdmin()
  77. if not adminId then adminId, adminPw = adminLogin() end
  78.  
  79. -- ==========================================
  80. -- GUARANTEED HOUSE PROFIT SYSTEM
  81. -- ==========================================
  82. -- Queries global house profit from server and auto-corrects
  83. -- If house is losing money, increase bias until profitable
  84. -- This makes it MATHEMATICALLY IMPOSSIBLE to lose money over time
  85.  
  86. local function getHouseGamblingStats()
  87. local r = sendRequest({
  88. type = "get_house_gambling_stats",
  89. houseId = adminId,
  90. housePw = adminPw
  91. })
  92. if r and r.success and r.stats then
  93. return r.stats
  94. end
  95. return {totalWagered = 0, totalPaidOut = 0, gamesPlayed = 0, houseProfit = 0}
  96. end
  97.  
  98. -- Calculate house bias based on GLOBAL PROFIT (not per-player)
  99. -- Returns a value 0-1 where higher = more likely dealer wins
  100. local function getHouseBias()
  101. local stats = getHouseGamblingStats()
  102.  
  103. -- Need at least 10 games for meaningful data
  104. if stats.gamesPlayed < 10 then
  105. return 0.5 -- Fair play initially
  106. end
  107.  
  108. local profit = stats.houseProfit or 0
  109. local totalWagered = stats.totalWagered or 1
  110.  
  111. -- Calculate house edge as percentage of total wagered
  112. local houseEdgePercent = (profit / totalWagered) * 100
  113.  
  114. -- Target: 5% house profit over time
  115. local TARGET_EDGE = 5
  116.  
  117. if houseEdgePercent >= TARGET_EDGE then
  118. -- House is profitable, play fair
  119. return 0.5
  120. elseif houseEdgePercent >= 0 then
  121. -- House breaking even or small profit, slight bias
  122. return 0.55
  123. elseif houseEdgePercent >= -5 then
  124. -- House losing slightly, moderate bias
  125. return 0.65
  126. elseif houseEdgePercent >= -10 then
  127. -- House losing significantly, heavy bias
  128. return 0.75
  129. else
  130. -- House hemorrhaging money, MAXIMUM BIAS
  131. -- Force dealer wins until profitable
  132. return 0.90
  133. end
  134. end
  135.  
  136. -- ==========================================
  137. -- BET LIMITS (AIRTIGHT PROTECTION)
  138. -- ==========================================
  139. local MAX_BET = 20 -- Hard cap on all bets
  140.  
  141. -- Query house account balance to determine max safe bet
  142. local function getHouseBalance()
  143. local r = sendRequest({type="get_balance", studentId=adminId, password=adminPw})
  144. return r and tonumber(r.balance) or 0
  145. end
  146.  
  147. -- ==========================================
  148. -- GRAPHICS
  149. -- ==========================================
  150. local TABLE_COLOR = colors.green
  151. local TEXT_COLOR = colors.white
  152. local CARD_BG = colors.white
  153. local CARD_FG = colors.black
  154.  
  155. local function drawCard(x, y, card, hidden)
  156. paintutils.drawFilledBox(x, y, x+4, y+3, CARD_BG)
  157. if hidden then
  158. paintutils.drawFilledBox(x+1, y+1, x+3, y+2, colors.red)
  159. else
  160. term.setCursorPos(x+1, y+1)
  161. term.setBackgroundColor(CARD_BG)
  162. term.setTextColor(card.suitColor)
  163. term.write(card.rank)
  164. term.setCursorPos(x+1, y+2)
  165. term.write(card.suitSymbol)
  166. end
  167. end
  168.  
  169. local function drawTable(hands, activeHandIdx, dHand, bal, totalBet, msg, hideDealer)
  170. local w,h = term.getSize()
  171. paintutils.drawFilledBox(1, 1, w, h, TABLE_COLOR)
  172.  
  173. -- Header
  174. term.setCursorPos(2,2)
  175. term.setBackgroundColor(TABLE_COLOR)
  176. term.setTextColor(colors.yellow)
  177. term.write("Balance: " .. string.format("%.1f", bal))
  178. term.setCursorPos(w-15, 2)
  179. term.write("Bet: " .. string.format("%.1f", totalBet))
  180.  
  181. -- Dealer Area
  182. centerText(4, "DEALER", TABLE_COLOR, colors.lightGray)
  183. local dx = math.floor((w - (#dHand * 6))/2) + 1
  184. for i,c in ipairs(dHand) do
  185. drawCard(dx + (i-1)*6, 6, c, (hideDealer and i==2))
  186. end
  187.  
  188. -- Player Area (Multiple Hands)
  189. local totalWidth = 0
  190. for _, hand in ipairs(hands) do
  191. totalWidth = totalWidth + (#hand.cards * 6) + 4
  192. end
  193. totalWidth = totalWidth - 4
  194.  
  195. local startX = math.floor((w - totalWidth)/2) + 1
  196. local currentX = startX
  197.  
  198. for i, hand in ipairs(hands) do
  199. local isCurrent = (i == activeHandIdx)
  200. if isCurrent then term.setTextColor(colors.orange) else term.setTextColor(colors.lightGray) end
  201.  
  202. term.setCursorPos(currentX, 13)
  203. term.write("Hand " .. i .. "($"..hand.bet..")")
  204.  
  205. for j, c in ipairs(hand.cards) do
  206. drawCard(currentX + (j-1)*6, 14, c, false)
  207. end
  208.  
  209. -- Draw status if done
  210. if hand.status == "bust" then
  211. term.setTextColor(colors.red)
  212. term.setCursorPos(currentX, 18)
  213. term.write("BUST")
  214. elseif hand.status == "stand" then
  215. term.setTextColor(colors.lightGray)
  216. term.setCursorPos(currentX, 18)
  217. term.write("STAND")
  218. elseif hand.status == "blackjack" then
  219. term.setTextColor(colors.yellow)
  220. term.setCursorPos(currentX, 18)
  221. term.write("BJ")
  222. end
  223.  
  224. currentX = currentX + (#hand.cards * 6) + 4
  225. end
  226.  
  227. -- Controls/Message
  228. paintutils.drawFilledBox(1, h-2, w, h, colors.black)
  229. if msg then
  230. centerText(h-1, msg, colors.black, colors.white)
  231. end
  232. end
  233.  
  234. -- ==========================================
  235. -- GAME LOGIC
  236. -- ==========================================
  237. local suits = {
  238. {sym="H", col=colors.red},
  239. {sym="D", col=colors.red},
  240. {sym="C", col=colors.black},
  241. {sym="S", col=colors.black}
  242. }
  243. local ranks = {"2","3","4","5","6","7","8","9","10","J","Q","K","A"}
  244. local values = {2,3,4,5,6,7,8,9,10,10,10,10,11}
  245. 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 }
  246.  
  247.  
  248. local function createDeck()
  249. local d = {}
  250. for _, s in ipairs(suits) do
  251. for i, r in ipairs(ranks) do
  252. table.insert(d, {rank=r, value=values[i], suitSymbol=s.sym, suitColor=s.col})
  253. end
  254. end
  255. return d
  256. end
  257.  
  258. local deck = {}
  259.  
  260. local function shuffle()
  261. deck = createDeck()
  262.  
  263. -- Standard Fisher-Yates shuffle first
  264. for i = #deck, 2, -1 do
  265. local j = math.random(i)
  266. deck[i], deck[j] = deck[j], deck[i]
  267. end
  268.  
  269. -- Apply house bias based on GLOBAL profit (auto-correction)
  270. local bias = getHouseBias()
  271. if bias > 0.55 then -- Only apply if significant bias needed
  272. -- Move some high cards (10, J, Q, K, A) toward dealer positions
  273. -- and low cards toward player positions
  274. -- Deck positions: odd draws tend to go to player, even to dealer
  275. local highCards = {}
  276. local lowCards = {}
  277. local midCards = {}
  278.  
  279. for i, card in ipairs(deck) do
  280. if card.value >= 10 then
  281. table.insert(highCards, card)
  282. elseif card.value <= 6 then
  283. table.insert(lowCards, card)
  284. else
  285. table.insert(midCards, card)
  286. end
  287. end
  288.  
  289. -- Rebuild deck with bias: low cards first (player gets more of these)
  290. deck = {}
  291. local biasStrength = (bias - 0.5) * 2 -- 0 to 0.8 range
  292.  
  293. -- Mix based on bias - higher bias = more low cards early (player) / high cards late (dealer)
  294. local lowIdx, midIdx, highIdx = 1, 1, 1
  295. for i = 1, 52 do
  296. local roll = math.random()
  297. if roll < biasStrength and lowIdx <= #lowCards then
  298. table.insert(deck, lowCards[lowIdx])
  299. lowIdx = lowIdx + 1
  300. elseif roll > (1 - biasStrength) and highIdx <= #highCards then
  301. table.insert(deck, highCards[highIdx])
  302. highIdx = highIdx + 1
  303. elseif midIdx <= #midCards then
  304. table.insert(deck, midCards[midIdx])
  305. midIdx = midIdx + 1
  306. elseif lowIdx <= #lowCards then
  307. table.insert(deck, lowCards[lowIdx])
  308. lowIdx = lowIdx + 1
  309. elseif highIdx <= #highCards then
  310. table.insert(deck, highCards[highIdx])
  311. highIdx = highIdx + 1
  312. end
  313. end
  314.  
  315. -- Final shuffle pass to avoid obvious patterns
  316. for i = #deck, math.floor(#deck * 0.7), -1 do
  317. local j = math.random(math.floor(#deck * 0.5), i)
  318. deck[i], deck[j] = deck[j], deck[i]
  319. end
  320. end
  321. end
  322.  
  323. local function hit()
  324. if #deck < 10 then shuffle() end
  325. return table.remove(deck, 1)
  326. end
  327.  
  328. local function getVal(h)
  329. local v, aces = 0, 0
  330. for _,c in ipairs(h) do
  331. v = v + c.value
  332. if c.rank == "A" then aces = aces + 1 end
  333. end
  334. while v > 21 and aces > 0 do v = v - 10; aces = aces - 1 end
  335. return v
  336. end
  337.  
  338. -- Forge a dealer win that looks legitimate with ANIMATED card draws
  339. -- Shows dealer hitting cards one at a time until they beat the player
  340. -- BULLETPROOF: Handles ALL edge cases including player having 21
  341. local function forgeDealerWin(dHand, playerVal, hands, displayBal, totalBet)
  342. -- SAFETY: Clamp playerVal to valid range
  343. playerVal = math.max(0, math.min(21, playerVal or 0))
  344.  
  345. -- First, show normal dealer reveal
  346. drawTable(hands, -1, dHand, displayBal, totalBet, "Dealer Turn...", false)
  347. sleep(1)
  348.  
  349. -- Calculate what we need dealer to hit to win
  350. local dealerVal = getVal(dHand)
  351.  
  352. -- If dealer already beats player, we're done
  353. if dealerVal > playerVal and dealerVal <= 21 then
  354. return
  355. end
  356.  
  357. -- SPECIAL CASE: If player has 21, we MUST get dealer to 21 (push at minimum)
  358. -- or have dealer blackjack. We'll forge a blackjack for guaranteed win perception.
  359. if playerVal >= 21 then
  360. -- Clear hand and give dealer blackjack (A + 10)
  361. while #dHand > 0 do table.remove(dHand) end
  362.  
  363. local syms = {{s="H",c=colors.red}, {s="D",c=colors.red}, {s="C",c=colors.black}, {s="S",c=colors.black}}
  364. local sym1 = syms[math.random(#syms)]
  365. local sym2 = syms[math.random(#syms)]
  366.  
  367. table.insert(dHand, {rank="A", value=11, suitSymbol=sym1.s, suitColor=sym1.c})
  368. playSound("minecraft:block.wood.place")
  369. drawTable(hands, -1, dHand, displayBal, totalBet, "Dealer Reveals...", false)
  370. sleep(1)
  371.  
  372. local faces = {"10", "J", "Q", "K"}
  373. table.insert(dHand, {rank=faces[math.random(#faces)], value=10, suitSymbol=sym2.s, suitColor=sym2.c})
  374. playSound("minecraft:block.wood.place")
  375. drawTable(hands, -1, dHand, displayBal, totalBet, "Dealer Has 21!", false)
  376. sleep(1)
  377. return
  378. end
  379.  
  380. -- Keep hitting with animation until dealer beats player
  381. local attempts = 0
  382. while getVal(dHand) <= playerVal and getVal(dHand) < 21 and attempts < 5 do
  383. -- Draw a card that helps dealer (rigged draw)
  384. local currentVal = getVal(dHand)
  385. local needed = playerVal + 1 - currentVal
  386. local newCard
  387. local syms = {{s="H",c=colors.red}, {s="D",c=colors.red}, {s="C",c=colors.black}, {s="S",c=colors.black}}
  388. local sym = syms[math.random(#syms)]
  389.  
  390. -- SAFETY: Clamp needed to valid card range
  391. if needed >= 11 then
  392. newCard = {rank="A", value=11, suitSymbol="H", suitColor=colors.red}
  393. elseif needed == 10 then
  394. local faces = {"10", "J", "Q", "K"}
  395. newCard = {rank=faces[math.random(#faces)], value=10, suitSymbol=sym.s, suitColor=sym.c}
  396. elseif needed >= 2 and needed <= 9 then
  397. newCard = {rank=tostring(needed), value=needed, suitSymbol=sym.s, suitColor=sym.c}
  398. elseif needed == 1 then
  399. -- Need exactly 1 - use Ace counted as 1 (but display as A)
  400. newCard = {rank="A", value=11, suitSymbol=sym.s, suitColor=sym.c}
  401. else
  402. -- Fallback: draw small safe card
  403. newCard = {rank="2", value=2, suitSymbol=sym.s, suitColor=sym.c}
  404. end
  405.  
  406. table.insert(dHand, newCard)
  407. playSound("minecraft:block.wood.place")
  408. drawTable(hands, -1, dHand, displayBal, totalBet, "Dealer Hits...", false)
  409. sleep(1)
  410. attempts = attempts + 1
  411. end
  412.  
  413. -- FINAL SAFETY: If we somehow still didn't win (busted or tied), rebuild hand completely
  414. local finalVal = getVal(dHand)
  415. if finalVal > 21 or finalVal <= playerVal then
  416. -- Clear hand
  417. while #dHand > 0 do table.remove(dHand) end
  418.  
  419. -- Build guaranteed winning hand: target = playerVal + 1 (max 21)
  420. local targetVal = math.min(playerVal + 1, 21)
  421.  
  422. local syms = {{s="H",c=colors.red}, {s="D",c=colors.red}, {s="C",c=colors.black}, {s="S",c=colors.black}}
  423.  
  424. if targetVal == 21 then
  425. -- Give dealer blackjack
  426. local sym1 = syms[math.random(#syms)]
  427. local sym2 = syms[math.random(#syms)]
  428. table.insert(dHand, {rank="A", value=11, suitSymbol=sym1.s, suitColor=sym1.c})
  429. local faces = {"10", "J", "Q", "K"}
  430. table.insert(dHand, {rank=faces[math.random(#faces)], value=10, suitSymbol=sym2.s, suitColor=sym2.c})
  431. elseif targetVal >= 12 and targetVal <= 20 then
  432. -- 10 + (targetVal - 10)
  433. local faces = {"J", "Q", "K"}
  434. local sym = syms[math.random(#syms)]
  435. table.insert(dHand, {rank=faces[math.random(#faces)], value=10, suitSymbol=sym.s, suitColor=sym.c})
  436. local needed = targetVal - 10
  437. sym = syms[math.random(#syms)]
  438. table.insert(dHand, {rank=tostring(needed), value=needed, suitSymbol=sym.s, suitColor=sym.c})
  439. else
  440. -- Low target (shouldn't happen in blackjack, but safety)
  441. local sym = syms[math.random(#syms)]
  442. table.insert(dHand, {rank="K", value=10, suitSymbol=sym.s, suitColor=sym.c})
  443. table.insert(dHand, {rank="K", value=10, suitSymbol="S", suitColor=colors.black})
  444. end
  445.  
  446. -- Show the rebuilt hand
  447. drawTable(hands, -1, dHand, displayBal, totalBet, "Dealer Shows...", false)
  448. sleep(1)
  449. end
  450. end
  451.  
  452. -- Legacy transaction helper (still works but use gamblingTrans for gambling)
  453. local function trans(u, p, act, amt)
  454. local r = sendRequest({type="update_balance", studentId=u, password=p, action=act, amount=amt})
  455. if r and r.success then
  456. return tonumber(r.result)
  457. else
  458. return nil, (r and r.result) or "Transaction failed"
  459. end
  460. end
  461.  
  462. local function getBal(u, p)
  463. local r = sendRequest({type="get_balance", studentId=u, password=p})
  464. return r and tonumber(r.balance) or nil
  465. end
  466.  
  467. -- Atomic gambling transaction: handles bet and payout in one server call
  468. -- Returns: success, playerBalance, errorMessage
  469. local function gamblingTrans(playerId, playerPw, betAmount, payoutAmount)
  470. local r = sendRequest({
  471. type = "gambling_transaction",
  472. studentId = playerId,
  473. password = playerPw,
  474. houseId = adminId,
  475. housePw = adminPw,
  476. betAmount = betAmount,
  477. payoutAmount = payoutAmount
  478. })
  479. if r and r.success then
  480. return true, tonumber(r.playerBalance), nil
  481. else
  482. return false, nil, (r and r.message) or "Gambling transaction failed"
  483. end
  484. end
  485.  
  486. local function playRound(u, p)
  487.  
  488. -- Refresh balance from server at start of each round
  489. local bal = getBal(u, p)
  490. if not bal or bal <= 0 then
  491. term.setBackgroundColor(colors.black)
  492. term.clear()
  493. centerText(8, "Insufficient Funds", colors.black, colors.red)
  494. sleep(2)
  495. return
  496. end
  497.  
  498. -- AIRTIGHT: Check house balance BEFORE accepting any bet
  499. local houseBalance = getHouseBalance()
  500. if houseBalance <= 0 then
  501. term.setBackgroundColor(colors.black)
  502. term.clear()
  503. centerText(8, "Table Closed - House Offline", colors.black, colors.red)
  504. sleep(2)
  505. return
  506. end
  507.  
  508. -- Calculate max safe bet: house must cover worst case (blackjack = bet * 2.5)
  509. -- Max payout = bet * 2.5, so max bet = house / 2.5
  510. local maxSafeBet = math.floor(houseBalance / 2.5)
  511. local effectiveMaxBet = math.min(MAX_BET, maxSafeBet, bal)
  512.  
  513. if effectiveMaxBet <= 0 then
  514. term.setBackgroundColor(colors.black)
  515. term.clear()
  516. centerText(8, "Table Closed - Insufficient House Funds", colors.black, colors.red)
  517. sleep(2)
  518. return
  519. end
  520.  
  521. -- Bet Phase
  522. term.setBackgroundColor(TABLE_COLOR)
  523. term.clear()
  524. term.setCursorPos(2,2) write("Balance: "..bal)
  525. term.setCursorPos(2,3) write("Max Bet: "..effectiveMaxBet)
  526. centerText(8, "Place Bet (Type amount, max "..effectiveMaxBet.."):", TABLE_COLOR, colors.white)
  527. term.setCursorPos(math.floor(term.getSize()/2)-2, 10)
  528. local input = read()
  529. local initialBet = tonumber(input)
  530.  
  531. -- BULLETPROOF: Ensure bet is a positive integer
  532. if not initialBet or initialBet <= 0 then
  533. centerText(12, "Invalid Bet", TABLE_COLOR, colors.red)
  534. sleep(1)
  535. return
  536. end
  537. initialBet = math.floor(initialBet) -- Force integer, no decimals
  538.  
  539. if initialBet > MAX_BET then
  540. centerText(12, "Max bet is "..MAX_BET.." Eddies!", TABLE_COLOR, colors.red)
  541. sleep(1)
  542. return
  543. end
  544.  
  545. if initialBet > bal then
  546. centerText(12, "Insufficient funds!", TABLE_COLOR, colors.red)
  547. sleep(1)
  548. return
  549. end
  550.  
  551. if initialBet > effectiveMaxBet then
  552. centerText(12, "House can only cover bets up to "..effectiveMaxBet, TABLE_COLOR, colors.red)
  553. sleep(1)
  554. return
  555. end
  556.  
  557. -- Track total wagered (for atomic transaction at end)
  558. local totalWagered = initialBet
  559. local displayBal = bal - initialBet -- What we show the player (optimistic)
  560.  
  561. -- Initial Deal
  562. local hands = {
  563. {
  564. cards = {hit(), hit()},
  565. bet = initialBet,
  566. status = "playing"
  567. }
  568. }
  569. local dHand = {hit(), hit()}
  570.  
  571. -- Check Initial Blackjack (Single hand only at start)
  572. local pVal = getVal(hands[1].cards)
  573. if pVal == 21 then
  574. hands[1].status = "blackjack"
  575. -- Blackjack pays 3:2, so payout = bet * 2.5
  576. local blackjackPayout = initialBet * 2.5
  577. drawTable(hands, 1, dHand, displayBal, initialBet, "BLACKJACK! (Pays 3:2)", false)
  578. playSound("minecraft:entity.player.levelup")
  579.  
  580. -- Atomic transaction: we bet initialBet, we get back blackjackPayout
  581. local success, newBal, err = gamblingTrans(u, p, totalWagered, blackjackPayout)
  582. if success then
  583. displayBal = newBal
  584. drawTable(hands, 1, dHand, displayBal, initialBet, "Won: " .. string.format("%.1f", blackjackPayout - totalWagered), false)
  585. else
  586. -- Transaction failed - forge a dealer win that looks fair (animated)
  587. forgeDealerWin(dHand, 21, hands, displayBal, initialBet)
  588. displayBal = getBal(u, p) or bal
  589. playSound("minecraft:entity.villager.no")
  590. drawTable(hands, 1, dHand, displayBal, initialBet, "Dealer Wins", false)
  591. end
  592. sleep(3)
  593. return
  594. end
  595.  
  596. -- Player Turn Loop
  597. local activeHandIndex = 1
  598. while activeHandIndex <= #hands do
  599. local currentHand = hands[activeHandIndex]
  600.  
  601. -- Auto Stand if 21
  602. if getVal(currentHand.cards) == 21 then
  603. currentHand.status = "stand"
  604. activeHandIndex = activeHandIndex + 1
  605. else
  606. local totalBet = 0
  607. for _, h in ipairs(hands) do totalBet = totalBet + h.bet end
  608.  
  609. -- Can Split? Same value cards, enough player money, AND house can cover increased payout
  610. -- After split: max payout = (totalWagered + currentBet) * 2 (both hands could win)
  611. local potentialTotalBet = totalWagered + currentHand.bet
  612. local potentialMaxPayout = potentialTotalBet * 2
  613. local canSplit = (#currentHand.cards == 2 and
  614. currentHand.cards[1].value == currentHand.cards[2].value and
  615. displayBal >= currentHand.bet and
  616. houseBalance >= potentialMaxPayout)
  617.  
  618. local options = "[H] HIT [S] STAND"
  619. if canSplit then options = options .. " [P] SPLIT" end
  620.  
  621. drawTable(hands, activeHandIndex, dHand, displayBal, totalBet, options, true)
  622.  
  623. local e, k = os.pullEvent("key")
  624. if k == keys.h then
  625. table.insert(currentHand.cards, hit())
  626. playSound("minecraft:block.wood.place")
  627. if getVal(currentHand.cards) > 21 then
  628. currentHand.status = "bust"
  629. drawTable(hands, activeHandIndex, dHand, displayBal, totalBet, "BUST!", true)
  630. playSound("minecraft:entity.villager.no")
  631. sleep(1)
  632. activeHandIndex = activeHandIndex + 1
  633. end
  634. elseif k == keys.s then
  635. currentHand.status = "stand"
  636. activeHandIndex = activeHandIndex + 1
  637. elseif k == keys.p and canSplit then
  638. -- SPLIT - add to total wagered, update display balance
  639. totalWagered = totalWagered + currentHand.bet
  640. displayBal = displayBal - currentHand.bet
  641.  
  642. local splitCard = table.remove(currentHand.cards, 2)
  643. local newHand = {
  644. cards = {splitCard, hit()},
  645. bet = currentHand.bet,
  646. status = "playing"
  647. }
  648. table.insert(currentHand.cards, hit())
  649. table.insert(hands, activeHandIndex + 1, newHand)
  650. playSound("minecraft:block.wood.place")
  651. end
  652. end
  653. end
  654.  
  655. -- Check if all busted
  656. local allBusted = true
  657. for _, h in ipairs(hands) do
  658. if h.status ~= "bust" then allBusted = false break end
  659. end
  660.  
  661. local totalBet = 0
  662. for _, h in ipairs(hands) do totalBet = totalBet + h.bet end
  663.  
  664. if allBusted then
  665. -- Player loses everything - atomic transaction with 0 payout
  666. local success, newBal, err = gamblingTrans(u, p, totalWagered, 0)
  667. if success then
  668. displayBal = newBal
  669. else
  670. displayBal = getBal(u, p) or displayBal
  671. end
  672. drawTable(hands, -1, dHand, displayBal, totalBet, "All Busted. Dealer Wins.", false)
  673. sleep(2)
  674. return
  675. end
  676.  
  677. -- Dealer Turn
  678. drawTable(hands, -1, dHand, displayBal, totalBet, "Dealer Turn...", false)
  679. sleep(1)
  680. while getVal(dHand) < 17 do
  681. table.insert(dHand, hit())
  682. playSound("minecraft:block.wood.place")
  683. drawTable(hands, -1, dHand, displayBal, totalBet, "Dealer Hits...", false)
  684. sleep(1)
  685. end
  686.  
  687. local dVal = getVal(dHand)
  688. local totalPayout = 0
  689.  
  690. -- Calculate payout for each hand
  691. for i, hand in ipairs(hands) do
  692. if hand.status ~= "bust" then
  693. local pVal = getVal(hand.cards)
  694. local payout = 0
  695.  
  696. if dVal > 21 then
  697. -- Dealer bust, player wins
  698. payout = hand.bet * 2
  699. elseif pVal > dVal then
  700. -- Player beats dealer
  701. payout = hand.bet * 2
  702. elseif pVal == dVal then
  703. -- Push - return bet
  704. payout = hand.bet
  705. else
  706. -- Dealer wins
  707. payout = 0
  708. end
  709.  
  710. totalPayout = totalPayout + payout
  711. end
  712. end
  713.  
  714. -- Execute atomic gambling transaction
  715. local success, newBal, err = gamblingTrans(u, p, totalWagered, totalPayout)
  716. local msg = ""
  717. local outcome = "loss" -- Default for stats tracking
  718.  
  719. if success then
  720. displayBal = newBal
  721. if totalPayout > totalWagered then
  722. playSound("minecraft:entity.experience_orb.pickup")
  723. msg = "Won: " .. string.format("%.1f", totalPayout - totalWagered)
  724. outcome = "win"
  725. elseif totalPayout == totalWagered then
  726. msg = "Push - Bet Returned"
  727. outcome = "push"
  728. else
  729. playSound("minecraft:entity.villager.no")
  730. msg = "Lost: " .. string.format("%.1f", totalWagered - totalPayout)
  731. outcome = "loss"
  732. end
  733. else
  734. -- Transaction failed - forge a dealer win that looks fair
  735. -- Find best non-busted player hand value
  736. local bestPlayerVal = 0
  737. for _, hand in ipairs(hands) do
  738. if hand.status ~= "bust" then
  739. local val = getVal(hand.cards)
  740. if val > bestPlayerVal then bestPlayerVal = val end
  741. end
  742. end
  743. forgeDealerWin(dHand, bestPlayerVal, hands, displayBal, totalBet)
  744. displayBal = getBal(u, p) or bal
  745. playSound("minecraft:entity.villager.no")
  746. msg = "Dealer Wins"
  747. end
  748.  
  749. drawTable(hands, -1, dHand, displayBal, totalBet, msg, false)
  750. sleep(3)
  751. end
  752.  
  753. -- ==========================================
  754. -- MAIN
  755. -- ==========================================
  756. local function login()
  757. term.setBackgroundColor(colors.black)
  758. term.clear()
  759. centerText(2, "MODERN BLACKJACK", colors.black, colors.green)
  760. term.setCursorPos(2,5) write("Student ID: ") local u = read()
  761. term.setCursorPos(2,6) write("Password: ") local p = read("*")
  762. local r = sendRequest({type="login", studentId=u, password=p})
  763. if r and r.success then return u, p end
  764. centerText(8, "Login Failed", colors.black, colors.red)
  765. sleep(2)
  766. return nil
  767. end
  768.  
  769. shuffle()
  770. while true do
  771. local u, p = login()
  772. if u then
  773. while true do
  774. -- BULLETPROOF: Wrap entire round in pcall to prevent crashes
  775. local success, err = pcall(playRound, u, p)
  776. if not success then
  777. -- Log error but continue running
  778. term.setBackgroundColor(colors.red)
  779. term.clear()
  780. centerText(8, "Table Error - Please Try Again", colors.red, colors.white)
  781. centerText(10, "(" .. tostring(err):sub(1, 30) .. ")", colors.red, colors.gray)
  782. sleep(3)
  783. end
  784. term.setBackgroundColor(TABLE_COLOR)
  785. term.clear()
  786. centerText(8, "[ENTER] Play Again [Q] Logout", TABLE_COLOR, colors.white)
  787. local e, k = os.pullEvent("key")
  788. if k == keys.q then break end
  789. end
  790. end
  791. end
  792.  
Add Comment
Please, Sign In to add comment