├── src
├── vite-env.d.ts
├── assets
│ ├── MemorySong.mp3
│ ├── images
│ │ ├── app-bg.jpg
│ │ ├── clock.png
│ │ ├── score.png
│ │ ├── sound.png
│ │ ├── fruit-bg.png
│ │ ├── gameboard.png
│ │ ├── restart-btn.png
│ │ ├── sidebar-bg.png
│ │ ├── sound-mute.png
│ │ └── fruits
│ │ │ ├── apple.png
│ │ │ ├── durian.png
│ │ │ ├── pineapple.png
│ │ │ ├── starfruit.png
│ │ │ ├── dragonfruit.png
│ │ │ ├── mangosteen.png
│ │ │ ├── watermelon.png
│ │ │ └── passionfruit.png
│ └── react.svg
├── main.tsx
├── index.css
├── components
│ ├── GameBoard.tsx
│ ├── StartModal.tsx
│ ├── SideBar.tsx
│ ├── FruitCard.tsx
│ └── Overlay.tsx
├── App.tsx
└── context
│ └── MemoryContext.tsx
├── postcss.config.js
├── tsconfig.json
├── vite.config.ts
├── .gitignore
├── index.html
├── tailwind.config.js
├── tsconfig.node.json
├── tsconfig.app.json
├── eslint.config.js
├── package.json
├── public
└── vite.svg
└── README.md
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/assets/MemorySong.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/MemorySong.mp3
--------------------------------------------------------------------------------
/src/assets/images/app-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/app-bg.jpg
--------------------------------------------------------------------------------
/src/assets/images/clock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/clock.png
--------------------------------------------------------------------------------
/src/assets/images/score.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/score.png
--------------------------------------------------------------------------------
/src/assets/images/sound.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/sound.png
--------------------------------------------------------------------------------
/src/assets/images/fruit-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/fruit-bg.png
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/src/assets/images/gameboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/gameboard.png
--------------------------------------------------------------------------------
/src/assets/images/restart-btn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/restart-btn.png
--------------------------------------------------------------------------------
/src/assets/images/sidebar-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/sidebar-bg.png
--------------------------------------------------------------------------------
/src/assets/images/sound-mute.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/sound-mute.png
--------------------------------------------------------------------------------
/src/assets/images/fruits/apple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/fruits/apple.png
--------------------------------------------------------------------------------
/src/assets/images/fruits/durian.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/fruits/durian.png
--------------------------------------------------------------------------------
/src/assets/images/fruits/pineapple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/fruits/pineapple.png
--------------------------------------------------------------------------------
/src/assets/images/fruits/starfruit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/fruits/starfruit.png
--------------------------------------------------------------------------------
/src/assets/images/fruits/dragonfruit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/fruits/dragonfruit.png
--------------------------------------------------------------------------------
/src/assets/images/fruits/mangosteen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/fruits/mangosteen.png
--------------------------------------------------------------------------------
/src/assets/images/fruits/watermelon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/fruits/watermelon.png
--------------------------------------------------------------------------------
/src/assets/images/fruits/passionfruit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rendy278/Memory-Games/HEAD/src/assets/images/fruits/passionfruit.png
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | { "path": "./tsconfig.app.json" },
5 | { "path": "./tsconfig.node.json" }
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import App from './App.tsx'
4 | import './index.css'
5 |
6 | createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Memory Game
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
4 | theme: {
5 | extend: {
6 | backgroundImage: {
7 | "app-banner": "url(assets/images/app-bg.jpg)",
8 | "gameboard-background": "url(assets/images/gameboard.png)",
9 | "fruit-background": "url(assets/images/fruit-bg.png)",
10 | "sidebar-background": "url(assets/images/sidebar-bg.png)",
11 | },
12 | },
13 | },
14 | plugins: [],
15 | };
16 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "lib": ["ES2023"],
5 | "module": "ESNext",
6 | "skipLibCheck": true,
7 |
8 | /* Bundler mode */
9 | "moduleResolution": "bundler",
10 | "allowImportingTsExtensions": true,
11 | "isolatedModules": true,
12 | "moduleDetection": "force",
13 | "noEmit": true,
14 |
15 | /* Linting */
16 | "strict": true,
17 | "noUnusedLocals": true,
18 | "noUnusedParameters": true,
19 | "noFallthroughCasesInSwitch": true
20 | },
21 | "include": ["vite.config.ts"]
22 | }
23 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | /* FruitCard.css */
6 |
7 | .perspective-1000 {
8 | perspective: 500px;
9 | }
10 |
11 | .flip-card-inner {
12 | width: 100%;
13 | height: 100%;
14 | transition: transform 0.6s;
15 | transform-style: preserve-3d;
16 | position: relative;
17 | }
18 |
19 | .flip-card-front,
20 | .flip-card-back {
21 | width: 100%;
22 | height: 100%;
23 | position: absolute;
24 | backface-visibility: hidden;
25 | }
26 |
27 | .flip-card-back {
28 | transform: rotateY(180deg);
29 | }
30 |
31 | .flip-card-flipped {
32 | transform: rotateY(180deg);
33 | }
34 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "isolatedModules": true,
13 | "moduleDetection": "force",
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src"]
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/GameBoard.tsx:
--------------------------------------------------------------------------------
1 | import FruitCard from "./FruitCard";
2 | import { useContext } from "react";
3 | import { GameContext } from "../context/MemoryContext";
4 |
5 | const GameBoard = () => {
6 | const gameContext = useContext(GameContext);
7 |
8 | if (!gameContext) {
9 | return null;
10 | }
11 |
12 | const { cards } = gameContext;
13 |
14 | return (
15 |
16 | {cards.map((_, index) => (
17 |
18 | ))}
19 |
20 | );
21 | };
22 |
23 | export default GameBoard;
24 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import globals from 'globals'
3 | import reactHooks from 'eslint-plugin-react-hooks'
4 | import reactRefresh from 'eslint-plugin-react-refresh'
5 | import tseslint from 'typescript-eslint'
6 |
7 | export default tseslint.config(
8 | { ignores: ['dist'] },
9 | {
10 | extends: [js.configs.recommended, ...tseslint.configs.recommended],
11 | files: ['**/*.{ts,tsx}'],
12 | languageOptions: {
13 | ecmaVersion: 2020,
14 | globals: globals.browser,
15 | },
16 | plugins: {
17 | 'react-hooks': reactHooks,
18 | 'react-refresh': reactRefresh,
19 | },
20 | rules: {
21 | ...reactHooks.configs.recommended.rules,
22 | 'react-refresh/only-export-components': [
23 | 'warn',
24 | { allowConstantExport: true },
25 | ],
26 | },
27 | },
28 | )
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "memorygame",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc -b && vite build",
9 | "lint": "eslint .",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "react": "^18.3.1",
14 | "react-confetti": "^6.1.0",
15 | "react-dom": "^18.3.1"
16 | },
17 | "devDependencies": {
18 | "@eslint/js": "^9.9.0",
19 | "@types/react": "^18.3.3",
20 | "@types/react-dom": "^18.3.0",
21 | "@vitejs/plugin-react": "^4.3.1",
22 | "autoprefixer": "^10.4.20",
23 | "eslint": "^9.9.0",
24 | "eslint-plugin-react-hooks": "^5.1.0-rc.0",
25 | "eslint-plugin-react-refresh": "^0.4.9",
26 | "globals": "^15.9.0",
27 | "postcss": "^8.4.47",
28 | "tailwindcss": "^3.4.13",
29 | "typescript": "^5.5.3",
30 | "typescript-eslint": "^8.0.1",
31 | "vite": "^5.4.1"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/StartModal.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | interface StartModalProps {
4 | isModalOpen: boolean;
5 | onStart: () => void;
6 | closeModal: () => void;
7 | }
8 |
9 | const StartModal: React.FC = ({
10 | isModalOpen,
11 | onStart,
12 | closeModal,
13 | }) => {
14 | return (
15 |
20 |
21 |
22 | Start the Game
23 |
24 |
33 |
34 | Game Build By : Rendev
35 |
36 |
37 |
38 | );
39 | };
40 |
41 | export default StartModal;
42 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/SideBar.tsx:
--------------------------------------------------------------------------------
1 | import { useContext } from "react";
2 | import ClockBtn from "../assets/images/clock.png";
3 | import RestartBtn from "../assets/images/restart-btn.png";
4 | import { GameContext } from "../context/MemoryContext";
5 |
6 | const SideBar: React.FC = () => {
7 | const gameContext = useContext(GameContext);
8 |
9 | if (!gameContext) {
10 | return null;
11 | }
12 |
13 | const { timeLeft, restartGame, gameOver } = gameContext;
14 |
15 | return (
16 |
20 |
21 |

22 |
23 | Time Left:{" "}
24 |
25 | {gameOver ? "Game Over" : `${timeLeft} secs`}
26 |
27 |
28 |
29 |
30 |
31 |
39 |
40 |
41 | );
42 | };
43 |
44 | export default SideBar;
45 |
--------------------------------------------------------------------------------
/src/components/FruitCard.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { GameContext } from "../context/MemoryContext";
3 |
4 | interface FruitCardProps {
5 | fruitId: number;
6 | }
7 |
8 | const FruitCard: React.FC = ({ fruitId }) => {
9 | const gameContext = useContext(GameContext);
10 |
11 | if (!gameContext) {
12 | return null;
13 | }
14 |
15 | const { cards, flipCard, showIcons } = gameContext;
16 |
17 | // Ensure cards and fruitId are valid
18 | if (!cards || cards.length === 0 || fruitId < 0 || fruitId >= cards.length) {
19 | return null;
20 | }
21 |
22 | const card = cards[fruitId];
23 |
24 | const handleClick = () => {
25 | if (!card.flipped && !showIcons) {
26 | flipCard(fruitId);
27 | }
28 | };
29 |
30 | return (
31 |
35 |
40 |
43 |
44 |

49 |
50 |
51 |
52 | );
53 | };
54 |
55 | export default FruitCard;
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React + TypeScript + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
10 | ## Expanding the ESLint configuration
11 |
12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
13 |
14 | - Configure the top-level `parserOptions` property like this:
15 |
16 | ```js
17 | export default tseslint.config({
18 | languageOptions: {
19 | // other options...
20 | parserOptions: {
21 | project: ['./tsconfig.node.json', './tsconfig.app.json'],
22 | tsconfigRootDir: import.meta.dirname,
23 | },
24 | },
25 | })
26 | ```
27 |
28 | - Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
29 | - Optionally add `...tseslint.configs.stylisticTypeChecked`
30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
31 |
32 | ```js
33 | // eslint.config.js
34 | import react from 'eslint-plugin-react'
35 |
36 | export default tseslint.config({
37 | // Set the react version
38 | settings: { react: { version: '18.3' } },
39 | plugins: {
40 | // Add the react plugin
41 | react,
42 | },
43 | rules: {
44 | // other rules...
45 | // Enable its recommended rules
46 | ...react.configs.recommended.rules,
47 | ...react.configs['jsx-runtime'].rules,
48 | },
49 | })
50 | ```
51 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState } from "react";
2 | import GameBoard from "./components/GameBoard";
3 | import SideBar from "./components/SideBar";
4 | import GameContextProvider, { GameContext } from "./context/MemoryContext";
5 | import Overlay from "./components/Overlay";
6 | import StartModal from "./components/StartModal";
7 |
8 | const App: React.FC = () => {
9 | const [isModalOpen, setIsModalOpen] = useState(true); // Track modal state
10 |
11 | return (
12 |
13 |
14 | {}}
16 | isModalOpen={isModalOpen}
17 | closeModal={() => setIsModalOpen(false)}
18 | />
19 |
20 | );
21 | };
22 |
23 | const GameContent: React.FC<{
24 | setIsModalOpen: React.Dispatch>;
25 | }> = ({ setIsModalOpen }) => {
26 | const { startGame, isGameStarted } = useContext(GameContext)!;
27 |
28 | const handleStartGame = () => {
29 | startGame(); // Start the game logic
30 | setIsModalOpen(false); // Close modal after starting the game
31 | };
32 |
33 | return (
34 |
35 |
36 |
37 | {isGameStarted && }
38 | {isGameStarted && }
39 |
40 |
41 | setIsModalOpen(false)} // Close modal function
45 | />
46 |
47 | );
48 | };
49 |
50 | export default App;
51 |
--------------------------------------------------------------------------------
/src/components/Overlay.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { GameContext } from "../context/MemoryContext"; // Pastikan Anda mengimpor tipe GameContextType
3 | import ScoreIcon from "../assets/images/score.png";
4 | import RestartIcon from "../assets/images/restart-btn.png";
5 | import Confetti from "react-confetti";
6 |
7 | const Overlay: React.FC = () => {
8 | const context = useContext(GameContext);
9 |
10 | // Periksa apakah context terdefinisi
11 | if (!context) {
12 | throw new Error("Overlay must be used within a GameContextProvider"); // Tambahkan error handling
13 | }
14 |
15 | const { gameOver, gameWon, restartGame, score } = context; // Ambil nilai dari context
16 |
17 | if (!gameOver && !gameWon) return null; // Return null if neither game over nor won
18 |
19 | return (
20 |
21 |
22 | {gameOver ? "Game Over 😥" : "You Win 🎉"}
23 |
24 |
25 |
26 |

27 |
28 | Your Score: {score}
29 |
30 |
31 |
32 |
33 |
41 |
42 |
43 | {gameWon && (
44 |
51 | )}
52 |
53 | );
54 | };
55 |
56 | export default Overlay;
57 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/context/MemoryContext.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | createContext,
3 | useEffect,
4 | useRef,
5 | useState,
6 | ReactNode,
7 | } from "react";
8 |
9 | // Import fruit images
10 | import apple from "../assets/images/fruits/apple.png";
11 | import dragonfruit from "../assets/images/fruits/dragonfruit.png";
12 | import durian from "../assets/images/fruits/durian.png";
13 | import mangosteen from "../assets/images/fruits/mangosteen.png";
14 | import passionfruit from "../assets/images/fruits/passionfruit.png";
15 | import pineapple from "../assets/images/fruits/pineapple.png";
16 | import starfruit from "../assets/images/fruits/starfruit.png";
17 | import watermelon from "../assets/images/fruits/watermelon.png";
18 |
19 | // Import memory song
20 | import memorySong from "../assets/MemorySong.mp3";
21 |
22 | interface Card {
23 | id: number;
24 | fruit: string;
25 | flipped: boolean;
26 | }
27 |
28 | interface GameContextType {
29 | cards: Card[];
30 | flipCard: (index: number) => void;
31 | matchedPairs: string[];
32 | showIcons: boolean;
33 | timeLeft: number;
34 | gameOver: boolean;
35 | gameWon: boolean;
36 | restartGame: () => void;
37 | score: number;
38 | startGame: () => void;
39 | isGameStarted: boolean; // State to track if the game has started
40 | }
41 |
42 | interface GameContextProviderProps {
43 | children: ReactNode;
44 | }
45 |
46 | export const GameContext = createContext(
47 | undefined
48 | );
49 |
50 | const GameContextProvider: React.FC = ({
51 | children,
52 | }) => {
53 | const [cards, setCards] = useState([]);
54 | const [flippedCards, setFlippedCards] = useState([]);
55 | const [matchedPairs, setMatchedPairs] = useState([]);
56 | const [showIcons, setShowIcons] = useState(true);
57 | const [timeLeft, setTimeLeft] = useState(45);
58 | const [gameOver, setGameOver] = useState(false);
59 | const [gameWon, setGameWon] = useState(false);
60 | const [isGameStarted, setIsGameStarted] = useState(false); // Initialize game state
61 | const timerRef = useRef | null>(null); // Fix for NodeJS issue
62 | const [score, setScore] = useState(0);
63 | const audioRef = useRef(null);
64 |
65 | const initializeGame = () => {
66 | const fruitIcons: string[] = [
67 | apple,
68 | dragonfruit,
69 | durian,
70 | mangosteen,
71 | passionfruit,
72 | pineapple,
73 | starfruit,
74 | watermelon,
75 | ];
76 |
77 | const pairIcons = [...fruitIcons, ...fruitIcons];
78 | const shuffledIcons = pairIcons.sort(() => Math.random() - 0.5);
79 |
80 | const cardItems: Card[] = shuffledIcons.map((fruit, index) => ({
81 | id: index,
82 | fruit,
83 | flipped: false,
84 | }));
85 |
86 | setCards(cardItems);
87 | setShowIcons(true);
88 | setIsGameStarted(true); // Mark game as started
89 |
90 | setTimeout(() => {
91 | setShowIcons(false);
92 | }, 3000);
93 |
94 | if (timerRef.current) clearInterval(timerRef.current);
95 | setTimeLeft(45);
96 | timerRef.current = setInterval(() => {
97 | setTimeLeft((prev) => {
98 | if (prev <= 1) {
99 | if (timerRef.current) clearInterval(timerRef.current);
100 | if (!gameWon) setGameOver(true);
101 | return 0;
102 | }
103 | return prev - 1;
104 | });
105 | }, 1000);
106 | };
107 |
108 | const startGame = () => {
109 | if (!audioRef.current) {
110 | audioRef.current = new Audio(memorySong);
111 | }
112 | audioRef.current.currentTime = 0; // Reset audio to start
113 | audioRef.current.play();
114 | initializeGame();
115 | };
116 |
117 | useEffect(() => {
118 | return () => {
119 | if (timerRef.current) clearInterval(timerRef.current);
120 | if (audioRef.current) audioRef.current.pause();
121 | };
122 | }, []);
123 |
124 | const flipCard = (index: number) => {
125 | if (flippedCards.length === 2 || cards[index].flipped) return;
126 |
127 | const updatedCards = [...cards];
128 | updatedCards[index].flipped = true;
129 | setCards(updatedCards);
130 |
131 | setFlippedCards((prevFlippedCards) => {
132 | const newFlippedCards = [...prevFlippedCards, index];
133 |
134 | if (newFlippedCards.length === 2) {
135 | const [firstIndex, secondIndex] = newFlippedCards;
136 |
137 | if (
138 | updatedCards[firstIndex].fruit === updatedCards[secondIndex].fruit
139 | ) {
140 | const newMatchedPairs = [
141 | ...matchedPairs,
142 | updatedCards[firstIndex].fruit,
143 | ];
144 | setMatchedPairs(newMatchedPairs);
145 |
146 | if (newMatchedPairs.length === cards.length / 2) {
147 | if (timerRef.current) clearInterval(timerRef.current);
148 | setTimeout(() => {
149 | setGameWon(true);
150 | setScore(timeLeft * 10);
151 | }, 1000);
152 | }
153 | } else {
154 | setTimeout(() => {
155 | const resetCards = [...updatedCards];
156 | resetCards[firstIndex].flipped = false;
157 | resetCards[secondIndex].flipped = false;
158 | setCards(resetCards);
159 | }, 1000);
160 | }
161 |
162 | return [];
163 | }
164 |
165 | return newFlippedCards;
166 | });
167 | };
168 |
169 | const restartGame = () => {
170 | setFlippedCards([]);
171 | setMatchedPairs([]);
172 | setGameOver(false);
173 | setGameWon(false);
174 | setScore(0);
175 | setIsGameStarted(false); // Reset game state
176 | initializeGame();
177 | };
178 |
179 | return (
180 |
195 | {children}
196 |
197 | );
198 | };
199 |
200 | export default GameContextProvider;
201 |
--------------------------------------------------------------------------------