--!name Billy's Blackjack --!icon game local BANK_START = 500 local MIN_BET, MAX_BET, BET_STEP = 5, 500, 5 local phase, deck, you, billy = "bet", {}, {}, {} local bank, bet, message, outcome = BANK_START, 25, "", "" local doubled, wins, losses, pushes = false, 0, 0, 0 local net = 0 -- what the last hand moved, for the settled screen local RANKS = { {"A", 11}, {"2", 2}, {"3", 3}, {"4", 4}, {"5", 5}, {"6", 6}, {"7", 7}, {"8", 8}, {"9", 9}, {"10", 10}, {"J", 10}, {"Q", 10}, {"K", 10}, } local SUITS = {"S", "H", "D", "C"} local function shuffle() deck = {} for s = 1, 4 do for r = 1, 13 do deck[#deck + 1] = {face = RANKS[r][1] .. SUITS[s], v = RANKS[r][2], rank = r} end end for i = #deck, 2, -1 do local j = math.random(i) deck[i], deck[j] = deck[j], deck[i] end end local function draw_card(hand) if #deck == 0 then shuffle() end hand[#hand + 1] = table.remove(deck) end local function total(hand) local t, aces = 0, 0 for _, c in ipairs(hand) do t = t + c.v if c.rank == 1 then aces = aces + 1 end end while t > 21 and aces > 0 do t, aces = t - 10, aces - 1 end return t end local function soft(hand) local t, aces = 0, 0 for _, c in ipairs(hand) do t = t + c.v if c.rank == 1 then aces = aces + 1 end end while t > 21 and aces > 0 do t, aces = t - 10, aces - 1 end return aces > 0 end local function is_blackjack(hand) return #hand == 2 and total(hand) == 21 end local function hand_spec(hand, hide) local out = {} for i, c in ipairs(hand) do out[i] = (hide and i == 2) and "?" or c.face end return table.concat(out, " ") end local function save() snail.save(string.format("%d %d %d %d", bank, wins, losses, pushes)) end local function load() local s = snail.load() if not s then return end local b, w, l, p = s:match("(%-?%d+) (%d+) (%d+) (%d+)") if b then bank, wins, losses, pushes = tonumber(b), tonumber(w), tonumber(l), tonumber(p) end if bank < MIN_BET then bank = BANK_START end end local function clamp_bet() bet = math.max(MIN_BET, math.min(bet, MAX_BET, bank)) end local function billy_says(kind) local lines = { deal = {"Place it and we'll see.", "Cards are cards.", "Let's have a look."}, hit = {"Bold.", "Another, then.", "You want it, you got it."}, close = {"That's a working hand.", "I'd sit on that.", "Now we're talking."}, bust = {"Over. Happens.", "Twenty-two is still twenty-two.", "The house thanks you."}, bbust = {"Over. Yours.", "I've overdone it.", "The deck turned on me."}, dealbj = {"Blackjack. Don't look at me like that.", "Twenty-one on the deal. That's the game."}, yourbj = {"Blackjack. Pays three to two.", "Well. Look at that."}, win = {"You take it.", "Fair and square.", "Good hand."}, lose = {"Mine.", "That's the house.", "Better luck next one."}, push = {"Nobody's hand.", "Push. Try again.", "A tie is a rest."}, broke = {"That's the bankroll. I'll spot you a fresh one.", "Cleaned out. Sit, I'll deal you back in."}, } local set = lines[kind] or lines.deal return set[math.random(#set)] end local function settle() local yt, bt = total(you), total(billy) local ybj, bbj = is_blackjack(you), is_blackjack(billy) local stake = doubled and bet * 2 or bet if yt > 21 then outcome, net, losses = "BUST", -stake, losses + 1 message = billy_says("bust") elseif ybj and not bbj then outcome, net, wins = "BLACKJACK", math.floor(stake * 3 / 2), wins + 1 message = billy_says("yourbj") elseif bbj and not ybj then outcome, net, losses = "BILLY HAS IT", -stake, losses + 1 message = billy_says("dealbj") elseif bt > 21 then outcome, net, wins = "BILLY BUSTS", stake, wins + 1 message = billy_says("bbust") elseif yt > bt then outcome, net, wins = "YOU WIN", stake, wins + 1 message = billy_says("win") elseif bt > yt then outcome, net, losses = "BILLY WINS", -stake, losses + 1 message = billy_says("lose") else outcome, net, pushes = "PUSH", 0, pushes + 1 message = billy_says("push") end bank = bank + net if bank < MIN_BET then bank = BANK_START message = billy_says("broke") end clamp_bet() phase = "over" snail.hint("OK deal again BACK menu") save() end local function dealer_plays() while total(billy) < 17 do draw_card(billy) end settle() end local function play_hint() snail.hint(#you == 2 and bank >= bet * 2 and "OK hit DOWN stand LEFT double BACK menu" or "OK hit DOWN stand BACK menu") end local function deal() shuffle() you, billy, doubled = {}, {}, false draw_card(you); draw_card(billy); draw_card(you); draw_card(billy) phase = "play" message = billy_says("deal") if is_blackjack(you) or is_blackjack(billy) then settle() end end function start() snail.ink("fast") -- solid tiles, so the short waveform is honest here load() clamp_bet() phase = "bet" message = "Sit down." snail.hint("UP/DOWN bet OK deal BACK menu") end function key(k) if phase == "bet" then if k == "up" then bet = math.min(math.min(MAX_BET, bank), bet + BET_STEP) elseif k == "down" then bet = math.max(MIN_BET, bet - BET_STEP) elseif k == "left" then bet = MIN_BET elseif k == "right" then bet = math.min(MAX_BET, bank) elseif k == "ok" then deal() if phase == "play" then play_hint() end end elseif phase == "play" then if k == "ok" then draw_card(you) local t = total(you) if t > 21 then settle() elseif t == 21 then dealer_plays() else message = billy_says(t >= 17 and "close" or "hit") play_hint() -- double left the table with the third card end elseif k == "down" then dealer_plays() elseif k == "left" and #you == 2 and bank >= bet * 2 then doubled = true draw_card(you) if total(you) > 21 then settle() else dealer_plays() end end else -- over if k == "ok" then phase = "bet" clamp_bet() message = "Again?" snail.hint("UP/DOWN bet OK deal BACK menu") end end end function draw() if phase == "bet" then snail.center(true) snail.image("billy") snail.title("$" .. bank) snail.small(string.format("%d won %d lost %d pushed", wins, losses, pushes)) snail.gap() snail.title("BET $" .. bet) snail.gap() snail.text(message) return end snail.center(true) snail.small("BILLY") snail.cards(hand_spec(billy, phase == "play")) snail.small(phase == "play" and ("showing " .. billy[1].v) or ("total " .. total(billy))) snail.gap() snail.small("YOU" .. (doubled and " (doubled)" or "")) snail.cards(hand_spec(you, false)) snail.small(string.format("total %d%s", total(you), soft(you) and " soft" or "")) snail.gap() if phase == "over" then snail.title(outcome .. (net > 0 and (" +$" .. net) or net < 0 and (" -$" .. -net) or "")) snail.text(message) snail.gap() snail.small("bank $" .. bank) else snail.text(message) end end