Download index.html from trd69/cps: direct link, hf CLI and curl.
- Browser
- Download file 46.5 kB
-
https://huggingface.co/spaces/trd69/cps/resolve/main/index.html
- Command line
-
hf download hf://spaces/trd69/cps/index.html
-
curl -L -o index.html https://huggingface.co/spaces/trd69/cps/resolve/main/index.html
46.5 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Chinese Poker Solver</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| .card { | |
| width: 70px; | |
| height: 105px; | |
| border-radius: 5px; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: space-between; | |
| padding: 5px; | |
| cursor: grab; | |
| user-select: none; | |
| position: relative; | |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); | |
| transition: transform 0.2s, box-shadow 0.2s; | |
| } | |
| .card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); | |
| } | |
| .card-red { | |
| background-color: white; | |
| color: red; | |
| } | |
| .card-black { | |
| background-color: white; | |
| color: black; | |
| } | |
| .card-blue { | |
| background-color: white; | |
| color: blue; | |
| } | |
| .card-green { | |
| background-color: white; | |
| color: green; | |
| } | |
| .card-value { | |
| font-size: 16px; | |
| font-weight: bold; | |
| text-align: center; | |
| } | |
| .card-suit { | |
| font-size: 18px; | |
| align-self: center; | |
| } | |
| .card-symbol { | |
| font-size: 10px; | |
| position: absolute; | |
| top: 1px; | |
| left: 2px; | |
| } | |
| .slot { | |
| width: 60px; | |
| height: 90px; | |
| border: 2px dashed #4B5563; | |
| border-radius: 5px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| background-color: rgba(75, 85, 99, 0.2); | |
| } | |
| .slot:hover { | |
| background-color: rgba(75, 85, 99, 0.4); | |
| } | |
| .opponent-row { | |
| opacity: 0.8; | |
| } | |
| .combination-label { | |
| font-size: 12px; | |
| color: #9CA3AF; | |
| margin-left: 10px; | |
| align-self: center; | |
| } | |
| .points-label { | |
| font-size: 12px; | |
| color: #FBBF24; | |
| font-weight: bold; | |
| margin-left: 10px; | |
| align-self: center; | |
| } | |
| .row-container { | |
| display: flex; | |
| align-items: center; | |
| } | |
| .deck-slot { | |
| width: 60px; | |
| height: 90px; | |
| margin: 0; | |
| padding: 4px; | |
| position: relative; | |
| background-color: white; | |
| border-radius: 5px; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: space-between; | |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); | |
| } | |
| @media (max-width: 768px) { | |
| .card { | |
| width: 50px; | |
| height: 75px; | |
| } | |
| .slot { | |
| width: 40px; | |
| height: 60px; | |
| } | |
| .card-value { | |
| font-size: 14px; | |
| } | |
| .card-suit { | |
| font-size: 16px; | |
| } | |
| .card-symbol { | |
| font-size: 8px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-white min-h-screen p-4"> | |
| <div class="container mx-auto max-w-6xl"> | |
| <h1 class="text-3xl font-bold text-center mb-6 text-yellow-300">Chinese Poker Solver</h1> | |
| <!-- Buttons --> | |
| <div class="flex flex-wrap justify-center gap-4 mb-8"> | |
| <button id="calculate-btn" class="px-6 py-3 bg-green-600 hover:bg-green-700 rounded-lg font-semibold disabled:opacity-50 disabled:cursor-not-allowed transition"> | |
| Calculate Best Moves | |
| </button> | |
| <button id="reset-btn" class="px-6 py-3 bg-red-600 hover:bg-red-700 rounded-lg font-semibold transition"> | |
| Reset All Cards | |
| </button> | |
| </div> | |
| <!-- Game Boards Container --> | |
| <div class="flex flex-row gap-8 mb-0"> | |
| <!-- Opponent's Board aligned left --> | |
| <div class="w-[300px]"> | |
| <h2 class="text-xl font-semibold mb-4 text-gray-400">Opponent's Rows</h2> | |
| <!-- Opponent's Top Row (3 cards) --> | |
| <div class="row-container mb-2 opponent-row"> | |
| <div class="flex gap-2"> | |
| <div id="opponent-top-0" class="slot" data-row="opponent-top"></div> | |
| <div id="opponent-top-1" class="slot" data-row="opponent-top"></div> | |
| <div id="opponent-top-2" class="slot" data-row="opponent-top"></div> | |
| </div> | |
| <div class="combination-label" id="opponent-top-combination">-</div> | |
| <div class="points-label" id="opponent-top-points">0</div> | |
| </div> | |
| <!-- Opponent's Middle Row (5 cards) --> | |
| <div class="row-container mb-6 opponent-row"> | |
| <div class="flex justify-center items-end gap-2"> | |
| <div id="opponent-middle-0" class="slot" data-row="opponent-middle"></div> | |
| <div id="opponent-middle-1" class="slot" data-row="opponent-middle"></div> | |
| <div id="opponent-middle-2" class="slot" data-row="opponent-middle"></div> | |
| <div id="opponent-middle-3" class="slot" data-row="opponent-middle"></div> | |
| <div id="opponent-middle-4" class="slot" data-row="opponent-middle"></div> | |
| </div> | |
| <div class="combination-label" id="opponent-middle-combination">-</div> | |
| <div class="points-label" id="opponent-middle-points">0</div> | |
| </div> | |
| <!-- Opponent's Bottom Row (5 cards) --> | |
| <div class="row-container opponent-row"> | |
| <div class="flex justify-center items-end gap-2"> | |
| <div id="opponent-bottom-0" class="slot" data-row="opponent-bottom"></div> | |
| <div id="opponent-bottom-1" class="slot" data-row="opponent-bottom"></div> | |
| <div id="opponent-bottom-2" class="slot" data-row="opponent-bottom"></div> | |
| <div id="opponent-bottom-3" class="slot" data-row="opponent-bottom"></div> | |
| <div id="opponent-bottom-4" class="slot" data-row="opponent-bottom"></div> | |
| </div> | |
| <div class="combination-label" id="opponent-bottom-combination">-</div> | |
| <div class="points-label" id="opponent-bottom-points">0</div> | |
| </div> | |
| </div> <!-- End player's board --> | |
| </div> <!-- End game boards container --> | |
| <!-- Player's Board --> | |
| <div class="w-[300px]"> | |
| <h2 class="text-xl font-semibold mb-4">Your Rows</h2> | |
| <!-- Player's Top Row (3 cards) --> | |
| <div class="row-container mb-2"> | |
| <div class="flex gap-2"> | |
| <div id="player-top-0" class="slot" data-row="player-top"></div> | |
| <div id="player-top-1" class="slot" data-row="player-top"></div> | |
| <div id="player-top-2" class="slot" data-row="player-top"></div> | |
| </div> | |
| <div class="combination-label" id="player-top-combination">-</div> | |
| <div class="points-label" id="player-top-points">0</div> | |
| </div> | |
| <!-- Player's Middle Row (5 cards) --> | |
| <div class="row-container mb-2"> | |
| <div class="flex justify-center gap-2"> | |
| <div id="player-middle-0" class="slot" data-row="player-middle"></div> | |
| <div id="player-middle-1" class="slot" data-row="player-middle"></div> | |
| <div id="player-middle-2" class="slot" data-row="player-middle"></div> | |
| <div id="player-middle-3" class="slot" data-row="player-middle"></div> | |
| <div id="player-middle-4" class="slot" data-row="player-middle"></div> | |
| </div> | |
| <div class="combination-label" id="player-middle-combination">-</div> | |
| <div class="points-label" id="player-middle-points">0</div> | |
| </div> | |
| <!-- Player's Bottom Row (5 cards) --> | |
| <div class="row-container mb-8"> | |
| <div class="flex justify-center gap-2"> | |
| <div id="player-bottom-0" class="slot" data-row="player-bottom"></div> | |
| <div id="player-bottom-1" class="slot" data-row="player-bottom"></div> | |
| <div id="player-bottom-2" class="slot" data-row="player-bottom"></div> | |
| <div id="player-bottom-3" class="slot" data-row="player-bottom"></div> | |
| <div id="player-bottom-4" class="slot" data-row="player-bottom"></div> | |
| </div> | |
| <div class="combination-label" id="player-bottom-combination">-</div> | |
| <div class="points-label" id="player-bottom-points">0</div> | |
| </div> | |
| </div> | |
| </div> <!-- End game boards container --> | |
| <!-- Player's Hand and Discard side by side --> | |
| <div class="flex flex-row gap-6 mt-6"> | |
| <!-- Player's Hand (5 cards) --> | |
| <div class="bg-gray-800 rounded-lg p-6 flex-1"> | |
| <h2 class="text-xl font-semibold mb-4">Your Hand</h2> | |
| <div class="flex justify-center gap-2"> | |
| <div id="hand-0" class="slot" data-row="hand"></div> | |
| <div id="hand-1" class="slot" data-row="hand"></div> | |
| <div id="hand-2" class="slot" data-row="hand"></div> | |
| <div id="hand-3" class="slot" data-row="hand"></div> | |
| <div id="hand-4" class="slot" data-row="hand"></div> | |
| </div> | |
| </div> | |
| <!-- Discard Area (3 cards) --> | |
| <div class="bg-gray-800 rounded-lg p-6 flex-1"> | |
| <h2 class="text-xl font-semibold mb-4">Discard</h2> | |
| <div class="flex justify-center gap-2"> | |
| <div id="discard-0" class="slot" data-row="discard"></div> | |
| <div id="discard-1" class="slot" data-row="discard"></div> | |
| <div id="discard-2" class="slot" data-row="discard"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right-side container for deck --> | |
| <div class="absolute right-8 top-32 mr-4" style="width: 600px; height: 500px;"> | |
| <!-- Deck --> | |
| <div class="bg-gray-800 rounded-lg p-2 h-full overflow-y-auto"> | |
| <h2 class="text-xl font-semibold mb-4">Deck</h2> | |
| <div class="grid grid-cols-8 gap-2 h-[calc(100%-48px)] overflow-y-auto"> | |
| <!-- Cards will be generated here --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Results --> | |
| <div id="results" class="bg-gray-800 rounded-lg p-6 hidden"> | |
| <h2 class="text-xl font-semibold mb-4">Best Moves</h2> | |
| <div id="results-content" class="grid gap-4"> | |
| <!-- Results will be shown here --> | |
| </div> | |
| </div> | |
| <!-- Score Summary --> | |
| <div id="score-summary" class="bg-gray-800 rounded-lg p-6 hidden"> | |
| <h2 class="text-xl font-semibold mb-4">Score Summary</h2> | |
| <div id="score-content" class="grid gap-2"> | |
| <div class="bg-gray-700 p-4 rounded-lg"> | |
| <h3 class="font-semibold text-center mb-2">Final Score</h3> | |
| <div class="flex justify-between"> | |
| <div class="text-green-400"> | |
| <p class="font-bold">You:</p> | |
| <p>Top: <span id="final-player-top"></span></p> | |
| <p>Middle: <span id="final-player-middle"></span></p> | |
| <p>Bottom: <span id="final-player-bottom"></span></p> | |
| <p class="font-bold text-lg mt-2">Total: <span id="final-player-score">0</span></p> | |
| </div> | |
| <div class="text-red-400 text-right"> | |
| <p class="font-bold">Opponent:</p> | |
| <p>Top: <span id="final-opponent-top"></span></p> | |
| <p>Middle: <span id="final-opponent-middle"></span></p> | |
| <p>Bottom: <span id="final-opponent-bottom"></span></p> | |
| <p class="font-bold text-lg mt-2">Total: <span id="final-opponent-score">0</span></p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Game state | |
| const gameState = { | |
| cards: [], | |
| slots: {}, | |
| cardPositions: {}, // Tracks which card is in which slot | |
| lastMoveValid: false | |
| }; | |
| // Initialize all slots | |
| function initializeSlots() { | |
| // Opponent's slots | |
| for (let i = 0; i < 3; i++) { | |
| gameState.slots[`opponent-top-${i}`] = null; | |
| } | |
| for (let i = 0; i < 5; i++) { | |
| gameState.slots[`opponent-middle-${i}`] = null; | |
| gameState.slots[`opponent-bottom-${i}`] = null; | |
| } | |
| // Player's hand and discard | |
| for (let i = 0; i < 5; i++) { | |
| gameState.slots[`hand-${i}`] = null; | |
| } | |
| for (let i = 0; i < 3; i++) { | |
| gameState.slots[`discard-${i}`] = null; | |
| } | |
| // Player's slots | |
| for (let i = 0; i < 3; i++) { | |
| gameState.slots[`player-top-${i}`] = null; | |
| } | |
| for (let i = 0; i < 5; i++) { | |
| gameState.slots[`player-middle-${i}`] = null; | |
| gameState.slots[`player-bottom-${i}`] = null; | |
| } | |
| // Deck slots (will be initialized with cards) | |
| } | |
| // Create a standard deck of 52 cards | |
| function createDeck() { | |
| const suits = { | |
| 'hearts': { symbol: '♥', class: 'card-red' }, | |
| 'diamonds': { symbol: '♦', class: 'card-blue' }, | |
| 'clubs': { symbol: '♣', class: 'card-green' }, | |
| 'spades': { symbol: '♠', class: 'card-black' } | |
| }; | |
| const values = ['2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A']; | |
| let deck = []; | |
| let id = 0; | |
| for (const [suit, suitInfo] of Object.entries(suits)) { | |
| for (const [index, value] of values.entries()) { | |
| deck.push({ | |
| id: id++, | |
| suit: suit, | |
| suitSymbol: suitInfo.symbol, | |
| suitClass: suitInfo.class, | |
| value: value, | |
| rank: index + 2, // 2 is lowest, Ace is highest | |
| element: null | |
| }); | |
| } | |
| } | |
| return deck; | |
| } | |
| // Render the deck | |
| function renderDeck() { | |
| const deckContainer = document.querySelector('.grid.gap-2'); | |
| deckContainer.innerHTML = ''; | |
| gameState.cards.forEach(card => { | |
| const cardElement = document.createElement('div'); | |
| cardElement.className = `deck-card ${card.suitClass} deck-slot`; | |
| cardElement.setAttribute('draggable', 'true'); | |
| cardElement.dataset.cardId = card.id; | |
| cardElement.innerHTML = ` | |
| <div class="card-symbol">${card.suitSymbol}</div> | |
| <div class="card-value text-center">${card.value}</div> | |
| <div class="card-suit">${card.suitSymbol}</div> | |
| `; | |
| cardElement.addEventListener('dragstart', handleDragStart); | |
| deckContainer.appendChild(cardElement); | |
| card.element = cardElement; | |
| gameState.cardPositions[card.id] = 'deck'; | |
| }); | |
| } | |
| // Handle drag start | |
| function handleDragStart(e) { | |
| e.dataTransfer.setData('text/plain', e.target.dataset.cardId); | |
| e.dataTransfer.effectAllowed = 'move'; | |
| } | |
| // Set up drop zones | |
| function setupDropZones() { | |
| const slots = document.querySelectorAll('.slot'); | |
| slots.forEach(slot => { | |
| slot.addEventListener('dragover', function(e) { | |
| e.preventDefault(); | |
| e.dataTransfer.dropEffect = 'move'; | |
| }); | |
| slot.addEventListener('drop', function(e) { | |
| e.preventDefault(); | |
| const cardId = e.dataTransfer.getData('text/plain'); | |
| moveCard(cardId, slot.id); | |
| }); | |
| }); | |
| } | |
| // Move card from one slot to another | |
| function moveCard(cardId, toSlotId) { | |
| const card = gameState.cards.find(c => c.id == cardId); | |
| if (!card) return; | |
| // Remove card from its current position | |
| const fromPosition = gameState.cardPositions[cardId]; | |
| if (fromPosition && fromPosition !== 'deck') { | |
| gameState.slots[fromPosition] = null; | |
| } | |
| // Check if target slot is already occupied | |
| if (gameState.slots[toSlotId]) { | |
| const otherCardId = gameState.slots[toSlotId]; | |
| gameState.slots[fromPosition] = otherCardId; | |
| gameState.cardPositions[otherCardId] = fromPosition || 'deck'; | |
| // Move the other card visually back to its original position | |
| if (fromPosition) { | |
| const otherSlot = document.getElementById(fromPosition); | |
| if (otherSlot) { | |
| otherSlot.innerHTML = ''; | |
| otherSlot.appendChild(gameState.cards.find(c => c.id == otherCardId).element); | |
| } | |
| } else { | |
| // Return to deck | |
| const deckContainer = document.querySelector('.grid.gap-2'); | |
| deckContainer.appendChild(gameState.cards.find(c => c.id == otherCardId).element); | |
| } | |
| } | |
| // Add card to new position | |
| gameState.slots[toSlotId] = card.id; | |
| gameState.cardPositions[cardId] = toSlotId; | |
| // Update UI | |
| const toSlot = document.getElementById(toSlotId); | |
| if (toSlot) { | |
| toSlot.innerHTML = ''; | |
| toSlot.appendChild(card.element); | |
| } | |
| // Check if we have a valid game state for calculation | |
| checkGameState(); | |
| } | |
| // Check if game state is valid for calculation | |
| function checkGameState() { | |
| const calculateBtn = document.getElementById('calculate-btn'); | |
| // Count cards in different areas | |
| const handCards = Object.values(gameState.slots).filter( | |
| v => v !== null && gameState.cardPositions[v]?.startsWith('hand-') | |
| ).length; | |
| const playerCards = Object.values(gameState.slots).filter( | |
| v => v !== null && gameState.cardPositions[v]?.startsWith('player-') | |
| ).length; | |
| const opponentCards = Object.values(gameState.slots).filter( | |
| v => v !== null && gameState.cardPositions[v]?.startsWith('opponent-') | |
| ).length; | |
| const discardCards = Object.values(gameState.slots).filter( | |
| v => v !== null && gameState.cardPositions[v]?.startsWith('discard-') | |
| ).length; | |
| // Valid game states for calculation | |
| const isValidState = ( | |
| (handCards === 5 && playerCards === 0 && opponentCards === 0 && discardCards === 0) || // First round | |
| (handCards === 3 && playerCards === 5 && opponentCards === 5 && discardCards === 0) || // Second phase | |
| (handCards === 3 && playerCards === 7 && opponentCards === 7 && discardCards === 1) || // Third phase | |
| (handCards === 3 && playerCards === 9 && opponentCards === 9 && discardCards === 2) || // Fourth phase | |
| (handCards === 3 && playerCards === 11 && opponentCards === 11 && discardCards === 3) // Final phase | |
| ); | |
| // All cards placed - show final scores | |
| if (isValidState) { | |
| calculateBtn.disabled = false; | |
| gameState.lastMoveValid = true; | |
| } | |
| else { | |
| calculateBtn.disabled = true; | |
| gameState.lastMoveValid = false; | |
| } | |
| showFinalScores(); | |
| // Update combination labels | |
| updateCombinationLabels(); | |
| } | |
| // Update combination labels for all rows | |
| function updateCombinationLabels() { | |
| // Player's rows | |
| updateRowCombination('player-top', 3); | |
| updateRowCombination('player-middle', 5); | |
| updateRowCombination('player-bottom', 5); | |
| // Opponent's rows | |
| updateRowCombination('opponent-top', 3); | |
| updateRowCombination('opponent-middle', 5); | |
| updateRowCombination('opponent-bottom', 5); | |
| } | |
| // Update combination label for a specific row | |
| function updateRowCombination(prefix, size) { | |
| const cardIds = []; | |
| for (let i = 0; i < size; i++) { | |
| const cardId = gameState.slots[`${prefix}-${i}`]; | |
| if (cardId !== null) { | |
| cardIds.push(gameState.cards.find(c => c.id == cardId)); | |
| } | |
| } | |
| if (cardIds.length < size) { | |
| document.getElementById(`${prefix}-combination`).textContent = '-'; | |
| document.getElementById(`${prefix}-points`).textContent = '0'; | |
| return null; | |
| } | |
| const result = evaluateCombinationWithCards(cardIds); | |
| document.getElementById(`${prefix}-combination`).textContent = result.combination; | |
| // Рассчитываем бонусы только для UI | |
| const rowType = prefix.split('-')[1]; | |
| const royalty = calculateRoyalty(result, rowType); | |
| document.getElementById(`${prefix}-points`).textContent = royalty; | |
| return result; | |
| } | |
| function getCombinationRank(combination) { | |
| const ranks = { | |
| 'High Card': 0, | |
| 'Pair': 1, | |
| 'Two Pairs': 2, | |
| 'Three of a Kind': 3, | |
| 'Straight': 4, | |
| 'Flush': 5, | |
| 'Full House': 6, | |
| 'Four of a Kind': 7, | |
| 'Straight Flush': 8, | |
| 'Royal Flush': 9 | |
| }; | |
| return ranks[combination] || 0; | |
| } | |
| // Get kicker strength (for comparing same combination types) | |
| function getCombinationStrength(cards, combination) { | |
| // Sort cards by rank (high to low) | |
| const sortedCards = [...cards].sort((a, b) => b.rank - a.rank); | |
| // For combinations with fixed kickers | |
| switch(combination) { | |
| case 'Pair': | |
| // Find pair value | |
| const pairValue = sortedCards.find(card => | |
| sortedCards.filter(c => c.rank === card.rank).length === 2 | |
| ).rank; | |
| // Get kickers | |
| const kickers = sortedCards | |
| .filter(c => c.rank !== pairValue) | |
| .map(c => c.rank) | |
| .sort((a, b) => b - a); | |
| return [pairValue, ...kickers]; | |
| case 'Two Pairs': | |
| const values = sortedCards.map(c => c.rank); | |
| const counts = {}; | |
| values.forEach(v => counts[v] = (counts[v] || 0) + 1); | |
| const pairs = Object.entries(counts) | |
| .filter(([_, count]) => count === 2) | |
| .map(([rank]) => parseInt(rank)) | |
| .sort((a, b) => b - a); | |
| const kicker = Object.entries(counts) | |
| .filter(([_, count]) => count === 1) | |
| .map(([rank]) => parseInt(rank))[0]; | |
| return [...pairs, kicker]; | |
| case 'Three of a Kind': | |
| const setValue = sortedCards.find(card => | |
| sortedCards.filter(c => c.rank === card.rank).length === 3 | |
| ).rank; | |
| return [setValue]; | |
| case 'Straight': | |
| case 'Straight Flush': | |
| // Handle wheel (A-2-3-4-5) | |
| const isWheel = sortedCards.some(c => c.rank === 14) && | |
| sortedCards.some(c => c.rank === 2) && | |
| sortedCards.some(c => c.rank === 3) && | |
| sortedCards.some(c => c.rank === 4) && | |
| sortedCards.some(c => c.rank === 5); | |
| return [isWheel ? 5 : sortedCards[0].rank]; | |
| case 'Flush': | |
| case 'High Card': | |
| return sortedCards.map(c => c.rank); | |
| case 'Full House': | |
| const setVal = sortedCards.find(card => | |
| sortedCards.filter(c => c.rank === card.rank).length === 3 | |
| ).rank; | |
| const pairVal = sortedCards.find(card => | |
| card.rank !== setVal && | |
| sortedCards.filter(c => c.rank === card.rank).length === 2 | |
| ).rank; | |
| return [setVal, pairVal]; | |
| case 'Four of a Kind': | |
| const quadValue = sortedCards.find(card => | |
| sortedCards.filter(c => c.rank === card.rank).length === 4 | |
| ).rank; | |
| const kickerValue = sortedCards.find(c => c.rank !== quadValue).rank; | |
| return [quadValue, kickerValue]; | |
| case 'Royal Flush': | |
| return [14]; // Always highest | |
| default: | |
| return [sortedCards[0].rank]; | |
| } | |
| } | |
| // Evaluate combination for a set of cards | |
| function evaluateCombinationWithCards(cards, rowType) { | |
| if (cards.length === 0) return { | |
| combination: 'None', | |
| strength: [], | |
| cards: [] | |
| }; | |
| // Sort cards by rank (high to low) | |
| const sortedCards = [...cards].sort((a, b) => b.rank - a.rank); | |
| // Check flush | |
| const isFlush = sortedCards.every(c => c.suit === sortedCards[0].suit); | |
| // Check straight | |
| let isStraight = true; | |
| for (let i = 1; i < sortedCards.length; i++) { | |
| if (sortedCards[i].rank !== sortedCards[i-1].rank - 1) { | |
| isStraight = false; | |
| break; | |
| } | |
| } | |
| // Check wheel (A-2-3-4-5) | |
| function isWheel(cards) { | |
| const hasAce = cards.some(c => c.rank === 14); | |
| const ranks = cards.map(c => c.rank).sort((a, b) => a - b); | |
| return hasAce && | |
| ranks.includes(2) && | |
| ranks.includes(3) && | |
| ranks.includes(4) && | |
| ranks.includes(5); | |
| } | |
| isStraight = isStraight || isWheel(sortedCards); | |
| // Check royal flush (10-J-Q-K-A of same suit) | |
| const isRoyal = isFlush && | |
| sortedCards[0].rank === 14 && | |
| sortedCards[1].rank === 13 && | |
| sortedCards[2].rank === 12 && | |
| sortedCards[3].rank === 11 && | |
| sortedCards[4].rank === 10; | |
| // Check straight flush and royal flush | |
| if (isStraight && isFlush && sortedCards.length >= 5) { | |
| const combination = isRoyal ? 'Royal Flush' : 'Straight Flush'; | |
| return { | |
| combination, | |
| strength: getCombinationStrength(sortedCards, combination), | |
| cards: sortedCards | |
| }; | |
| } | |
| // Check four of a kind | |
| const rankCounts = {}; | |
| sortedCards.forEach(c => { | |
| rankCounts[c.rank] = (rankCounts[c.rank] || 0) + 1; | |
| }); | |
| const counts = Object.values(rankCounts); | |
| if (counts.includes(4)) { | |
| return { | |
| combination: 'Four of a Kind', | |
| strength: getCombinationStrength(sortedCards, 'Four of a Kind'), | |
| cards: sortedCards | |
| }; | |
| } | |
| // Check full house | |
| if (counts.includes(3) && counts.includes(2) && sortedCards.length >= 5) { | |
| return { | |
| combination: 'Full House', | |
| strength: getCombinationStrength(sortedCards, 'Full House'), | |
| cards: sortedCards | |
| }; | |
| } | |
| // Check flush (only for 5+ cards) | |
| if (isFlush && sortedCards.length >= 5) { | |
| return { | |
| combination: 'Flush', | |
| strength: getCombinationStrength(sortedCards, 'Flush'), | |
| cards: sortedCards | |
| }; | |
| } | |
| // Check straight (only for 5+ cards) | |
| if (isStraight && sortedCards.length >= 5) { | |
| return { | |
| combination: 'Straight', | |
| strength: getCombinationStrength(sortedCards, 'Straight'), | |
| cards: sortedCards | |
| }; | |
| } | |
| // Check three of a kind | |
| if (counts.includes(3)) { | |
| return { | |
| combination: 'Three of a Kind', | |
| strength: getCombinationStrength(sortedCards, 'Three of a Kind'), | |
| cards: sortedCards | |
| }; | |
| } | |
| // Check two pairs | |
| const pairCount = counts.filter(c => c === 2).length; | |
| if (pairCount === 2) { | |
| return { | |
| combination: 'Two Pairs', | |
| strength: getCombinationStrength(sortedCards, 'Two Pairs'), | |
| cards: sortedCards | |
| }; | |
| } | |
| // Check pair | |
| if (pairCount === 1) { | |
| return { | |
| combination: 'Pair', | |
| strength: getCombinationStrength(sortedCards, 'Pair'), | |
| cards: sortedCards | |
| }; | |
| } | |
| // High card | |
| return { | |
| combination: `High ${sortedCards[0].value}`, | |
| strength: getCombinationStrength(sortedCards, 'High Card'), | |
| cards: sortedCards | |
| }; | |
| } | |
| function compareCombinations(comb1, comb2) { | |
| // Compare combination ranks | |
| const rank1 = getCombinationRank(comb1.combination); | |
| const rank2 = getCombinationRank(comb2.combination); | |
| if (rank1 !== rank2) { | |
| return rank1 > rank2 ? 1 : -1; | |
| } | |
| // Same rank - compare strengths and kickers | |
| const strength1 = comb1.strength; | |
| const strength2 = comb2.strength; | |
| for (let i = 0; i < Math.min(strength1.length, strength2.length); i++) { | |
| if (strength1[i] !== strength2[i]) { | |
| return strength1[i] > strength2[i] ? 1 : -1; | |
| } | |
| } | |
| // All kickers match | |
| return 0; | |
| } | |
| function isDeadHand(rows) { | |
| const { top, middle, bottom } = rows; | |
| return !(compareCombinations(top, middle) <= 0 && | |
| compareCombinations(middle, bottom) <= 0); | |
| } | |
| // Calculate royalty points for a row | |
| function calculateRoyalty(row, rowType) { | |
| const combination = row.combination; | |
| if (rowType === 'bottom') { | |
| switch(combination) { | |
| case 'Straight': return 2; | |
| case 'Flush': return 4; | |
| case 'Full House': return 6; | |
| case 'Four of a Kind': return 8; | |
| case 'Straight Flush': return 10; | |
| case 'Royal Flush': return 15; | |
| default: return 0; | |
| } | |
| } | |
| if (rowType === 'middle') { | |
| switch(combination) { | |
| case 'Three of a Kind': return 2; | |
| case 'Straight': return 4; | |
| case 'Flush': return 8; | |
| case 'Full House': return 12; | |
| case 'Four of a Kind': return 16; | |
| case 'Straight Flush': return 20; | |
| case 'Royal Flush': return 30; | |
| default: return 0; | |
| } | |
| } | |
| if (rowType === 'top') { | |
| if (combination === 'Three of a Kind') { | |
| const value = row.cards[0].value; | |
| const rankMap = { | |
| '2': 10, '3': 11, '4': 12, '5': 13, '6': 14, | |
| '7': 15, '8': 16, '9': 17, 'T': 18, 'J': 19, | |
| 'Q': 20, 'K': 21, 'A': 22 | |
| }; | |
| return rankMap[value] || 0; | |
| } | |
| if (combination === 'Pair') { | |
| // Find pair value | |
| const values = row.cards.map(c => c.value); | |
| const valueCounts = {}; | |
| values.forEach(v => valueCounts[v] = (valueCounts[v] || 0) + 1); | |
| for (const [value, count] of Object.entries(valueCounts)) { | |
| if (count === 2) { | |
| const rankMap = { | |
| '6': 1, '7': 2, '8': 3, '9': 4, 'T': 5, | |
| 'J': 6, 'Q': 7, 'K': 8, 'A': 9 | |
| }; | |
| return rankMap[value] || 0; | |
| } | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| function calculateRoyalties(rows) { | |
| return calculateRoyalty(rows.top, 'top') + | |
| calculateRoyalty(rows.middle, 'middle') + | |
| calculateRoyalty(rows.bottom, 'bottom'); | |
| } | |
| function calculateScores(playerRows, opponentRows) { | |
| if (!playerRows.top || !playerRows.middle || !playerRows.bottom || | |
| !opponentRows.top || !opponentRows.middle || !opponentRows.bottom) { | |
| return { | |
| player: 0, | |
| opponent: 0, | |
| details: { // Всегда возвращаем details | |
| player: { rowWins: 0, royalties: 0 }, | |
| opponent: { rowWins: 0, royalties: 0 } | |
| } | |
| }; | |
| } | |
| // Dead hand penalties | |
| const playerDead = isDeadHand(playerRows); | |
| const opponentDead = isDeadHand(opponentRows); | |
| if (playerDead && opponentDead) { | |
| return { | |
| player: 0, | |
| opponent: 0, | |
| details: { // Всегда возвращаем details | |
| player: { rowWins: 0, royalties: 0 }, | |
| opponent: { rowWins: 0, royalties: 0 } | |
| } | |
| }; | |
| } | |
| if (playerDead) { | |
| const opponentRoyalties = calculateRoyalties(opponentRows); | |
| return { | |
| player: 0, | |
| opponent: 6 + opponentRoyalties, | |
| details: { // Всегда возвращаем details | |
| player: { rowWins: 0, royalties: 0 }, | |
| opponent: { rowWins: 3, royalties: opponentRoyalties } | |
| } | |
| }; | |
| } | |
| if (opponentDead) { | |
| const playerRoyalties = calculateRoyalties(playerRows); | |
| return { | |
| player: 6 + playerRoyalties, | |
| opponent: 0, | |
| details: { // Всегда возвращаем details | |
| player: { rowWins: 3, royalties: playerRoyalties }, | |
| opponent: { rowWins: 0, royalties: 0 } | |
| } | |
| }; | |
| } | |
| // Compare rows | |
| const topResult = compareCombinations(playerRows.top, opponentRows.top); | |
| const middleResult = compareCombinations(playerRows.middle, opponentRows.middle); | |
| const bottomResult = compareCombinations(playerRows.bottom, opponentRows.bottom); | |
| // Calculate row wins | |
| let playerRowWins = 0; | |
| let opponentRowWins = 0; | |
| if (topResult > 0) playerRowWins++; | |
| else if (topResult < 0) opponentRowWins++; | |
| if (middleResult > 0) playerRowWins++; | |
| else if (middleResult < 0) opponentRowWins++; | |
| if (bottomResult > 0) playerRowWins++; | |
| else if (bottomResult < 0) opponentRowWins++; | |
| // Scoop bonus | |
| let playerScore = playerRowWins; | |
| let opponentScore = opponentRowWins; | |
| if (playerRowWins === 3) playerScore += 3; | |
| else if (opponentRowWins === 3) opponentScore += 3; | |
| const playerRoyalties = calculateRoyalties(playerRows); | |
| const opponentRoyalties = calculateRoyalties(opponentRows); | |
| // Add royalties | |
| playerScore += calculateRoyalties(playerRows); | |
| opponentScore += calculateRoyalties(opponentRows); | |
| return { | |
| player: playerScore, | |
| opponent: opponentScore, | |
| details: { // Всегда возвращаем details | |
| player: { rowWins: playerRowWins, royalties: playerRoyalties }, | |
| opponent: { rowWins: opponentRowWins, royalties: opponentRoyalties } | |
| } | |
| }; | |
| } | |
| // Calculate best moves (simplified for demo) | |
| function calculateBestMoves() { | |
| // Check if all cards are placed (13 in each) | |
| const playerCards = Object.values(gameState.slots).filter( | |
| v => v !== null && gameState.cardPositions[v]?.startsWith('player-') | |
| ).length; | |
| const opponentCards = Object.values(gameState.slots).filter( | |
| v => v !== null && gameState.cardPositions[v]?.startsWith('opponent-') | |
| ).length; | |
| const resultsContainer = document.getElementById('results'); | |
| const resultsContent = document.getElementById('results-content'); | |
| resultsContent.innerHTML = ''; | |
| if (!gameState.lastMoveValid) { | |
| resultsContent.innerHTML = '<p class="text-red-400">Invalid game state for calculation.</p>'; | |
| resultsContainer.classList.remove('hidden'); | |
| return; | |
| } | |
| // For now, we'll generate random recommendations | |
| // In a real app, this would use an actual algorithm | |
| // Get cards in hand | |
| const handCardIds = []; | |
| for (let i = 0; i < 5; i++) { | |
| const cardId = gameState.slots[`hand-${i}`]; | |
| if (cardId !== null) { | |
| handCardIds.push(cardId); | |
| } | |
| } | |
| // Make 3 random recommendations | |
| for (let i = 0; i < 3; i++) { | |
| const resultDiv = document.createElement('div'); | |
| resultDiv.className = 'bg-gray-700 p-4 rounded-lg'; | |
| const header = document.createElement('h3'); | |
| header.className = 'font-semibold text-yellow-300 mb-2'; | |
| header.textContent = `Option ${i+1}`; | |
| const evText = document.createElement('p'); | |
| evText.className = 'text-sm mb-2'; | |
| evText.textContent = `Expected Value: ${(Math.random() * 3 + 1).toFixed(2)}`; | |
| const movesText = document.createElement('p'); | |
| movesText.className = 'text-sm'; | |
| // Generate random placement for the hand cards | |
| handCardIds.forEach(cardId => { | |
| const randomRow = ['top', 'middle', 'bottom'][Math.floor(Math.random() * 3)]; | |
| let randomSlot; | |
| if (randomRow === 'top') { | |
| randomSlot = `player-top-${Math.floor(Math.random() * 3)}`; | |
| } else { | |
| randomSlot = `player-${randomRow}-${Math.floor(Math.random() * 5)}`; | |
| } | |
| const card = gameState.cards.find(c => c.id === cardId); | |
| movesText.innerHTML += `Place ${card.value}${card.suitSymbol} in ${randomRow} row (slot ${randomSlot.split('-').pop()})<br>`; | |
| }); | |
| resultDiv.appendChild(header); | |
| resultDiv.appendChild(evText); | |
| resultDiv.appendChild(movesText); | |
| resultsContent.appendChild(resultDiv); | |
| } | |
| resultsContainer.classList.remove('hidden'); | |
| } | |
| // Show final scores when all cards are placed | |
| function showFinalScores() { | |
| const playerRows = { | |
| top: updateRowCombination('player-top', 3), | |
| middle: updateRowCombination('player-middle', 5), | |
| bottom: updateRowCombination('player-bottom', 5) | |
| }; | |
| const opponentRows = { | |
| top: updateRowCombination('opponent-top', 3), | |
| middle: updateRowCombination('opponent-middle', 5), | |
| bottom: updateRowCombination('opponent-bottom', 5) | |
| }; | |
| // Проверяем, что все ряды заполнены | |
| const allRowsFilled = playerRows.top && playerRows.middle && playerRows.bottom && | |
| opponentRows.top && opponentRows.middle && opponentRows.bottom; | |
| if (!allRowsFilled) { | |
| document.getElementById('score-summary').classList.add('hidden'); | |
| return; | |
| } | |
| const scores = calculateScores(playerRows, opponentRows); | |
| // Обновляем интерфейс | |
| document.getElementById('final-player-score').textContent = scores.player; | |
| document.getElementById('final-opponent-score').textContent = scores.opponent; | |
| // Берем значения из UI (они уже обновлены в updateRowCombination) | |
| document.getElementById('final-player-top').textContent = | |
| document.getElementById('player-top-points').textContent; | |
| document.getElementById('final-player-middle').textContent = | |
| document.getElementById('player-middle-points').textContent; | |
| document.getElementById('final-player-bottom').textContent = | |
| document.getElementById('player-bottom-points').textContent; | |
| document.getElementById('final-opponent-top').textContent = | |
| document.getElementById('opponent-top-points').textContent; | |
| document.getElementById('final-opponent-middle').textContent = | |
| document.getElementById('opponent-middle-points').textContent; | |
| document.getElementById('final-opponent-bottom').textContent = | |
| document.getElementById('opponent-bottom-points').textContent; | |
| document.getElementById('score-summary').classList.remove('hidden'); | |
| } | |
| // Reset all cards | |
| function resetAllCards() { | |
| // Clear all slots | |
| Object.keys(gameState.slots).forEach(slot => { | |
| gameState.slots[slot] = null; | |
| }); | |
| // Reset card positions | |
| gameState.cards.forEach(card => { | |
| gameState.cardPositions[card.id] = 'deck'; | |
| }); | |
| // Clear all visual slots | |
| document.querySelectorAll('.slot').forEach(slot => { | |
| slot.innerHTML = ''; | |
| }); | |
| // Re-render deck | |
| renderDeck(); | |
| // Hide results | |
| document.getElementById('results').classList.add('hidden'); | |
| document.getElementById('score-summary').classList.add('hidden'); | |
| // Reset combination labels | |
| ['player-top', 'player-middle', 'player-bottom', | |
| 'opponent-top', 'opponent-middle', 'opponent-bottom'].forEach(prefix => { | |
| document.getElementById(`${prefix}-combination`).textContent = '-'; | |
| document.getElementById(`${prefix}-points`).textContent = '0'; | |
| }); | |
| // Clear final score display | |
| ['final-player-top', 'final-player-middle', 'final-player-bottom', | |
| 'final-opponent-top', 'final-opponent-middle', 'final-opponent-bottom', | |
| 'final-player-score', 'final-opponent-score'].forEach(id => { | |
| document.getElementById(id).textContent = ''; | |
| }); | |
| // Disable calculate button | |
| document.getElementById('calculate-btn').disabled = true; | |
| gameState.lastMoveValid = false; | |
| } | |
| // Initialize the game | |
| function initGame() { | |
| initializeSlots(); | |
| gameState.cards = createDeck(); | |
| renderDeck(); | |
| setupDropZones(); | |
| // Set up buttons | |
| document.getElementById('calculate-btn').addEventListener('click', calculateBestMoves); | |
| document.getElementById('reset-btn').addEventListener('click', resetAllCards); | |
| // Disable calculate button initially | |
| document.getElementById('calculate-btn').disabled = true; | |
| } | |
| // Start the game | |
| initGame(); | |
| }); | |
| </script> |