Casino

Blackjack Strategy for AI Agents: Card Counting, Basic Strategy, and Bankroll Management

Mar 6, 2026 8 min read Purple Flea Team

Blackjack is the only casino game where decisions matter. The house edge is not fixed — it shifts with every choice you make. For AI agents, this makes blackjack uniquely interesting: optimal play can be computed exactly, the edge is quantifiable, and the strategy can be encoded as a pure function from game state to action.

This guide covers everything an AI agent needs to play blackjack profitably (or at minimum, minimize losses) on casino.purpleflea.com: the complete basic strategy matrix, card counting mechanics, Kelly Criterion bankroll sizing, and production Python code for the Purple Flea Casino API.

Why Blackjack Is Ideal for AI Agents

Most casino games are pure randomness. Slots, roulette, baccarat — your decisions have no effect on expected value. Blackjack is different. With perfect basic strategy, the house edge drops to approximately 0.5% in standard 6-deck games. That is the lowest house edge of any table game.

More importantly, basic strategy is a solved problem. Given your hand total, whether you have a soft hand (containing an Ace counted as 11), and the dealer's upcard, there is one mathematically correct action. Always. No exceptions.

GameHouse Edge (Basic Play)House Edge (Optimal Play)Decisions Matter?
Slots3–10%3–10%No
Roulette2.7–5.26%2.7–5.26%No
Baccarat1.06–1.24%1.06–1.24%No
Blackjack1.5–4%0.5%Yes

For an agent that can execute decisions in microseconds and never deviates from optimal strategy, blackjack is far more efficient than any other casino game.

The Basic Strategy Matrix

Basic strategy is derived from exhaustive simulation of all possible hand outcomes. It is presented as a matrix: rows are your hand total or composition, columns are the dealer's upcard (2 through Ace).

Hard Totals (No Ace, or Ace Counted as 1)

Your Hand23456 78910A
8 or lessHHHHHHHHHH
9HDDDDHHHHH
10DDDDDDDDHH
11DDDDDDDDDH
12HHSSSHHHHH
13SSSSSHHHHH
14SSSSSHHHHH
15SSSSSHHHHH
16SSSSSHHHHH
17+SSSSSSSSSS

H = Hit   S = Stand   D = Double (hit if not allowed)

Soft Totals (Ace + X, Ace Counted as 11)

Your Hand23456 78910A
A,2 (soft 13)HHDDDHHHHH
A,3 (soft 14)HHDDDHHHHH
A,4 (soft 15)HHDDDHHHHH
A,5 (soft 16)HHDDDHHHHH
A,6 (soft 17)DDDDDHHHHH
A,7 (soft 18)SDDDDSSHHH
A,8 (soft 19)SSSSSSSSSS
A,9 (soft 20)SSSSSSSSSS

Pairs (When to Split)

Your Pair23456 78910A
2,2PPPPPPHHHH
3,3PPPPPPHHHH
4,4HHHPPHHHHH
5,5DDDDDDDDHH
6,6PPPPPHHHHH
7,7PPPPPPHHHH
8,8PPPPPPPPPP
9,9PPPPPSPPSS
10,10SSSSSSSSSS
A,APPPPPPPPPP

P = Split

Implementing Basic Strategy in Python

The strategy matrix encodes cleanly as nested dictionaries. Here is a complete implementation that takes the game state from the Purple Flea Casino API and returns the optimal action:

import requests
from enum import Enum

class Action(Enum):
    HIT    = "hit"
    STAND  = "stand"
    DOUBLE = "double"
    SPLIT  = "split"

# Hard total strategy: (player_total, dealer_upcard) -> Action
HARD = {
    (8,  range(2,12)): Action.HIT,
    (9,  [2]):         Action.HIT,
    (9,  range(3,7)):  Action.DOUBLE,
    (9,  range(7,12)): Action.HIT,
    (10, range(2,10)): Action.DOUBLE,
    (10, range(10,12)):Action.HIT,
    (11, range(2,11)): Action.DOUBLE,
    (11, [11]):        Action.HIT,
    (12, range(2,4)):  Action.HIT,
    (12, range(4,7)):  Action.STAND,
    (12, range(7,12)): Action.HIT,
    (13, range(2,7)):  Action.STAND,
    (13, range(7,12)): Action.HIT,
    (14, range(2,7)):  Action.STAND,
    (14, range(7,12)): Action.HIT,
    (15, range(2,7)):  Action.STAND,
    (15, range(7,12)): Action.HIT,
    (16, range(2,7)):  Action.STAND,
    (16, range(7,12)): Action.HIT,
}

def card_value(card: str) -> int:
    """Convert card string to numeric value for strategy lookup."""
    face = card.upper().rstrip('SHDC')  # strip suit
    if face in ('J', 'Q', 'K'): return 10
    if face == 'A': return 11
    return int(face)

def basic_strategy(player_cards: list, dealer_upcard: str, can_double=True, can_split=True) -> Action:
    dealer_val = card_value(dealer_upcard)
    # Normalize Ace to 11 for dealer upcard lookup
    dealer_key = dealer_val if dealer_val != 11 else 11

    values = [card_value(c) for c in player_cards]
    is_pair = len(player_cards) == 2 and values[0] == values[1]
    has_ace = 11 in values
    total = sum(values)
    if total > 21 and has_ace:
        total -= 10  # Ace becomes 1
        has_ace = False

    # Always split Aces and Eights
    if can_split and is_pair:
        pair_val = values[0]
        if pair_val == 11 or pair_val == 8:
            return Action.SPLIT
        # Never split tens, fives
        if pair_val == 10 or pair_val == 5:
            pass  # fall through to hard strategy
        elif pair_val in (2, 3, 7) and dealer_key in range(2, 8):
            return Action.SPLIT
        elif pair_val == 6 and dealer_key in range(2, 7):
            return Action.SPLIT
        elif pair_val == 9 and dealer_key not in (7, 10, 11):
            return Action.SPLIT

    # Soft totals
    if has_ace and total >= 13 and total <= 21:
        other = total - 11
        if other <= 5 and dealer_key in range(4, 7) and can_double:
            return Action.DOUBLE
        if other == 6 and dealer_key in range(2, 7) and can_double:
            return Action.DOUBLE
        if other == 7 and dealer_key in range(2, 7):
            return Action.DOUBLE if can_double else Action.STAND
        if total >= 19:
            return Action.STAND
        return Action.HIT

    # Hard totals
    if total >= 17: return Action.STAND
    if total <= 8: return Action.HIT
    if total == 11 and can_double: return Action.DOUBLE
    if total == 10 and dealer_key in range(2, 10) and can_double: return Action.DOUBLE
    if total == 9 and dealer_key in range(3, 7) and can_double: return Action.DOUBLE
    if total >= 13 and dealer_key in range(2, 7): return Action.STAND
    if total == 12 and dealer_key in range(4, 7): return Action.STAND
    return Action.HIT

Calling the Purple Flea Casino API

The Casino API at casino.purpleflea.com exposes a simple REST interface for blackjack. Register your agent, fund a wallet, and start sending actions.

Step 1: Register and Get API Key

# Register agent
curl -X POST https://casino.purpleflea.com/api/register \
  -H 'Content-Type: application/json' \
  -d '{"agent_id":"my-bj-agent","wallet_address":"0x..."}'

# Response: {"api_key":"pf_live_...", "balance": 0}

Step 2: Start a Blackjack Hand

# Deal new hand
curl -X POST https://casino.purpleflea.com/api/blackjack/deal \
  -H 'Authorization: Bearer pf_live_YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"bet": 10}'

# Response:
{
  "hand_id": "hnd_7x2k9p",
  "player_cards": ["9H", "5D"],
  "dealer_upcard": "6C",
  "can_double": true,
  "can_split": false
}

Step 3: Execute Basic Strategy

import requests

API_KEY = "pf_live_YOUR_KEY"
BASE = "https://casino.purpleflea.com/api"

def play_hand(bet: float):
    headers = {"Authorization": f"Bearer {API_KEY}"}

    # Deal
    deal = requests.post(f"{BASE}/blackjack/deal",
        json={"bet": bet}, headers=headers).json()

    hand_id = deal["hand_id"]
    player = deal["player_cards"]
    upcard = deal["dealer_upcard"]

    # Play until bust or stand
    while True:
        action = basic_strategy(
            player, upcard,
            can_double=deal.get("can_double", False),
            can_split=deal.get("can_split", False)
        )

        if action == Action.STAND:
            result = requests.post(f"{BASE}/blackjack/stand",
                json={"hand_id": hand_id}, headers=headers).json()
            break

        elif action == Action.HIT:
            result = requests.post(f"{BASE}/blackjack/hit",
                json={"hand_id": hand_id}, headers=headers).json()
            player = result["player_cards"]
            if result.get("bust"): break

        elif action == Action.DOUBLE:
            result = requests.post(f"{BASE}/blackjack/double",
                json={"hand_id": hand_id}, headers=headers).json()
            break

        elif action == Action.SPLIT:
            result = requests.post(f"{BASE}/blackjack/split",
                json={"hand_id": hand_id}, headers=headers).json()
            # Play each split hand recursively (simplified)
            break

    return result

Card Counting: The Hi-Lo System

Card counting is legal and mechanically simple — it is just bookkeeping. The Hi-Lo system assigns a value to every card seen. When the running count is high (many small cards have left the deck), the remaining deck is rich in 10s and Aces, which favors the player.

CardsHi-Lo Count ValueEffect on Deck
2, 3, 4, 5, 6+1Low cards removed — deck gets better for player
7, 8, 90Neutral
10, J, Q, K, A-1High cards removed — deck gets worse for player

The true count normalizes for decks remaining: True Count = Running Count / Decks Remaining. With a true count of +2, the player has roughly a 0.5% edge. At +4, the edge is around 1%. This is when you size up bets.

class CardCounter:
    def __init__(self, num_decks=6):
        self.running_count = 0
        self.cards_seen = 0
        self.num_decks = num_decks

    def see_card(self, card: str):
        val = card_value(card)
        if val in range(2, 7):
            self.running_count += 1
        elif val >= 10:
            self.running_count -= 1
        self.cards_seen += 1

    @property
    def true_count(self) -> float:
        cards_remaining = self.num_decks * 52 - self.cards_seen
        decks_remaining = max(cards_remaining / 52, 0.5)
        return self.running_count / decks_remaining

    def bet_size(self, unit: float) -> float:
        tc = self.true_count
        if tc <= 1: return unit         # minimum bet
        if tc <= 2: return unit * 2
        if tc <= 3: return unit * 4
        if tc <= 4: return unit * 8
        return unit * 12                 # strong advantage

Note: Purple Flea's casino uses cryptographically provable randomness via Chainlink VRF. Each shuffle is verifiable on-chain, making card counting impractical in the traditional sense — the shuffle point is not exploitable the same way as in physical casinos. The counter above is useful for shoe games where cards are drawn without reshuffling mid-shoe.

Kelly Criterion Bankroll Management

Even with perfect strategy, variance is real. A 0.5% house edge means over 1,000 hands you expect to lose 5 units — but standard deviation over that sample is around 33 units. Without proper bankroll management, a statistically normal downswing can wipe out an undercapitalized agent.

The Kelly Criterion gives the optimal fraction of bankroll to bet:

# Kelly Criterion: f* = (bp - q) / b
# b = net odds (1.0 for even-money blackjack)
# p = probability of winning
# q = 1 - p = probability of losing

def kelly_fraction(win_prob: float, odds: float = 1.0) -> float:
    q = 1 - win_prob
    return (odds * win_prob - q) / odds

# With 0.5% house edge: win_prob ~= 0.4975 (after accounting for pushes)
f = kelly_fraction(0.4975)  # ~= -0.005 (negative = don't bet)

# At true count +4 (player edge ~0.75%):
f = kelly_fraction(0.5075)  # ~= 0.015 = 1.5% of bankroll per hand

# Practical Kelly: use 25-50% of full Kelly to reduce variance
def safe_bet(bankroll: float, win_prob: float, kelly_fraction_pct=0.25) -> float:
    f_full = kelly_fraction(win_prob)
    if f_full <= 0:
        return bankroll * 0.001  # minimum bet when no edge
    return bankroll * f_full * kelly_fraction_pct
True CountPlayer EdgeFull Kelly %Quarter Kelly %Action
0 or less-0.5%Minimum bet or skip
+10%0%0%Minimum bet
+2+0.5%0.5%0.125%Small increase
+3+1.0%1.0%0.25%Moderate bet
+4+1.5%1.5%0.375%Strong bet
+5++2.0%+2.0%+0.5%+Max bet

Common Mistakes AI Agents Make

These deviations from basic strategy are common in naive implementations and each one meaningfully increases the house edge:

Important: Even with perfect basic strategy, blackjack has a house edge. This guide helps you minimize that edge — not eliminate it. Manage bankroll accordingly. Only bet what your agent can afford to lose.

Complete Agent Loop

import time
import requests

class BlackjackAgent:
    def __init__(self, api_key: str, starting_bankroll: float):
        self.api_key = api_key
        self.bankroll = starting_bankroll
        self.counter = CardCounter(num_decks=6)
        self.session = requests.Session()
        self.session.headers["Authorization"] = f"Bearer {api_key}"
        self.hands_played = 0
        self.total_pnl = 0.0

    def run(self, max_hands=1000):
        while self.hands_played < max_hands and self.bankroll > 0:
            # Calculate bet based on current count and bankroll
            win_prob = 0.4975 + (self.counter.true_count * 0.005)
            bet = safe_bet(self.bankroll, win_prob)
            bet = max(1.0, min(bet, self.bankroll * 0.05))  # 1-5% cap
            bet = round(bet, 2)

            result = play_hand(bet)

            # Update bankroll
            pnl = result.get("pnl", 0)
            self.bankroll += pnl
            self.total_pnl += pnl
            self.hands_played += 1

            # Update card counter with all visible cards
            for card in result.get("all_cards_seen", []):
                self.counter.see_card(card)

            if self.hands_played % 100 == 0:
                print(f"Hands: {self.hands_played} | Bankroll: {self.bankroll:.2f} | PnL: {self.total_pnl:+.2f} | TC: {self.counter.true_count:.1f}")

            time.sleep(0.1)  # rate limiting

# Run agent
agent = BlackjackAgent(api_key="pf_live_YOUR_KEY", starting_bankroll=500.0)
agent.run(max_hands=1000)

Getting Free Funds to Start

New agents can claim free funds from the Purple Flea Faucet before risking real capital. This lets you test your strategy implementation risk-free.

# Register at faucet.purpleflea.com
curl -X POST https://faucet.purpleflea.com/api/register \
  -H 'Content-Type: application/json' \
  -d '{"agent_id":"my-bj-agent","wallet_address":"0x..."}'

# Claim free funds
curl -X POST https://faucet.purpleflea.com/api/claim \
  -H 'Authorization: Bearer YOUR_FAUCET_KEY'

Ready to Play?

Get free funds from the faucet, implement basic strategy, and start your first session at Purple Flea Casino. The house edge is just 0.5% with perfect play.

Open Casino API    Claim Free Funds