| <!DOCTYPE html> |
| <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: 100%; |
| height: 90px; |
| border-radius: 6px; |
| 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: #e53e3e; |
| color: white; |
| } |
| |
| .card-black { |
| background-color: #1a202c; |
| color: white; |
| } |
| |
| .card-blue { |
| background-color: #3182ce; |
| color: white; |
| } |
| |
| .card-green { |
| background-color: #38a169; |
| color: white; |
| } |
| |
| .card-value { |
| font-size: 24px; |
| font-weight: bold; |
| text-align: center; |
| } |
| |
| .card-suit { |
| font-size: 32px; |
| align-self: center; |
| } |
| |
| .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: 0; |
| position: relative; |
| border-radius: 5px; |
| display: flex; |
| flex-direction: column; |
| justify-content: space-between; |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); |
| cursor: pointer; |
| } |
| |
| .deck-card { |
| width: 60px; |
| height: 90px; |
| border-radius: 5px; |
| display: flex; |
| flex-direction: column; |
| justify-content: space-between; |
| padding: 5px; |
| user-select: none; |
| } |
| |
| @media (max-width: 768px) { |
| .card { |
| width: 40px; |
| height: 60px; |
| } |
| |
| .slot { |
| width: 40px; |
| height: 60px; |
| } |
| |
| .card-value { |
| font-size: 18px; |
| } |
| |
| .card-suit { |
| font-size: 24px; |
| } |
| |
| .card-symbol { |
| font-size: 14px; |
| } |
| |
| } |
| </style> |
| </head> |
| <body class="bg-gray-900 text-white min-h-screen p-4"> |
| |
| <div class="flex flex-col items-center mb-6"> |
| <div class="flex items-center gap-3"> |
| <div class="w-10 h-10 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center text-white font-bold text-xl">S</div> |
| <h1 class="text-3xl font-bold text-yellow-300">SolveIT</h1> |
| </div> |
| <p class="text-sm text-gray-400 mt-1">Chinese Poker Solver</p> |
| </div> |
|
|
| |
| <div class="absolute right-0 top-0 flex gap-2 p-4"> |
| <button class="px-4 py-2 bg-gray-800 hover:bg-gray-700 rounded-lg text-sm transition"> |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129" /> |
| </svg> |
| </button> |
| <button class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg text-sm transition flex items-center gap-2"> |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /> |
| </svg> |
| Sign In |
| </button> |
| </div> |
|
|
| |
| <div class="container mx-auto max-w-6xl mb-6"> |
| <div class="flex border-b border-gray-700"> |
| <button id="solve-tab" class="px-6 py-3 font-medium text-yellow-300 border-b-2 border-yellow-300"> |
| Solve |
| </button> |
| <button id="train-tab" class="px-6 py-3 font-medium text-gray-400 hover:text-white"> |
| Train |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div id="train-content" class="hidden"> |
| <div class="flex flex-col items-center mb-8"> |
| <h2 class="text-2xl font-bold mb-4">Chinese Poker Trainer</h2> |
| <p class="text-gray-400 mb-6">Practice your Chinese Poker skills with interactive training</p> |
| |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8 w-full"> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="container mx-auto max-w-6xl relative"> |
| |
| <div id="solve-content"> |
|
|
| |
| <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 class="flex items-center gap-2"> |
| <input type="checkbox" id="add-opponent" class="h-5 w-5"> |
| <label for="add-opponent" class="text-sm">Add Opponent</label> |
| </div> |
| </div> |
| |
| |
| <div class="flex flex-row gap-4 mb-0"> |
| |
| <div class="w-[300px] flex-shrink-0"> |
| <h2 class="text-xl font-semibold mb-4">Your Rows</h2> |
|
|
| |
| <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> |
| |
| |
| <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> |
| |
| |
| <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 class="w-[300px] flex-shrink-0 ml-28"> |
| <h2 class="text-xl font-semibold mb-4 text-gray-400">Opponent 1's Rows</h2> |
|
|
| |
| <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> |
| |
| |
| <div class="row-container mb-2 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> |
| |
| |
| <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> |
|
|
| |
| <div id="opponent2-board" class="w-[300px] flex-shrink-0 hidden ml-28"> |
| <h2 class="text-xl font-semibold mb-4 text-gray-400">Opponent 2's Rows</h2> |
|
|
| |
| <div class="row-container mb-2 opponent-row"> |
| <div class="flex gap-2"> |
| <div id="opponent2-top-0" class="slot" data-row="opponent2-top"></div> |
| <div id="opponent2-top-1" class="slot" data-row="opponent2-top"></div> |
| <div id="opponent2-top-2" class="slot" data-row="opponent2-top"></div> |
| </div> |
| <div class="combination-label" id="opponent2-top-combination">-</div> |
| <div class="points-label" id="opponent2-top-points">0</div> |
| </div> |
| |
| |
| <div class="row-container mb-2 opponent-row"> |
| <div class="flex justify-center items-end gap-2"> |
| <div id="opponent2-middle-0" class="slot" data-row="opponent2-middle"></div> |
| <div id="opponent2-middle-1" class="slot" data-row="opponent2-middle"></div> |
| <div id="opponent2-middle-2" class="slot" data-row="opponent2-middle"></div> |
| <div id="opponent2-middle-3" class="slot" data-row="opponent2-middle"></div> |
| <div id="opponent2-middle-4" class="slot" data-row="opponent2-middle"></div> |
| </div> |
| <div class="combination-label" id="opponent2-middle-combination">-</div> |
| <div class="points-label" id="opponent2-middle-points">0</div> |
| </div> |
| |
| |
| <div class="row-container opponent-row"> |
| <div class="flex justify-center items-end gap-2"> |
| <div id="opponent2-bottom-0" class="slot" data-row="opponent2-bottom"></div> |
| <div id="opponent2-bottom-1" class="slot" data-row="opponent2-bottom"></div> |
| <div id="opponent2-bottom-2" class="slot" data-row="opponent2-bottom"></div> |
| <div id="opponent2-bottom-3" class="slot" data-row="opponent2-bottom"></div> |
| <div id="opponent2-bottom-4" class="slot" data-row="opponent2-bottom"></div> |
| </div> |
| <div class="combination-label" id="opponent2-bottom-combination">-</div> |
| <div class="points-label" id="opponent2-bottom-points">0</div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="flex flex-row gap-6 mt-6 mb-8"> |
| |
| <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> |
|
|
| |
| <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> |
| |
| |
| <div class="mt-8 mx-auto" style="width: 1000px; height: 475px;"> |
| |
| <div class="bg-gray-800 rounded-lg p-4 h-full overflow-y-auto"> |
| <h2 class="text-xl font-semibold mb-4">Deck</h2> |
| <div class="flex flex-col gap-2"> |
| |
| <div class="flex gap-2" id="deck-hearts"> |
| |
| </div> |
| |
| <div class="flex gap-2" id="deck-diamonds"> |
| |
| </div> |
| |
| <div class="flex gap-2" id="deck-clubs"> |
| |
| </div> |
| |
| <div class="flex gap-2" id="deck-spades"> |
| |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| |
| <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"> |
| |
| </div> |
| </div> |
| |
| |
| <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() { |
| |
| const gameState = { |
| cards: [], |
| slots: {}, |
| cardPositions: {}, |
| lastMoveValid: false |
| }; |
| |
| |
| function initializeSlots() { |
| |
| 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; |
| } |
| |
| |
| for (let i = 0; i < 3; i++) { |
| gameState.slots[`opponent2-top-${i}`] = null; |
| } |
| for (let i = 0; i < 5; i++) { |
| gameState.slots[`opponent2-middle-${i}`] = null; |
| gameState.slots[`opponent2-bottom-${i}`] = null; |
| } |
| |
| |
| for (let i = 0; i < 5; i++) { |
| gameState.slots[`hand-${i}`] = null; |
| } |
| for (let i = 0; i < 3; i++) { |
| gameState.slots[`discard-${i}`] = null; |
| } |
| |
| |
| 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; |
| } |
| |
| |
| } |
| |
| |
| 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, |
| element: null |
| }); |
| } |
| } |
| |
| return deck; |
| } |
| |
| |
| function renderDeck() { |
| const suits = { |
| 'hearts': document.getElementById('deck-hearts'), |
| 'diamonds': document.getElementById('deck-diamonds'), |
| 'clubs': document.getElementById('deck-clubs'), |
| 'spades': document.getElementById('deck-spades') |
| }; |
| |
| |
| Object.values(suits).forEach(row => row.innerHTML = ''); |
| |
| |
| const sortedCards = [...gameState.cards].sort((a, b) => { |
| if (a.suit !== b.suit) return a.suit.localeCompare(b.suit); |
| return b.rank - a.rank; |
| }); |
| |
| |
| sortedCards.forEach(card => { |
| const slot = document.createElement('div'); |
| slot.className = 'deck-slot'; |
| slot.dataset.slotId = `deck-${card.suit}-${card.value}`; |
| |
| const cardElement = document.createElement('div'); |
| cardElement.className = `deck-card ${card.suitClass}`; |
| cardElement.dataset.cardId = card.id; |
| cardElement.innerHTML = ` |
| <div class="card-value text-center">${card.value}</div> |
| <div class="card-suit">${card.suitSymbol}</div> |
| `; |
| |
| cardElement.draggable = true; |
| cardElement.addEventListener('dragstart', handleDragStart); |
| cardElement.addEventListener('contextmenu', function(e) { |
| e.preventDefault(); |
| returnCardToDeck(card.id); |
| }); |
| |
| slot.dataset.slotId = `deck-${card.suit}-${card.value}`; |
| slot.dataset.row = 'deck'; |
| |
| slot.appendChild(cardElement); |
| suits[card.suit].appendChild(slot); |
| card.element = cardElement; |
| gameState.cardPositions[card.id] = `deck-${card.suit}-${card.value}`; |
| }); |
| } |
| |
| |
| function returnCardToDeck(cardId) { |
| const card = gameState.cards.find(c => c.id == cardId); |
| if (!card) return; |
| |
| |
| const currentPos = gameState.cardPositions[cardId]; |
| if (currentPos && currentPos.startsWith('deck-')) return; |
| |
| if (currentPos && currentPos !== 'deck') { |
| gameState.slots[currentPos] = null; |
| } |
| |
| |
| const deckSlotId = `deck-${card.suit}-${card.value}`; |
| const deckSlot = document.querySelector(`[data-slot-id="${deckSlotId}"]`); |
| |
| if (deckSlot) { |
| deckSlot.innerHTML = ''; |
| deckSlot.appendChild(card.element); |
| gameState.cardPositions[cardId] = deckSlotId; |
| |
| |
| checkGameState(); |
| } |
| } |
| |
| |
| function handleDragStart(e) { |
| const cardId = e.target.dataset.cardId; |
| e.dataTransfer.setData('text/plain', cardId); |
| e.dataTransfer.effectAllowed = 'move'; |
| |
| |
| const card = gameState.cards.find(c => c.id == cardId); |
| if (card) { |
| e.dataTransfer.setData('originalPosition', gameState.cardPositions[cardId] || 'deck'); |
| } |
| } |
| |
| |
| function setupDropZones() { |
| const slots = document.querySelectorAll('.slot, .deck-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); |
| }); |
| }); |
| } |
| |
| |
| function moveCard(cardId, toSlotId) { |
| const card = gameState.cards.find(c => c.id == cardId); |
| if (!card) return; |
| |
| |
| const fromPosition = gameState.cardPositions[cardId]; |
| if (fromPosition) { |
| if (fromPosition.startsWith('deck-')) { |
| |
| const deckSlot = document.querySelector(`[data-slot-id="${fromPosition}"]`); |
| if (deckSlot) deckSlot.innerHTML = ''; |
| } else { |
| gameState.slots[fromPosition] = null; |
| } |
| } |
| |
| |
| if (gameState.slots[toSlotId]) { |
| const otherCardId = gameState.slots[toSlotId]; |
| gameState.slots[fromPosition] = otherCardId; |
| gameState.cardPositions[otherCardId] = fromPosition || `deck-${card.suit}-${card.value}`; |
| |
| |
| if (fromPosition) { |
| const otherSlot = document.getElementById(fromPosition); |
| if (otherSlot) { |
| otherSlot.innerHTML = ''; |
| otherSlot.appendChild(gameState.cards.find(c => c.id == otherCardId).element); |
| } |
| } else { |
| |
| const otherCard = gameState.cards.find(c => c.id == otherCardId); |
| const deckSlotId = `deck-${otherCard.suit}-${otherCard.value}`; |
| const deckSlot = document.querySelector(`[data-slot-id="${deckSlotId}"]`); |
| if (deckSlot) { |
| deckSlot.innerHTML = ''; |
| deckSlot.appendChild(otherCard.element); |
| } |
| } |
| } |
| |
| |
| gameState.slots[toSlotId] = card.id; |
| gameState.cardPositions[cardId] = toSlotId; |
| |
| |
| const toSlot = document.getElementById(toSlotId); |
| if (toSlot) { |
| toSlot.innerHTML = ''; |
| toSlot.appendChild(card.element); |
| } |
| |
| |
| checkGameState(); |
| } |
| |
| |
| function checkGameState() { |
| const calculateBtn = document.getElementById('calculate-btn'); |
| |
| |
| 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 opponent1Cards = Object.values(gameState.slots).filter( |
| v => v !== null && gameState.cardPositions[v]?.startsWith('opponent-') |
| ).length; |
| |
| const opponent2Cards = document.getElementById('add-opponent').checked ? |
| Object.values(gameState.slots).filter( |
| v => v !== null && gameState.cardPositions[v]?.startsWith('opponent2-') |
| ).length : 0; |
| |
| const opponentCards = opponent1Cards + opponent2Cards; |
| |
| const discardCards = Object.values(gameState.slots).filter( |
| v => v !== null && gameState.cardPositions[v]?.startsWith('discard-') |
| ).length; |
| |
| |
| const isValidState = ( |
| |
| (handCards === 5 && playerCards === 0 && opponentCards === 0 && discardCards === 0) || |
| (handCards === 5 && playerCards === 0 && opponent1Cards === 5 && opponent2Cards === 0 && discardCards === 0) || |
| (handCards === 5 && playerCards === 0 && opponent1Cards === 0 && opponent2Cards === 5 && discardCards === 0) || |
| (handCards === 5 && playerCards === 0 && opponent1Cards === 5 && opponent2Cards === 5 && discardCards === 0) || |
| |
| |
| (handCards === 3 && playerCards === 5 && opponent1Cards === 5 && opponent2Cards === 5 && discardCards === 0) || |
| (handCards === 3 && playerCards === 5 && opponent1Cards === 5 && opponent2Cards === 7 && discardCards === 0) || |
| (handCards === 3 && playerCards === 5 && opponent1Cards === 7 && opponent2Cards === 5 && discardCards === 0) || |
| (handCards === 3 && playerCards === 5 && opponent1Cards === 7 && opponent2Cards === 7 && discardCards === 0) || |
| |
| |
| (handCards === 3 && playerCards === 7 && opponent1Cards === 7 && opponent2Cards === 7 && discardCards === 1) || |
| (handCards === 3 && playerCards === 7 && opponent1Cards === 7 && opponent2Cards === 9 && discardCards === 1) || |
| (handCards === 3 && playerCards === 7 && opponent1Cards === 9 && opponent2Cards === 7 && discardCards === 1) || |
| (handCards === 3 && playerCards === 7 && opponent1Cards === 9 && opponent2Cards === 9 && discardCards === 1) || |
| |
| |
| (handCards === 3 && playerCards === 9 && opponent1Cards === 9 && opponent2Cards === 9 && discardCards === 2) || |
| (handCards === 3 && playerCards === 9 && opponent1Cards === 9 && opponent2Cards === 11 && discardCards === 2) || |
| (handCards === 3 && playerCards === 9 && opponent1Cards === 11 && opponent2Cards === 9 && discardCards === 2) || |
| (handCards === 3 && playerCards === 9 && opponent1Cards === 11 && opponent2Cards === 11 && discardCards === 2) || |
| |
| |
| (handCards === 3 && playerCards === 11 && opponent1Cards === 11 && opponent2Cards === 11 && discardCards === 3) || |
| (handCards === 3 && playerCards === 11 && opponent1Cards === 11 && opponent2Cards === 13 && discardCards === 3) || |
| (handCards === 3 && playerCards === 11 && opponent1Cards === 13 && opponent2Cards === 11 && discardCards === 3) || |
| (handCards === 3 && playerCards === 11 && opponent1Cards === 13 && opponent2Cards === 13 && discardCards === 3) |
| ); |
| |
| |
| |
| |
| |
| if (isValidState) { |
| calculateBtn.disabled = false; |
| gameState.lastMoveValid = true; |
| } |
| else { |
| calculateBtn.disabled = true; |
| gameState.lastMoveValid = false; |
| } |
| |
| showFinalScores(); |
| |
| updateCombinationLabels(); |
| } |
| |
| |
| function updateCombinationLabels() { |
| |
| updateRowCombination('player-top', 3); |
| updateRowCombination('player-middle', 5); |
| updateRowCombination('player-bottom', 5); |
| |
| |
| updateRowCombination('opponent-top', 3); |
| updateRowCombination('opponent-middle', 5); |
| updateRowCombination('opponent-bottom', 5); |
| |
| |
| if (document.getElementById('add-opponent').checked) { |
| updateRowCombination('opponent2-top', 3); |
| updateRowCombination('opponent2-middle', 5); |
| updateRowCombination('opponent2-bottom', 5); |
| } |
| } |
| |
| |
| 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; |
| |
| |
| 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; |
| } |
| |
| |
| function getCombinationStrength(cards, combination) { |
| |
| const sortedCards = [...cards].sort((a, b) => b.rank - a.rank); |
| |
| |
| switch(combination) { |
| case 'Pair': |
| |
| const pairValue = sortedCards.find(card => |
| sortedCards.filter(c => c.rank === card.rank).length === 2 |
| ).rank; |
| |
| 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': |
| |
| 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]; |
| |
| default: |
| return [sortedCards[0].rank]; |
| } |
| } |
| |
| |
| |
| function evaluateCombinationWithCards(cards, rowType) { |
| if (cards.length === 0) return { |
| combination: 'None', |
| strength: [], |
| cards: [] |
| }; |
| |
| |
| const sortedCards = [...cards].sort((a, b) => b.rank - a.rank); |
| |
| |
| const isFlush = sortedCards.every(c => c.suit === sortedCards[0].suit); |
| |
| |
| let isStraight = true; |
| for (let i = 1; i < sortedCards.length; i++) { |
| if (sortedCards[i].rank !== sortedCards[i-1].rank - 1) { |
| isStraight = false; |
| break; |
| } |
| } |
| |
| |
| 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); |
| |
| |
| const isRoyal = isFlush && |
| sortedCards[0].rank === 14 && |
| sortedCards[1].rank === 13 && |
| sortedCards[2].rank === 12 && |
| sortedCards[3].rank === 11 && |
| sortedCards[4].rank === 10; |
| |
| |
| if (isStraight && isFlush && sortedCards.length >= 5) { |
| const combination = isRoyal ? 'Royal Flush' : 'Straight Flush'; |
| return { |
| combination, |
| strength: getCombinationStrength(sortedCards, combination), |
| cards: sortedCards |
| }; |
| } |
| |
| |
| 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 |
| }; |
| } |
| |
| |
| if (counts.includes(3) && counts.includes(2) && sortedCards.length >= 5) { |
| return { |
| combination: 'Full House', |
| strength: getCombinationStrength(sortedCards, 'Full House'), |
| cards: sortedCards |
| }; |
| } |
| |
| |
| if (isFlush && sortedCards.length >= 5) { |
| return { |
| combination: 'Flush', |
| strength: getCombinationStrength(sortedCards, 'Flush'), |
| cards: sortedCards |
| }; |
| } |
| |
| |
| if (isStraight && sortedCards.length >= 5) { |
| return { |
| combination: 'Straight', |
| strength: getCombinationStrength(sortedCards, 'Straight'), |
| cards: sortedCards |
| }; |
| } |
| |
| |
| if (counts.includes(3)) { |
| return { |
| combination: 'Three of a Kind', |
| strength: getCombinationStrength(sortedCards, 'Three of a Kind'), |
| cards: sortedCards |
| }; |
| } |
| |
| |
| const pairCount = counts.filter(c => c === 2).length; |
| if (pairCount === 2) { |
| return { |
| combination: 'Two Pairs', |
| strength: getCombinationStrength(sortedCards, 'Two Pairs'), |
| cards: sortedCards |
| }; |
| } |
| |
| |
| if (pairCount === 1) { |
| return { |
| combination: 'Pair', |
| strength: getCombinationStrength(sortedCards, 'Pair'), |
| cards: sortedCards |
| }; |
| } |
| |
| |
| return { |
| combination: `High ${sortedCards[0].value}`, |
| strength: getCombinationStrength(sortedCards, 'High Card'), |
| cards: sortedCards |
| }; |
| } |
| |
| function compareCombinations(comb1, comb2) { |
| |
| const rank1 = getCombinationRank(comb1.combination); |
| const rank2 = getCombinationRank(comb2.combination); |
| |
| if (rank1 !== rank2) { |
| return rank1 > rank2 ? 1 : -1; |
| } |
| |
| |
| 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; |
| } |
| } |
| |
| |
| return 0; |
| } |
| function isDeadHand(rows) { |
| const { top, middle, bottom } = rows; |
| return !(compareCombinations(top, middle) <= 0 && |
| compareCombinations(middle, bottom) <= 0); |
| } |
| |
| 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') { |
| |
| 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: { |
| player: { rowWins: 0, royalties: 0 }, |
| opponent: { rowWins: 0, royalties: 0 } |
| } |
| }; |
| } |
| |
| const playerDead = isDeadHand(playerRows); |
| const opponentDead = isDeadHand(opponentRows); |
| |
| if (playerDead && opponentDead) { |
| return { |
| player: 0, |
| opponent: 0, |
| details: { |
| player: { rowWins: 0, royalties: 0 }, |
| opponent: { rowWins: 0, royalties: 0 } |
| } |
| }; |
| } |
| |
| if (playerDead) { |
| const opponentRoyalties = calculateRoyalties(opponentRows); |
| return { |
| player: 0, |
| opponent: 6 + opponentRoyalties, |
| details: { |
| player: { rowWins: 0, royalties: 0 }, |
| opponent: { rowWins: 3, royalties: opponentRoyalties } |
| } |
| }; |
| } |
| |
| if (opponentDead) { |
| const playerRoyalties = calculateRoyalties(playerRows); |
| return { |
| player: 6 + playerRoyalties, |
| opponent: 0, |
| details: { |
| player: { rowWins: 3, royalties: playerRoyalties }, |
| opponent: { rowWins: 0, royalties: 0 } |
| } |
| }; |
| } |
| |
| const topResult = compareCombinations(playerRows.top, opponentRows.top); |
| const middleResult = compareCombinations(playerRows.middle, opponentRows.middle); |
| const bottomResult = compareCombinations(playerRows.bottom, opponentRows.bottom); |
| |
| |
| 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++; |
| |
| |
| 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); |
| |
| |
| playerScore += calculateRoyalties(playerRows); |
| opponentScore += calculateRoyalties(opponentRows); |
| |
| return { |
| player: playerScore, |
| opponent: opponentScore, |
| details: { |
| player: { rowWins: playerRowWins, royalties: playerRoyalties }, |
| opponent: { rowWins: opponentRowWins, royalties: opponentRoyalties } |
| } |
| }; |
| } |
| |
| |
| function calculateBestMoves() { |
| |
| 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; |
| } |
| |
| |
| |
| |
| |
| const handCardIds = []; |
| for (let i = 0; i < 5; i++) { |
| const cardId = gameState.slots[`hand-${i}`]; |
| if (cardId !== null) { |
| handCardIds.push(cardId); |
| } |
| } |
| |
| |
| 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'; |
| |
| |
| 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'); |
| } |
| |
| |
| 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; |
| |
| |
| 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'); |
| } |
| |
| |
| function resetAllCards() { |
| |
| Object.keys(gameState.slots).forEach(slot => { |
| gameState.slots[slot] = null; |
| }); |
| |
| |
| gameState.cards.forEach(card => { |
| gameState.cardPositions[card.id] = 'deck'; |
| }); |
| |
| |
| document.querySelectorAll('.slot').forEach(slot => { |
| slot.innerHTML = ''; |
| }); |
| |
| |
| renderDeck(); |
| |
| |
| document.getElementById('results').classList.add('hidden'); |
| document.getElementById('score-summary').classList.add('hidden'); |
| |
| |
| ['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'; |
| }); |
| |
| |
| ['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 = ''; |
| }); |
| |
| |
| document.getElementById('calculate-btn').disabled = true; |
| gameState.lastMoveValid = false; |
| } |
| |
| |
| document.getElementById('add-opponent').addEventListener('change', function(e) { |
| const opponent2Board = document.getElementById('opponent2-board'); |
| if (e.target.checked) { |
| opponent2Board.classList.remove('hidden'); |
| |
| document.querySelector('.absolute.right-8').style.right = '320px'; |
| } else { |
| opponent2Board.classList.add('hidden'); |
| |
| document.querySelector('.absolute.right-8').style.right = '8px'; |
| |
| ['top', 'middle', 'bottom'].forEach(row => { |
| for (let i = 0; i < (row === 'top' ? 3 : 5); i++) { |
| const slotId = `opponent2-${row}-${i}`; |
| gameState.slots[slotId] = null; |
| const slot = document.getElementById(slotId); |
| if (slot) slot.innerHTML = ''; |
| } |
| }); |
| } |
| checkGameState(); |
| }); |
| |
| function initGame() { |
| initializeSlots(); |
| gameState.cards = createDeck(); |
| renderDeck(); |
| setupDropZones(); |
| |
| |
| |
| document.getElementById('calculate-btn').addEventListener('click', calculateBestMoves); |
| document.getElementById('reset-btn').addEventListener('click', resetAllCards); |
| |
| |
| document.getElementById('calculate-btn').disabled = true; |
| } |
| |
| |
| document.getElementById('solve-tab').addEventListener('click', function() { |
| document.getElementById('solve-content').classList.remove('hidden'); |
| document.getElementById('train-content').classList.add('hidden'); |
| this.classList.add('text-yellow-300', 'border-b-2', 'border-yellow-300'); |
| document.getElementById('train-tab').classList.remove('text-yellow-300', 'border-b-2', 'border-yellow-300'); |
| document.getElementById('train-tab').classList.add('text-gray-400'); |
| }); |
| |
| document.getElementById('train-tab').addEventListener('click', function() { |
| document.getElementById('train-content').classList.remove('hidden'); |
| document.getElementById('solve-content').classList.add('hidden'); |
| this.classList.add('text-yellow-300', 'border-b-2', 'border-yellow-300'); |
| document.getElementById('solve-tab').classList.remove('text-yellow-300', 'border-b-2', 'border-yellow-300'); |
| document.getElementById('solve-tab').classList.add('text-gray-400'); |
| }); |
| |
| |
| initGame(); |
| }); |
| </script> |