A comprehensive, developer-focused guide to representing the Basic Strategy as JSON and performing lightning-fast lookups in code.
🎯 Learn Basic Strategy — Interactive TrainerThe Basic Strategy is a deterministic decision table that minimizes the house edge to just 0.5%. For developers building a simulator, trainer, or AI-assisted dealer, encoding that table as structured data (JSON) and using a fast lookup function is far simpler and more maintainable than hundreds of nested conditionals.
This approach separates data from logic, making your code more readable, testable, and adaptable to different rule variations. Whether you're building a casino game, training application, or statistical analyzer, this pattern will save you time and reduce bugs.
Store the strategy chart as a JSON object. Each key represents the player's hand row (hard totals, soft totals and pairs), and each value is a map from the dealer up-card to the action code:
// small excerpt (full table would include all rows)
const blackjackStrategy = {
"5-8": {"2":"H","3":"H","4":"H","5":"H","6":"H","7":"H","8":"H","9":"H","10":"H","A":"H"},
"9": {"2":"H","3":"D","4":"D","5":"D","6":"D","7":"H","8":"H","9":"H","10":"H","A":"H"},
"10": {"2":"D","3":"D","4":"D","5":"D","6":"D","7":"D","8":"D","9":"D","10":"H","A":"H"},
"11": {"2":"D","3":"D","4":"D","5":"D","6":"D","7":"D","8":"D","9":"D","10":"D","A":"D"},
"12": {"2":"H","3":"H","4":"S","5":"S","6":"S","7":"H","8":"H","9":"H","10":"H","A":"H"},
"13": {"2":"S","3":"S","4":"S","5":"S","6":"S","7":"H","8":"H","9":"H","10":"H","A":"H"},
"14": {"2":"S","3":"S","4":"S","5":"S","6":"S","7":"H","8":"H","9":"H","10":"H","A":"H"},
"15": {"2":"S","3":"S","4":"S","5":"S","6":"S","7":"H","8":"H","9":"H","10":"SR","A":"H"},
"16": {"2":"S","3":"S","4":"S","5":"S","6":"S","7":"H","8":"H","9":"SR","10":"SR","A":"H"},
"A2": {"2":"H","3":"H","4":"H","5":"D","6":"D","7":"H","8":"H","9":"H","10":"H","A":"H"},
"A3": {"2":"H","3":"H","4":"D","5":"D","6":"D","7":"H","8":"H","9":"H","10":"H","A":"H"},
"A4": {"2":"H","3":"H","4":"D","5":"D","6":"D","7":"H","8":"H","9":"H","10":"H","A":"H"},
"A5": {"2":"H","3":"H","4":"D","5":"D","6":"D","7":"H","8":"H","9":"H","10":"H","A":"H"},
"A6": {"2":"H","3":"D","4":"D","5":"D","6":"D","7":"H","8":"H","9":"H","10":"H","A":"H"},
"A7": {"2":"S","3":"D/S","4":"D/S","5":"D/S","6":"D/S","7":"S","8":"S","9":"H","10":"H","A":"H"},
"2-2": {"2":"P","3":"P","4":"P","5":"H","6":"P","7":"P","8":"H","9":"H","10":"H","A":"H"},
"3-3": {"2":"P","3":"P","4":"P","5":"H","6":"P","7":"P","8":"H","9":"H","10":"H","A":"H"},
"4-4": {"2":"H","3":"H","4":"H","5":"P","6":"P","7":"H","8":"H","9":"H","10":"H","A":"H"},
"6-6": {"2":"P","3":"P","4":"P","5":"P","6":"P","7":"H","8":"H","9":"H","10":"H","A":"H"},
"7-7": {"2":"P","3":"P","4":"P","5":"P","6":"P","7":"P","8":"H","9":"H","10":"H","A":"H"},
"8-8": {"2":"P","3":"P","4":"P","5":"P","6":"P","7":"P","8":"P","9":"P","10":"P","A":"P"},
"9-9": {"2":"P","3":"P","4":"P","5":"P","6":"P","7":"S","8":"P","9":"P","10":"S","A":"S"}
};
Use a small function to look up the recommended move by table key and dealer up-card. This returns the action code directly and is O(1) complexity.
// lookup function
function getBlackjackAction(playerHand, dealerCard, strategyTable) {
if (!strategyTable) strategyTable = blackjackStrategy; // fallback to global if not passed
playerHand = String(playerHand).toUpperCase();
dealerCard = String(dealerCard).toUpperCase();
const row = strategyTable[playerHand];
if (!row) return null; // unknown row
return row[dealerCard] || null; // action or null if unknown dealer card
}
// examples
console.log(getBlackjackAction("A7", "6")); // "D/S"
console.log(getBlackjackAction("16", "10")); // "SR"
console.log(getBlackjackAction("9-9", "7")); // "S"
This approach keeps strategy and logic separated: the JSON is the data, the function is the lookup/adapter. It's easy to swap in rule variants (e.g., dealer hits/stands on soft 17, or different surrender rules) by loading a different JSON object.
O(1) complexity means consistent performance even with complex rule variations.
Update strategy rules by modifying JSON data, not complex conditional logic.
Perfect for simulations running thousands of hands per second.
For production use you might want the function to accept raw cards (e.g. ['A','6']) instead of pre-computed row keys. Implement a small normalizer that:
// example normalizer (very small)
function normalizeHand(cards) {
// cards: array of ranks, e.g. ['A','6'] or ['9','9'] or ['10','6']
const ranks = cards.map(c => String(c).toUpperCase());
if (ranks.length === 2 && ranks[0] === ranks[1]) {
// pair
return `${ranks[0]}-${ranks[1]}`;
}
const hasAce = ranks.includes('A');
const numeric = ranks.reduce((sum, r) => sum + (r === 'A' ? 11 : (r === 'J' || r === 'Q' || r === 'K' ? 10 : parseInt(r,10) || 0)), 0);
// naive soft detection for two-card hands
if (hasAce && ranks.length === 2) {
const other = ranks.find(r => r !== 'A');
return `A${other}`;
}
return String(numeric);
}
Combine the normalizer with getBlackjackAction to accept either structured keys or raw card arrays. This makes your API more flexible and user-friendly.
Data-driven approach keeps logic readable and maintainable. No more spaghetti code with nested if-statements.
Constant-time lookup suitable for simulations that run thousands of hands per minute.
Swap tables for rule variants or implement training modes without touching your core logic.
Easy to unit test with different strategy tables and edge cases.
Strategy data can be loaded from files, APIs, or databases independently.
Support different regional blackjack variations with separate JSON files.
Ready to put this knowledge into practice? Try our interactive trainer to see the Basic Strategy in action:
🎯 21Count — Interactive Basic Strategy Trainer
Includes the full strategy table, lookup functions, normalizer, and usage examples ready for your projects.