Align Pyramid Solitaire with standard rules and layout

This commit is contained in:
chris 2026-05-25 00:55:05 -04:00
parent 0fcd6b190d
commit 91d4bcc25f
2 changed files with 62 additions and 41 deletions

View File

@ -1,4 +1,4 @@
.pyramid-board { #pyramid-board {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@ -15,23 +15,33 @@
max-width: 1000px; max-width: 1000px;
} }
.pyramid-layout .card {
position: absolute; /* Essential for correct placement in layout */
transition: all 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
z-index: 100;
}
.pyramid-bottom-piles { .pyramid-bottom-piles {
display: flex; display: flex;
width: 100%; width: 100%;
justify-content: center; justify-content: center;
gap: 100px; gap: 80px;
align-items: flex-start; align-items: flex-start;
margin-top: 20px;
} }
.discard-pile-container { /* Make stock look like a stack */
display: flex; #pyramid-stock::after {
flex-direction: column; content: '';
align-items: center; position: absolute;
} top: 4px;
left: 4px;
.pyramid-layout .card { width: 100%;
transition: all 0.4s cubic-bezier(0.2, 0.8, 0.2, 1); height: 100%;
z-index: 100; border: 2px solid var(--line);
border-radius: var(--card-radius);
z-index: -1;
background: var(--card-bg);
} }
.card.is-selected { .card.is-selected {

View File

@ -144,47 +144,50 @@
const boardRect = gameBoard.getBoundingClientRect(); const boardRect = gameBoard.getBoundingClientRect();
const layoutRect = pyramidLayout.getBoundingClientRect(); const layoutRect = pyramidLayout.getBoundingClientRect();
// If the board isn't visible yet, wait for the next frame // If the board isn't visible yet or dimensions aren't ready, wait for the next frame
if (boardRect.width === 0) { if (boardRect.width === 0 || layoutRect.width === 0) {
requestAnimationFrame(renderBoard); requestAnimationFrame(renderBoard);
return; return;
} }
const rootStyle = getComputedStyle(document.documentElement); const rootStyle = getComputedStyle(document.documentElement);
let cardWidth = parseFloat(rootStyle.getPropertyValue('--card-width')); let cardWidth = parseFloat(rootStyle.getPropertyValue('--card-width'));
if (isNaN(cardWidth) || cardWidth === 0) cardWidth = 80;
// Fallback for cardWidth if parsing fails
if (isNaN(cardWidth) || cardWidth === 0) {
cardWidth = 90;
}
const cardHeight = cardWidth * 1.4; const cardHeight = cardWidth * 1.4;
const rowOverlap = 0.45; const rowOverlap = 0.45;
const horizontalGap = 10; const horizontalGap = cardWidth * 0.15; // Proportional gap
// Position Pyramid Cards
let index = 0;
const boardTop = boardRect.top; const boardTop = boardRect.top;
const boardLeft = boardRect.left; const boardLeft = boardRect.left;
// Position Pyramid Cards
let index = 0;
for (let row = 0; row < 7; row++) { for (let row = 0; row < 7; row++) {
const rowWidth = (row + 1) * cardWidth + row * horizontalGap; // Calculate total width of this row to center it
const startX = (layoutRect.width - rowWidth) / 2; const numCards = row + 1;
const totalRowWidth = numCards * cardWidth + (numCards - 1) * horizontalGap;
const startX = (layoutRect.width - totalRowWidth) / 2;
const y = row * (cardHeight * rowOverlap); const y = row * (cardHeight * rowOverlap);
for (let i = 0; i <= row; i++) { for (let i = 0; i <= row; i++) {
const card = pyramid[index]; const card = pyramid[index];
if (card) { if (card) {
const el = cardElements[card.id]; const el = cardElements[card.id];
// Calculate final positions relative to the board // Absolute position relative to the gameBoard
const finalTop = y + (layoutRect.top - boardTop); const finalTop = y + (layoutRect.top - boardTop);
const finalLeft = startX + i * (cardWidth + horizontalGap) + (layoutRect.left - boardLeft); const finalLeft = startX + i * (cardWidth + horizontalGap) + (layoutRect.left - boardLeft);
el.style.top = `${finalTop}px`; el.style.top = `${finalTop}px`;
el.style.left = `${finalLeft}px`; el.style.left = `${finalLeft}px`;
el.style.zIndex = 100 + row; el.style.zIndex = 100 + index;
el.classList.add('is-flipped'); el.classList.add('is-flipped');
el.draggable = false;
// Mark if it's currently playable for visual feedback
const exposed = isExposed(card.id);
el.classList.toggle('is-exposed', exposed);
el.style.opacity = exposed ? '1' : '0.85';
// el.style.filter = exposed ? 'none' : 'brightness(0.8)'; // Optional: dim unexposed cards
} }
index++; index++;
} }
@ -194,32 +197,36 @@
const sRect = stockEl.getBoundingClientRect(); const sRect = stockEl.getBoundingClientRect();
stock.forEach((card, i) => { stock.forEach((card, i) => {
const el = cardElements[card.id]; const el = cardElements[card.id];
el.style.top = `${sRect.top - boardRect.top + PILE_BORDER_WIDTH}px`; el.style.top = `${sRect.top - boardTop + PILE_BORDER_WIDTH}px`;
el.style.left = `${sRect.left - boardRect.left + PILE_BORDER_WIDTH}px`; el.style.left = `${sRect.left - boardLeft + PILE_BORDER_WIDTH}px`;
el.style.zIndex = 10 + i; el.style.zIndex = 10 + i;
el.classList.remove('is-flipped'); el.classList.remove('is-flipped');
el.classList.remove('is-selected'); // Selection only on top of waste/pyramid el.classList.remove('is-selected');
el.classList.remove('is-exposed');
}); });
// Position Waste // Position Waste
const wRect = wasteEl.getBoundingClientRect(); const wRect = wasteEl.getBoundingClientRect();
waste.forEach((card, i) => { waste.forEach((card, i) => {
const el = cardElements[card.id]; const el = cardElements[card.id];
el.style.top = `${wRect.top - boardRect.top + PILE_BORDER_WIDTH}px`; const isTop = (i === waste.length - 1);
el.style.left = `${wRect.left - boardRect.left + PILE_BORDER_WIDTH}px`; el.style.top = `${wRect.top - boardTop + PILE_BORDER_WIDTH}px`;
el.style.left = `${wRect.left - boardLeft + PILE_BORDER_WIDTH}px`;
el.style.zIndex = 50 + i; el.style.zIndex = 50 + i;
el.classList.add('is-flipped'); el.classList.add('is-flipped');
el.classList.toggle('is-exposed', isTop);
}); });
// Position Discard // Position Discard
const dRect = discardEl.getBoundingClientRect(); const dRect = discardEl.getBoundingClientRect();
discard.forEach((card, i) => { discard.forEach((card, i) => {
const el = cardElements[card.id]; const el = cardElements[card.id];
el.style.top = `${dRect.top - boardRect.top + PILE_BORDER_WIDTH}px`; el.style.top = `${dRect.top - boardTop + PILE_BORDER_WIDTH}px`;
el.style.left = `${dRect.left - boardRect.left + PILE_BORDER_WIDTH}px`; el.style.left = `${dRect.left - boardLeft + PILE_BORDER_WIDTH}px`;
el.style.zIndex = 10 + i; el.style.zIndex = 10 + i;
el.classList.add('is-flipped'); el.classList.add('is-flipped');
el.classList.remove('is-selected'); el.classList.remove('is-selected');
el.classList.remove('is-exposed');
}); });
checkWin(); checkWin();
@ -228,12 +235,12 @@
function handleCardClick(cardId) { function handleCardClick(cardId) {
if (!isActive) return; if (!isActive) return;
// Find card in pyramid or waste // Find card
let card = null; let card = null;
let source = ''; let source = '';
let index = pyramid.findIndex(c => c && c.id === cardId); let pIdx = pyramid.findIndex(c => c && c.id === cardId);
if (index !== -1) { if (pIdx !== -1) {
card = pyramid[index]; card = pyramid[pIdx];
source = 'pyramid'; source = 'pyramid';
} else if (waste.length > 0 && waste[waste.length - 1].id === cardId) { } else if (waste.length > 0 && waste[waste.length - 1].id === cardId) {
card = waste[waste.length - 1]; card = waste[waste.length - 1];
@ -245,10 +252,13 @@
const el = cardElements[card.id]; const el = cardElements[card.id];
// King is special (13) // King is special (13) - remove immediately
if (getValueRank(card.value) === 13) { if (getValueRank(card.value) === 13) {
removeCards([card]); if (selectedCard) {
cardElements[selectedCard.id].classList.remove('is-selected');
selectedCard = null; selectedCard = null;
}
removeCards([card]);
return; return;
} }
@ -257,15 +267,16 @@
el.classList.add('is-selected'); el.classList.add('is-selected');
} else { } else {
if (selectedCard.id === card.id) { if (selectedCard.id === card.id) {
// Deselect // Deselect if clicking the same card
el.classList.remove('is-selected'); el.classList.remove('is-selected');
selectedCard = null; selectedCard = null;
} else { } else {
// Try to match
if (getValueRank(selectedCard.value) + getValueRank(card.value) === 13) { if (getValueRank(selectedCard.value) + getValueRank(card.value) === 13) {
removeCards([selectedCard, card]); removeCards([selectedCard, card]);
selectedCard = null; selectedCard = null;
} else { } else {
// Invalid pair - switch selection to new card // Invalid pair: switch selection to the new card
cardElements[selectedCard.id].classList.remove('is-selected'); cardElements[selectedCard.id].classList.remove('is-selected');
selectedCard = card; selectedCard = card;
el.classList.add('is-selected'); el.classList.add('is-selected');