diff --git a/package.json b/package.json
index 5b56dd344a..201d08c824 100644
--- a/package.json
+++ b/package.json
@@ -3,9 +3,10 @@
"version": "1.0.0",
"private": true,
"dependencies": {
+ "@giscus/react": "^2.0.3",
"@types/react": "^18.0.6",
"@types/react-dom": "^18.0.2",
- "@giscus/react": "^2.0.3",
+ "node-sass": "^7.0.1",
"plop": "^3.0.5",
"react": "^18.0.0",
"react-dom": "^18.0.0",
@@ -50,7 +51,7 @@
]
},
"devDependencies": {
- "typescript": "^4.6.4",
- "react-snap": "^1.23.0"
+ "react-snap": "^1.23.0",
+ "typescript": "^4.6.4"
}
}
diff --git a/src/meta/play-meta.js b/src/meta/play-meta.js
index 94370b664d..71025d7b9d 100644
--- a/src/meta/play-meta.js
+++ b/src/meta/play-meta.js
@@ -14,6 +14,7 @@ import {
AnalogClock,
PasswordGenerator,
WhyTypescript,
+NetlifyCardGame,
//import play here
} from "plays";
@@ -212,5 +213,18 @@ export const plays = [
blog: '',
video: '',
language: 'ts'
+ }, {
+ id: 'pl-memory-game',
+ name: 'Memory Game',
+ description: 'simple memory game or memory testing game build with ReactJS',
+ component: () => {return },
+ path: '/plays/memory-game',
+ level: 'Advanced',
+ tags: 'MemoryGame, CardGame, ReactJS',
+ github: 'Angryman18',
+ cover: 'https://cdn.pixabay.com/photo/2017/01/03/16/42/klee-1949946_960_720.jpg',
+ blog: '',
+ video: '',
+ language: 'js'
}, //replace new play item here
];
diff --git a/src/plays/index.js b/src/plays/index.js
index 754a500644..aaf98bc912 100644
--- a/src/plays/index.js
+++ b/src/plays/index.js
@@ -16,4 +16,5 @@ export { default as AnalogClock } from 'plays/analog-clock/AnalogClock';
export { default as PasswordGenerator } from 'plays/password-generator/PasswordGenerator';
export { default as WhyTypescript } from 'plays/why-typescript/WhyTypescript';
+export { default as NetlifyCardGame } from 'plays/netlify-card-game/NetlifyCardGame';
//add export here
diff --git a/src/plays/netlify-card-game/NetlifyCardGame.jsx b/src/plays/netlify-card-game/NetlifyCardGame.jsx
new file mode 100644
index 0000000000..7cd08d2e50
--- /dev/null
+++ b/src/plays/netlify-card-game/NetlifyCardGame.jsx
@@ -0,0 +1,254 @@
+import { getPlayById } from "meta/play-meta-util";
+
+import PlayHeader from "common/playlists/PlayHeader";
+import { useState, useEffect, useRef } from "react";
+
+// css
+import "./NetlifyCardGame.scss";
+
+// components
+import Modal from "./modal";
+
+import q1 from "./icons/q1.png";
+import q2 from "./icons/q2.png";
+import q3 from "./icons/q3.png";
+import q4 from "./icons/q4.png";
+import q5 from "./icons/q5.png";
+import q6 from "./icons/q6.png";
+import q7 from "./icons/q7.png";
+import q8 from "./icons/q8.png";
+
+const imgArr = [q1, q2, q3, q4, q5, q6, q7, q8];
+
+const initialState = {
+ moves: 0,
+ time: 0,
+ elapsedTime: 0,
+};
+
+function NetlifyCardGame(props) {
+ // Do not remove the below lines.
+ // The following code is to fetch the current play from the URL
+ const { id } = props;
+ const play = getPlayById(id);
+
+ // Your Code Start below.
+ // static 16 images with 2 duplicate image
+ const imgArray = useRef(imgArr.concat(imgArr));
+
+ // dynamic 16 images after shuffling
+ const [newImgArray, setNewImgArray] = useState([]);
+
+ // board staticstics
+ const [boardStats, setBoardStats] = useState({ ...initialState });
+
+ // duplicate array without matched images
+ const duplicateImgArray = useRef([]);
+
+ // disabled click while updating state if taking time
+ const disableClick = useRef(false);
+
+ // show guidence modal
+ const [showModal, setShowModal] = useState(false);
+
+ const toggle = (e) => {
+ setShowModal(!showModal);
+ };
+
+ // timer ref
+ const timer = useRef(null);
+
+ // matched objects on the board
+ const [matchedItems, setMatchedItems] = useState([]);
+
+ // shuffling actuall work is done by this function
+ const shufflingArray = () => {
+ const shuffledArray = imgArray.current.sort(() => Math.random() - 0.5);
+ return shuffledArray.map((item, index) => ({
+ img: item,
+ id: index,
+ show: false,
+ }));
+ };
+
+ // shuffling function exteranally to manage "Play Again Button Click"
+ const shuffle = () => {
+ const shuffledArray = shufflingArray();
+ duplicateImgArray.current = shuffledArray;
+ setNewImgArray(shuffledArray);
+ };
+
+ const shuffling = useRef(shuffle);
+
+ useEffect(() => {
+ shuffling.current(); // shuffle at first reload
+ }, [shuffling]);
+
+ useEffect(() => {
+ if (boardStats.time !== 0 && matchedItems.length < 8) {
+ // starts timer when user clicks
+ const secondCalculations = setInterval(() => {
+ const calcSeconds = new Date().getTime() - boardStats.time.getTime();
+ setBoardStats((pre) => ({ ...pre, elapsedTime: calcSeconds / 1000 }));
+ }, 1000);
+
+ return () => clearInterval(secondCalculations); // after upateing every second we have to clean the event listener
+ }
+ }, [boardStats.time, matchedItems.length]);
+
+ const boxClickHandler =
+ ({ id, img }) =>
+ () => {
+ const currentlyShownItem = duplicateImgArray.current.find(
+ (i) => i.show === true
+ ); // if false means no active item
+ if (currentlyShownItem?.id === id) return; // if same item clicked again
+ if (matchedItems.some((i) => i.img === img)) return;
+
+ // count the moves and set current time of the first move is done
+ setBoardStats({
+ ...boardStats,
+ time: boardStats?.moves === 0 ? new Date() : boardStats.time,
+ moves: boardStats.moves + 1,
+ });
+
+ // clicked item image pair
+ const imageItems = newImgArray.filter((i) => i.img === img);
+ if (currentlyShownItem) {
+ // checking if clicked item img is same as active item img
+ const findPair = imageItems.find(
+ (i) => i.img === currentlyShownItem.img
+ );
+ if (!findPair) {
+ // if not same, then find the item from the main array
+ const findClickedItem = imageItems.find((i) => i.id === id);
+ findClickedItem.show = true; // need to display it for sometime
+ const updatedItemList = newImgArray.map((i) =>
+ i.id === findClickedItem.id ? findClickedItem : i
+ );
+ setNewImgArray(updatedItemList); // updating the main array coz we need to show a glimpse of the clicked item
+ disableClick.current = true; // while rage clicking state update taking time so we need to disable click
+ return timeOutCall(findClickedItem, currentlyShownItem); // timeout will hide the item after 1 second
+ } else {
+ // if user clicks the same imgage of the active image then we find which one is clicked specefically by id this time
+ disableClick.current = true; // disable the click
+ const otherPair = imageItems.find(
+ (i) => i.id !== currentlyShownItem.id
+ ); // searching for same image other object
+ otherPair.show = true; // have to render it
+ const updatedItemList = newImgArray.map((i) =>
+ i.id === otherPair.id ? otherPair : i
+ ); // updated array maintaining the index
+ duplicateImgArray.current = updatedItemList.filter(
+ (i) => i.show === false
+ ); // updating the duplicate array
+ setNewImgArray(updatedItemList); // updating the main object
+ setMatchedItems([...matchedItems, findPair]);
+ disableClick.current = false;
+ }
+ } else {
+ const findClickedItem = newImgArray.find((i) => i.id === id);
+ findClickedItem.show = true;
+ const updatedArray = newImgArray.map((i) =>
+ i.id === findClickedItem.id ? findClickedItem : i
+ );
+ setNewImgArray(updatedArray);
+ }
+ };
+
+ const timeOutCall = (clickedItem, activeItem) => {
+ return (timer.current = setTimeout(() => {
+ clickedItem.show = false;
+ if (activeItem) {
+ activeItem.show = false; // for the first time when user clicks the current object can be undefined so have to check it
+ }
+ const updatedArray = newImgArray.map((i) => {
+ if (i.id === clickedItem.id) {
+ return clickedItem;
+ } else if (activeItem && i.id === activeItem?.id) {
+ return activeItem;
+ } else {
+ return i;
+ }
+ });
+ disableClick.current = false;
+ setNewImgArray(updatedArray);
+ clearTimeout(timer.current);
+ }, 1500));
+ };
+
+ const resetHandler = () => {
+ // resetting the entire game
+ setBoardStats({ ...initialState });
+ setMatchedItems([]);
+ shuffle();
+ };
+
+ const calculateMerit = () => {
+ const time = Math.floor(boardStats?.elapsedTime);
+ if (time <= 40) {
+ return "Execelent";
+ } else if (time > 40 && time <= 60) {
+ return "Good";
+ } else {
+ return "Average";
+ }
+ };
+
+ return (
+ <>
+
+
+
+ {/* Your Code Starts Here */}
+
+
How to Play?
+
+
+
+ {newImgArray.map((item, idx) => {
+ return (
+
+
+

+
+
+ );
+ })}
+
+
Moves: {boardStats.moves}
+
+ Elapsed Time: {Math.floor(boardStats.elapsedTime)} Seconds
+
+ {matchedItems.length === 8 && (
+
+ Congrats! {calculateMerit()} Performance
+
+ )}
+
+
+
+
+ {/* Your Code Ends Here */}
+
+
+
+ >
+ );
+}
+
+export default NetlifyCardGame;
diff --git a/src/plays/netlify-card-game/NetlifyCardGame.scss b/src/plays/netlify-card-game/NetlifyCardGame.scss
new file mode 100644
index 0000000000..823fb39fdd
--- /dev/null
+++ b/src/plays/netlify-card-game/NetlifyCardGame.scss
@@ -0,0 +1,136 @@
+$memory-game-box-width: 50%;
+
+$memory-game-single-item-img: url("./question.png");
+$memory-game-single-item-bg: rgb(241, 239, 239);
+
+$memory-game-single-item-hover: rgba(59, 154, 156, 1);
+$memory-game-single-item-hover-bg: url("./question1.png");
+
+.memory-game .container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.memory-game .guide {
+ text-align: center;
+ padding: 0;
+ margin: 0;
+ color: #1f6ed4;
+
+ p {
+ cursor: pointer;
+ display: inline;
+ }
+}
+
+.memory-game .container .App {
+ margin: 5px;
+ width: $memory-game-box-width;
+ height: calc(90vh - 25px);
+ display: grid;
+ padding: 25px;
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ grid-template-rows: 1fr 1fr 1fr 1fr;
+ gap: 20px;
+
+ .item {
+ position: relative;
+ width: 100%;
+ background: $memory-game-single-item-bg;
+ border-radius: 10px;
+ background-image: $memory-game-single-item-img;
+ background-size: 50%;
+ background-repeat: no-repeat;
+ background-position: center;
+ transition: all 100ms ease-out;
+
+ .img-container {
+ width: 100%;
+ height: 100%;
+ background-color: rgb(240, 240, 240);
+ border-radius: 10px;
+ display: none;
+ }
+
+ .shown {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ .item-img {
+ width: 60%;
+ animation: picAppear 0.5s ease-out forwards;
+ }
+
+ @keyframes picAppear {
+ 0% {
+ width: 0%;
+ }
+ 100% {
+ width: 60%;
+ }
+ }
+ }
+
+ .hidden {
+ display: none;
+ }
+
+ &:hover {
+ background-color: $memory-game-single-item-hover;
+ background-image: $memory-game-single-item-hover-bg;
+ cursor: pointer;
+ }
+ }
+ .footer {
+ grid-column: 1/5;
+ .reset {
+ padding: 5px 10px;
+ border: 1px solid #ccc;
+ background-color: white;
+ color: black;
+ border-radius: 5px;
+ outline: none;
+ font-size: 1rem;
+ cursor: pointer;
+ &:hover {
+ color: white;
+ background-color: black;
+ transition: all 100ms ease-out;
+ }
+ }
+ }
+}
+
+@media screen and (max-width: 1366px) {
+ .memory-game .container .App {
+ width: 80vh;
+ margin: 5px;
+ padding: 10px;
+ gap: 10px;
+ }
+}
+
+@media screen and (min-width: 1600px) {
+ .memory-game .container .App {
+ width: 80vh;
+ height: 70vh;
+ margin: 5px;
+ padding: 10px;
+ gap: 10px;
+ }
+}
+
+@media screen and (max-width: 768px) {
+ .memory-game .container .App {
+ margin: 5px;
+ width: calc(100vh - 35px);
+ height: auto;
+
+ .item {
+ height: 90px;
+ border-radius: 10px;
+ }
+ }
+}
diff --git a/src/plays/netlify-card-game/Readme.md b/src/plays/netlify-card-game/Readme.md
new file mode 100644
index 0000000000..31b4c65d1e
--- /dev/null
+++ b/src/plays/netlify-card-game/Readme.md
@@ -0,0 +1,15 @@
+# Memory Game
+
+The `Memory Game` is a very fun play build with ReactJS & moreover its a memory test game which is actually to uncover cards and memorize them for next move.
+
+
+There is some key things we can also learn from projects are
+
+- Handling complex logics in ReactJS.
+- Advanced use of useEffect and handling asynchronous function and cleanup function inside it without memory leakage.
+- Managing Complex and Nested States.
+- use of scss in ReactJS.
+
+# File Contents
+
+- main file `NetlifyCardGame.jsx` contents all the functions and logics. the `icons` folder contains all the required icons and `NetlifyCardGame.scss` contents the sass code for designing.
diff --git a/src/plays/netlify-card-game/close.png b/src/plays/netlify-card-game/close.png
new file mode 100644
index 0000000000..61330d6b1b
Binary files /dev/null and b/src/plays/netlify-card-game/close.png differ
diff --git a/src/plays/netlify-card-game/guideimages/s1.png b/src/plays/netlify-card-game/guideimages/s1.png
new file mode 100644
index 0000000000..fb61a43be6
Binary files /dev/null and b/src/plays/netlify-card-game/guideimages/s1.png differ
diff --git a/src/plays/netlify-card-game/guideimages/s2.png b/src/plays/netlify-card-game/guideimages/s2.png
new file mode 100644
index 0000000000..2d11747a38
Binary files /dev/null and b/src/plays/netlify-card-game/guideimages/s2.png differ
diff --git a/src/plays/netlify-card-game/guideimages/s3.png b/src/plays/netlify-card-game/guideimages/s3.png
new file mode 100644
index 0000000000..7bfdfc28c1
Binary files /dev/null and b/src/plays/netlify-card-game/guideimages/s3.png differ
diff --git a/src/plays/netlify-card-game/guideimages/s4.png b/src/plays/netlify-card-game/guideimages/s4.png
new file mode 100644
index 0000000000..76eacb0d51
Binary files /dev/null and b/src/plays/netlify-card-game/guideimages/s4.png differ
diff --git a/src/plays/netlify-card-game/icons/q1.png b/src/plays/netlify-card-game/icons/q1.png
new file mode 100644
index 0000000000..c8dca579d8
Binary files /dev/null and b/src/plays/netlify-card-game/icons/q1.png differ
diff --git a/src/plays/netlify-card-game/icons/q2.png b/src/plays/netlify-card-game/icons/q2.png
new file mode 100644
index 0000000000..b4e1fa0dee
Binary files /dev/null and b/src/plays/netlify-card-game/icons/q2.png differ
diff --git a/src/plays/netlify-card-game/icons/q3.png b/src/plays/netlify-card-game/icons/q3.png
new file mode 100644
index 0000000000..55ec9bbae4
Binary files /dev/null and b/src/plays/netlify-card-game/icons/q3.png differ
diff --git a/src/plays/netlify-card-game/icons/q4.png b/src/plays/netlify-card-game/icons/q4.png
new file mode 100644
index 0000000000..854c44e53d
Binary files /dev/null and b/src/plays/netlify-card-game/icons/q4.png differ
diff --git a/src/plays/netlify-card-game/icons/q5.png b/src/plays/netlify-card-game/icons/q5.png
new file mode 100644
index 0000000000..3a5e9d981c
Binary files /dev/null and b/src/plays/netlify-card-game/icons/q5.png differ
diff --git a/src/plays/netlify-card-game/icons/q6.png b/src/plays/netlify-card-game/icons/q6.png
new file mode 100644
index 0000000000..00dccf4b35
Binary files /dev/null and b/src/plays/netlify-card-game/icons/q6.png differ
diff --git a/src/plays/netlify-card-game/icons/q7.png b/src/plays/netlify-card-game/icons/q7.png
new file mode 100644
index 0000000000..3d8dcc3e68
Binary files /dev/null and b/src/plays/netlify-card-game/icons/q7.png differ
diff --git a/src/plays/netlify-card-game/icons/q8.png b/src/plays/netlify-card-game/icons/q8.png
new file mode 100644
index 0000000000..28da5f6bf5
Binary files /dev/null and b/src/plays/netlify-card-game/icons/q8.png differ
diff --git a/src/plays/netlify-card-game/modal.js b/src/plays/netlify-card-game/modal.js
new file mode 100644
index 0000000000..87f991dec6
--- /dev/null
+++ b/src/plays/netlify-card-game/modal.js
@@ -0,0 +1,70 @@
+import { Fragment, useState, useEffect } from "react";
+
+// css
+import "./modal.scss";
+
+// assets
+import s1 from "./guideimages/s1.png";
+import s2 from "./guideimages/s2.png";
+import s3 from "./guideimages/s3.png";
+import s4 from "./guideimages/s4.png";
+import close from './close.png'
+
+const Modal = ({ showModal, toggle }) => {
+ const [currState, setCurrentState] = useState(0);
+
+ const structuringData = [
+ {
+ info: "Click any of the box to unvail the the hidden image.",
+ image: s1,
+ },
+ {
+ info: "Now Click another box to unvail another image. if its not the same the both of them will be hidden again.",
+ image: s2,
+ },
+ {
+ info: "If the both images are matched they will stay visible and you want to uncover other images and also find their pair",
+ image: s3,
+ },
+ {
+ info: "Lastly images position will be different on every 'reset' button click any you can keep continue memorise the glimpse of the images and find their pair. ",
+ image: s4,
+ },
+ ];
+
+ useEffect(() => {
+ setCurrentState(0);
+ }, [showModal]);
+
+ const buttonHandler = (val) => (e) => {
+ if ((currState === 0 && val < 0) || (currState === 3 && val > 0)) return;
+ setCurrentState(currState + val);
+ };
+
+ if (!showModal) return false;
+ return (
+
+
+

+
+
How to Play!
+
{structuringData[currState].info}
+

+
+ {currState > 0 ?
:
}
+
+
+
+
+
+
+ );
+};
+
+export default Modal;
diff --git a/src/plays/netlify-card-game/modal.scss b/src/plays/netlify-card-game/modal.scss
new file mode 100644
index 0000000000..24010f7a7c
--- /dev/null
+++ b/src/plays/netlify-card-game/modal.scss
@@ -0,0 +1,99 @@
+.memory-game-backdrop {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 10;
+}
+
+.memory-game-modal {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: #fff;
+ width: 50vw;
+ max-width: 800px;
+ z-index: 11;
+ border-radius: 10px;
+ animation: modalApear 0.3s ease-in-out;
+
+ .close-icon {
+ position: absolute;
+ right: 1.5rem;
+ top: 1.5rem;
+ width: 30px;
+ padding: 5px;
+ filter: brightness(0.1);
+ cursor: pointer;
+
+ @media screen and (max-width: 768px) {
+ right: 1rem;
+ top: 1rem;
+ width: 30px;
+ }
+ }
+
+ .content {
+ margin: 0 2rem;
+ padding-bottom: 1rem;
+ .text {
+ text-align: center;
+ }
+ p {
+ text-align: center;
+ }
+ .guide-image {
+ width: 70%;
+ display: block;
+ aspect-ratio: 16/9;
+ height: 100%;
+ margin: 0 auto;
+ }
+
+ .button-section {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 32px;
+
+ button {
+ padding: 0.5rem 2rem;
+ font-size: 1rem;
+ border-radius: 20px;
+ border: none;
+ background-color: rgba(81, 91, 212, 1);
+ outline: none;
+ color: #fff;
+ font-weight: bold;
+ &:disabled {
+ background-color: rgba(81, 91, 212, 0.5);
+ pointer-events: none;
+ }
+ }
+ @media screen and (max-width: 768px) {
+ padding: 0;
+ margin-top: 10px;
+ }
+ }
+ }
+}
+
+@keyframes modalApear {
+ 0% {
+ opacity: 0;
+ top: 48%;
+ }
+ 100% {
+ opacity: 1;
+ top: 50%;
+ }
+}
+
+@media screen and (max-width: 768px) {
+ .memory-game-modal {
+ width: calc(100vw - 20px);
+ }
+}
diff --git a/src/plays/netlify-card-game/question.png b/src/plays/netlify-card-game/question.png
new file mode 100644
index 0000000000..83479f531d
Binary files /dev/null and b/src/plays/netlify-card-game/question.png differ
diff --git a/src/plays/netlify-card-game/question1.png b/src/plays/netlify-card-game/question1.png
new file mode 100644
index 0000000000..6f013b4ded
Binary files /dev/null and b/src/plays/netlify-card-game/question1.png differ