cps / index.html
trd69's picture
Update index.html
a4122c9 verified
Raw
History Blame Contribute Delete
46.5 kB
<!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: 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>