Created
April 11, 2025 22:22
-
-
Save secdev02/a2eab2f55ae031497dff03d8d21904e8 to your computer and use it in GitHub Desktop.
An AI generated Texas Holdem Simulator written by Claude, to demonstrate and teach with.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Texas Hold'em Poker Simulator</title> | |
<style> | |
body { | |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
max-width: 1000px; | |
margin: 0 auto; | |
padding: 20px; | |
background-color: #f5f5f5; | |
} | |
.container { | |
background-color: white; | |
border-radius: 8px; | |
padding: 20px; | |
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); | |
} | |
h1 { | |
text-align: center; | |
color: #2c3e50; | |
} | |
.control-panel { | |
display: flex; | |
justify-content: space-between; | |
margin-bottom: 20px; | |
padding: 15px; | |
background-color: #e8f4f8; | |
border-radius: 5px; | |
} | |
.control-group { | |
display: flex; | |
flex-direction: column; | |
} | |
label { | |
margin-bottom: 5px; | |
font-weight: bold; | |
} | |
button { | |
background-color: #27ae60; | |
color: white; | |
border: none; | |
padding: 10px 15px; | |
font-size: 16px; | |
border-radius: 5px; | |
cursor: pointer; | |
transition: background-color 0.3s; | |
} | |
button:hover { | |
background-color: #219653; | |
} | |
button:disabled { | |
background-color: #95a5a6; | |
cursor: not-allowed; | |
} | |
input, select { | |
padding: 8px; | |
border: 1px solid #ddd; | |
border-radius: 4px; | |
font-size: 16px; | |
} | |
.game-board { | |
margin-top: 20px; | |
} | |
.community-cards { | |
margin-bottom: 15px; | |
padding: 15px; | |
background-color: #f0f9ff; | |
border-radius: 5px; | |
border: 1px solid #bde0fe; | |
} | |
.results-table { | |
width: 100%; | |
border-collapse: collapse; | |
margin-bottom: 20px; | |
} | |
.results-table th, .results-table td { | |
border: 1px solid #ddd; | |
padding: 10px; | |
text-align: left; | |
} | |
.results-table th { | |
background-color: #34495e; | |
color: white; | |
} | |
.results-table tr:nth-child(even) { | |
background-color: #f2f2f2; | |
} | |
.winner-row { | |
background-color: #d4edda !important; | |
font-weight: bold; | |
} | |
.stats-container { | |
margin-top: 20px; | |
padding: 15px; | |
background-color: #f8f9fa; | |
border-radius: 5px; | |
border: 1px solid #e9ecef; | |
} | |
.card { | |
display: inline-block; | |
width: 70px; | |
height: 100px; | |
background-color: white; | |
border-radius: 8px; | |
box-shadow: 0 3px 6px rgba(0,0,0,0.3); | |
margin: 8px; | |
text-align: center; | |
position: relative; | |
padding: 8px 5px; | |
line-height: normal; | |
font-weight: bold; | |
} | |
.card-value { | |
display: block; | |
font-size: 16px; | |
margin-bottom: 5px; | |
} | |
.card-symbol { | |
display: block; | |
font-size: 30px; /* Much larger symbol */ | |
margin-top: 5px; | |
} | |
/* Four distinct colors for each suit */ | |
.hearts { | |
color: #e60000; /* Red */ | |
border: 2px solid #e60000; | |
background-color: #fff0f0; | |
} | |
.diamonds { | |
color: #0066cc; /* Blue */ | |
border: 2px solid #0066cc; | |
background-color: #f0f5ff; | |
} | |
.clubs { | |
color: #006600; /* Green */ | |
border: 2px solid #006600; | |
background-color: #f0fff0; | |
} | |
.spades { | |
color: #000000; /* Black */ | |
border: 2px solid #000000; | |
background-color: #f5f5f5; | |
} | |
#round-counter { | |
text-align: center; | |
font-size: 18px; | |
margin-bottom: 15px; | |
font-weight: bold; | |
} | |
.log-container { | |
margin-top: 20px; | |
border: 1px solid #ddd; | |
padding: 10px; | |
height: 300px; | |
overflow-y: auto; | |
background-color: #1e1e1e; | |
border-radius: 5px; | |
font-family: 'Consolas', 'Monaco', monospace; | |
color: #f0f0f0; | |
font-size: 12px; | |
white-space: pre-wrap; | |
} | |
#debug-mode { | |
width: 20px; | |
height: 20px; | |
} | |
#debug-btn { | |
background-color: #9c27b0; | |
} | |
#debug-btn:hover { | |
background-color: #7b1fa2; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<h1>Texas Hold'em Poker Simulator</h1> | |
<div class="control-panel"> | |
<div class="control-group"> | |
<label for="num-players">Number of Players:</label> | |
<input type="number" id="num-players" min="2" max="8" value="4"> | |
</div> | |
<div class="control-group"> | |
<label for="num-rounds">Number of Rounds:</label> | |
<input type="number" id="num-rounds" min="1" max="100" value="5"> | |
</div> | |
<div class="control-group"> | |
<label for="speed">Simulation Speed:</label> | |
<select id="speed"> | |
<option value="fast">Fast</option> | |
<option value="medium" selected>Medium</option> | |
<option value="slow">Slow</option> | |
</select> | |
</div> | |
<div class="control-group"> | |
<label> </label> | |
<button id="start-btn">Start Simulation</button> | |
</div> | |
<div class="control-group"> | |
<label> </label> | |
<button id="reset-btn" disabled>Reset</button> | |
</div> | |
<div class="control-group"> | |
<label for="debug-mode">Debug Mode:</label> | |
<input type="checkbox" id="debug-mode"> | |
</div> | |
<div class="control-group"> | |
<label> </label> | |
<button id="debug-btn">Run Debug</button> | |
</div> | |
</div> | |
<div id="round-counter">Round: 0 / 0</div> | |
<div class="game-board"> | |
<div class="community-cards"> | |
<h3>Community Cards</h3> | |
<div id="community-cards-display"></div> | |
</div> | |
<h3>Round Results</h3> | |
<table class="results-table" id="results-table"> | |
<thead> | |
<tr> | |
<th>Player</th> | |
<th>Cards</th> | |
<th>Hand Type</th> | |
<th>High Card</th> | |
<th>Rank</th> | |
</tr> | |
</thead> | |
<tbody id="results-body"> | |
<!-- Results will be populated here --> | |
</tbody> | |
</table> | |
</div> | |
<div class="stats-container"> | |
<h3>Game Statistics</h3> | |
<table class="results-table" id="stats-table"> | |
<thead> | |
<tr> | |
<th>Player</th> | |
<th>Wins</th> | |
<th>Win Percentage</th> | |
</tr> | |
</thead> | |
<tbody id="stats-body"> | |
<!-- Stats will be populated here --> | |
</tbody> | |
</table> | |
</div> | |
<div class="log-container" id="log-output"> | |
<!-- Console.log output will be captured here --> | |
</div> | |
</div> | |
<script> | |
/** | |
* Texas Hold'em Poker Simulator | |
* This script simulates a basic Texas Hold'em poker game | |
* and outputs the results of each round in CSV format. | |
*/ | |
// Card Constants | |
const SUITS = ['Hearts', 'Diamonds', 'Clubs', 'Spades']; | |
const VALUES = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King', 'Ace']; | |
// Hand Types (from lowest to highest) | |
const HAND_TYPES = [ | |
'High Card', | |
'Pair', | |
'Two Pair', | |
'Three of a Kind', | |
'Straight', | |
'Flush', | |
'Full House', | |
'Four of a Kind', | |
'Straight Flush', | |
'Royal Flush' | |
]; | |
// Debug mode flag | |
let debugMode = false; | |
// Debug log function | |
function debugLog(...args) { | |
if (debugMode) { | |
console.log(...args); | |
} | |
} | |
// Create a deck of cards | |
function createDeck() { | |
const deck = []; | |
for (const suit of SUITS) { | |
for (const value of VALUES) { | |
deck.push({ value, suit }); | |
} | |
} | |
if (debugMode) { | |
const suitCounts = {}; | |
for (const card of deck) { | |
suitCounts[card.suit] = (suitCounts[card.suit] || 0) + 1; | |
} | |
debugLog("Created deck with distribution:", suitCounts); | |
} | |
return deck; | |
} | |
// Shuffle the deck using Fisher-Yates algorithm | |
function shuffleDeck(deck) { | |
const shuffled = [...deck]; | |
if (debugMode) { | |
debugLog("Original deck order (first 10 cards):", | |
shuffled.slice(0, 10).map(card => `${card.value} of ${card.suit}`)); | |
} | |
for (let i = shuffled.length - 1; i > 0; i--) { | |
const j = Math.floor(Math.random() * (i + 1)); | |
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; | |
if (debugMode && i > shuffled.length - 11) { | |
debugLog(`Swap at positions ${i} and ${j}: ${shuffled[i].value} of ${shuffled[i].suit} <-> ${shuffled[j].value} of ${shuffled[j].suit}`); | |
} | |
} | |
if (debugMode) { | |
debugLog("Shuffled deck order (first 10 cards):", | |
shuffled.slice(0, 10).map(card => `${card.value} of ${card.suit}`)); | |
const suitCounts = {}; | |
for (const card of shuffled) { | |
suitCounts[card.suit] = (suitCounts[card.suit] || 0) + 1; | |
} | |
debugLog("Shuffled deck distribution:", suitCounts); | |
// Check randomness by counting value distribution | |
const valueCounts = {}; | |
for (const card of shuffled) { | |
valueCounts[card.value] = (valueCounts[card.value] || 0) + 1; | |
} | |
debugLog("Shuffled deck value distribution:", valueCounts); | |
} | |
return shuffled; | |
} | |
// Deal cards to players | |
function dealCards(deck, numPlayers, numCardsPerPlayer) { | |
// Create a copy of the deck to avoid modifying the original | |
const deckCopy = [...deck]; | |
if (debugMode) { | |
debugLog(`Dealing ${numCardsPerPlayer} cards to ${numPlayers} players from deck with ${deckCopy.length} cards`); | |
} | |
const hands = []; | |
for (let i = 0; i < numPlayers; i++) { | |
hands.push([]); | |
} | |
for (let j = 0; j < numCardsPerPlayer; j++) { | |
for (let i = 0; i < numPlayers; i++) { | |
if (deckCopy.length > 0) { | |
const card = deckCopy.pop(); | |
hands[i].push(card); | |
if (debugMode) { | |
debugLog(`Dealt ${card.value} of ${card.suit} to Player ${i+1} (card ${j+1})`); | |
} | |
} else { | |
debugLog("Warning: Deck ran out of cards during dealing to players"); | |
} | |
} | |
} | |
if (debugMode) { | |
hands.forEach((hand, i) => { | |
debugLog(`Player ${i+1} hand: ${hand.map(c => `${c.value} of ${c.suit}`).join(', ')}`); | |
}); | |
// Check suit distribution in dealt hands | |
const suitCounts = { 'Hearts': 0, 'Diamonds': 0, 'Clubs': 0, 'Spades': 0 }; | |
hands.forEach(hand => { | |
hand.forEach(card => { | |
suitCounts[card.suit]++; | |
}); | |
}); | |
debugLog("Suit distribution in player hands:", suitCounts); | |
} | |
return hands; | |
} | |
// Deal community cards | |
function dealCommunityCards(deck, numCards) { | |
// Create a copy of the deck to avoid modifying the original | |
const deckCopy = [...deck]; | |
if (debugMode) { | |
debugLog(`Dealing ${numCards} community cards from deck with ${deckCopy.length} cards`); | |
} | |
const communityCards = []; | |
for (let i = 0; i < numCards; i++) { | |
if (deckCopy.length > 0) { | |
const card = deckCopy.pop(); | |
communityCards.push(card); | |
if (debugMode) { | |
debugLog(`Community card ${i+1}: ${card.value} of ${card.suit}`); | |
} | |
} else { | |
debugLog("Warning: Deck ran out of cards during dealing community cards"); | |
} | |
} | |
if (debugMode && communityCards.length > 0) { | |
// Check suit distribution in community cards | |
const suitCounts = { 'Hearts': 0, 'Diamonds': 0, 'Clubs': 0, 'Spades': 0 }; | |
communityCards.forEach(card => { | |
suitCounts[card.suit]++; | |
}); | |
debugLog("Suit distribution in community cards:", suitCounts); | |
debugLog("Community cards: " + communityCards.map(c => `${c.value} of ${c.suit}`).join(', ')); | |
} | |
return communityCards; | |
} | |
// Format card for display | |
function formatCard(card) { | |
return `${card.value} of ${card.suit}`; | |
} | |
// Get card numerical value for comparison | |
function getCardValue(card) { | |
return VALUES.indexOf(card.value); | |
} | |
// Evaluate a hand (player cards + community cards) | |
function evaluateHand(playerCards, communityCards) { | |
const allCards = [...playerCards, ...communityCards]; | |
// Check for Royal Flush | |
if (hasRoyalFlush(allCards)) return { type: HAND_TYPES[9], rank: 9, highCard: getHighCard(allCards) }; | |
// Check for Straight Flush | |
const straightFlushResult = hasStraightFlush(allCards); | |
if (straightFlushResult.found) return { type: HAND_TYPES[8], rank: 8, highCard: straightFlushResult.highCard }; | |
// Check for Four of a Kind | |
const fourOfAKindResult = hasFourOfAKind(allCards); | |
if (fourOfAKindResult.found) return { type: HAND_TYPES[7], rank: 7, highCard: fourOfAKindResult.highCard }; | |
// Check for Full House | |
const fullHouseResult = hasFullHouse(allCards); | |
if (fullHouseResult.found) return { type: HAND_TYPES[6], rank: 6, highCard: fullHouseResult.highCard }; | |
// Check for Flush | |
const flushResult = hasFlush(allCards); | |
if (flushResult.found) return { type: HAND_TYPES[5], rank: 5, highCard: flushResult.highCard }; | |
// Check for Straight | |
const straightResult = hasStraight(allCards); | |
if (straightResult.found) return { type: HAND_TYPES[4], rank: 4, highCard: straightResult.highCard }; | |
// Check for Three of a Kind | |
const threeOfAKindResult = hasThreeOfAKind(allCards); | |
if (threeOfAKindResult.found) return { type: HAND_TYPES[3], rank: 3, highCard: threeOfAKindResult.highCard }; | |
// Check for Two Pair | |
const twoPairResult = hasTwoPair(allCards); | |
if (twoPairResult.found) return { type: HAND_TYPES[2], rank: 2, highCard: twoPairResult.highCard }; | |
// Check for Pair | |
const pairResult = hasPair(allCards); | |
if (pairResult.found) return { type: HAND_TYPES[1], rank: 1, highCard: pairResult.highCard }; | |
// High Card | |
return { type: HAND_TYPES[0], rank: 0, highCard: getHighCard(allCards) }; | |
} | |
// Find the highest card in a set of cards | |
function getHighCard(cards) { | |
let highestCard = cards[0]; | |
for (const card of cards) { | |
if (getCardValue(card) > getCardValue(highestCard)) { | |
highestCard = card; | |
} | |
} | |
return highestCard; | |
} | |
// Check for a pair | |
function hasPair(cards) { | |
const valueCount = getValueCount(cards); | |
for (const value in valueCount) { | |
if (valueCount[value] === 2) { | |
const pairCard = cards.find(card => card.value === value); | |
return { found: true, highCard: pairCard }; | |
} | |
} | |
return { found: false }; | |
} | |
// Check for two pair | |
function hasTwoPair(cards) { | |
const valueCount = getValueCount(cards); | |
const pairs = []; | |
for (const value in valueCount) { | |
if (valueCount[value] === 2) { | |
pairs.push(value); | |
} | |
} | |
if (pairs.length >= 2) { | |
// Get the higher pair for high card | |
const highestPairValue = pairs.sort((a, b) => VALUES.indexOf(b) - VALUES.indexOf(a))[0]; | |
const highCard = cards.find(card => card.value === highestPairValue); | |
return { found: true, highCard }; | |
} | |
return { found: false }; | |
} | |
// Check for three of a kind | |
function hasThreeOfAKind(cards) { | |
const valueCount = getValueCount(cards); | |
for (const value in valueCount) { | |
if (valueCount[value] === 3) { | |
const threeCard = cards.find(card => card.value === value); | |
return { found: true, highCard: threeCard }; | |
} | |
} | |
return { found: false }; | |
} | |
// Check for a straight | |
function hasStraight(cards) { | |
// Get unique values and sort them | |
let values = [...new Set(cards.map(card => getCardValue(card)))]; | |
values.sort((a, b) => a - b); | |
// Handle Ace as low | |
if (values.includes(VALUES.indexOf('Ace')) && values.includes(VALUES.indexOf('2'))) { | |
const aceAsLow = -1; | |
values = [aceAsLow, ...values]; | |
} | |
// Find the longest sequence | |
let currentSeq = 1; | |
let maxSeq = 1; | |
let highCardIndex = values[0]; | |
for (let i = 1; i < values.length; i++) { | |
if (values[i] === values[i-1] + 1) { | |
currentSeq++; | |
if (currentSeq > maxSeq) { | |
maxSeq = currentSeq; | |
highCardIndex = values[i]; | |
} | |
} else if (values[i] !== values[i-1]) { | |
currentSeq = 1; | |
} | |
} | |
if (maxSeq >= 5) { | |
const highCardValue = VALUES[highCardIndex >= VALUES.length ? VALUES.length - 1 : highCardIndex]; | |
const highCard = cards.find(card => card.value === highCardValue) || cards[0]; // Fallback | |
return { found: true, highCard }; | |
} | |
return { found: false }; | |
} | |
// Check for a flush | |
function hasFlush(cards) { | |
const suitCount = {}; | |
for (const card of cards) { | |
suitCount[card.suit] = (suitCount[card.suit] || 0) + 1; | |
} | |
for (const suit in suitCount) { | |
if (suitCount[suit] >= 5) { | |
// Get the highest card of the flush suit | |
const flushCards = cards.filter(card => card.suit === suit); | |
const highCard = getHighCard(flushCards); | |
return { found: true, highCard }; | |
} | |
} | |
return { found: false }; | |
} | |
// Check for a full house | |
function hasFullHouse(cards) { | |
const threeOfAKindResult = hasThreeOfAKind(cards); | |
if (!threeOfAKindResult.found) return { found: false }; | |
// Make a copy of cards without the three of a kind | |
const threeValue = threeOfAKindResult.highCard.value; | |
const remainingCards = cards.filter(card => card.value !== threeValue); | |
const pairResult = hasPair(remainingCards); | |
if (pairResult.found) { | |
return { found: true, highCard: threeOfAKindResult.highCard }; | |
} | |
return { found: false }; | |
} | |
// Check for four of a kind | |
function hasFourOfAKind(cards) { | |
const valueCount = getValueCount(cards); | |
for (const value in valueCount) { | |
if (valueCount[value] === 4) { | |
const fourCard = cards.find(card => card.value === value); | |
return { found: true, highCard: fourCard }; | |
} | |
} | |
return { found: false }; | |
} | |
// Check for a straight flush | |
function hasStraightFlush(cards) { | |
// Check for flush first | |
const flushResult = hasFlush(cards); | |
if (!flushResult.found) return { found: false }; | |
// Now check if the flush cards form a straight | |
const flushSuit = flushResult.highCard.suit; | |
const flushCards = cards.filter(card => card.suit === flushSuit); | |
const straightResult = hasStraight(flushCards); | |
if (straightResult.found) { | |
return { found: true, highCard: straightResult.highCard }; | |
} | |
return { found: false }; | |
} | |
// Check for a royal flush | |
function hasRoyalFlush(cards) { | |
const straightFlushResult = hasStraightFlush(cards); | |
if (!straightFlushResult.found) return false; | |
// A royal flush is a straight flush with an Ace high | |
return straightFlushResult.highCard.value === 'Ace'; | |
} | |
// Helper to count occurrences of each card value | |
function getValueCount(cards) { | |
const valueCount = {}; | |
for (const card of cards) { | |
valueCount[card.value] = (valueCount[card.value] || 0) + 1; | |
} | |
return valueCount; | |
} | |
// Determine the winner | |
function determineWinner(playerHands, communityCards) { | |
const results = []; | |
for (let i = 0; i < playerHands.length; i++) { | |
const handEvaluation = evaluateHand(playerHands[i], communityCards); | |
results.push({ | |
player: i + 1, | |
hand: playerHands[i], | |
evaluation: handEvaluation | |
}); | |
} | |
// Sort by hand rank, then by high card if tied | |
results.sort((a, b) => { | |
if (b.evaluation.rank !== a.evaluation.rank) { | |
return b.evaluation.rank - a.evaluation.rank; | |
} | |
return getCardValue(b.evaluation.highCard) - getCardValue(a.evaluation.highCard); | |
}); | |
return results; | |
} | |
// UI-related code | |
const startBtn = document.getElementById('start-btn'); | |
const resetBtn = document.getElementById('reset-btn'); | |
const debugBtn = document.getElementById('debug-btn'); | |
const debugModeCheckbox = document.getElementById('debug-mode'); | |
const numPlayersInput = document.getElementById('num-players'); | |
const numRoundsInput = document.getElementById('num-rounds'); | |
const speedSelect = document.getElementById('speed'); | |
const roundCounter = document.getElementById('round-counter'); | |
const communityCardsDisplay = document.getElementById('community-cards-display'); | |
const resultsBody = document.getElementById('results-body'); | |
const statsBody = document.getElementById('stats-body'); | |
const logOutput = document.getElementById('log-output'); | |
// Intercept console.log to display in the log container | |
const originalConsoleLog = console.log; | |
console.log = function(...args) { | |
originalConsoleLog.apply(console, args); | |
const message = args.map(arg => | |
typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg | |
).join(' '); | |
logOutput.innerHTML += message + '<br>'; | |
logOutput.scrollTop = logOutput.scrollHeight; | |
}; | |
// Speed settings in milliseconds | |
const speeds = { | |
fast: 500, | |
medium: 1000, | |
slow: 2000 | |
}; | |
let currentRound = 0; | |
let totalRounds = 0; | |
let winCount = {}; | |
let simulationRunning = false; | |
let timeoutIds = []; | |
// Debug mode toggle | |
debugModeCheckbox.addEventListener('change', function() { | |
debugMode = this.checked; | |
console.log(`Debug mode ${debugMode ? 'enabled' : 'disabled'}`); | |
}); | |
// Debug test function | |
function runDebugTests() { | |
console.log("=== STARTING DEBUG TESTS ==="); | |
logOutput.innerHTML = ''; | |
// Temporarily force debug mode on | |
const previousDebugMode = debugMode; | |
debugMode = true; | |
// Test 1: Check deck creation | |
console.log("\n--- TEST 1: DECK CREATION ---"); | |
const deck = createDeck(); | |
console.log(`Deck created with ${deck.length} cards`); | |
// Test 2: Check shuffle randomness with multiple shuffles | |
console.log("\n--- TEST 2: SHUFFLE RANDOMNESS ---"); | |
const numShuffles = 10; | |
const shuffledDecks = []; | |
// Perform multiple shuffles | |
for (let i = 0; i < numShuffles; i++) { | |
shuffledDecks.push(shuffleDeck([...deck])); | |
} | |
// Analyze position changes of cards | |
analyzeShuffleRandomness(deck, shuffledDecks); | |
// Test 3: Deal cards multiple times and check suit distribution | |
console.log("\n--- TEST 3: DEALING RANDOMNESS ---"); | |
const numDeals = 10; | |
const suitDistribution = { 'Hearts': 0, 'Diamonds': 0, 'Clubs': 0, 'Spades': 0 }; | |
const valueDistribution = {}; | |
VALUES.forEach(value => valueDistribution[value] = 0); | |
for (let i = 0; i < numDeals; i++) { | |
const shuffled = shuffleDeck([...deck]); | |
const playerHands = dealCards(shuffled, 4, 2); | |
const communityCards = dealCommunityCards(shuffled, 5); | |
// Count cards in this deal | |
const allDealtCards = [...playerHands.flat(), ...communityCards]; | |
allDealtCards.forEach(card => { | |
suitDistribution[card.suit]++; | |
valueDistribution[card.value]++; | |
}); | |
} | |
// Calculate expected distribution | |
const totalDealtCards = numDeals * (4 * 2 + 5); // 4 players * 2 cards + 5 community cards per deal | |
const expectedPerSuit = totalDealtCards / 4; | |
const expectedPerValue = totalDealtCards / 13; | |
console.log(`Analysis of ${numDeals} deals (${totalDealtCards} cards total):`); | |
console.log(`Expected per suit: ${expectedPerSuit.toFixed(2)}`); | |
console.log("Suit distribution:", suitDistribution); | |
// Calculate deviation from expected | |
let maxSuitDeviation = 0; | |
SUITS.forEach(suit => { | |
const deviation = Math.abs(suitDistribution[suit] - expectedPerSuit) / expectedPerSuit * 100; | |
console.log(`${suit}: ${deviation.toFixed(2)}% deviation from expected`); | |
maxSuitDeviation = Math.max(maxSuitDeviation, deviation); | |
}); | |
console.log(`Maximum suit deviation: ${maxSuitDeviation.toFixed(2)}%`); | |
console.log(`Expected per value: ${expectedPerValue.toFixed(2)}`); | |
let maxValueDeviation = 0; | |
Object.keys(valueDistribution).forEach(value => { | |
const deviation = Math.abs(valueDistribution[value] - expectedPerValue) / expectedPerValue * 100; | |
maxValueDeviation = Math.max(maxValueDeviation, deviation); | |
}); | |
console.log(`Maximum value deviation: ${maxValueDeviation.toFixed(2)}%`); | |
// Run a single test round | |
console.log("\n--- TEST 4: FULL ROUND SIMULATION ---"); | |
runDebugRound(); | |
// Restore original debug mode setting | |
debugMode = previousDebugMode; | |
console.log("\n=== DEBUG TESTS COMPLETED ==="); | |
} | |
// Run a debug round | |
function runDebugRound() { | |
console.log("Running debug round simulation..."); | |
// Create and shuffle deck | |
let deck = createDeck(); | |
console.log("New deck created with distribution:", | |
Object.fromEntries( | |
SUITS.map(suit => [ | |
suit, | |
deck.filter(card => card.suit === suit).length | |
]) | |
) | |
); | |
deck = shuffleDeck(deck); | |
// Deal 2 cards to each player | |
const numPlayers = 4; | |
const playerHands = dealCards([...deck], numPlayers, 2); | |
// Deal 5 community cards | |
const communityCards = dealCommunityCards([...deck], 5); | |
// Determine the winner | |
const results = determineWinner(playerHands, communityCards); | |
// Print detailed results | |
console.log("Round Results:"); | |
results.forEach((result, i) => { | |
console.log(`Player ${result.player}: ${result.hand.map(formatCard).join(', ')} - ${result.evaluation.type} (High Card: ${formatCard(result.evaluation.highCard)})`); | |
}); | |
console.log(`Winner: Player ${results[0].player} with ${results[0].evaluation.type}`); | |
return results; | |
} | |
// Analyze shuffle randomness | |
function analyzeShuffleRandomness(originalDeck, shuffledDecks) { | |
// Track position changes for each card | |
const positionChanges = []; | |
for (let i = 0; i < originalDeck.length; i++) { | |
const originalCard = originalDeck[i]; | |
const cardDesc = `${originalCard.value} of ${originalCard.suit}`; | |
// Find positions in each shuffled deck | |
const newPositions = shuffledDecks.map(deck => { | |
const index = deck.findIndex(card => | |
card.value === originalCard.value && card.suit === originalCard.suit | |
); | |
return index; | |
}); | |
// Calculate average position change | |
const avgPositionChange = newPositions.reduce((sum, pos) => sum + Math.abs(pos - i), 0) / newPositions.length; | |
positionChanges.push({ | |
card: cardDesc, | |
originalPosition: i, | |
avgPositionChange, | |
newPositions | |
}); | |
} | |
// Calculate overall average position change | |
const overallAvgChange = positionChanges.reduce((sum, change) => sum + change.avgPositionChange, 0) / positionChanges.length; | |
// Calculate standard deviation of position changes | |
const variance = positionChanges.reduce((sum, change) => { | |
return sum + Math.pow(change.avgPositionChange - overallAvgChange, 2); | |
}, 0) / positionChanges.length; | |
const stdDev = Math.sqrt(variance); | |
console.log(`Analysis of ${shuffledDecks.length} shuffles:`); | |
console.log(`Average position change: ${overallAvgChange.toFixed(2)} positions`); | |
console.log(`Standard deviation: ${stdDev.toFixed(2)}`); | |
// A good shuffle should have position changes roughly equal to 1/3 of deck size | |
const expectedAvgChange = originalDeck.length / 3; | |
const changeRatio = overallAvgChange / expectedAvgChange; | |
console.log(`Expected avg position change for good shuffle: ~${expectedAvgChange.toFixed(2)}`); | |
console.log(`Ratio of actual to expected: ${changeRatio.toFixed(2)}`); | |
if (changeRatio > 0.8 && changeRatio < 1.2) { | |
console.log("RESULT: Shuffle appears properly random ✓"); | |
} else if (changeRatio < 0.5) { | |
console.log("RESULT: Shuffle may not be sufficiently random ✗"); | |
} else { | |
console.log("RESULT: Shuffle randomness is acceptable"); | |
} | |
// Analyze the most and least moved cards | |
positionChanges.sort((a, b) => b.avgPositionChange - a.avgPositionChange); | |
console.log("\nMost moved cards:"); | |
positionChanges.slice(0, 5).forEach(change => { | |
console.log(`${change.card} (pos ${change.originalPosition}): Avg change ${change.avgPositionChange.toFixed(2)} positions`); | |
}); | |
console.log("\nLeast moved cards:"); | |
positionChanges.slice(-5).forEach(change => { | |
console.log(`${change.card} (pos ${change.originalPosition}): Avg change ${change.avgPositionChange.toFixed(2)} positions`); | |
}); | |
} | |
// Debug button event | |
debugBtn.addEventListener('click', runDebugTests); | |
// Display a card visually | |
function displayCard(card) { | |
const cardDiv = document.createElement('div'); | |
cardDiv.className = `card ${card.suit.toLowerCase()}`; | |
let symbol = ''; | |
switch(card.suit) { | |
case 'Hearts': symbol = '♥'; break; | |
case 'Diamonds': symbol = '♦'; break; | |
case 'Clubs': symbol = '♣'; break; | |
case 'Spades': symbol = '♠'; break; | |
} | |
// Create a more structured card display | |
const valueSpan = document.createElement('span'); | |
valueSpan.className = 'card-value'; | |
valueSpan.textContent = card.value; | |
const symbolSpan = document.createElement('span'); | |
symbolSpan.className = 'card-symbol'; | |
symbolSpan.textContent = symbol; | |
cardDiv.innerHTML = ''; | |
cardDiv.appendChild(valueSpan); | |
cardDiv.appendChild(document.createElement('br')); | |
cardDiv.appendChild(symbolSpan); | |
// Log to verify the card is created correctly | |
console.log(`Created card: ${card.value} of ${card.suit} with symbol ${symbol}`); | |
return cardDiv; | |
} | |
// Display community cards | |
function displayCommunityCards(cards) { | |
communityCardsDisplay.innerHTML = ''; | |
cards.forEach(card => { | |
communityCardsDisplay.appendChild(displayCard(card)); | |
}); | |
} | |
// Update round results table | |
function updateResultsTable(results) { | |
resultsBody.innerHTML = ''; | |
results.forEach((result, index) => { | |
const row = document.createElement('tr'); | |
if (index === 0) { | |
row.className = 'winner-row'; | |
} | |
row.innerHTML = ` | |
<td>Player ${result.player}</td> | |
<td>${result.hand.map(formatCard).join(' & ')}</td> | |
<td>${result.evaluation.type}</td> | |
<td>${formatCard(result.evaluation.highCard)}</td> | |
<td>${result.evaluation.rank}</td> | |
`; | |
resultsBody.appendChild(row); | |
}); | |
} | |
// Update statistics table | |
function updateStatsTable() { | |
statsBody.innerHTML = ''; | |
for (let player = 1; player <= Object.keys(winCount).length; player++) { | |
const winPercentage = ((winCount[player] / currentRound) * 100).toFixed(2); | |
const row = document.createElement('tr'); | |
row.innerHTML = ` | |
<td>Player ${player}</td> | |
<td>${winCount[player] || 0}</td> | |
<td>${currentRound > 0 ? winPercentage : '0.00'}%</td> | |
`; | |
statsBody.appendChild(row); | |
} | |
} | |
// Run a single round of Texas Hold'em | |
function runRound() { | |
currentRound++; | |
roundCounter.textContent = `Round: ${currentRound} / ${totalRounds}`; | |
console.log(`\n--- Starting Round ${currentRound} ---`); | |
// Create and shuffle deck | |
let deck = createDeck(); | |
console.log("New deck created with distribution:", | |
Object.fromEntries( | |
SUITS.map(suit => [ | |
suit, | |
deck.filter(card => card.suit === suit).length | |
]) | |
) | |
); | |
deck = shuffleDeck(deck); | |
console.log("Deck shuffled successfully"); | |
// Deal 2 cards to each player | |
const numPlayers = parseInt(numPlayersInput.value); | |
const playerHands = dealCards([...deck], numPlayers, 2); // Use a copy | |
// Print what was dealt | |
playerHands.forEach((hand, idx) => { | |
console.log(`Player ${idx + 1} was dealt: ${hand.map(c => `${c.value} of ${c.suit}`).join(', ')}`); | |
}); | |
// Deal 5 community cards (using a fresh copy of the deck) | |
const communityCards = dealCommunityCards([...deck], 5); | |
console.log(`Community cards: ${communityCards.map(c => `${c.value} of ${c.suit}`).join(', ')}`); | |
// Display community cards | |
displayCommunityCards(communityCards); | |
// Determine the winner | |
const results = determineWinner(playerHands, communityCards); | |
// Update UI with results | |
updateResultsTable(results); | |
// Update win count | |
const winner = results[0].player; | |
winCount[winner] = (winCount[winner] || 0) + 1; | |
// Update stats | |
updateStatsTable(); | |
// Log results in CSV format | |
console.log(`\n--- Round ${currentRound} Results ---`); | |
console.log(`Community Cards: ${communityCards.map(formatCard).join(', ')}`); | |
console.log('Player,Cards,Hand Type,High Card,Rank'); | |
results.forEach(result => { | |
console.log(`${result.player},${result.hand.map(formatCard).join(' & ')},${result.evaluation.type},${formatCard(result.evaluation.highCard)},${result.evaluation.rank}`); | |
}); | |
console.log(`\nWinner: Player ${winner} with ${results[0].evaluation.type}`); | |
// Continue or finish simulation | |
if (currentRound < totalRounds) { | |
const timeoutId = setTimeout(runRound, speeds[speedSelect.value]); | |
timeoutIds.push(timeoutId); | |
} else { | |
// Simulation complete | |
console.log('\n--- Final Statistics ---'); | |
console.log('Player,Wins,Win Percentage'); | |
for (let player = 1; player <= numPlayers; player++) { | |
const winPercentage = ((winCount[player] || 0) / totalRounds * 100).toFixed(2); | |
console.log(`${player},${winCount[player] || 0},${winPercentage}%`); | |
} | |
simulationRunning = false; | |
startBtn.disabled = false; | |
resetBtn.disabled = false; | |
} | |
} | |
// Start simulation | |
startBtn.addEventListener('click', () => { | |
if (simulationRunning) return; | |
// Reset state | |
currentRound = 0; | |
totalRounds = parseInt(numRoundsInput.value); | |
winCount = {}; | |
simulationRunning = true; | |
// Clear previous results | |
communityCardsDisplay.innerHTML = ''; | |
resultsBody.innerHTML = ''; | |
statsBody.innerHTML = ''; | |
logOutput.innerHTML = ''; | |
// Update UI | |
roundCounter.textContent = `Round: 0 / ${totalRounds}`; | |
startBtn.disabled = true; | |
resetBtn.disabled = true; | |
const numPlayers = parseInt(numPlayersInput.value); | |
console.log(`Starting Texas Hold'em simulation with ${numPlayers} players for ${totalRounds} rounds\n`); | |
// Initialize win count object | |
for (let i = 1; i <= numPlayers; i++) { | |
winCount[i] = 0; | |
} | |
// Start first round after a delay | |
const timeoutId = setTimeout(runRound, 1000); | |
timeoutIds.push(timeoutId); | |
}); | |
// Reset simulation | |
resetBtn.addEventListener('click', () => { | |
// Clear all pending timeouts | |
timeoutIds.forEach(id => clearTimeout(id)); | |
timeoutIds = []; | |
// Reset state | |
currentRound = 0; | |
totalRounds = 0; | |
winCount = {}; | |
simulationRunning = false; | |
// Reset UI | |
communityCardsDisplay.innerHTML = ''; | |
resultsBody.innerHTML = ''; | |
statsBody.innerHTML = ''; | |
logOutput.innerHTML = ''; | |
roundCounter.textContent = 'Round: 0 / 0'; | |
// Enable controls | |
startBtn.disabled = false; | |
resetBtn.disabled = true; | |
}); | |
// Initialize | |
window.addEventListener('load', () => { | |
resetBtn.disabled = true; | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment