Skip to content

Instantly share code, notes, and snippets.

@secdev02
Created April 11, 2025 22:22
Show Gist options
  • Save secdev02/a2eab2f55ae031497dff03d8d21904e8 to your computer and use it in GitHub Desktop.
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.
<!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>&nbsp;</label>
<button id="start-btn">Start Simulation</button>
</div>
<div class="control-group">
<label>&nbsp;</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>&nbsp;</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