|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 656 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 60 KiB |
@ -0,0 +1,80 @@ |
|||||
|
@font-face { |
||||
|
font-family: "flipper"; |
||||
|
src: url("font.ttf") format("truetype"); |
||||
|
} |
||||
|
.flipper { |
||||
|
position: relative; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
background-image: url(./bg.jpg); |
||||
|
background-size: cover; |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr 1fr 1fr; |
||||
|
grid-auto-rows: 22%; |
||||
|
grid-gap: 1.25%; |
||||
|
padding: 26.48148148148148% 16.34259259259259% 0 16.34259259259259%; |
||||
|
} |
||||
|
.flipper .meta { |
||||
|
position: absolute; |
||||
|
top: 16.6%; |
||||
|
font-family: flipper; |
||||
|
font-weight: bolder; |
||||
|
color: #4294dd; |
||||
|
} |
||||
|
.flipper .meta.l { |
||||
|
left: 33%; |
||||
|
} |
||||
|
.flipper .meta.r { |
||||
|
left: 76%; |
||||
|
} |
||||
|
.flipper.no-event { |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
.flipper .card { |
||||
|
position: relative; |
||||
|
cursor: pointer; |
||||
|
-webkit-perspective: 700px; |
||||
|
perspective: 700px; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
.flipper .card.flipped, |
||||
|
.flipper .card.has-match { |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
.flipper .card.flipped .back, |
||||
|
.flipper .card.has-match .back { |
||||
|
-webkit-transform: rotateY(180deg); |
||||
|
transform: rotateY(180deg); |
||||
|
} |
||||
|
.flipper .card.flipped .front, |
||||
|
.flipper .card.has-match .front { |
||||
|
-webkit-transform: rotateY(360deg); |
||||
|
transform: rotateY(360deg); |
||||
|
} |
||||
|
.flipper .card .back, |
||||
|
.flipper .card .front { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
right: 0; |
||||
|
left: 0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
-webkit-backface-visibility: hidden; |
||||
|
backface-visibility: hidden; |
||||
|
transition: -webkit-transform 400ms; |
||||
|
transition: transform 400ms; |
||||
|
transition: transform 400ms, -webkit-transform 400ms; |
||||
|
} |
||||
|
.flipper .card .back { |
||||
|
z-index: 1; |
||||
|
background-image: url(./back.png); |
||||
|
background-size: cover; |
||||
|
} |
||||
|
.flipper .card .front { |
||||
|
-webkit-transform: rotateY(180deg); |
||||
|
transform: rotateY(180deg); |
||||
|
background-size: cover; |
||||
|
} |
||||
|
After Width: | Height: | Size: 52 KiB |
@ -0,0 +1,155 @@ |
|||||
|
import "./flipper.css"; |
||||
|
function importAll(r) { |
||||
|
let obj = {}; |
||||
|
r.keys().forEach((key) => { |
||||
|
obj[key.replace("./", "").replace(".png", "")] = r(key); |
||||
|
}); |
||||
|
return obj; |
||||
|
} |
||||
|
|
||||
|
const images = importAll(require.context("./", false, /\.(png|jpe?g|svg)$/)); |
||||
|
const cardNames = "abcdefghabcdefgh"; |
||||
|
export default class Game { |
||||
|
duration = 1000; |
||||
|
constructor({ containerId, onLose, onWon }) { |
||||
|
Object.assign(this, { |
||||
|
container: document.getElementById(containerId), |
||||
|
onLose, |
||||
|
onWon, |
||||
|
}); |
||||
|
|
||||
|
this.addBg(); |
||||
|
this.init(); |
||||
|
} |
||||
|
init() { |
||||
|
Object.assign(this, { |
||||
|
steps: 25, |
||||
|
score: 0, |
||||
|
}); |
||||
|
this.shuffleCards(); |
||||
|
this.update(); |
||||
|
} |
||||
|
update() { |
||||
|
this.scoreEl.textContent = this.score; |
||||
|
this.stepEl.textContent = this.steps; |
||||
|
} |
||||
|
setSize() { |
||||
|
Object.assign(this, { |
||||
|
width: this.container.clientWidth, |
||||
|
height: this.container.clientHeight, |
||||
|
}); |
||||
|
this.scoreEl.style.fontSize = (this.width / 2160) * 100 + "px"; |
||||
|
this.scoreEl.style.lineHeight = (this.width / 2160) * 116 + "px"; |
||||
|
this.stepEl.style.fontSize = (this.width / 2160) * 100 + "px"; |
||||
|
this.stepEl.style.lineHeight = (this.width / 2160) * 116 + "px"; |
||||
|
} |
||||
|
addBg() { |
||||
|
this.scoreEl = document.createElement("div"); |
||||
|
this.scoreEl.className = "meta l"; |
||||
|
this.stepEl = document.createElement("div"); |
||||
|
this.stepEl.className = "meta r"; |
||||
|
const bg = document.createElement("div"); |
||||
|
bg.className = "flipper"; |
||||
|
this.cardsContainer = bg; |
||||
|
this.container.appendChild(bg); |
||||
|
this.setSize(); |
||||
|
bg.appendChild(this.scoreEl); |
||||
|
bg.appendChild(this.stepEl); |
||||
|
this.resizeListener = window.addEventListener("resize", () => { |
||||
|
this.setSize(); |
||||
|
}); |
||||
|
this.cards = Array.from(cardNames).map((name) => { |
||||
|
const url = images[name]; |
||||
|
const card = document.createElement("div"); |
||||
|
card.className = "card"; |
||||
|
card.setAttribute("data-name", name); |
||||
|
const front = document.createElement("div"); |
||||
|
front.className = "front"; |
||||
|
front.style.backgroundImage = `url(${url})`; |
||||
|
card.appendChild(front); |
||||
|
const back = document.createElement("div"); |
||||
|
back.className = "back"; |
||||
|
card.appendChild(back); |
||||
|
return card; |
||||
|
}); |
||||
|
this.cards.forEach((card) => { |
||||
|
this.cardsContainer.appendChild(card); |
||||
|
}); |
||||
|
this.cards.forEach((card) => { |
||||
|
card.addEventListener("click", this.flip.bind(this, card)); |
||||
|
}); |
||||
|
} |
||||
|
pause() {} |
||||
|
resume() {} |
||||
|
restart() { |
||||
|
this.init(); |
||||
|
} |
||||
|
dispose() { |
||||
|
window.removeEventListener("resize", this.resizeListener); |
||||
|
} |
||||
|
shuffleCards() { |
||||
|
this.cards.forEach((card) => { |
||||
|
const randomNumber = Math.floor(Math.random() * this.cards.length) + 1; |
||||
|
|
||||
|
card.classList.remove("has-match"); |
||||
|
card.classList.remove("flipped"); |
||||
|
setTimeout(() => { |
||||
|
card.style.order = `${randomNumber}`; |
||||
|
}, 400); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
checkAllCards() { |
||||
|
if (this.cards.every((card) => card.classList.contains("has-match"))) { |
||||
|
this.onWon && this.onWon(); |
||||
|
setTimeout(() => { |
||||
|
this.restart(); |
||||
|
}, this.duration); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
stopEvent() { |
||||
|
this.cardsContainer.classList.add("no-event"); |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
this.cardsContainer.classList.remove("no-event"); |
||||
|
}, this.duration); |
||||
|
} |
||||
|
|
||||
|
checkIfMatched(firstCard, secondCard) { |
||||
|
if (firstCard.dataset.name === secondCard.dataset.name) { |
||||
|
this.score += 20; |
||||
|
firstCard.classList.remove("flipped"); |
||||
|
secondCard.classList.remove("flipped"); |
||||
|
|
||||
|
firstCard.classList.add("has-match"); |
||||
|
secondCard.classList.add("has-match"); |
||||
|
|
||||
|
this.checkAllCards(); |
||||
|
} else { |
||||
|
setTimeout(() => { |
||||
|
firstCard.classList.remove("flipped"); |
||||
|
secondCard.classList.remove("flipped"); |
||||
|
}, this.duration); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
flip(selectedCard) { |
||||
|
this.steps--; |
||||
|
if (this.steps < 0) { |
||||
|
this.onLose && this.onLose(); |
||||
|
this.restart(); |
||||
|
} |
||||
|
selectedCard.classList.add("flipped"); |
||||
|
|
||||
|
const flippedCards = this.cards.filter((card) => |
||||
|
card.classList.contains("flipped") |
||||
|
); |
||||
|
|
||||
|
if (flippedCards.length === 2) { |
||||
|
this.stopEvent(); |
||||
|
this.checkIfMatched(flippedCards[0], flippedCards[1]); |
||||
|
} |
||||
|
this.update(); |
||||
|
} |
||||
|
} |
||||
|
After Width: | Height: | Size: 47 KiB |