Files
html-tools/index.html
T
2025-12-25 13:53:23 -07:00

384 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cribbage Hand Practice</title>
<style>
body {
background-color: #006400; /* darkgreen */
color: white;
font-family: Arial, sans-serif;
text-align: center;
padding-top: 20px;
}
#app {
max-width: 600px;
margin: auto;
padding: 0 10px;
}
button {
font-size: 1.2em;
padding: 10px 20px;
margin: 10px;
cursor: pointer;
border-radius: 5px;
border: 1px solid #ccc;
background-color: #f0f0f0;
}
#cardsContainer {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
gap: 10px;
margin: 20px 0;
min-height: 120px;
}
#hand {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 10px;
}
#starter {
margin-left: 20px;
}
.card {
width: 70px;
height: 100px;
border: 1px solid black;
border-radius: 5px;
background-color: white;
color: black;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 5px;
font-size: 24px;
font-weight: bold;
box-shadow: 2px 2px 5px rgba(0,0,0,0.3);
box-sizing: border-box;
}
.card.red {
color: red;
}
.card .rank {
text-align: left;
}
.card .suit {
font-size: 36px;
text-align: center;
line-height: 0.8;
}
#starter .card {
border: 3px solid gold;
}
@media (max-width: 480px) {
#cardsContainer {
flex-direction: column;
gap: 20px;
}
#starter {
margin-left: 0;
}
}
#scoreContainer {
margin-top: 20px;
background-color: rgba(0,0,0,0.2);
padding: 15px;
border-radius: 10px;
}
#scoreBreakdown ul {
list-style-type: none;
padding: 0;
text-align: left;
display: inline-block;
margin: 0;
}
#scoreBreakdown li {
margin-bottom: 5px;
font-size: 1.1em;
}
</style>
</head>
<body>
<div id="app">
<h1>Cribbage Hand Practice</h1>
<button id="dealButton">Deal New Hand</button>
<div id="cardsContainer">
<div id="hand"></div>
<div id="starter"></div>
</div>
<button id="revealButton" style="display: none;">Reveal Score</button>
<div id="scoreContainer" style="display: none;">
<h2>Total Score: <span id="totalScore"></span></h2>
<div id="scoreBreakdown"></div>
</div>
</div>
<script>
// --- Constants and Globals ---
const SUITS = ['♠', '♥', '♦', '♣'];
const RANKS = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'];
const VALUES = { 'A': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10, 'J': 10, 'Q': 10, 'K': 10 };
const RANK_ORDER = { 'A': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10, 'J': 11, 'Q': 12, 'K': 13 };
let currentHand = [];
let starterCard = null;
// --- DOM Elements ---
const dealButton = document.getElementById('dealButton');
const revealButton = document.getElementById('revealButton');
const handContainer = document.getElementById('hand');
const starterContainer = document.getElementById('starter');
const scoreContainer = document.getElementById('scoreContainer');
const totalScoreEl = document.getElementById('totalScore');
const scoreBreakdownEl = document.getElementById('scoreBreakdown');
// --- Event Listeners ---
dealButton.addEventListener('click', dealNewHand);
revealButton.addEventListener('click', showScore);
// --- Core Functions ---
function createDeck() {
const deck = [];
for (const suit of SUITS) {
for (const rank of RANKS) {
deck.push({
rank: rank,
suit: suit,
value: VALUES[rank],
order: RANK_ORDER[rank]
});
}
}
return deck;
}
function shuffleDeck(deck) {
for (let i = deck.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[deck[i], deck[j]] = [deck[j], deck[i]];
}
}
function dealNewHand() {
const deck = createDeck();
shuffleDeck(deck);
currentHand = deck.slice(0, 4);
starterCard = deck[4];
displayCards(currentHand, starterCard);
scoreContainer.style.display = 'none';
revealButton.style.display = 'inline-block';
}
// --- UI Functions ---
function createCardElement(card) {
const cardEl = document.createElement('div');
cardEl.classList.add('card');
const isRed = card.suit === '♥' || card.suit === '♦';
if (isRed) {
cardEl.classList.add('red');
}
cardEl.innerHTML = `
<div class="rank">${card.rank}</div>
<div class="suit">${card.suit}</div>
<div class="rank" style="transform: rotate(180deg); text-align: right;">${card.rank}</div>
`;
return cardEl;
}
function displayCards(hand, starter) {
handContainer.innerHTML = '';
starterContainer.innerHTML = '';
hand.forEach(card => {
handContainer.appendChild(createCardElement(card));
});
starterContainer.appendChild(createCardElement(starter));
}
function showScore() {
const { score, breakdown } = calculateScore(currentHand, starterCard);
totalScoreEl.textContent = score;
let breakdownHtml = '<ul>';
if (breakdown.length > 0) {
for (const item of breakdown) {
breakdownHtml += `<li>${item.reason}: ${item.points} points</li>`;
}
} else {
breakdownHtml += `<li>No score</li>`;
}
breakdownHtml += '</ul>';
scoreBreakdownEl.innerHTML = breakdownHtml;
scoreContainer.style.display = 'block';
revealButton.style.display = 'none';
}
// --- Scoring Logic ---
function getCombinations(arr, k) {
const result = [];
function backtrack(combination, start) {
if (combination.length === k) {
result.push([...combination]);
return;
}
for (let i = start; i < arr.length; i++) {
combination.push(arr[i]);
backtrack(combination, i + 1);
combination.pop();
}
}
backtrack([], 0);
return result;
}
function calculateScore(hand, starter) {
let score = 0;
const breakdown = [];
const allCards = [...hand, starter];
// 1. Fifteens
let fifteensCount = 0;
for (let k = 2; k <= 5; k++) {
const combos = getCombinations(allCards, k);
for (const combo of combos) {
const sum = combo.reduce((acc, card) => acc + card.value, 0);
if (sum === 15) {
fifteensCount++;
}
}
}
if (fifteensCount > 0) {
const points = fifteensCount * 2;
score += points;
breakdown.push({ reason: `Fifteens`, points });
}
// 2. Pairs
let pairsCount = 0;
const pairCombos = getCombinations(allCards, 2);
for (const pair of pairCombos) {
if (pair[0].rank === pair[1].rank) {
pairsCount++;
}
}
if (pairsCount > 0) {
const points = pairsCount * 2;
score += points;
breakdown.push({ reason: `Pairs`, points });
}
// 3. Runs
const runsScore = scoreRuns(allCards);
if (runsScore.points > 0) {
score += runsScore.points;
breakdown.push({ reason: runsScore.reason, points: runsScore.points });
}
// 4. Flush
const flushScore = scoreFlush(hand, starter);
if (flushScore.points > 0) {
score += flushScore.points;
breakdown.push({ reason: flushScore.reason, points: flushScore.points });
}
// 5. Nobs
for (const card of hand) {
if (card.rank === 'J' && card.suit === starter.suit) {
score += 1;
breakdown.push({ reason: 'Nobs', points: 1 });
break;
}
}
return { score, breakdown };
}
function scoreRuns(allCards) {
const sortedUniqueRanks = [...new Set(allCards.map(c => c.order))].sort((a, b) => a - b);
if (sortedUniqueRanks.length < 3) return { points: 0, reason: '' };
let runs = [];
let currentRun = [sortedUniqueRanks[0]];
for (let i = 1; i < sortedUniqueRanks.length; i++) {
if (sortedUniqueRanks[i] === sortedUniqueRanks[i - 1] + 1) {
currentRun.push(sortedUniqueRanks[i]);
} else {
if (currentRun.length >= 3) {
runs.push(currentRun);
}
currentRun = [sortedUniqueRanks[i]];
}
}
if (currentRun.length >= 3) {
runs.push(currentRun);
}
if (runs.length === 0) return { points: 0, reason: '' };
const runToScore = runs.reduce((longest, current) => current.length > longest.length ? current : longest, []);
const runLength = runToScore.length;
let multiplier = 1;
const rankCounts = {};
allCards.forEach(c => {
rankCounts[c.order] = (rankCounts[c.order] || 0) + 1;
});
for (const rank of runToScore) {
multiplier *= rankCounts[rank];
}
const points = runLength * multiplier;
const prefix = multiplier > 1 ? (['', 'Double', 'Triple', 'Quadruple'][multiplier - 1] || `${multiplier}x`) : '';
const reason = `${prefix} Run of ${runLength}`.trim();
return { points, reason };
}
function scoreFlush(hand, starter) {
const firstSuit = hand[0].suit;
const isHandFlush = hand.every(card => card.suit === firstSuit);
if (!isHandFlush) {
return { points: 0, reason: '' };
}
if (starter.suit === firstSuit) {
return { points: 5, reason: 'Flush of 5' };
} else {
return { points: 4, reason: 'Flush of 4' };
}
}
// --- Initial Load ---
dealNewHand();
</script>
</body>
</html>