React.js – Тема 3 – Результат – Courses WebSkill

Слідкуйте за нами:

Тема 3. React.js Memory Game App

Створення Memory Game React.js додатку

Створення Memory Game React.js додатку, введення команд в консоль
D: або C: - переключення між дисками
cd - переходимо в необхідну вам папку
npx create-react-app memory-game- створення нового React.js проєкту
npx create-react-app - сама команда створення
memory-game- назва папки в якій буде додаток
cd memory-game - переходимо в створену папку

Видаляємо непотрібні файли, чистимо index.js та App.js від непотрібних команд
Видаляємо файли: setupTests.js,repor,WebVitals.js,logo.svg,index.css,App.test.js
З файлу index.js видаляємо такі рядки
import './index.css'; import reportWebVitals from './reportWebVitals'; reportWebVitals();
Та коментарі: // If you want...
З файлу App.js видаляємо import logo from './logo.svg';
Тег <div className="app"> і все що всередині нього

В папці src залишаємо лише такі файли:
App.js, App.css, index.js

Скопіюйте стилізацію нижче та замініть її в файлі App.css

Стилізація App.css
.App {
  text-align: center;
  font-family: Arial, sans-serif;
  margin-top: 20px;
}

h1 {
  margin-bottom: 10px;
}

.card-container {
  display: grid;
  grid-template-columns: repeat(8, 100px);
  gap: 10px;
  justify-content: center;
  margin-top: 40px;
}

.card {
  width: 100px;
  height: 100px;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #007bff;
  color: white;
  font-size: 24px;
  cursor: pointer;
  border: 2px solid #007bff;
  border-radius: 5px;
  transition: background-color 0.3s ease;
}

.card.flipped {
  background-color: white;
  color: #007bff;
  transform: rotateY(180deg);
}

button {
  margin-top: 20px;
  padding: 10px 20px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 16px;
  transition: background-color 0.3s ease;
}

button:hover {
  background-color: #0056b3;
}

Робимо заміну всієї стилізації в файлі App.css на свою, яку копіювали вище

Заготовки файлів для додатку

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
import React, { useState } from "react";
import './App.css';
 
function App() {
  return (
    <div className='app'>
 
    </div>
  );
}
export default App;

Початок роботи із App.js

Для початку створимо об'єкт levels із рівнями гри, по задумці їх буде 3(легкий, середній, важкий)

Іконки для рівнів:
['🐱', '🐶', '🐰', '🐻', '🐯', '🦁', '🐵', '🦊', '🐷', '🐮', '🐔', '🐧']

Створення змінних стану та функцій

  • currentLevel: Зберігає обраний рівень складності гри (легкий, середній, важкий).
  • cards: Масив карток, який містить змішані картки для поточного рівня.
  • flippedIndices: Масив індексів карток, які вже були перевернуті (відкриті).
  • matchedPairs: Масив індексів пар карток, які знайдені.
  • moves: Лічильник кількості ходів гравця.
  • gameOver: Змінна, яка вказує, чи завершена гра.
  • Функція initializeGame ініціалізує гру. Вона перемішує карти, скидає всі індекси перевернутих та зіграних пар, а також скидає лічильник ходів та встановлює gameOver на false.

  • Ви використовуєте useEffect, щоб викликати initializeGame при зміні currentLevel. Це означає, що гра буде ініціалізуватися знову, коли гравець змінює рівень гри.

  • Функція shuffle перемішує переданий масив, використовуючи алгоритм "Fisher-Yates". Вона створює копію масиву, перемішує його елементи і повертає перемішаний масив.

Функція handleCardClick()

  • handleCardClick(): Функція, яка викликається при натисканні на картку. Вона визначає логіку гри, додає або видаляє картки зі списку перевернутих, перевіряє, чи пара карток співпадає, та визначає, чи завершена гра.

Рендеринг компонентів

  • return():

    • У рендері відображається заголовок гри, кількість ходів, поточний рівень складності та повідомлення про перемогу, якщо гра завершилася.
    • Кнопки для вибору рівня складності відображаються на сторінці.
    • Картки відображаються у вигляді блоків з емодзі, і їх стан залежить від того, чи вони були перевернуті або знайдені.
    • Є кнопка "Розпочати нову гру", яка дозволяє перезапустити гру.

Скріншоти усіх файлів в нашому додатку

Група легких завдань

Розбиття коду на компоненти
Зазвичай, розбиття коду на компоненти робить його більш читабельним та обслуговуваним. У вашому коді можна виділити кілька компонентів:
Game (Гра):
Містить весь код гри, включаючи стани та функції для ініціалізації гри, перемішування карток та обробки кліків на картках.
Levels :
Містить об'єкт із рівнями та картинками для них
App:
Містить стани рівня гри та інших глобальних аспектів.
Включає компонент Game та передає йому відповідні стани і функції.

Добавлення нових рівнів
Для додавання нових рівнів у гру «Мемографія» на React ми створюємо різні набори зображень і рівня складності. Додати два нових рівня складності: "Легкий" і "Середній". Легкий рівень містить менше карток, а середній - більше. Ви можете легко розширити цей шаблон для створення ще більш складних рівнів.

Замінити картки(іконки) на картинки
Для додавання нових рівнів у гру «Мемографія» на React ми створюємо різні набори зображень і рівня складності. Додати два нових рівня складності: "Легкий" і "Середній". Легкий рівень містить менше карток, а середній - більше. Ви можете легко розширити цей шаблон для створення ще більш складних рівнів.

Добавити таймер гри
Додавання таймера до цього коду вимагає створення та оновлення змінної, яка відстежує час, і відображення цього часу в компоненті.

Завдання 5

Створення локалізації гри з різними мовами
Завдання може показатись важким, нехай вчитель його вам пояснить

Скріншоти початкових файлів в нашому додатку

Посилання на Тему 9. Введення в React.js з попереднього курсу

Посилання на Тему 1. Створення React.js, проєкт ToDoList

Посилання на Тему 2. Створення React.js Quiz, Temperature App's

Завдання 1 та 2. Розбиття коду на компоненти та добавлення нових рівнів

game.js

function Game({ currentLevel, cards, flippedIndices, matchedPairs, moves, gameOver, setCurrentLevel, initializeGame, handleCardClick }) {
return (
<div className="App">
<h1>Мемографія</h1>
<p>Кількість ходів: {moves}</p>
<p>Обраний рівень: {levels[currentLevel].name}</p>
{gameOver && <p>Ви виграли! Вітаємо!</p>}
<div className="level-buttons">
{Object.keys(levels).map((level) => (
<button key={level} onClick={() => setCurrentLevel(level)}>{levels[level].name}</button>
))}
</div>
<div className="card-container">
{cards.map((card, index) => (
<div
key={index}
className={`card ${flippedIndices.includes(index) || matchedPairs.includes(index) ? 'flipped' : ''}`}
onClick={() => handleCardClick(index)}
>
{flippedIndices.includes(index) || matchedPairs.includes(index) ? card : '❓'}
</div>
))}
</div>
<button onClick={initializeGame}>Розпочати нову гру</button>
</div>
);
}

App.js

function App() {
const [currentLevel, setCurrentLevel] = useState('hard');
const [cards, setCards] = useState([]);
const [flippedIndices, setFlippedIndices] = useState([]);
const [matchedPairs, setMatchedPairs] = useState([]);
const [moves, setMoves] = useState(0);
const [gameOver, setGameOver] = useState(false);

const initializeGame = () => {
// ...
};

useEffect(() => {
initializeGame();
}, [currentLevel]);

const handleCardClick = (index) => {
// ...
};

return (
<Game
currentLevel={currentLevel}
cards={cards}
flippedIndices={flippedIndices}
matchedPairs={matchedPairs}
moves={moves}
gameOver={gameOver}
setCurrentLevel={setCurrentLevel}
initializeGame={initializeGame}
handleCardClick={handleCardClick}
/>
);
}

export default App;

levels.js
const levels = {
    easy: {
      name: 'Легкий',
      cards: ['🐱', '🐶', '🐰', '🐻'],
    },
    medium: {
      name: 'Середній',
      cards: ['🐱', '🐶', '🐰', '🐻', '🐯', '🦁', '🐵', '🦊'],
    },
    hard: {
      name: 'Важкий',
      cards: ['🐱', '🐶', '🐰', '🐻', '🐯', '🦁', '🐵', '🦊', '🐷', '🐮', '🐔', '🐧'],
    },
    // Можна добавити нові рівні
  };
  export default levels;

Код із цих файлів необхідно доповнити вже готовими функціями

Завдання 3. Заміна іконок на картинки із відповідністю

levels.js

const levels = { easy: { name: 'Легкий', cards: [ 'https://example.com/image1.png', 'https://example.com/image2.png', 'https://example.com/image3.png', 'https://example.com/image4.png', 'https://example.com/image5.png', 'https://example.com/image6.png', 'https://example.com/image7.png', 'https://example.com/image8.png', ], },
medium: { name: 'Середній', cards: [ 'https://example.com/image9.png', 'https://example.com/image10.png', 'https://example.com/image11.png', 'https://example.com/image12.png', 'https://example.com/image13.png', 'https://example.com/image14.png', 'https://example.com/image15.png', 'https://example.com/image16.png', 'https://example.com/image17.png', 'https://example.com/image18.png', 'https://example.com/image19.png', 'https://example.com/image20.png', ], },
 };

Завдання 4. Добавлення таймеру гри

Таймер

Додавання таймера до цього коду вимагає створення та оновлення змінної, яка відстежує час, і відображення цього часу в компоненті. Ось приклад, як ви можете додати таймер до вашого коду:

  1. Створіть новий стан для зберігання часу гри:
const [time, setTime] = useState(0);
  1. Створіть функцію для запуску та зупинки таймера:
const startTimer = () => {
const timerInterval = setInterval(() => {
setTime((prevTime) => prevTime + 1);
}, 1000);
return timerInterval;
};

const stopTimer = (timerInterval) => {
clearInterval(timerInterval);
};

  1. Додайте таймер у функцію initializeGame, щоб почати відлік часу після ініціалізації гри:
const initializeGame = () => {
// Останній код ініціалізації гри

// Почнемо таймер
const timerInterval = startTimer();

// Збережемо інтервал таймера у стані
setTimerInterval(timerInterval);
};

  1. Додайте відображення часу в компоненті:
<p>Час гри: {formatTime(time)}</p>
  1. Створіть функцію для форматування часу в годинах, хвилинах та секундах:
const formatTime = (timeInSeconds) => {
const hours = Math.floor(timeInSeconds / 3600);
const minutes = Math.floor((timeInSeconds % 3600) / 60);
const seconds = timeInSeconds % 60;
return `${hours}:${minutes}:${seconds}`;
};
  1. Не забудьте створити стан для збереження інтервалу таймера:
const [timerInterval, setTimerInterval] = useState(null);
  1. Зупиняйте таймер при завершенні гри:
if (matchedPairs.length + 2 === cards.length) {
setGameOver(true);
stopTimer(timerInterval); // Зупинити таймер при завершенні гри
}

Це додасть таймер до вашої гри, який буде відображати час гри у форматі годин:хвилини:секунди. Таймер запускатиметься при ініціалізації гри і зупинятиметься при завершенні гри.

Завдання 5. Добавлення локалізації гри

Локалізація

Для додавання локалізації в чистий код React вам спочатку потрібно налаштувати бібліотеку для локалізації. Однією з популярних бібліотек для локалізації є i18next. Ось як ви можете налаштувати локалізацію і використовувати її в вашому коді:

  1. Встановіть i18next та react-i18next за допомогою npm або yarn:
npm install i18next react-i18next
  1. Створіть теку locales у кореневому каталозі вашого проекту і додайте файли локалізації для кожної мови, наприклад, en.json і uk.json:
// locales/en.json { "gameTitle": "Memory Game", "selectSet": "Select Set", "moves": "Moves", "level": "Level", "youWin": "You Win", // Додайте інші ключі локалізації для англійської мови } // locales/uk.json { "gameTitle": "Мемографія", "selectSet": "Оберіть набір", "moves": "Ходи", "level": "Рівень", "youWin": "Ви виграли", // Додайте інші ключі локалізації для української мови }
  1. Створіть файл i18n.js для налаштування i18next у кореневому каталозі вашого проекту:
// i18n.js import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import enTranslation from './locales/en.json'; import ukTranslation from './locales/uk.json'; i18n .use(initReactI18next) .init({ resources: { en: { translation: enTranslation, }, uk: { translation: ukTranslation, }, }, lng: 'en', // Початкова мова (англійська) fallbackLng: 'en', // Мова за умовчанням, якщо вказана неіснуюча мова interpolation: { escapeValue: false, // Не потрібно екранувати спеціальні символи }, }); export default i18n;
  1. В вашому компоненті App.js імпортуйте useTranslation і використовуйте його для отримання перекладу тексту:
import React, { useState, useEffect } from 'react'; import './App.css'; import { useTranslation } from 'react-i18next'; // Імпортуйте useTranslation // ... function App() { const { t } = useTranslation(); // Ініціалізуйте переклад return ( <div className="App"> <h1>{t('gameTitle')}</h1> <p>{t('moves')}: {moves}</p> <p>{t('level')}: {levels[currentLevel].name}</p> {gameOver && <p>{t('youWin')}</p>} {/* Решта вашого коду ... */} </div> ); } export default App;

Це дозволить вам використовувати локалізований текст у вашому компоненті. При зміні мови ви можете змінити значення lng у файлі i18n.js, і бібліотека i18next автоматично вибере потрібну локалізацію.

App.js
import React, { useState, useEffect } from 'react';
import './App.css';
import { useTranslation } from 'react-i18next';

const levels = {
  easy: {
    name: 'Легкий',
    cards: ['🐱', '🐶', '🐰', '🐻'],
  },
  medium: {
    name: 'Середній',
    cards: ['🐱', '🐶', '🐰', '🐻', '🐯', '🦁', '🐵', '🦊'],
  },
  hard: {
    name: 'Важкий',
    cards: ['🐱', '🐶', '🐰', '🐻', '🐯', '🦁', '🐵', '🦊', '🐷', '🐮', '🐔', '🐧'],
  },
  // Можна добавити нові рівні
};

function App() {
  const { t, i18n } = useTranslation();

  const changeLanguage = (language) => {
    i18n.changeLanguage(language);
  };
const [currentLevel, setCurrentLevel] = useState('hard'); // Зберігаємо поточний рівень гри(Важкий - hard)
const [cards, setCards] = useState([]); // Масив для зберігання карт гри
const [flippedIndices, setFlippedIndices] = useState([]); // Масив для зберігання індексів перевернутих карт
const [matchedPairs, setMatchedPairs] = useState([]); // Масив для зберігання індексів зіграних пар карт
const [moves, setMoves] = useState(0); // Лічильник кількості ходів гравця
const [gameOver, setGameOver] = useState(false); // Прапорець, що вказує, чи завершилася гра


const initializeGame = () => { // Функція для ініціалізації гри
  // Перемішуємо карти на основі обраного рівня гри
  const shuffledImages = shuffle([...levels[currentLevel].cards, ...levels[currentLevel].cards]);
 
  // Зберігаємо перемішані карти та скидаємо інші стани
  setCards(shuffledImages);
  setFlippedIndices([]);
  setMatchedPairs([]);
  setMoves(0);
  setGameOver(false);
};

useEffect(() => { // Викликаємо функцію ініціалізації гри після зміни рівня гри
  initializeGame();
}, [currentLevel]);

const shuffle = (array) => { // Функція для перемішування масиву
  const shuffledArray = [...array];
  for (let i = shuffledArray.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]];
  }
  return shuffledArray;
};

const handleCardClick = (index) => { // Функція для обробки кліків на картках гри
 
  if (flippedIndices.length === 2 || matchedPairs.includes(index)) return; // Перевіряємо, чи можна обробити клік на картці
  const newFlippedIndices = [...flippedIndices, index]; // Створюємо новий масив з індексами перевернутих карт

  setFlippedIndices(newFlippedIndices); // Зберігаємо новий масив індексів перевернутих карт у стані

  if (newFlippedIndices.length === 2) { // Перевіряємо, чи було перевернуто дві картки
    const [firstIndex, secondIndex] = newFlippedIndices;

    if (cards[firstIndex] === cards[secondIndex]) { // Перевіряємо, чи обрані карти утворюють пару
      setMatchedPairs([...matchedPairs, firstIndex, secondIndex]); // Якщо так, додаємо їхні індекси до масиву зіграних пар
     
      if (matchedPairs.length + 2 === cards.length) { // Перевіряємо, чи всі пари були зіграні і гра завершена
        setGameOver(true);
      }
    }
    setTimeout(() => setFlippedIndices([]), 1000); // Затримуємо відкриття карток на деякий час перед їхнім закриттям
    setMoves(moves + 1); // Збільшуємо лічильник ходів
  }
};

  return (
    <div className="App">
      <button onClick={() => changeLanguage('en')}>English</button>
      <button onClick={() => changeLanguage('uk')}>Українська</button>
     <h1>{t('gameTitle')}</h1>
      <p>{t('moves')}: {moves}</p>
      <p>{t('level')}: {levels[currentLevel].name}</p>
      {gameOver && <p>{t('youWin')}</p>}
      <div className="level-buttons">
        {Object.keys(levels).map((level) => (
          <button key={level} onClick={() => setCurrentLevel(level)}>{levels[level].name}</button>
        ))}
      </div>
      <div className="card-container">
        {cards.map((card, index) => (
          <div
            key={index}
            className={`card ${flippedIndices.includes(index) || matchedPairs.includes(index) ? 'flipped' : ''}`}
            onClick={() => handleCardClick(index)}
          >
            {flippedIndices.includes(index) || matchedPairs.includes(index) ? card : '❓'}
          </div>
        ))}
      </div>
      <button onClick={initializeGame}>Розпочати нову гру</button>
    </div>
  );
}

export default App;

Вміст кожного із файлів Memory Game App

index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import i18n from './i18n';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
App.js
import React, { useState, useEffect } from 'react';
import './App.css';
import { useTranslation } from 'react-i18next';

const levels = {
  easy: {
    name: 'Легкий',
    cards: ['🐱', '🐶', '🐰', '🐻'],
  },
  medium: {
    name: 'Середній',
    cards: ['🐱', '🐶', '🐰', '🐻', '🐯', '🦁', '🐵', '🦊'],
  },
  hard: {
    name: 'Важкий',
    cards: ['🐱', '🐶', '🐰', '🐻', '🐯', '🦁', '🐵', '🦊', '🐷', '🐮', '🐔', '🐧'],
  },
  // Можна добавити нові рівні
};

function App() {
  const { t, i18n } = useTranslation();

  const changeLanguage = (language) => {
    i18n.changeLanguage(language);
  };
const [currentLevel, setCurrentLevel] = useState('hard'); // Зберігаємо поточний рівень гри(Важкий - hard)
const [cards, setCards] = useState([]); // Масив для зберігання карт гри
const [flippedIndices, setFlippedIndices] = useState([]); // Масив для зберігання індексів перевернутих карт
const [matchedPairs, setMatchedPairs] = useState([]); // Масив для зберігання індексів зіграних пар карт
const [moves, setMoves] = useState(0); // Лічильник кількості ходів гравця
const [gameOver, setGameOver] = useState(false); // Прапорець, що вказує, чи завершилася гра


const initializeGame = () => { // Функція для ініціалізації гри
  // Перемішуємо карти на основі обраного рівня гри
  const shuffledImages = shuffle([...levels[currentLevel].cards, ...levels[currentLevel].cards]);
 
  // Зберігаємо перемішані карти та скидаємо інші стани
  setCards(shuffledImages);
  setFlippedIndices([]);
  setMatchedPairs([]);
  setMoves(0);
  setGameOver(false);
};

useEffect(() => { // Викликаємо функцію ініціалізації гри після зміни рівня гри
  initializeGame();
}, [currentLevel]);

const shuffle = (array) => { // Функція для перемішування масиву
  const shuffledArray = [...array];
  for (let i = shuffledArray.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]];
  }
  return shuffledArray;
};

const handleCardClick = (index) => { // Функція для обробки кліків на картках гри
 
  if (flippedIndices.length === 2 || matchedPairs.includes(index)) return; // Перевіряємо, чи можна обробити клік на картці
  const newFlippedIndices = [...flippedIndices, index]; // Створюємо новий масив з індексами перевернутих карт

  setFlippedIndices(newFlippedIndices); // Зберігаємо новий масив індексів перевернутих карт у стані

  if (newFlippedIndices.length === 2) { // Перевіряємо, чи було перевернуто дві картки
    const [firstIndex, secondIndex] = newFlippedIndices;

    if (cards[firstIndex] === cards[secondIndex]) { // Перевіряємо, чи обрані карти утворюють пару
      setMatchedPairs([...matchedPairs, firstIndex, secondIndex]); // Якщо так, додаємо їхні індекси до масиву зіграних пар
     
      if (matchedPairs.length + 2 === cards.length) { // Перевіряємо, чи всі пари були зіграні і гра завершена
        setGameOver(true);
      }
    }
    setTimeout(() => setFlippedIndices([]), 1000); // Затримуємо відкриття карток на деякий час перед їхнім закриттям
    setMoves(moves + 1); // Збільшуємо лічильник ходів
  }
};

  return (
    <div className="App">
      <button onClick={() => changeLanguage('en')}>English</button>
      <button onClick={() => changeLanguage('uk')}>Українська</button>
     <h1>{t('gameTitle')}</h1>
      <p>{t('moves')}: {moves}</p>
      <p>{t('level')}: {levels[currentLevel].name}</p>
      {gameOver && <p>{t('youWin')}</p>}
      <div className="level-buttons">
        {Object.keys(levels).map((level) => (
          <button key={level} onClick={() => setCurrentLevel(level)}>{levels[level].name}</button>
        ))}
      </div>
      <div className="card-container">
        {cards.map((card, index) => (
          <div
            key={index}
            className={`card ${flippedIndices.includes(index) || matchedPairs.includes(index) ? 'flipped' : ''}`}
            onClick={() => handleCardClick(index)}
          >
            {flippedIndices.includes(index) || matchedPairs.includes(index) ? card : '❓'}
          </div>
        ))}
      </div>
      <button onClick={initializeGame}>Розпочати нову гру</button>
    </div>
  );
}

export default App;
Copyright © 2023 Courses WebSkill | Powered by WebSkill
Scroll to Top