Add files via upload
This commit is contained in:
commit
1c99d6fbb9
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2022 by Jessica Guarato (https://codepen.io/jguarato/pen/PoRQPqG)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
4
README.md
Normal file
4
README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Solitaire (desktop browsers only)
|
||||
|
||||
A Pen created on CodePen.io. Original URL: [https://codepen.io/jguarato/pen/PoRQPqG](https://codepen.io/jguarato/pen/PoRQPqG).
|
||||
|
49
dist/index.html
vendored
Normal file
49
dist/index.html
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" >
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>CodePen - Solitaire (desktop browsers only)</title>
|
||||
<link rel="stylesheet" href="./style.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<!-- partial:index.partial.html -->
|
||||
<div class="top-area">
|
||||
<div class="container">
|
||||
<div class="left-area">
|
||||
<div id="deck" class="deck"></div>
|
||||
<div id="top-cards" class="top-cards"></div>
|
||||
</div>
|
||||
<div class="right-area">
|
||||
<div class="pile"><span id="spade-cards">♠</span></div>
|
||||
<div class="pile"><span id="heart-cards">♥</span></div>
|
||||
<div class="pile"><span id="diamond-cards">♦</span></div>
|
||||
<div class="pile"><span id="club-cards">♣</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="bottom-area">
|
||||
<div class="row"></div>
|
||||
<div class="row"></div>
|
||||
<div class="row"></div>
|
||||
<div class="row"></div>
|
||||
<div class="row"></div>
|
||||
<div class="row"></div>
|
||||
<div class="row"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>
|
||||
Created by <a class="name-dark">jguarato</a> •
|
||||
<a href="https://codepen.io/jguarato" target="_blank">Check out my other Pens</a> •
|
||||
<a href="https://github.com/jguarato" target="_blank">Follow me on GitHub</a>
|
||||
</p>
|
||||
</footer>
|
||||
<!-- partial -->
|
||||
<script src="./script.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
414
dist/script.js
vendored
Normal file
414
dist/script.js
vendored
Normal file
@ -0,0 +1,414 @@
|
||||
function createCard(rank, suit, higher) {
|
||||
|
||||
let suitSymbol, cardColor;
|
||||
switch (suit) {
|
||||
case 'spade':
|
||||
suitSymbol = '♠';
|
||||
cardColor = 'black';
|
||||
break;
|
||||
case 'heart':
|
||||
suitSymbol = '♥';
|
||||
cardColor = 'red';
|
||||
break;
|
||||
case 'diamond':
|
||||
suitSymbol = '♦';
|
||||
cardColor = 'red';
|
||||
break;
|
||||
case 'club':
|
||||
suitSymbol = '♣';
|
||||
cardColor = 'black';
|
||||
break;
|
||||
default:
|
||||
suitSymbol = '🃏';
|
||||
cardColor = 'black';
|
||||
break;
|
||||
}
|
||||
|
||||
let frontInnerHTML = `<div class="inner-info card-rank">${rank}</div>`;
|
||||
frontInnerHTML += `<div class="inner-info card-suit-small">${suitSymbol}</div>`;
|
||||
frontInnerHTML += `<div class="inner-info card-suit-big">${suitSymbol}</div>`;
|
||||
|
||||
return {
|
||||
rank,
|
||||
higher,
|
||||
inner: frontInnerHTML,
|
||||
suit,
|
||||
color: cardColor,
|
||||
below: null
|
||||
}
|
||||
}
|
||||
|
||||
function createDeck() {
|
||||
|
||||
const cardRanking = ['A', '2', '3', '4', '5',
|
||||
'6', '7', '8', '9', '10', 'J', 'Q', 'K'];
|
||||
|
||||
const cardSuits = ['spade', 'heart', 'diamond', 'club'];
|
||||
|
||||
const deck = [];
|
||||
|
||||
for (let i = 0; i < cardSuits.length; i++) {
|
||||
for (let j = 0; j < cardRanking.length; j++) {
|
||||
|
||||
const higherRank = cardRanking[j + 1] ? cardRanking[j + 1] : null;
|
||||
const newCard = createCard(cardRanking[j], cardSuits[i], higherRank);
|
||||
deck.push(newCard);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return deck;
|
||||
|
||||
}
|
||||
|
||||
function shuffleCards(deck) {
|
||||
return deck.sort(() => Math.random() - 0.5);
|
||||
}
|
||||
|
||||
function putCardsInDeck(cards) {
|
||||
|
||||
const deck = document.getElementById('deck');
|
||||
|
||||
cards.forEach(() => {
|
||||
deck.innerHTML += `<div class="card back"></div>`;
|
||||
});
|
||||
|
||||
const cardsInDeck = deck.querySelectorAll('.card');
|
||||
|
||||
cards.forEach((card, index) => {
|
||||
cardsInDeck[index].cardInfo = card;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function turnCard() {
|
||||
|
||||
const isLastCard = this === this.parentNode.lastElementChild;
|
||||
const isCardVisible = this.style.visibility !== 'hidden';
|
||||
const isCardFacingDown = this.classList.contains('back');
|
||||
|
||||
if (isLastCard && isCardVisible) {
|
||||
|
||||
if (isCardFacingDown) {
|
||||
|
||||
this.classList.remove('back');
|
||||
this.classList.add(`${this.cardInfo.suit}`);
|
||||
this.innerHTML = this.cardInfo.inner;
|
||||
}
|
||||
|
||||
this.setAttribute('draggable', true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function turnCardBack(card) {
|
||||
|
||||
card.classList.remove(`${card.cardInfo.suit}`);
|
||||
card.classList.add('back');
|
||||
card.innerHTML = '';
|
||||
card.setAttribute('draggable', false);
|
||||
|
||||
}
|
||||
|
||||
function distributeCards() {
|
||||
|
||||
const piles = document.querySelectorAll('.row');
|
||||
|
||||
piles.forEach((pile, index) => {
|
||||
|
||||
for (let i = 0; i < index + 1; i++) {
|
||||
|
||||
const deck = document.getElementById('deck');
|
||||
const topCard = deck.lastElementChild;
|
||||
|
||||
if (i === index) {
|
||||
turnCard.apply(topCard);
|
||||
}
|
||||
|
||||
deck.removeChild(topCard);
|
||||
pile.append(topCard);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function showTopCard(topCards, deck, deckTopCard) {
|
||||
|
||||
if (deckTopCard.cardInfo) {
|
||||
|
||||
deck.removeChild(deckTopCard);
|
||||
topCards.append(deckTopCard);
|
||||
|
||||
if (topCards.childElementCount > 1) {
|
||||
topCards.childNodes[0].setAttribute('draggable', false);
|
||||
}
|
||||
|
||||
if (topCards.childElementCount > 2) {
|
||||
topCards.childNodes[1].setAttribute('draggable', false);
|
||||
}
|
||||
|
||||
if (topCards.childElementCount > 3) {
|
||||
|
||||
topCards.childNodes[2].setAttribute('draggable', false);
|
||||
|
||||
const firstCard = topCards.firstElementChild;
|
||||
|
||||
turnCardBack(firstCard);
|
||||
firstCard.style.visibility = 'hidden';
|
||||
|
||||
topCards.removeChild(firstCard);
|
||||
deck.prepend(firstCard);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function returnCardsToDeck(topCards, deck) {
|
||||
|
||||
const cardsToDeck = [];
|
||||
|
||||
topCards.childNodes.forEach(card => {
|
||||
turnCardBack(card);
|
||||
card.style.visibility = 'hidden';
|
||||
cardsToDeck.push(card);
|
||||
});
|
||||
|
||||
cardsToDeck.forEach(card => {
|
||||
deck.prepend(card);
|
||||
});
|
||||
|
||||
topCards.innerHTML = '';
|
||||
|
||||
const cardsInDeck = deck.querySelectorAll('.card');
|
||||
|
||||
cardsInDeck.forEach(card => {
|
||||
card.style.visibility = 'visible';
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
function checkDeckCards() {
|
||||
|
||||
const topCards = document.getElementById('top-cards');
|
||||
const deck = document.getElementById('deck');
|
||||
const deckTopCard = deck.lastElementChild;
|
||||
|
||||
if (!deckTopCard) {
|
||||
returnCardsToDeck(topCards, deck);
|
||||
} else if (deckTopCard.style.visibility === 'hidden') {
|
||||
returnCardsToDeck(topCards, deck);
|
||||
} else {
|
||||
showTopCard(topCards, deck, deckTopCard);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function changeCardPosition(cardToDrop, dropSpot, checkCondition) {
|
||||
|
||||
if (checkCondition) {
|
||||
|
||||
const cardAbove = cardToDrop.previousSibling;
|
||||
|
||||
cardToDrop.parentNode.removeChild(cardToDrop);
|
||||
dropSpot.append(cardToDrop);
|
||||
|
||||
if (cardToDrop.cardInfo.below) {
|
||||
cardToDrop.cardInfo.below.forEach(card => dropSpot.append(card));
|
||||
}
|
||||
|
||||
return cardAbove;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function dropInRow(cardToDrop, dropSpot) {
|
||||
|
||||
const cardToDropInfo = cardToDrop.cardInfo;
|
||||
const lastCardInRow = dropSpot.lastElementChild;
|
||||
|
||||
if (lastCardInRow) {
|
||||
|
||||
const lastCardInfo = lastCardInRow.cardInfo;
|
||||
|
||||
if (lastCardInfo && lastCardInRow.draggable) {
|
||||
|
||||
const checkRank = lastCardInfo.rank === cardToDropInfo.higher;
|
||||
const checkColor = lastCardInfo.color !== cardToDropInfo.color;
|
||||
|
||||
return changeCardPosition(cardToDrop, dropSpot, (checkRank && checkColor));
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
const checkRank = cardToDrop.cardInfo.rank === 'K';
|
||||
|
||||
return changeCardPosition(cardToDrop, dropSpot, checkRank);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function dropInPile(cardToDrop, dropSpot) {
|
||||
|
||||
const cardToDropInfo = cardToDrop.cardInfo;
|
||||
const lastCardInPile = dropSpot.lastElementChild;
|
||||
|
||||
if (!cardToDropInfo.below) {
|
||||
|
||||
if (lastCardInPile) {
|
||||
|
||||
const lastCardInfo = lastCardInPile.cardInfo;
|
||||
|
||||
if (lastCardInfo) {
|
||||
|
||||
const checkRank = lastCardInfo.higher === cardToDropInfo.rank;
|
||||
const checkSuit = lastCardInfo.suit === cardToDropInfo.suit;
|
||||
|
||||
if (checkRank && checkSuit) {
|
||||
lastCardInPile.setAttribute('draggable', false);
|
||||
}
|
||||
|
||||
return changeCardPosition(cardToDrop, dropSpot, (checkRank && checkSuit));
|
||||
|
||||
} else {
|
||||
|
||||
const checkRank = cardToDropInfo.rank === 'A';
|
||||
const checkSuit = lastCardInPile.id.split('-')[0] === cardToDropInfo.suit;
|
||||
|
||||
return changeCardPosition(cardToDrop, dropSpot, (checkRank && checkSuit));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dropCard(event, cardToDrop) {
|
||||
|
||||
const tgt = event.target;
|
||||
|
||||
const findSpot = function (element, htmlClass) {
|
||||
|
||||
const classList = element.classList;
|
||||
|
||||
if (classList) {
|
||||
|
||||
if (classList.contains(htmlClass)) {
|
||||
return element;
|
||||
|
||||
} else {
|
||||
return findSpot(element.parentNode, htmlClass);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
const dropSpot = findSpot(tgt, 'row') || findSpot(tgt, 'pile');
|
||||
|
||||
let cardAbove;
|
||||
|
||||
if (dropSpot) {
|
||||
|
||||
if (dropSpot.classList.contains('row')) {
|
||||
|
||||
cardAbove = dropInRow(cardToDrop, dropSpot);
|
||||
|
||||
} else if (dropSpot.classList.contains('pile')) {
|
||||
|
||||
cardAbove = dropInPile(cardToDrop, dropSpot);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (cardAbove) {
|
||||
turnCard.apply(cardAbove);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function checkGameOver() {
|
||||
|
||||
const piles = document.querySelectorAll('.pile');
|
||||
|
||||
let allPilesComplete = true;
|
||||
|
||||
piles.forEach(pile => {
|
||||
allPilesComplete = allPilesComplete && (pile.childElementCount - 1) === 13;
|
||||
});
|
||||
|
||||
if (allPilesComplete) {
|
||||
alert('YOU WIN!');
|
||||
}
|
||||
}
|
||||
|
||||
function resetSelections(card) {
|
||||
|
||||
card.cardInfo.below = null;
|
||||
card = null;
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
function dragRowCards(card) {
|
||||
|
||||
const cardParent = card.parentNode;
|
||||
const isLastCard = card === cardParent.lastElementChild;
|
||||
|
||||
if (!isLastCard) {
|
||||
|
||||
card.cardInfo.below = [];
|
||||
|
||||
for (let i = cardParent.childElementCount - 1; i >= 0; i--) {
|
||||
|
||||
if (cardParent.childNodes[i] === card) {
|
||||
return;
|
||||
}
|
||||
|
||||
card.cardInfo.below.unshift(cardParent.childNodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const globalDeck = createDeck();
|
||||
const shuffledDeck = shuffleCards(globalDeck);
|
||||
putCardsInDeck(shuffledDeck);
|
||||
distributeCards();
|
||||
|
||||
const deck = document.getElementById('deck');
|
||||
deck.addEventListener('click', checkDeckCards);
|
||||
|
||||
const cards = document.getElementsByClassName('card');
|
||||
for (let i = 0; i < cards.length; i++) {
|
||||
cards[i].addEventListener('click', turnCard);
|
||||
}
|
||||
|
||||
let selectedCard = null;
|
||||
|
||||
document.addEventListener("dragstart", event => {
|
||||
if (event.target.draggable) {
|
||||
selectedCard = event.target;
|
||||
dragRowCards(selectedCard);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("dragover", event => {
|
||||
event.preventDefault();
|
||||
}, false);
|
||||
|
||||
document.addEventListener("drop", event => {
|
||||
event.preventDefault();
|
||||
dropCard(event, selectedCard);
|
||||
selectedCard = resetSelections(selectedCard);
|
||||
checkGameOver();
|
||||
});
|
256
dist/style.css
vendored
Normal file
256
dist/style.css
vendored
Normal file
@ -0,0 +1,256 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&family=Poppins:wght@300;400;500;600&display=swap');
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f7f7f7;
|
||||
background-color: #147246;
|
||||
font: 400 1.2em sans-serif;
|
||||
line-height: 1.2;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: fit-content;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
||||
.card {
|
||||
padding: 0;
|
||||
margin: 2vmin;
|
||||
width: 7.6vmin;
|
||||
height: 11vmin;
|
||||
box-shadow: inset 0px 0px 1vmin 0.2vmin #c3c6c6;
|
||||
border-radius: 0.8vmin;
|
||||
background-color: #f5f9fa;
|
||||
font-family: Roboto;
|
||||
}
|
||||
|
||||
.card-rank {
|
||||
width: 48%;
|
||||
float: left;
|
||||
margin-left: 0.4vmin;
|
||||
margin-right: 0.3vmin;
|
||||
letter-spacing: -0.8vmin;
|
||||
font-size: 4vmin;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.card-suit-small {
|
||||
width: 40%;
|
||||
float: left;
|
||||
font-size: 4vmin;
|
||||
}
|
||||
|
||||
.card-suit-big {
|
||||
width: 100%;
|
||||
height: inherit;
|
||||
line-height: 0.6;
|
||||
font-size: 8vmin;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@-moz-document url-prefix() {
|
||||
.card-rank {
|
||||
margin-right: 0.3vmin;
|
||||
font-size: 4vmin;
|
||||
}
|
||||
|
||||
.card-suit-small {
|
||||
line-height: 1.6;
|
||||
font-size: 3vmin;
|
||||
}
|
||||
|
||||
.card-suit-big {
|
||||
line-height: 0.8;
|
||||
font-size: 7vmin;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.card.diamond .inner-info,
|
||||
.card.heart .inner-info {
|
||||
background: linear-gradient(to right, #c21f02, #ff2802, #c21f02, #ff2802, #c21f02);
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.card.club .inner-info,
|
||||
.card.spade .inner-info {
|
||||
background: linear-gradient(to right, #242222, #534f51, #242222, #534f51, #242222);
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.back {
|
||||
box-shadow: inset 0px 0px 0.2vmin 0.2vmin #0a2f41;
|
||||
background: linear-gradient(315deg, #242222, #534f51, #242222, #534f51, #242222);
|
||||
background: linear-gradient(315deg, #0a3142, #104c66, #12506b, #1b7ca5, #2195ca);
|
||||
}
|
||||
|
||||
.top-area {
|
||||
padding-top: 2vmin;
|
||||
width: 100vw;
|
||||
height: 16vmin;
|
||||
background-color: #1b4934;
|
||||
}
|
||||
|
||||
.left-area,
|
||||
.right-area {
|
||||
display: flex;
|
||||
place-content: center;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.left-area {
|
||||
width: 41vmin;
|
||||
}
|
||||
|
||||
.right-area {
|
||||
width: 57vmin;
|
||||
}
|
||||
|
||||
.deck {
|
||||
margin: 2vmin;
|
||||
width: 7.6vmin;
|
||||
height: 11vmin;
|
||||
box-shadow: inset 0px 0px 0vmin 0.5vmin #c3c6c644;
|
||||
border-radius: 0.8vmin;
|
||||
background-color: #c3c6c60c;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.deck::before {
|
||||
content: "⟲";
|
||||
width: 100%;
|
||||
height: inherit;
|
||||
line-height: 1.6;
|
||||
font-size: 7vmin;
|
||||
text-align: center;
|
||||
color: #c3c6c644;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.deck .card {
|
||||
position: absolute;
|
||||
transform: translateX(-2vmin) translateY(-2vmin);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.top-cards {
|
||||
width: 55%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.top-cards .card {
|
||||
margin-right: -5vmin;
|
||||
}
|
||||
|
||||
|
||||
.pile {
|
||||
padding: 0;
|
||||
margin: 2vmin;
|
||||
width: 7.6vmin;
|
||||
height: 11vmin;
|
||||
box-shadow: inset 0px 0px 0vmin 0.5vmin #c3c6c644;
|
||||
border-radius: 0.8vmin;
|
||||
background-color: #c3c6c60c;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pile span {
|
||||
width: 100%;
|
||||
height: inherit;
|
||||
line-height: 1.4;
|
||||
font-size: 8vmin;
|
||||
text-align: center;
|
||||
color: #c3c6c644;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
||||
.pile .card {
|
||||
position: absolute;
|
||||
transform: translateX(-2vmin) translateY(-2vmin);
|
||||
}
|
||||
|
||||
.bottom-area {
|
||||
display: flex;
|
||||
place-content: center;
|
||||
width: 100vmin;
|
||||
min-height: 100%;
|
||||
height: fit-content;
|
||||
padding-top: 4vmin;
|
||||
padding-bottom: 10vmin;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: grid;
|
||||
margin-right: 1%;
|
||||
width: 12%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.row::before {
|
||||
content: "";
|
||||
width: 7.6vmin;
|
||||
height: 11vmin;
|
||||
box-shadow: inset 0px 0px 0vmin 0.5vmin #c3c6c644;
|
||||
border-radius: 0.8vmin;
|
||||
background-color: #c3c6c60c;
|
||||
transform: translateX(2vmin) translateY(2vmin);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.row .card {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.row .card:not(:first-child) {
|
||||
margin-top: -8vmin;
|
||||
}
|
||||
|
||||
footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
margin-top: 25vmin;
|
||||
padding: 1vmin 0;
|
||||
background-color: #1e1f26;
|
||||
font-size: 1.6vmin;
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 1vmin;
|
||||
text-align: center;
|
||||
color: #F5F5F5;
|
||||
font-family: Poppins;
|
||||
}
|
||||
|
||||
footer a:link {
|
||||
color: #b3a290;
|
||||
}
|
||||
footer a:visited {
|
||||
color: #91d4d0;
|
||||
}
|
||||
footer a:hover {
|
||||
color: #ff3300;
|
||||
}
|
||||
footer a:active {
|
||||
color: #ff3300;
|
||||
}
|
||||
|
||||
.name-dark {
|
||||
color: #91d4d0;
|
||||
text-decoration: none;
|
||||
background: linear-gradient(to right, #91d4d0 50%, #ff3300 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
34
src/index.html
Normal file
34
src/index.html
Normal file
@ -0,0 +1,34 @@
|
||||
<div class="top-area">
|
||||
<div class="container">
|
||||
<div class="left-area">
|
||||
<div id="deck" class="deck"></div>
|
||||
<div id="top-cards" class="top-cards"></div>
|
||||
</div>
|
||||
<div class="right-area">
|
||||
<div class="pile"><span id="spade-cards">♠</span></div>
|
||||
<div class="pile"><span id="heart-cards">♥</span></div>
|
||||
<div class="pile"><span id="diamond-cards">♦</span></div>
|
||||
<div class="pile"><span id="club-cards">♣</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="bottom-area">
|
||||
<div class="row"></div>
|
||||
<div class="row"></div>
|
||||
<div class="row"></div>
|
||||
<div class="row"></div>
|
||||
<div class="row"></div>
|
||||
<div class="row"></div>
|
||||
<div class="row"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>
|
||||
Created by <a class="name-dark">jguarato</a> •
|
||||
<a href="https://codepen.io/jguarato" target="_blank">Check out my other Pens</a> •
|
||||
<a href="https://github.com/jguarato" target="_blank">Follow me on GitHub</a>
|
||||
</p>
|
||||
</footer>
|
414
src/script.js
Normal file
414
src/script.js
Normal file
@ -0,0 +1,414 @@
|
||||
function createCard(rank, suit, higher) {
|
||||
|
||||
let suitSymbol, cardColor;
|
||||
switch (suit) {
|
||||
case 'spade':
|
||||
suitSymbol = '♠';
|
||||
cardColor = 'black';
|
||||
break;
|
||||
case 'heart':
|
||||
suitSymbol = '♥';
|
||||
cardColor = 'red';
|
||||
break;
|
||||
case 'diamond':
|
||||
suitSymbol = '♦';
|
||||
cardColor = 'red';
|
||||
break;
|
||||
case 'club':
|
||||
suitSymbol = '♣';
|
||||
cardColor = 'black';
|
||||
break;
|
||||
default:
|
||||
suitSymbol = '🃏';
|
||||
cardColor = 'black';
|
||||
break;
|
||||
}
|
||||
|
||||
let frontInnerHTML = `<div class="inner-info card-rank">${rank}</div>`;
|
||||
frontInnerHTML += `<div class="inner-info card-suit-small">${suitSymbol}</div>`;
|
||||
frontInnerHTML += `<div class="inner-info card-suit-big">${suitSymbol}</div>`;
|
||||
|
||||
return {
|
||||
rank,
|
||||
higher,
|
||||
inner: frontInnerHTML,
|
||||
suit,
|
||||
color: cardColor,
|
||||
below: null
|
||||
}
|
||||
}
|
||||
|
||||
function createDeck() {
|
||||
|
||||
const cardRanking = ['A', '2', '3', '4', '5',
|
||||
'6', '7', '8', '9', '10', 'J', 'Q', 'K'];
|
||||
|
||||
const cardSuits = ['spade', 'heart', 'diamond', 'club'];
|
||||
|
||||
const deck = [];
|
||||
|
||||
for (let i = 0; i < cardSuits.length; i++) {
|
||||
for (let j = 0; j < cardRanking.length; j++) {
|
||||
|
||||
const higherRank = cardRanking[j + 1] ? cardRanking[j + 1] : null;
|
||||
const newCard = createCard(cardRanking[j], cardSuits[i], higherRank);
|
||||
deck.push(newCard);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return deck;
|
||||
|
||||
}
|
||||
|
||||
function shuffleCards(deck) {
|
||||
return deck.sort(() => Math.random() - 0.5);
|
||||
}
|
||||
|
||||
function putCardsInDeck(cards) {
|
||||
|
||||
const deck = document.getElementById('deck');
|
||||
|
||||
cards.forEach(() => {
|
||||
deck.innerHTML += `<div class="card back"></div>`;
|
||||
});
|
||||
|
||||
const cardsInDeck = deck.querySelectorAll('.card');
|
||||
|
||||
cards.forEach((card, index) => {
|
||||
cardsInDeck[index].cardInfo = card;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function turnCard() {
|
||||
|
||||
const isLastCard = this === this.parentNode.lastElementChild;
|
||||
const isCardVisible = this.style.visibility !== 'hidden';
|
||||
const isCardFacingDown = this.classList.contains('back');
|
||||
|
||||
if (isLastCard && isCardVisible) {
|
||||
|
||||
if (isCardFacingDown) {
|
||||
|
||||
this.classList.remove('back');
|
||||
this.classList.add(`${this.cardInfo.suit}`);
|
||||
this.innerHTML = this.cardInfo.inner;
|
||||
}
|
||||
|
||||
this.setAttribute('draggable', true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function turnCardBack(card) {
|
||||
|
||||
card.classList.remove(`${card.cardInfo.suit}`);
|
||||
card.classList.add('back');
|
||||
card.innerHTML = '';
|
||||
card.setAttribute('draggable', false);
|
||||
|
||||
}
|
||||
|
||||
function distributeCards() {
|
||||
|
||||
const piles = document.querySelectorAll('.row');
|
||||
|
||||
piles.forEach((pile, index) => {
|
||||
|
||||
for (let i = 0; i < index + 1; i++) {
|
||||
|
||||
const deck = document.getElementById('deck');
|
||||
const topCard = deck.lastElementChild;
|
||||
|
||||
if (i === index) {
|
||||
turnCard.apply(topCard);
|
||||
}
|
||||
|
||||
deck.removeChild(topCard);
|
||||
pile.append(topCard);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function showTopCard(topCards, deck, deckTopCard) {
|
||||
|
||||
if (deckTopCard.cardInfo) {
|
||||
|
||||
deck.removeChild(deckTopCard);
|
||||
topCards.append(deckTopCard);
|
||||
|
||||
if (topCards.childElementCount > 1) {
|
||||
topCards.childNodes[0].setAttribute('draggable', false);
|
||||
}
|
||||
|
||||
if (topCards.childElementCount > 2) {
|
||||
topCards.childNodes[1].setAttribute('draggable', false);
|
||||
}
|
||||
|
||||
if (topCards.childElementCount > 3) {
|
||||
|
||||
topCards.childNodes[2].setAttribute('draggable', false);
|
||||
|
||||
const firstCard = topCards.firstElementChild;
|
||||
|
||||
turnCardBack(firstCard);
|
||||
firstCard.style.visibility = 'hidden';
|
||||
|
||||
topCards.removeChild(firstCard);
|
||||
deck.prepend(firstCard);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function returnCardsToDeck(topCards, deck) {
|
||||
|
||||
const cardsToDeck = [];
|
||||
|
||||
topCards.childNodes.forEach(card => {
|
||||
turnCardBack(card);
|
||||
card.style.visibility = 'hidden';
|
||||
cardsToDeck.push(card);
|
||||
});
|
||||
|
||||
cardsToDeck.forEach(card => {
|
||||
deck.prepend(card);
|
||||
});
|
||||
|
||||
topCards.innerHTML = '';
|
||||
|
||||
const cardsInDeck = deck.querySelectorAll('.card');
|
||||
|
||||
cardsInDeck.forEach(card => {
|
||||
card.style.visibility = 'visible';
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
function checkDeckCards() {
|
||||
|
||||
const topCards = document.getElementById('top-cards');
|
||||
const deck = document.getElementById('deck');
|
||||
const deckTopCard = deck.lastElementChild;
|
||||
|
||||
if (!deckTopCard) {
|
||||
returnCardsToDeck(topCards, deck);
|
||||
} else if (deckTopCard.style.visibility === 'hidden') {
|
||||
returnCardsToDeck(topCards, deck);
|
||||
} else {
|
||||
showTopCard(topCards, deck, deckTopCard);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function changeCardPosition(cardToDrop, dropSpot, checkCondition) {
|
||||
|
||||
if (checkCondition) {
|
||||
|
||||
const cardAbove = cardToDrop.previousSibling;
|
||||
|
||||
cardToDrop.parentNode.removeChild(cardToDrop);
|
||||
dropSpot.append(cardToDrop);
|
||||
|
||||
if (cardToDrop.cardInfo.below) {
|
||||
cardToDrop.cardInfo.below.forEach(card => dropSpot.append(card));
|
||||
}
|
||||
|
||||
return cardAbove;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function dropInRow(cardToDrop, dropSpot) {
|
||||
|
||||
const cardToDropInfo = cardToDrop.cardInfo;
|
||||
const lastCardInRow = dropSpot.lastElementChild;
|
||||
|
||||
if (lastCardInRow) {
|
||||
|
||||
const lastCardInfo = lastCardInRow.cardInfo;
|
||||
|
||||
if (lastCardInfo && lastCardInRow.draggable) {
|
||||
|
||||
const checkRank = lastCardInfo.rank === cardToDropInfo.higher;
|
||||
const checkColor = lastCardInfo.color !== cardToDropInfo.color;
|
||||
|
||||
return changeCardPosition(cardToDrop, dropSpot, (checkRank && checkColor));
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
const checkRank = cardToDrop.cardInfo.rank === 'K';
|
||||
|
||||
return changeCardPosition(cardToDrop, dropSpot, checkRank);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function dropInPile(cardToDrop, dropSpot) {
|
||||
|
||||
const cardToDropInfo = cardToDrop.cardInfo;
|
||||
const lastCardInPile = dropSpot.lastElementChild;
|
||||
|
||||
if (!cardToDropInfo.below) {
|
||||
|
||||
if (lastCardInPile) {
|
||||
|
||||
const lastCardInfo = lastCardInPile.cardInfo;
|
||||
|
||||
if (lastCardInfo) {
|
||||
|
||||
const checkRank = lastCardInfo.higher === cardToDropInfo.rank;
|
||||
const checkSuit = lastCardInfo.suit === cardToDropInfo.suit;
|
||||
|
||||
if (checkRank && checkSuit) {
|
||||
lastCardInPile.setAttribute('draggable', false);
|
||||
}
|
||||
|
||||
return changeCardPosition(cardToDrop, dropSpot, (checkRank && checkSuit));
|
||||
|
||||
} else {
|
||||
|
||||
const checkRank = cardToDropInfo.rank === 'A';
|
||||
const checkSuit = lastCardInPile.id.split('-')[0] === cardToDropInfo.suit;
|
||||
|
||||
return changeCardPosition(cardToDrop, dropSpot, (checkRank && checkSuit));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dropCard(event, cardToDrop) {
|
||||
|
||||
const tgt = event.target;
|
||||
|
||||
const findSpot = function (element, htmlClass) {
|
||||
|
||||
const classList = element.classList;
|
||||
|
||||
if (classList) {
|
||||
|
||||
if (classList.contains(htmlClass)) {
|
||||
return element;
|
||||
|
||||
} else {
|
||||
return findSpot(element.parentNode, htmlClass);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
const dropSpot = findSpot(tgt, 'row') || findSpot(tgt, 'pile');
|
||||
|
||||
let cardAbove;
|
||||
|
||||
if (dropSpot) {
|
||||
|
||||
if (dropSpot.classList.contains('row')) {
|
||||
|
||||
cardAbove = dropInRow(cardToDrop, dropSpot);
|
||||
|
||||
} else if (dropSpot.classList.contains('pile')) {
|
||||
|
||||
cardAbove = dropInPile(cardToDrop, dropSpot);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (cardAbove) {
|
||||
turnCard.apply(cardAbove);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function checkGameOver() {
|
||||
|
||||
const piles = document.querySelectorAll('.pile');
|
||||
|
||||
let allPilesComplete = true;
|
||||
|
||||
piles.forEach(pile => {
|
||||
allPilesComplete = allPilesComplete && (pile.childElementCount - 1) === 13;
|
||||
});
|
||||
|
||||
if (allPilesComplete) {
|
||||
alert('YOU WIN!');
|
||||
}
|
||||
}
|
||||
|
||||
function resetSelections(card) {
|
||||
|
||||
card.cardInfo.below = null;
|
||||
card = null;
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
function dragRowCards(card) {
|
||||
|
||||
const cardParent = card.parentNode;
|
||||
const isLastCard = card === cardParent.lastElementChild;
|
||||
|
||||
if (!isLastCard) {
|
||||
|
||||
card.cardInfo.below = [];
|
||||
|
||||
for (let i = cardParent.childElementCount - 1; i >= 0; i--) {
|
||||
|
||||
if (cardParent.childNodes[i] === card) {
|
||||
return;
|
||||
}
|
||||
|
||||
card.cardInfo.below.unshift(cardParent.childNodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const globalDeck = createDeck();
|
||||
const shuffledDeck = shuffleCards(globalDeck);
|
||||
putCardsInDeck(shuffledDeck);
|
||||
distributeCards();
|
||||
|
||||
const deck = document.getElementById('deck');
|
||||
deck.addEventListener('click', checkDeckCards);
|
||||
|
||||
const cards = document.getElementsByClassName('card');
|
||||
for (let i = 0; i < cards.length; i++) {
|
||||
cards[i].addEventListener('click', turnCard);
|
||||
}
|
||||
|
||||
let selectedCard = null;
|
||||
|
||||
document.addEventListener("dragstart", event => {
|
||||
if (event.target.draggable) {
|
||||
selectedCard = event.target;
|
||||
dragRowCards(selectedCard);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("dragover", event => {
|
||||
event.preventDefault();
|
||||
}, false);
|
||||
|
||||
document.addEventListener("drop", event => {
|
||||
event.preventDefault();
|
||||
dropCard(event, selectedCard);
|
||||
selectedCard = resetSelections(selectedCard);
|
||||
checkGameOver();
|
||||
});
|
256
src/style.css
Normal file
256
src/style.css
Normal file
@ -0,0 +1,256 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&family=Poppins:wght@300;400;500;600&display=swap');
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f7f7f7;
|
||||
background-color: #147246;
|
||||
font: 400 1.2em sans-serif;
|
||||
line-height: 1.2;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: fit-content;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
||||
.card {
|
||||
padding: 0;
|
||||
margin: 2vmin;
|
||||
width: 7.6vmin;
|
||||
height: 11vmin;
|
||||
box-shadow: inset 0px 0px 1vmin 0.2vmin #c3c6c6;
|
||||
border-radius: 0.8vmin;
|
||||
background-color: #f5f9fa;
|
||||
font-family: Roboto;
|
||||
}
|
||||
|
||||
.card-rank {
|
||||
width: 48%;
|
||||
float: left;
|
||||
margin-left: 0.4vmin;
|
||||
margin-right: 0.3vmin;
|
||||
letter-spacing: -0.8vmin;
|
||||
font-size: 4vmin;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.card-suit-small {
|
||||
width: 40%;
|
||||
float: left;
|
||||
font-size: 4vmin;
|
||||
}
|
||||
|
||||
.card-suit-big {
|
||||
width: 100%;
|
||||
height: inherit;
|
||||
line-height: 0.6;
|
||||
font-size: 8vmin;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@-moz-document url-prefix() {
|
||||
.card-rank {
|
||||
margin-right: 0.3vmin;
|
||||
font-size: 4vmin;
|
||||
}
|
||||
|
||||
.card-suit-small {
|
||||
line-height: 1.6;
|
||||
font-size: 3vmin;
|
||||
}
|
||||
|
||||
.card-suit-big {
|
||||
line-height: 0.8;
|
||||
font-size: 7vmin;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.card.diamond .inner-info,
|
||||
.card.heart .inner-info {
|
||||
background: linear-gradient(to right, #c21f02, #ff2802, #c21f02, #ff2802, #c21f02);
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.card.club .inner-info,
|
||||
.card.spade .inner-info {
|
||||
background: linear-gradient(to right, #242222, #534f51, #242222, #534f51, #242222);
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.back {
|
||||
box-shadow: inset 0px 0px 0.2vmin 0.2vmin #0a2f41;
|
||||
background: linear-gradient(315deg, #242222, #534f51, #242222, #534f51, #242222);
|
||||
background: linear-gradient(315deg, #0a3142, #104c66, #12506b, #1b7ca5, #2195ca);
|
||||
}
|
||||
|
||||
.top-area {
|
||||
padding-top: 2vmin;
|
||||
width: 100vw;
|
||||
height: 16vmin;
|
||||
background-color: #1b4934;
|
||||
}
|
||||
|
||||
.left-area,
|
||||
.right-area {
|
||||
display: flex;
|
||||
place-content: center;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.left-area {
|
||||
width: 41vmin;
|
||||
}
|
||||
|
||||
.right-area {
|
||||
width: 57vmin;
|
||||
}
|
||||
|
||||
.deck {
|
||||
margin: 2vmin;
|
||||
width: 7.6vmin;
|
||||
height: 11vmin;
|
||||
box-shadow: inset 0px 0px 0vmin 0.5vmin #c3c6c644;
|
||||
border-radius: 0.8vmin;
|
||||
background-color: #c3c6c60c;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.deck::before {
|
||||
content: "⟲";
|
||||
width: 100%;
|
||||
height: inherit;
|
||||
line-height: 1.6;
|
||||
font-size: 7vmin;
|
||||
text-align: center;
|
||||
color: #c3c6c644;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.deck .card {
|
||||
position: absolute;
|
||||
transform: translateX(-2vmin) translateY(-2vmin);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.top-cards {
|
||||
width: 55%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.top-cards .card {
|
||||
margin-right: -5vmin;
|
||||
}
|
||||
|
||||
|
||||
.pile {
|
||||
padding: 0;
|
||||
margin: 2vmin;
|
||||
width: 7.6vmin;
|
||||
height: 11vmin;
|
||||
box-shadow: inset 0px 0px 0vmin 0.5vmin #c3c6c644;
|
||||
border-radius: 0.8vmin;
|
||||
background-color: #c3c6c60c;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pile span {
|
||||
width: 100%;
|
||||
height: inherit;
|
||||
line-height: 1.4;
|
||||
font-size: 8vmin;
|
||||
text-align: center;
|
||||
color: #c3c6c644;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
||||
.pile .card {
|
||||
position: absolute;
|
||||
transform: translateX(-2vmin) translateY(-2vmin);
|
||||
}
|
||||
|
||||
.bottom-area {
|
||||
display: flex;
|
||||
place-content: center;
|
||||
width: 100vmin;
|
||||
min-height: 100%;
|
||||
height: fit-content;
|
||||
padding-top: 4vmin;
|
||||
padding-bottom: 10vmin;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: grid;
|
||||
margin-right: 1%;
|
||||
width: 12%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.row::before {
|
||||
content: "";
|
||||
width: 7.6vmin;
|
||||
height: 11vmin;
|
||||
box-shadow: inset 0px 0px 0vmin 0.5vmin #c3c6c644;
|
||||
border-radius: 0.8vmin;
|
||||
background-color: #c3c6c60c;
|
||||
transform: translateX(2vmin) translateY(2vmin);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.row .card {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.row .card:not(:first-child) {
|
||||
margin-top: -8vmin;
|
||||
}
|
||||
|
||||
footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
margin-top: 25vmin;
|
||||
padding: 1vmin 0;
|
||||
background-color: #1e1f26;
|
||||
font-size: 1.6vmin;
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 1vmin;
|
||||
text-align: center;
|
||||
color: #F5F5F5;
|
||||
font-family: Poppins;
|
||||
}
|
||||
|
||||
footer a:link {
|
||||
color: #b3a290;
|
||||
}
|
||||
footer a:visited {
|
||||
color: #91d4d0;
|
||||
}
|
||||
footer a:hover {
|
||||
color: #ff3300;
|
||||
}
|
||||
footer a:active {
|
||||
color: #ff3300;
|
||||
}
|
||||
|
||||
.name-dark {
|
||||
color: #91d4d0;
|
||||
text-decoration: none;
|
||||
background: linear-gradient(to right, #91d4d0 50%, #ff3300 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user