Rename file
This commit is contained in:
+383
@@ -0,0 +1,383 @@
|
||||
<!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>
|
||||
<div id="cardsContainer">
|
||||
<div id="hand"></div>
|
||||
<div id="starter"></div>
|
||||
</div>
|
||||
<button id="dealButton">Deal New Hand</button>
|
||||
<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>
|
||||
Reference in New Issue
Block a user