<main>
<div id="select-difficulty">
<label for="difficulty">Difficulty</label>
<select name="difficulty" id="difficulty" autocomplete="off">
<option value="easy">Easy</option>
<option value="medium" selected>Medium</option>
<option value="hard">Hard</option>
</select>
</div>
<div id="container-cards"></div>
<button id="play-again">Play again</button>
</main>
@import url("https://fonts.googleapis.com/css2?family=Fredoka+One&family=Signika+Negative&display=swap");
html,
body {
height: 100vh;
}
body {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
margin: 0;
box-sizing: border-box;
background-color: #61d4b3;
font-family: "Fredoka One", cursive;
}
main {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 50vw;
height: 80vh;
padding: 1em;
gap: 1em;
border-radius: 12px;
background-color: #fdd365;
}
#select-difficulty {
display: flex;
flex-wrap: wrap;
gap: 1em;
margin-right: 70%;
}
select {
border: 2px solid #fd2eb3;
border-radius: 5px;
background: transparent;
font-family: "Signika Negative", sans-serif;
}
#container-cards {
display: grid;
justify-content: center;
align-content: center;
grid-gap: 0.5em;
width: 100%;
height: 100%;
}
button[id^="card"] {
display: flex;
justify-content: center;
align-items: center;
perspective: 600px;
background: transparent;
border: none;
width: 100%;
height: 100%;
cursor: pointer;
}
button[id^="card"] .front,
button[id^="card"] .back {
position: absolute;
transform-style: preserve-3d;
backface-visibility: hidden;
width: inherit;
height: inherit;
border-radius: 5px;
font-family: "Fredoka One", cursive;
transition: all 0.4s ease-in-out;
}
button[id^="card"] .front {
transform: rotateX(0deg) rotateY(0deg);
background: #fb8d62;
z-index: 3;
}
button[id^="card"].hover .front {
transform: rotateY(180deg);
}
button[id^="card"] .back {
display: flex;
justify-content: center;
align-items: center;
transform: rotateX(0deg) rotateY(180deg);
background: #fd2eb3;
color: white;
z-index: 1;
}
button[id^="card"].hover .back,
button[id^="card"].selected .back {
transform: rotateY(0deg);
z-index: 6;
}
button[id^="card"].found {
visibility: hidden;
opacity: 0;
}
button[id^="play-again"] {
position: absolute;
top: 50%;
left: auto;
z-index: 10;
width: 10em;
padding: 1em;
background: #adffe8;
border: 3px solid #fd2eb3;
border-radius: 10px;
font-family: "Fredoka One", cursive;
cursor: pointer;
visibility: hidden;
opacity: 0;
}
button[id^="play-again"]:hover {
border: 3px solid #ff50c2;
background: #77ffd8;
}
button[id^="play-again"].visible {
visibility: visible;
opacity: 1;
}
@media (max-width: 650px) {
main {
width: 85vw;
height: 50vh;
}
}
@media (max-width: 800px) {
main {
width: 85vw;
height: 60vh;
}
}
const difficulty = {
//difficulty : quantity of cards
easy: 16,
medium: 36,
hard: 64,
};
let difficultySelected = difficulty.medium; // Default difficulty
let availableCards = [];
let previousRevealedCard = null;
const container = document.getElementById("container-cards");
const playAgainButton = document.getElementById("play-again");
document
.getElementById("difficulty")
.addEventListener("change", selectDifficulty);
playAgainButton.addEventListener("click", playAgain);
function selectDifficulty(e) {
difficultySelected = difficulty[e.currentTarget.value];
// When we select a difficulty in the middle of a game
// we restart the game before loading the cards
if (
availableCards.length > 0 ||
playAgainButton.classList.contains("visible")
) {
restartGame();
}
loadCards();
}
function restartGame() {
playAgainButton.classList.remove("visible");
container.innerHTML = "";
availableCards = [];
previousRevealedCard = null;
}
function randomCardsValues() {
// This is a function for get random numbers to place cards randomly
while (availableCards.length < difficultySelected) {
let randomNum =
Math.floor(Math.random() * (difficultySelected / 2 - 1 + 1)) + 1;
let index = availableCards.indexOf(randomNum);
// If the random number is not in the list of available cards at least twice,
// we add it to the list
if (index === -1 || availableCards.indexOf(randomNum, index + 1) === -1) {
availableCards.push(randomNum);
}
}
}
function loadCards() {
randomCardsValues();
// CSS grid template
const grid = Math.sqrt(difficultySelected);
const gridTemplate = `repeat(${grid}, ${Math.floor(100 / grid - 1)}%)`;
container.style.gridTemplateColumns = gridTemplate;
container.style.gridTemplateRows = gridTemplate;
for (let i = 0; i < availableCards.length; i++) {
container.innerHTML += `
<button id='card-${i}'>
<div class='front'></div>
<div class='back'>
${availableCards[i]}
</div>
</button>
`;
}
// Adding a event to each card
cardsEvent((card) => {
card.addEventListener("mouseover", onMouseOver);
card.addEventListener("mouseout", onMouseOut);
card.addEventListener("click", onClickCard);
});
}
function changeStateCards() {
// This function is for disable or enable cards
cardsEvent((card) => {
card.disabled = !card.disabled;
if (card.disabled) {
card.classList.remove("hover");
card.removeEventListener("mouseover", onMouseOver);
card.removeEventListener("mouseout", onMouseOut);
} else {
card.addEventListener("mouseover", onMouseOver);
card.addEventListener("mouseout", onMouseOut);
}
});
}
function onClickCard(event) {
let currRevealedCard = event.currentTarget;
currRevealedCard.classList.add("selected");
if (previousRevealedCard) {
changeStateCards(); // Disabling all the cards to not allow the selection of another card
// We check if the previous selected card is equal to the current selected card
if (
previousRevealedCard.innerText === currRevealedCard.innerText &&
previousRevealedCard.id !== currRevealedCard.id
) {
setTimeout(() => {
currRevealedCard.classList.add("found");
previousRevealedCard.classList.add("found");
previousRevealedCard = null; // Set the previous selected card to null
// to select another card and play
changeStateCards();
updateAvailableCards(currRevealedCard.innerText);
}, 1000);
} else {
setTimeout(() => {
previousRevealedCard.classList.remove("selected");
currRevealedCard.classList.remove("selected");
previousRevealedCard = null;
changeStateCards();
}, 1000);
}
} else {
previousRevealedCard = currRevealedCard;
}
}
function updateAvailableCards(card) {
let firstCard = availableCards.indexOf(card);
let secondCard = availableCards.indexOf(card, firstCard + 1);
// Removing the uncovered cards from the list of available cards
availableCards.splice(firstCard, 1);
availableCards.splice(secondCard - 1, 1);
// If there are not any cards, we can play again
if (availableCards.length === 0) {
setTimeout(() => {
playAgainButton.classList.add("visible");
}, 1000);
}
}
function playAgain() {
playAgainButton.classList.remove("visible");
container.innerHTML = "";
loadCards();
}
function onMouseOver(event) {
const card = event.currentTarget;
card.classList.add("hover");
}
function onMouseOut(event) {
const card = event.currentTarget;
card.classList.remove("hover");
}
function cardsEvent(fn) {
Object.values(container.children).forEach((card) => fn(card));
}
window.addEventListener("load", () => loadCards());
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.