react-card-memory-game
Version:
Memory Game made with React using UTF-8 icons.
86 lines (85 loc) • 5 kB
JavaScript
import React, { useEffect, useState } from "react";
import ReactFlipCard from "reactjs-flip-card";
import allEqual from "./AllEqual";
import getGeneratedGrid from "./getGeneratedGrid";
import './MemoryGame.css';
import { availableCardStatuses } from "./consts";
function validateGridNumber(gridNumber) {
if (!Number.isInteger(gridNumber) || !(gridNumber >= 4 && gridNumber <= 6))
throw new Error("grid number must be an Integer number between 4 and 6");
return null;
}
export default function MemoryGame({ containerStyle = {}, gridNumber = 4, foundPair = () => undefined, notFoundPair = () => undefined, gameFinished = () => undefined, holeCardsColor = 'orange', foundCardsColor = 'yellow', frontCardsCss = '', backCardsCss = '' }) {
useState(validateGridNumber(gridNumber));
const [iconsGrid] = useState(getGeneratedGrid(gridNumber)); // <-- Initialize the grid just one time, only when this component is mounted
const [cardStatuses, setCardsStatuses] = useState(iconsGrid.map(_ => availableCardStatuses.hole));
useEffect(() => {
const finished = allEqual(cardStatuses, availableCardStatuses.discovered);
if (finished) {
setTimeout(() => {
gameFinished();
}, 200);
}
// omit gameFinished: we can safely omit function props.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cardStatuses]);
// handle user card statuses clicks
useEffect(() => {
let cardsClickedArray = [];
cardStatuses.forEach((status, index) => {
const icon = iconsGrid[index];
if (status === availableCardStatuses.clicked) {
cardsClickedArray.push({ status, icon });
}
});
if (cardsClickedArray.length === 2) {
if (cardsClickedArray[0].icon === cardsClickedArray[1].icon) {
foundPair();
setTimeout(() => {
const newCardStatuses = cardStatuses.map(status => {
if (status === availableCardStatuses.clicked)
return availableCardStatuses.discovered;
return status;
});
setCardsStatuses(newCardStatuses);
}, 200);
}
else {
notFoundPair();
setTimeout(() => {
const newCardStatuses = cardStatuses.map(status => {
if (status === availableCardStatuses.clicked)
return availableCardStatuses.hole;
return status;
});
setCardsStatuses(newCardStatuses);
}, 700);
}
}
// omit foundPair and notFoundPair: we can safely omit function props.
// If not, we have to handle the "onetime" called function with additional flag state
// we can safely omit iconsGrid because it's initialized just one time on constructor
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cardStatuses]);
const getCardStyle = (currentCardStatus) => ({
background: currentCardStatus === availableCardStatuses.discovered
? foundCardsColor
: holeCardsColor
});
return (React.createElement("div", null,
React.createElement("div", { className: `MemoryGame_grid MemoryGame_gridTemplateColumns${gridNumber}`, style: containerStyle }, iconsGrid.map((icon, index) => {
const currentCardStatus = cardStatuses[index];
return React.createElement(ReactFlipCard, { key: index, flipCardContainerCss: `${currentCardStatus === availableCardStatuses.discovered ? 'MemoryGame_pairFound' : ''}`, flipByProp: currentCardStatus !== availableCardStatuses.hole, width: "auto", height: "auto", cursor: "pointer", flipTrigger: 'disabled', frontStyle: getCardStyle(currentCardStatus), backStyle: getCardStyle(currentCardStatus), frontCss: `MemoryGame_cardCss ${frontCardsCss}`, backCss: `MemoryGame_cardCss ${backCardsCss}`, onClick: () => {
const cardStatusesClone = [...cardStatuses];
const currentCard = cardStatusesClone[index];
const twoCardsClicked = cardStatusesClone.filter(c => c === availableCardStatuses.clicked).length === 2;
if (twoCardsClicked ||
currentCard === availableCardStatuses.clicked ||
currentCard === availableCardStatuses.discovered) {
return;
}
cardStatusesClone[index] = availableCardStatuses.clicked;
setCardsStatuses(cardStatusesClone);
}, frontComponent: React.createElement("div", null), backComponent: React.createElement("div", { className: "MemoryGame_padding20" }, icon) });
}))));
}