From 5d326c8fd1327477557266a9b8b522fd22d029f2 Mon Sep 17 00:00:00 2001 From: HydroponKing Date: Sun, 15 Sep 2024 03:44:00 +0500 Subject: [PATCH 01/20] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D1=83=20=D1=82=D1=80?= =?UTF-8?q?=D0=B5=D1=85=20=D0=B6=D0=B8=D0=B7=D0=BD=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Cards/Cards.jsx | 143 ++++++++++++++++---------- src/components/Cards/Cards.module.css | 6 ++ 2 files changed, 94 insertions(+), 55 deletions(-) diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index 7526a56c8..9f54a4dea 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -1,3 +1,5 @@ +// src/components/Cards/Cards.jsx + import { shuffle } from "lodash"; import { useEffect, useState } from "react"; import { generateDeck } from "../../utils/cards"; @@ -6,12 +8,10 @@ import { EndGameModal } from "../../components/EndGameModal/EndGameModal"; import { Button } from "../../components/Button/Button"; import { Card } from "../../components/Card/Card"; -// Игра закончилась +// Константы статусов игры const STATUS_LOST = "STATUS_LOST"; const STATUS_WON = "STATUS_WON"; -// Идет игра: карты закрыты, игрок может их открыть const STATUS_IN_PROGRESS = "STATUS_IN_PROGRESS"; -// Начало игры: игрок видит все карты в течении нескольких секунд const STATUS_PREVIEW = "STATUS_PREVIEW"; function getTimerValue(startDate, endDate) { @@ -26,9 +26,9 @@ function getTimerValue(startDate, endDate) { endDate = new Date(); } - const diffInSecconds = Math.floor((endDate.getTime() - startDate.getTime()) / 1000); - const minutes = Math.floor(diffInSecconds / 60); - const seconds = diffInSecconds % 60; + const diffInSeconds = Math.floor((endDate.getTime() - startDate.getTime()) / 1000); + const minutes = Math.floor(diffInSeconds / 60); + const seconds = diffInSeconds % 60; return { minutes, seconds, @@ -41,53 +41,63 @@ function getTimerValue(startDate, endDate) { * previewSeconds - сколько секунд пользователь будет видеть все карты открытыми до начала игры */ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { - // В cards лежит игровое поле - массив карт и их состояние открыта\закрыта + // Состояние для игровых карт const [cards, setCards] = useState([]); // Текущий статус игры const [status, setStatus] = useState(STATUS_PREVIEW); - - // Дата начала игры + // Дата начала и окончания игры const [gameStartDate, setGameStartDate] = useState(null); - // Дата конца игры const [gameEndDate, setGameEndDate] = useState(null); - - // Стейт для таймера, высчитывается в setInteval на основе gameStartDate и gameEndDate + // Состояние таймера const [timer, setTimer] = useState({ seconds: 0, minutes: 0, }); + // Количество оставшихся жизней + const [lives, setLives] = useState(3); + // Выбранные в данный момент карты + const [selectedCards, setSelectedCards] = useState([]); + // Флаг для блокировки кликов во время проверки пар + const [isProcessing, setIsProcessing] = useState(false); - function finishGame(status = STATUS_LOST) { + // Функция для завершения игры + function finishGame(gameStatus = STATUS_LOST) { setGameEndDate(new Date()); - setStatus(status); + setStatus(gameStatus); } + + // Функция для старта игры function startGame() { const startDate = new Date(); setGameEndDate(null); setGameStartDate(startDate); setTimer(getTimerValue(startDate, null)); setStatus(STATUS_IN_PROGRESS); + setLives(3); + setSelectedCards([]); + setIsProcessing(false); } + + // Функция для перезапуска игры function resetGame() { setGameStartDate(null); setGameEndDate(null); setTimer(getTimerValue(null, null)); setStatus(STATUS_PREVIEW); + setLives(3); + setSelectedCards([]); + setIsProcessing(false); } /** - * Обработка основного действия в игре - открытие карты. - * После открытия карты игра может пепереходит в следующие состояния - * - "Игрок выиграл", если на поле открыты все карты - * - "Игрок проиграл", если на поле есть две открытые карты без пары - * - "Игра продолжается", если не случилось первых двух условий + * Обработка открытия карты */ const openCard = clickedCard => { - // Если карта уже открыта, то ничего не делаем - if (clickedCard.open) { + if (isProcessing || clickedCard.open) { return; } - // Игровое поле после открытия кликнутой карты + + // Открываем кликнутую карту const nextCards = cards.map(card => { if (card.id !== clickedCard.id) { return card; @@ -101,56 +111,72 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { setCards(nextCards); - const isPlayerWon = nextCards.every(card => card.open); - - // Победа - все карты на поле открыты - if (isPlayerWon) { - finishGame(STATUS_WON); - return; - } - - // Открытые карты на игровом поле - const openCards = nextCards.filter(card => card.open); + // Добавляем карту в выбранные + const nextSelectedCards = [...selectedCards, clickedCard]; + setSelectedCards(nextSelectedCards); - // Ищем открытые карты, у которых нет пары среди других открытых - const openCardsWithoutPair = openCards.filter(card => { - const sameCards = openCards.filter(openCard => card.suit === openCard.suit && card.rank === openCard.rank); + if (nextSelectedCards.length === 2) { + setIsProcessing(true); + const [firstCard, secondCard] = nextSelectedCards; - if (sameCards.length < 2) { - return true; - } + const isMatch = firstCard.rank === secondCard.rank && firstCard.suit === secondCard.suit; - return false; - }); + if (isMatch) { + // Карты совпали + setSelectedCards([]); - const playerLost = openCardsWithoutPair.length >= 2; + // Проверяем, выиграл ли игрок + const isPlayerWon = nextCards.every(card => card.open); + if (isPlayerWon) { + finishGame(STATUS_WON); + } + setIsProcessing(false); + } else { + // Карты не совпали + const nextLives = lives - 1; + setLives(nextLives); - // "Игрок проиграл", т.к на поле есть две открытые карты без пары - if (playerLost) { - finishGame(STATUS_LOST); - return; + if (nextLives === 0) { + // Жизни закончились, игрок проиграл + finishGame(STATUS_LOST); + setIsProcessing(false); + } else { + // Закрываем карты обратно после задержки + setTimeout(() => { + setCards(currentCards => + currentCards.map(card => { + if (card.id === firstCard.id || card.id === secondCard.id) { + return { + ...card, + open: false, + }; + } + return card; + }), + ); + setSelectedCards([]); + setIsProcessing(false); + }, 1000); // Задержка в 1 секунду + } + } } - - // ... игра продолжается }; const isGameEnded = status === STATUS_LOST || status === STATUS_WON; - // Игровой цикл + // Инициализация игры useEffect(() => { - // В статусах кроме превью доп логики не требуется if (status !== STATUS_PREVIEW) { return; } - // В статусе превью мы if (pairsCount > 36) { alert("Столько пар сделать невозможно"); return; } setCards(() => { - return shuffle(generateDeck(pairsCount, 10)); + return shuffle(generateDeck(pairsCount)); }); const timerId = setTimeout(() => { @@ -162,7 +188,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { }; }, [status, pairsCount, previewSeconds]); - // Обновляем значение таймера в интервале + // Обновление таймера useEffect(() => { const intervalId = setInterval(() => { setTimer(getTimerValue(gameStartDate, gameEndDate)); @@ -185,17 +211,24 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { <>
min
-
{timer.minutes.toString().padStart("2", "0")}
+
{timer.minutes.toString().padStart(2, "0")}
.
sec
-
{timer.seconds.toString().padStart("2", "0")}
+
{timer.seconds.toString().padStart(2, "0")}
)} - {status === STATUS_IN_PROGRESS ? : null} + {status === STATUS_IN_PROGRESS ? ( + <> +
+

Жизни: {lives}

+
+ + + ) : null}
diff --git a/src/components/Cards/Cards.module.css b/src/components/Cards/Cards.module.css index 000c5006c..5524de90b 100644 --- a/src/components/Cards/Cards.module.css +++ b/src/components/Cards/Cards.module.css @@ -70,3 +70,9 @@ margin-bottom: -12px; } +.lives { + font-size: 18px; + margin-left: 20px; + display: flex; + align-items: center; +} \ No newline at end of file From b3c74edf21301505a559656aa7810141f3d73126 Mon Sep 17 00:00:00 2001 From: HydroponKing Date: Sun, 15 Sep 2024 16:20:39 +0500 Subject: [PATCH 02/20] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B2=D1=80=D0=B5=D0=BC=D1=8F=20=D0=B2=D1=8B=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D1=8B=20=D0=B2=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 9b90842c4..9497c875a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +## Оценка времени выполнения работы "Вводный урок" + +- **Ожидаемое время:** 6 часов +- **Фактическое время:** 2 часа + # MVP Карточная игра "Мемо" В этом репозитории реализован MVP карточкой игры "Мемо" по [тех.заданию](./docs/mvp-spec.md) From 34d49b5036ee01ff4f26315f6611986191506be9 Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Tue, 17 Sep 2024 00:02:22 +0500 Subject: [PATCH 03/20] =?UTF-8?q?=D0=BF=D0=BE=D1=8F=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=D1=81=D1=8F=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B6=D0=B8=D0=BC=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierrc.js | 1 + src/components/Cards/Cards.jsx | 25 +++++++++++++------ src/context/GameModeContext.js | 8 ++++++ src/index.js | 18 +++++++++++-- src/pages/SelectLevelPage/SelectLevelPage.jsx | 13 ++++++++++ 5 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 src/context/GameModeContext.js diff --git a/.prettierrc.js b/.prettierrc.js index 65e18f5ff..a037d25c9 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -7,4 +7,5 @@ module.exports = { bracketSpacing: true, arrowParens: "avoid", htmlWhitespaceSensitivity: "ignore", + endOfLine: "auto", // для своместной разработки и с linux и с windows }; diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index 9f54a4dea..83a667e54 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -1,12 +1,13 @@ // src/components/Cards/Cards.jsx import { shuffle } from "lodash"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useContext } from "react"; import { generateDeck } from "../../utils/cards"; import styles from "./Cards.module.css"; import { EndGameModal } from "../../components/EndGameModal/EndGameModal"; import { Button } from "../../components/Button/Button"; import { Card } from "../../components/Card/Card"; +import { GameModeContext } from "../../context/GameModeContext"; // Константы статусов игры const STATUS_LOST = "STATUS_LOST"; @@ -41,6 +42,8 @@ function getTimerValue(startDate, endDate) { * previewSeconds - сколько секунд пользователь будет видеть все карты открытыми до начала игры */ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { + const { livesMode } = useContext(GameModeContext); // Используем контекст + // Состояние для игровых карт const [cards, setCards] = useState([]); // Текущий статус игры @@ -54,12 +57,18 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { minutes: 0, }); // Количество оставшихся жизней - const [lives, setLives] = useState(3); + const initialLives = livesMode ? 3 : 1; + const [lives, setLives] = useState(initialLives); // Выбранные в данный момент карты const [selectedCards, setSelectedCards] = useState([]); // Флаг для блокировки кликов во время проверки пар const [isProcessing, setIsProcessing] = useState(false); + // Обновляем количество жизней при изменении режима игры + useEffect(() => { + setLives(initialLives); + }, [initialLives]); + // Функция для завершения игры function finishGame(gameStatus = STATUS_LOST) { setGameEndDate(new Date()); @@ -73,7 +82,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { setGameStartDate(startDate); setTimer(getTimerValue(startDate, null)); setStatus(STATUS_IN_PROGRESS); - setLives(3); + setLives(initialLives); setSelectedCards([]); setIsProcessing(false); } @@ -84,7 +93,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { setGameEndDate(null); setTimer(getTimerValue(null, null)); setStatus(STATUS_PREVIEW); - setLives(3); + setLives(initialLives); setSelectedCards([]); setIsProcessing(false); } @@ -223,9 +232,11 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
{status === STATUS_IN_PROGRESS ? ( <> -
-

Жизни: {lives}

-
+ {livesMode && ( +
+

Жизни: {lives}

+
+ )} ) : null} diff --git a/src/context/GameModeContext.js b/src/context/GameModeContext.js new file mode 100644 index 000000000..8651bbcef --- /dev/null +++ b/src/context/GameModeContext.js @@ -0,0 +1,8 @@ +// src/GameModeContext.js + +import { createContext } from "react"; + +export const GameModeContext = createContext({ + livesMode: false, // false - оригинальный режим, true - режим с тремя жизнями + setLivesMode: () => {}, +}); diff --git a/src/index.js b/src/index.js index f689c5f0b..19b128507 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,26 @@ -import React from "react"; +// src/index.js + +import React, { useState } from "react"; import ReactDOM from "react-dom/client"; import "./index.css"; import { RouterProvider } from "react-router-dom"; import { router } from "./router"; +import { GameModeContext } from "./context/GameModeContext"; const root = ReactDOM.createRoot(document.getElementById("root")); + +function App() { + const [livesMode, setLivesMode] = useState(false); + + return ( + + + + ); +} + root.render( - + , ); diff --git a/src/pages/SelectLevelPage/SelectLevelPage.jsx b/src/pages/SelectLevelPage/SelectLevelPage.jsx index 758942e51..472b4d618 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.jsx +++ b/src/pages/SelectLevelPage/SelectLevelPage.jsx @@ -1,11 +1,24 @@ +// src/pages/SelectLevelPage/SelectLevelPage.jsx + import { Link } from "react-router-dom"; import styles from "./SelectLevelPage.module.css"; +import { useContext } from "react"; +import { GameModeContext } from "../../context/GameModeContext"; export function SelectLevelPage() { + const { livesMode, setLivesMode } = useContext(GameModeContext); + + const handleCheckboxChange = event => { + setLivesMode(event.target.checked); + }; return (

Выбери сложность

+
  • From b0136ed3992b575bd694174f6bf5a6450eb40b5b Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Tue, 17 Sep 2024 02:24:14 +0500 Subject: [PATCH 04/20] =?UTF-8?q?=D0=B1=D0=BE=D0=BC=D0=B1=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=8F=20=D1=81=D1=82=D0=B8=D0=BB=D0=B8=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/components/Cards/Cards.module.css | 18 +++++--- .../SelectLevelPage.module.css | 43 +++++++++++++++++++ 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9497c875a..dfb7cb167 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## Оценка времени выполнения работы "Вводный урок" - **Ожидаемое время:** 6 часов -- **Фактическое время:** 2 часа +- **Фактическое время:** 6 часов # MVP Карточная игра "Мемо" diff --git a/src/components/Cards/Cards.module.css b/src/components/Cards/Cards.module.css index 5524de90b..c2298bb16 100644 --- a/src/components/Cards/Cards.module.css +++ b/src/components/Cards/Cards.module.css @@ -1,3 +1,15 @@ +@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap'); + +.lives { + font-size: 24px; /* Увеличим размер шрифта */ + margin-left: 20px; + display: flex; + align-items: center; + color: #fff; + font-family: 'Orbitron', sans-serif; /* Добавляем угловатый шрифт */ +} + + .container { width: 672px; margin: 0 auto; @@ -69,10 +81,4 @@ line-height: 32px; margin-bottom: -12px; -} -.lives { - font-size: 18px; - margin-left: 20px; - display: flex; - align-items: center; } \ No newline at end of file diff --git a/src/pages/SelectLevelPage/SelectLevelPage.module.css b/src/pages/SelectLevelPage/SelectLevelPage.module.css index 390ac0def..49e69f3dc 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.module.css +++ b/src/pages/SelectLevelPage/SelectLevelPage.module.css @@ -1,3 +1,46 @@ +@import url('https://fonts.googleapis.com/css2?family=Cabin+Sketch:wght@400;700&display=swap'); + +.checkboxContainer { + display: flex; + align-items: center; + font-family: 'Cabin Sketch', cursive; /* Подключённый шрифт */ + font-size: 18px; + color: #333; +} + +.checkboxContainer input[type="checkbox"] { + width: 20px; + height: 20px; + margin-right: 10px; + position: relative; + cursor: pointer; + appearance: none; + background-color: #fff; + border: 2px solid #333; + border-radius: 5px; + transition: background-color 0.2s ease; + display: flex; + justify-content: center; + align-items: center; +} + +.checkboxContainer input[type="checkbox"]:checked { + background-color: #4caf50; +} + +.checkboxContainer input[type="checkbox"]:checked::before { + content: '✓'; + font-size: 18px; /* Сделаем галочку чуть больше */ + color: #fff; + font-weight: bold; /* Галочка станет толще */ + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); /* Выравнивание галочки по центру */ + font-family: 'Comic Sans MS', cursive; /* стили для галочки */ +} + + .container { width: 100%; min-height: 100%; From 513fe8bb02c9eebbe8404f15ce0d7728528a4031 Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Tue, 17 Sep 2024 03:51:35 +0500 Subject: [PATCH 05/20] =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BD=D1=83=20=D0=B2=D0=BE=D0=BE?= =?UTF-8?q?=D0=B1=D1=89=D0=B5=20=D0=B1=D0=BE=D0=BC=D0=B1=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Cards/Cards.jsx | 2 +- src/components/Cards/Cards.module.css | 13 +++++++++---- .../SelectLevelPage/SelectLevelPage.module.css | 14 +++++--------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index 83a667e54..604d1df23 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -233,7 +233,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { {status === STATUS_IN_PROGRESS ? ( <> {livesMode && ( -
    +

    Жизни: {lives}

    )} diff --git a/src/components/Cards/Cards.module.css b/src/components/Cards/Cards.module.css index c2298bb16..d6d59f50a 100644 --- a/src/components/Cards/Cards.module.css +++ b/src/components/Cards/Cards.module.css @@ -1,15 +1,20 @@ -@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Handjet:wght@100..900&display=swap'); .lives { - font-size: 24px; /* Увеличим размер шрифта */ + font-size: 32px; margin-left: 20px; display: flex; align-items: center; - color: #fff; - font-family: 'Orbitron', sans-serif; /* Добавляем угловатый шрифт */ + font-family: "Handjet", sans-serif; + color: #fff; /* По умолчанию белый цвет */ +} + +.livesCritical { + color: rgb(255, 55, 55); /* Красный цвет при 1 жизни */ } + .container { width: 672px; margin: 0 auto; diff --git a/src/pages/SelectLevelPage/SelectLevelPage.module.css b/src/pages/SelectLevelPage/SelectLevelPage.module.css index 49e69f3dc..650261dc7 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.module.css +++ b/src/pages/SelectLevelPage/SelectLevelPage.module.css @@ -15,7 +15,7 @@ position: relative; cursor: pointer; appearance: none; - background-color: #fff; + background-color: #c2f5ff; border: 2px solid #333; border-radius: 5px; transition: background-color 0.2s ease; @@ -24,18 +24,14 @@ align-items: center; } -.checkboxContainer input[type="checkbox"]:checked { - background-color: #4caf50; -} - .checkboxContainer input[type="checkbox"]:checked::before { content: '✓'; - font-size: 18px; /* Сделаем галочку чуть больше */ - color: #fff; + font-size: 32px; /* Сделаем галочку чуть больше */ + color: #004980; font-weight: bold; /* Галочка станет толще */ position: absolute; - left: 50%; - top: 50%; + left: 63%; + top: 18%; transform: translate(-50%, -50%); /* Выравнивание галочки по центру */ font-family: 'Comic Sans MS', cursive; /* стили для галочки */ } From b2ff97c13e5513318e0bb55e65268a096a3d7f0f Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Tue, 17 Sep 2024 04:00:07 +0500 Subject: [PATCH 06/20] =?UTF-8?q?=D1=80=D0=B5=D0=B6=D0=B8=D0=BC=20=D0=B8?= =?UTF-8?q?=D0=B3=D1=80=D1=8B=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D1=8F?= =?UTF-8?q?=D0=B5=D1=82=D1=81=D1=8F=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=82?= =?UTF-8?q?=D0=B5=D0=BA=D1=81=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/context/GameModeContext.js | 19 +++++++++++++++---- src/index.js | 12 ++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/context/GameModeContext.js b/src/context/GameModeContext.js index 8651bbcef..d0d6976b4 100644 --- a/src/context/GameModeContext.js +++ b/src/context/GameModeContext.js @@ -1,8 +1,19 @@ -// src/GameModeContext.js - -import { createContext } from "react"; +import { createContext, useState, useEffect } from "react"; export const GameModeContext = createContext({ - livesMode: false, // false - оригинальный режим, true - режим с тремя жизнями + livesMode: false, setLivesMode: () => {}, }); + +export function GameModeProvider({ children }) { + const [livesMode, setLivesMode] = useState(() => { + const savedMode = localStorage.getItem("livesMode"); + return savedMode === "true"; + }); + + useEffect(() => { + localStorage.setItem("livesMode", livesMode); + }, [livesMode]); + + return {children}; +} diff --git a/src/index.js b/src/index.js index 19b128507..30c3029f6 100644 --- a/src/index.js +++ b/src/index.js @@ -1,21 +1,17 @@ -// src/index.js - -import React, { useState } from "react"; +import React from "react"; import ReactDOM from "react-dom/client"; import "./index.css"; import { RouterProvider } from "react-router-dom"; import { router } from "./router"; -import { GameModeContext } from "./context/GameModeContext"; +import { GameModeProvider } from "./context/GameModeContext"; // Импортируем обновленный провайдер const root = ReactDOM.createRoot(document.getElementById("root")); function App() { - const [livesMode, setLivesMode] = useState(false); - return ( - + - + ); } From 876285905f539d9096d9a2a320c6dc2fd010009e Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Thu, 19 Sep 2024 07:07:34 +0500 Subject: [PATCH 07/20] =?UTF-8?q?=D1=87=D0=B5=D1=80=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20=D0=BB?= =?UTF-8?q?=D0=B8=D0=B4=D0=B5=D1=80=D0=B1=D0=BE=D1=80=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/EndGameModal/EndGameModal.jsx | 45 +++++++++++-- src/pages/LeaderboardPage/LeaderboardPage.jsx | 53 +++++++++++++++ .../LeaderboardPage.module.css | 66 +++++++++++++++++++ src/pages/SelectLevelPage/SelectLevelPage.jsx | 6 +- src/router.js | 5 ++ 5 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 src/pages/LeaderboardPage/LeaderboardPage.jsx create mode 100644 src/pages/LeaderboardPage/LeaderboardPage.module.css diff --git a/src/components/EndGameModal/EndGameModal.jsx b/src/components/EndGameModal/EndGameModal.jsx index 722394833..45af7957a 100644 --- a/src/components/EndGameModal/EndGameModal.jsx +++ b/src/components/EndGameModal/EndGameModal.jsx @@ -1,27 +1,58 @@ +import { useState } from "react"; import styles from "./EndGameModal.module.css"; - import { Button } from "../Button/Button"; - import deadImageUrl from "./images/dead.png"; import celebrationImageUrl from "./images/celebration.png"; export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick }) { + const [playerName, setPlayerName] = useState(""); const title = isWon ? "Вы победили!" : "Вы проиграли!"; - const imgSrc = isWon ? celebrationImageUrl : deadImageUrl; - const imgAlt = isWon ? "celebration emodji" : "dead emodji"; + const handleSubmit = () => { + const name = playerName || "Пользователь"; + const totalTime = gameDurationMinutes * 60 + gameDurationSeconds; + + // Отправляем результат на сервер + fetch("https://wedev-api.sky.pro/api/leaderboard", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ name, time: totalTime }), + }) + .then(response => response.json()) + .then(data => { + console.log("Лидер добавлен:", data); + onClick(); + }) + .catch(error => { + console.error("Ошибка при добавлении лидера:", error); + }); + }; + return (
    {imgAlt}

    {title}

    Затраченное время:

    - {gameDurationMinutes.toString().padStart("2", "0")}.{gameDurationSeconds.toString().padStart("2", "0")} + {gameDurationMinutes.toString().padStart(2, "0")}:{gameDurationSeconds.toString().padStart(2, "0")}
    - - + {isWon && ( + <> + setPlayerName(e.target.value)} + /> + + + )} + {!isWon && }
    ); } diff --git a/src/pages/LeaderboardPage/LeaderboardPage.jsx b/src/pages/LeaderboardPage/LeaderboardPage.jsx new file mode 100644 index 000000000..07b59ac72 --- /dev/null +++ b/src/pages/LeaderboardPage/LeaderboardPage.jsx @@ -0,0 +1,53 @@ +import { useState, useEffect } from "react"; +import styles from "./LeaderboardPage.module.css"; + +export function LeaderboardPage() { + const [leaders, setLeaders] = useState([]); + + useEffect(() => { + fetch("https://wedev-api.sky.pro/api/leaderboard") + .then(response => response.json()) + .then(data => { + setLeaders(data.leaders); + }) + .catch(error => { + console.error("Ошибка при получении списка лидеров:", error); + }); + }, []); + + return ( +
    +

    Лидерборд

    + + + + + + + + + + {leaders.map((leader, index) => ( + + + + + + ))} + +
    ПозицияПользовательВремя
    {`# ${index + 1}`}{leader.name}{formatTime(leader.time)}
    +
    + +
    +
    + ); +} + +// Функция для форматирования времени в минуты и секунды +function formatTime(timeInSeconds) { + const minutes = Math.floor(timeInSeconds / 60) + .toString() + .padStart(2, "0"); + const seconds = (timeInSeconds % 60).toString().padStart(2, "0"); + return `${minutes}:${seconds}`; +} diff --git a/src/pages/LeaderboardPage/LeaderboardPage.module.css b/src/pages/LeaderboardPage/LeaderboardPage.module.css new file mode 100644 index 000000000..903377c8c --- /dev/null +++ b/src/pages/LeaderboardPage/LeaderboardPage.module.css @@ -0,0 +1,66 @@ +.leaderboard { + background-color: #043864; + color: white; + padding: 20px; + border-radius: 8px; + } + + .title { + font-size: 24px; + font-weight: bold; + margin-bottom: 20px; + } + + .table { + width: 100%; + border-collapse: collapse; + } + + .table th, + .table td { + text-align: left; + padding: 10px 15px; + background-color: white; + color: #043864; + border-radius: 8px; + margin: 5px 0; + } + + .table th { + font-size: 16px; + font-weight: bold; + background-color: #d8eaff; + color: #043864; + } + + .table tbody tr:nth-child(odd) { + background-color: #f5faff; + } + + .table tbody tr:nth-child(even) { + background-color: #e1f2ff; + } + + .table tbody tr:hover { + background-color: #c0e1ff; + } + + .buttonContainer { + text-align: right; + margin-top: 20px; + } + + .button { + background-color: #4caf50; + color: white; + padding: 10px 20px; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 16px; + } + + .button:hover { + background-color: #45a049; + } + \ No newline at end of file diff --git a/src/pages/SelectLevelPage/SelectLevelPage.jsx b/src/pages/SelectLevelPage/SelectLevelPage.jsx index 472b4d618..880c98e22 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.jsx +++ b/src/pages/SelectLevelPage/SelectLevelPage.jsx @@ -1,5 +1,3 @@ -// src/pages/SelectLevelPage/SelectLevelPage.jsx - import { Link } from "react-router-dom"; import styles from "./SelectLevelPage.module.css"; import { useContext } from "react"; @@ -11,6 +9,7 @@ export function SelectLevelPage() { const handleCheckboxChange = event => { setLivesMode(event.target.checked); }; + return (
    @@ -36,6 +35,9 @@ export function SelectLevelPage() {
+ + Лидерборд +
); diff --git a/src/router.js b/src/router.js index da6e94b51..702dad55d 100644 --- a/src/router.js +++ b/src/router.js @@ -1,6 +1,7 @@ import { createBrowserRouter } from "react-router-dom"; import { GamePage } from "./pages/GamePage/GamePage"; import { SelectLevelPage } from "./pages/SelectLevelPage/SelectLevelPage"; +import { LeaderboardPage } from "./pages/LeaderboardPage/LeaderboardPage"; export const router = createBrowserRouter( [ @@ -18,4 +19,8 @@ export const router = createBrowserRouter( * он же указан в homepage package.json и в index.html */ { basename: "/react-memo" }, + { + path: "/leaderboard", // Добавлен маршрут для лидерборда + element: , + }, ); From bbd51628ebd2d36e60f42d814573a442be4aae04 Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Thu, 19 Sep 2024 22:42:28 +0500 Subject: [PATCH 08/20] =?UTF-8?q?leaderboard=20=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=B5=D1=82=20=D0=BA=D0=BE=D1=80=D1=80=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D0=BD=D0=BE=20=D0=B1=D0=B5=D0=B7=20=D0=BE=D1=88=D0=B8?= =?UTF-8?q?=D0=B1=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/EndGameModal/EndGameModal.jsx | 20 +++++++++++++------ src/pages/LeaderboardPage/LeaderboardPage.jsx | 10 ++++++---- .../LeaderboardPage.module.css | 7 ++++++- src/router.js | 8 ++++---- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/components/EndGameModal/EndGameModal.jsx b/src/components/EndGameModal/EndGameModal.jsx index 45af7957a..c01d2ba0d 100644 --- a/src/components/EndGameModal/EndGameModal.jsx +++ b/src/components/EndGameModal/EndGameModal.jsx @@ -14,18 +14,26 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, const name = playerName || "Пользователь"; const totalTime = gameDurationMinutes * 60 + gameDurationSeconds; - // Отправляем результат на сервер + console.log("Отправляем данные:", { name, time: totalTime }); + + // Отправляем данные в формате JSON fetch("https://wedev-api.sky.pro/api/leaderboard", { method: "POST", headers: { - "Content-Type": "application/json", + // Убираем Content-Type, так как API требует это убрать + // Не указываем заголовок Content-Type }, - body: JSON.stringify({ name, time: totalTime }), + body: JSON.stringify({ name, time: totalTime }), // Преобразуем данные в JSON }) - .then(response => response.json()) + .then(response => { + if (!response.ok) { + throw new Error(`Ошибка HTTP: ${response.status}`); + } + return response.json(); + }) .then(data => { - console.log("Лидер добавлен:", data); - onClick(); + console.log("Лидер добавлен:", data); // Лог для проверки результата + onClick(); // Закрытие модального окна }) .catch(error => { console.error("Ошибка при добавлении лидера:", error); diff --git a/src/pages/LeaderboardPage/LeaderboardPage.jsx b/src/pages/LeaderboardPage/LeaderboardPage.jsx index 07b59ac72..26d8914e0 100644 --- a/src/pages/LeaderboardPage/LeaderboardPage.jsx +++ b/src/pages/LeaderboardPage/LeaderboardPage.jsx @@ -17,7 +17,12 @@ export function LeaderboardPage() { return (
-

Лидерборд

+
+

Лидерборд

+
+ +
+
@@ -36,9 +41,6 @@ export function LeaderboardPage() { ))}
-
- -
); } diff --git a/src/pages/LeaderboardPage/LeaderboardPage.module.css b/src/pages/LeaderboardPage/LeaderboardPage.module.css index 903377c8c..5a40bbff0 100644 --- a/src/pages/LeaderboardPage/LeaderboardPage.module.css +++ b/src/pages/LeaderboardPage/LeaderboardPage.module.css @@ -63,4 +63,9 @@ .button:hover { background-color: #45a049; } - \ No newline at end of file + + .header{ + display: flex; + justify-content: space-between; + align-items: baseline; + } \ No newline at end of file diff --git a/src/router.js b/src/router.js index 702dad55d..43cfca483 100644 --- a/src/router.js +++ b/src/router.js @@ -13,14 +13,14 @@ export const router = createBrowserRouter( path: "/game/:pairsCount", element: , }, + { + path: "/leaderboard", // Добавлен маршрут для лидерборда + element: , + }, ], /** * basename нужен для корректной работы в gh pages * он же указан в homepage package.json и в index.html */ { basename: "/react-memo" }, - { - path: "/leaderboard", // Добавлен маршрут для лидерборда - element: , - }, ); From ec8c581c4c9156c2344a88a2d6a63a13141114de Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Fri, 20 Sep 2024 02:41:48 +0500 Subject: [PATCH 09/20] =?UTF-8?q?=D1=84=D0=B8=D0=BD=D0=B0=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D0=B0=D1=8F=20=20=D0=B2=D0=B5=D1=80=D1=81=D1=82=D0=BA?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/EndGameModal/EndGameModal.jsx | 20 +++-- .../EndGameModal/EndGameModal.module.css | 32 +++++++- src/pages/LeaderboardPage/LeaderboardPage.jsx | 11 ++- .../LeaderboardPage.module.css | 81 +++++++++---------- src/pages/SelectLevelPage/SelectLevelPage.jsx | 2 +- .../SelectLevelPage.module.css | 9 +++ 6 files changed, 101 insertions(+), 54 deletions(-) diff --git a/src/components/EndGameModal/EndGameModal.jsx b/src/components/EndGameModal/EndGameModal.jsx index c01d2ba0d..900afb3b9 100644 --- a/src/components/EndGameModal/EndGameModal.jsx +++ b/src/components/EndGameModal/EndGameModal.jsx @@ -3,10 +3,11 @@ import styles from "./EndGameModal.module.css"; import { Button } from "../Button/Button"; import deadImageUrl from "./images/dead.png"; import celebrationImageUrl from "./images/celebration.png"; +import { Link } from "react-router-dom"; export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick }) { const [playerName, setPlayerName] = useState(""); - const title = isWon ? "Вы победили!" : "Вы проиграли!"; + const title = isWon ? "Вы попали\nна Лидерборд!" : "Вы проиграли!"; const imgSrc = isWon ? celebrationImageUrl : deadImageUrl; const imgAlt = isWon ? "celebration emodji" : "dead emodji"; @@ -44,23 +45,26 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes,
{imgAlt}

{title}

+ setPlayerName(e.target.value)} + />

Затраченное время:

{gameDurationMinutes.toString().padStart(2, "0")}:{gameDurationSeconds.toString().padStart(2, "0")}
{isWon && ( <> - setPlayerName(e.target.value)} - /> )} {!isWon && } + + Перейти к лидерборду +
); } diff --git a/src/components/EndGameModal/EndGameModal.module.css b/src/components/EndGameModal/EndGameModal.module.css index 9368cb8b5..90defb71a 100644 --- a/src/components/EndGameModal/EndGameModal.module.css +++ b/src/components/EndGameModal/EndGameModal.module.css @@ -1,6 +1,6 @@ .modal { width: 480px; - height: 459px; + height: 570px; border-radius: 12px; background: #c2f5ff; display: flex; @@ -23,7 +23,7 @@ font-style: normal; font-weight: 400; line-height: 48px; - + text-align: center; margin-bottom: 28px; } @@ -49,3 +49,31 @@ margin-bottom: 40px; } +.input { + background-color: #ffffff; /* Светло-голубой фон */ + border: 1px solid #dcdcdc; /* Светлая рамка */ + border-radius: 10px; /* Закругленные углы */ + font-family: 'Arial', sans-serif; + font-size: 18px; + color: #000000; + padding: 7px; + outline: none; /* Убираем стандартную обводку */ + width: 250px; /* Полная ширина */ + text-align: center; /* Выравнивание плейсхолдера по центру */ + margin-bottom: 18px; +} + +.input::placeholder { + color: #9e9e9e; /* Цвет текста плейсхолдера */ + font-weight: normal; /* Обычный вес шрифта для плейсхолдера */ + text-align: center; /* Выравнивание плейсхолдера по центру */ +} +.leaderboardLink{ + padding-top: 18px; + padding-bottom: 8px; + font-family: StratosSkyeng; + font-size: 16px; /* размер шрифта */ + color: #605bc9; /* сиреневый цвет ссылки */ + text-decoration: underline; /* подчеркивание */ + transition: color 0.3s ease; /* плавное изменение цвета при наведении */ +} \ No newline at end of file diff --git a/src/pages/LeaderboardPage/LeaderboardPage.jsx b/src/pages/LeaderboardPage/LeaderboardPage.jsx index 26d8914e0..37b9c716e 100644 --- a/src/pages/LeaderboardPage/LeaderboardPage.jsx +++ b/src/pages/LeaderboardPage/LeaderboardPage.jsx @@ -1,8 +1,10 @@ import { useState, useEffect } from "react"; import styles from "./LeaderboardPage.module.css"; +import { useNavigate } from "react-router-dom"; export function LeaderboardPage() { const [leaders, setLeaders] = useState([]); + const navigate = useNavigate(); useEffect(() => { fetch("https://wedev-api.sky.pro/api/leaderboard") @@ -15,12 +17,19 @@ export function LeaderboardPage() { }); }, []); + // Обработчик нажатия на кнопку + const handleStartGame = () => { + navigate("/"); // Выполняем переход на главную страницу + }; + return (

Лидерборд

- +
diff --git a/src/pages/LeaderboardPage/LeaderboardPage.module.css b/src/pages/LeaderboardPage/LeaderboardPage.module.css index 5a40bbff0..91bba5664 100644 --- a/src/pages/LeaderboardPage/LeaderboardPage.module.css +++ b/src/pages/LeaderboardPage/LeaderboardPage.module.css @@ -3,54 +3,50 @@ color: white; padding: 20px; border-radius: 8px; - } - - .title { +} + +.title { font-size: 24px; - font-weight: bold; + font-weight: 100; margin-bottom: 20px; - } - - .table { + font-family: StratosSkyeng; +} + +.table { width: 100%; - border-collapse: collapse; - } - - .table th, - .table td { + border-spacing: 0 10px; +} + +.table th, +.table td { text-align: left; padding: 10px 15px; background-color: white; color: #043864; - border-radius: 8px; - margin: 5px 0; - } - - .table th { + font-family: StratosSkyeng; +} + +.table th { font-size: 16px; font-weight: bold; background-color: #d8eaff; color: #043864; - } - - .table tbody tr:nth-child(odd) { - background-color: #f5faff; - } - - .table tbody tr:nth-child(even) { - background-color: #e1f2ff; - } - - .table tbody tr:hover { - background-color: #c0e1ff; - } - - .buttonContainer { + +} + +.table tbody tr { + background-color: white; + border-radius: 16px; + box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1); + /* Добавляем тень */ +} + +.buttonContainer { text-align: right; margin-top: 20px; - } - - .button { +} + +.button { background-color: #4caf50; color: white; padding: 10px 20px; @@ -58,14 +54,15 @@ border-radius: 5px; cursor: pointer; font-size: 16px; - } - - .button:hover { + font-family: StratosSkyeng; +} + +.button:hover { background-color: #45a049; - } - - .header{ +} + +.header { display: flex; justify-content: space-between; align-items: baseline; - } \ No newline at end of file +} \ No newline at end of file diff --git a/src/pages/SelectLevelPage/SelectLevelPage.jsx b/src/pages/SelectLevelPage/SelectLevelPage.jsx index 880c98e22..240f3e07b 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.jsx +++ b/src/pages/SelectLevelPage/SelectLevelPage.jsx @@ -36,7 +36,7 @@ export function SelectLevelPage() { - Лидерборд + Перейти к лидерборду diff --git a/src/pages/SelectLevelPage/SelectLevelPage.module.css b/src/pages/SelectLevelPage/SelectLevelPage.module.css index 650261dc7..9348d81ba 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.module.css +++ b/src/pages/SelectLevelPage/SelectLevelPage.module.css @@ -101,3 +101,12 @@ .levelLink:visited { color: #0080c1; } +.leaderboardLink{ + padding-top: 18px; + padding-bottom: 8px; + font-family: StratosSkyeng; + font-size: 16px; /* размер шрифта */ + color: #004980; /* сиреневый цвет ссылки */ + text-decoration: underline; /* подчеркивание */ + transition: color 0.3s ease; /* плавное изменение цвета при наведении */ +} From 4e0bd7857b54a20c6180e2121b2efb3dd443e504 Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Fri, 20 Sep 2024 02:56:18 +0500 Subject: [PATCH 10/20] =?UTF-8?q?=D1=84=D0=B8=D1=87=D0=B0:=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BA=D0=BD=D0=BE?= =?UTF-8?q?=D0=BF=D0=BA=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D1=8B=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=D0=B0=20=D0=B2=20=D0=BE=D0=BA=D0=BD=D0=BE=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B1=D0=BE=D1=80=D0=B0=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC?= =?UTF-8?q?=D0=B0=20=D0=B2=20=D0=BE=D0=BA=D0=BD=D0=B5=20game=20(GamePage)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Cards/Cards.jsx | 11 +++++++++-- src/components/Cards/Cards.module.css | 23 +++++++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index 604d1df23..3eefa3cc2 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -2,6 +2,7 @@ import { shuffle } from "lodash"; import { useEffect, useState, useContext } from "react"; +import { useNavigate } from "react-router-dom"; import { generateDeck } from "../../utils/cards"; import styles from "./Cards.module.css"; import { EndGameModal } from "../../components/EndGameModal/EndGameModal"; @@ -172,7 +173,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { }; const isGameEnded = status === STATUS_LOST || status === STATUS_WON; - + const navigate = useNavigate(); // Инициализация игры useEffect(() => { if (status !== STATUS_PREVIEW) { @@ -207,6 +208,10 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { }; }, [gameStartDate, gameEndDate]); + const handleStartGame = () => { + navigate("/"); // Выполняем переход на главную страницу + }; + return (
@@ -253,7 +258,9 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { /> ))}
- + {isGameEnded ? (
Date: Fri, 20 Sep 2024 02:58:12 +0500 Subject: [PATCH 11/20] =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=84=D0=B0=D0=B9=D0=BB=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dfb7cb167..72a64d3b5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -## Оценка времени выполнения работы "Вводный урок" +## Оценка времени выполнения работы "Внедрение лидерборда" - **Ожидаемое время:** 6 часов -- **Фактическое время:** 6 часов +- **Фактическое время:** 4 часa # MVP Карточная игра "Мемо" From 31f61d9230c001ed9bd3af32a74837400ce3b23a Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Fri, 20 Sep 2024 03:09:09 +0500 Subject: [PATCH 12/20] =?UTF-8?q?=D1=81=D0=BE=D1=80=D1=82=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=BB=D0=B8=D0=B4=D0=B5=D1=80=D0=B1?= =?UTF-8?q?=D0=BE=D1=80=D0=B4=D0=B0=20=D0=BF=D0=BE=20=D0=B2=D1=80=D0=B5?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/LeaderboardPage/LeaderboardPage.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/LeaderboardPage/LeaderboardPage.jsx b/src/pages/LeaderboardPage/LeaderboardPage.jsx index 37b9c716e..7565c22da 100644 --- a/src/pages/LeaderboardPage/LeaderboardPage.jsx +++ b/src/pages/LeaderboardPage/LeaderboardPage.jsx @@ -10,7 +10,9 @@ export function LeaderboardPage() { fetch("https://wedev-api.sky.pro/api/leaderboard") .then(response => response.json()) .then(data => { - setLeaders(data.leaders); + // Сортируем лидеров по времени (чем меньше время, тем выше позиция) + const sortedLeaders = data.leaders.sort((a, b) => a.time - b.time); + setLeaders(sortedLeaders); }) .catch(error => { console.error("Ошибка при получении списка лидеров:", error); From ea26884bcc506be2738856335e2d0d5f3221dba7 Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Fri, 20 Sep 2024 03:31:57 +0500 Subject: [PATCH 13/20] =?UTF-8?q?=D0=B2=20=D0=BB=D0=B8=D0=B4=D0=B5=D1=80?= =?UTF-8?q?=D0=B1=D0=B1=D0=BE=D1=80=D0=B4=20=D0=BF=D1=80=D0=B8=D1=85=D1=8F?= =?UTF-8?q?=D0=B4=20=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B7=D1=83=D0=BB=D1=8C=D1=82=D0=B0=D1=82=D1=8B=20=D1=82=D1=80?= =?UTF-8?q?=D0=B5=D1=82=D1=8C=D0=B5=D0=B3=D0=BE=20=D1=83=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=BD=D1=8F=20=D1=81=D0=BB=D0=BE=D0=B6=D0=BD=D0=BE=D1=81=D1=8C?= =?UTF-8?q?=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/pages/GamePage/GamePage.jsx | 35 ++++++++++++++++++- src/pages/LeaderboardPage/LeaderboardPage.jsx | 14 ++++++-- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 72a64d3b5..44e31d627 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## Оценка времени выполнения работы "Внедрение лидерборда" - **Ожидаемое время:** 6 часов -- **Фактическое время:** 4 часa +- **Фактическое время:** 5 часов # MVP Карточная игра "Мемо" diff --git a/src/pages/GamePage/GamePage.jsx b/src/pages/GamePage/GamePage.jsx index a4be871db..61f7b102e 100644 --- a/src/pages/GamePage/GamePage.jsx +++ b/src/pages/GamePage/GamePage.jsx @@ -1,10 +1,43 @@ import { useParams } from "react-router-dom"; - import { Cards } from "../../components/Cards/Cards"; +import { useEffect } from "react"; export function GamePage() { const { pairsCount } = useParams(); + // Пример функции для отправки результата в API + const sendResultToLeaderboard = (name, time) => { + const result = { + name, + time, + level: pairsCount / 3, // предположим, что level = количество пар / 3 + }; + + fetch("https://wedev-api.sky.pro/api/leaderboard", { + method: "POST", + body: JSON.stringify(result), // JSON всё ещё отправляется, но без указания заголовка + }) + .then(response => response.json()) + .then(data => { + console.log("Результат успешно добавлен:", data); + }) + .catch(error => { + console.error("Ошибка при добавлении результата:", error); + }); + }; + + // Пример эффекта для отправки данных при завершении игры + useEffect(() => { + // Это может быть любая логика завершения игры + const gameEnded = true; // Предположим, что игра завершена + const playerName = "Игрок"; // Можно получить откуда-то имя игрока + const gameTime = 120; // Время игры в секундах + + if (gameEnded) { + sendResultToLeaderboard(playerName, gameTime); + } + }, [pairsCount]); // Этот эффект будет срабатывать при изменении количества пар + return ( <> diff --git a/src/pages/LeaderboardPage/LeaderboardPage.jsx b/src/pages/LeaderboardPage/LeaderboardPage.jsx index 7565c22da..5cc84a341 100644 --- a/src/pages/LeaderboardPage/LeaderboardPage.jsx +++ b/src/pages/LeaderboardPage/LeaderboardPage.jsx @@ -10,9 +10,19 @@ export function LeaderboardPage() { fetch("https://wedev-api.sky.pro/api/leaderboard") .then(response => response.json()) .then(data => { + // Поскольку API не поддерживает level, фильтруем по времени или другому признаку + const thresholdForHardLevel = 30; // Порог для сложного уровня (время в секундах) + + // Фильтрация по порогу времени + const filteredLeaders = data.leaders.filter(leader => leader.time >= thresholdForHardLevel); + // Сортируем лидеров по времени (чем меньше время, тем выше позиция) - const sortedLeaders = data.leaders.sort((a, b) => a.time - b.time); - setLeaders(sortedLeaders); + const sortedLeaders = filteredLeaders.sort((a, b) => a.time - b.time); + + // Ограничиваем список до топ 10 игроков + const topLeaders = sortedLeaders.slice(0, 10); + + setLeaders(topLeaders); }) .catch(error => { console.error("Ошибка при получении списка лидеров:", error); From eae33c109e66d186d610e675cfc44667c241e74e Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Mon, 23 Sep 2024 04:16:44 +0500 Subject: [PATCH 14/20] =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=81=D1=83=D0=BF=D0=B5=D1=80-=D1=81=D0=B8=D0=BB=D1=8B=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5=D1=82=20=D0=B1=D0=B5?= =?UTF-8?q?=D0=B7=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Cards/Cards.jsx | 148 +++++++++++++------------------- src/pages/GamePage/GamePage.jsx | 40 ++++++++- 2 files changed, 98 insertions(+), 90 deletions(-) diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index 3eefa3cc2..bccce5cbf 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -1,5 +1,3 @@ -// src/components/Cards/Cards.jsx - import { shuffle } from "lodash"; import { useEffect, useState, useContext } from "react"; import { useNavigate } from "react-router-dom"; @@ -16,54 +14,31 @@ const STATUS_WON = "STATUS_WON"; const STATUS_IN_PROGRESS = "STATUS_IN_PROGRESS"; const STATUS_PREVIEW = "STATUS_PREVIEW"; -function getTimerValue(startDate, endDate) { - if (!startDate && !endDate) { - return { - minutes: 0, - seconds: 0, - }; - } - - if (endDate === null) { - endDate = new Date(); - } - - const diffInSeconds = Math.floor((endDate.getTime() - startDate.getTime()) / 1000); - const minutes = Math.floor(diffInSeconds / 60); - const seconds = diffInSeconds % 60; +function getTimerValue(secondsElapsed) { + const minutes = Math.floor(secondsElapsed / 60); + const seconds = secondsElapsed % 60; return { minutes, seconds, }; } -/** - * Основной компонент игры, внутри него находится вся игровая механика и логика. - * pairsCount - сколько пар будет в игре - * previewSeconds - сколько секунд пользователь будет видеть все карты открытыми до начала игры - */ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { - const { livesMode } = useContext(GameModeContext); // Используем контекст + const { livesMode } = useContext(GameModeContext); // Состояние для игровых карт const [cards, setCards] = useState([]); - // Текущий статус игры + const [initialCardsState, setInitialCardsState] = useState([]); const [status, setStatus] = useState(STATUS_PREVIEW); - // Дата начала и окончания игры - const [gameStartDate, setGameStartDate] = useState(null); - const [gameEndDate, setGameEndDate] = useState(null); - // Состояние таймера - const [timer, setTimer] = useState({ - seconds: 0, - minutes: 0, - }); - // Количество оставшихся жизней + const [secondsElapsed, setSecondsElapsed] = useState(0); + const [timer, setTimer] = useState({ minutes: 0, seconds: 0 }); + const [intervalId, setIntervalId] = useState(null); + const [isProcessing, setIsProcessing] = useState(false); + const [isSuperPowerUsed, setIsSuperPowerUsed] = useState(false); + const initialLives = livesMode ? 3 : 1; const [lives, setLives] = useState(initialLives); - // Выбранные в данный момент карты const [selectedCards, setSelectedCards] = useState([]); - // Флаг для блокировки кликов во время проверки пар - const [isProcessing, setIsProcessing] = useState(false); // Обновляем количество жизней при изменении режима игры useEffect(() => { @@ -72,109 +47,117 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { // Функция для завершения игры function finishGame(gameStatus = STATUS_LOST) { - setGameEndDate(new Date()); setStatus(gameStatus); + clearInterval(intervalId); } // Функция для старта игры function startGame() { - const startDate = new Date(); - setGameEndDate(null); - setGameStartDate(startDate); - setTimer(getTimerValue(startDate, null)); setStatus(STATUS_IN_PROGRESS); - setLives(initialLives); + setSecondsElapsed(0); setSelectedCards([]); setIsProcessing(false); + setIsSuperPowerUsed(false); + setTimer(getTimerValue(0)); + + const newIntervalId = setInterval(() => { + setSecondsElapsed(prev => prev + 1); + }, 1000); + + setIntervalId(newIntervalId); } + useEffect(() => { + if (status === STATUS_IN_PROGRESS) { + setTimer(getTimerValue(secondsElapsed)); + } + }, [secondsElapsed, status]); + // Функция для перезапуска игры function resetGame() { - setGameStartDate(null); - setGameEndDate(null); - setTimer(getTimerValue(null, null)); setStatus(STATUS_PREVIEW); - setLives(initialLives); + setSecondsElapsed(0); + setTimer({ minutes: 0, seconds: 0 }); setSelectedCards([]); setIsProcessing(false); + setIsSuperPowerUsed(false); + clearInterval(intervalId); } - /** - * Обработка открытия карты - */ const openCard = clickedCard => { if (isProcessing || clickedCard.open) { return; } - // Открываем кликнутую карту const nextCards = cards.map(card => { if (card.id !== clickedCard.id) { return card; } - - return { - ...card, - open: true, - }; + return { ...card, open: true }; }); setCards(nextCards); - - // Добавляем карту в выбранные const nextSelectedCards = [...selectedCards, clickedCard]; setSelectedCards(nextSelectedCards); if (nextSelectedCards.length === 2) { setIsProcessing(true); const [firstCard, secondCard] = nextSelectedCards; - const isMatch = firstCard.rank === secondCard.rank && firstCard.suit === secondCard.suit; if (isMatch) { - // Карты совпали setSelectedCards([]); - - // Проверяем, выиграл ли игрок const isPlayerWon = nextCards.every(card => card.open); if (isPlayerWon) { finishGame(STATUS_WON); } setIsProcessing(false); } else { - // Карты не совпали const nextLives = lives - 1; setLives(nextLives); - if (nextLives === 0) { - // Жизни закончились, игрок проиграл finishGame(STATUS_LOST); - setIsProcessing(false); } else { - // Закрываем карты обратно после задержки setTimeout(() => { setCards(currentCards => currentCards.map(card => { if (card.id === firstCard.id || card.id === secondCard.id) { - return { - ...card, - open: false, - }; + return { ...card, open: false }; } return card; }), ); setSelectedCards([]); setIsProcessing(false); - }, 1000); // Задержка в 1 секунду + }, 1000); } } } }; + const handleSuperPower = () => { + if (!isSuperPowerUsed) { + setIsSuperPowerUsed(true); + setInitialCardsState(cards); + + const openedCards = cards.map(card => ({ ...card, open: true })); + setCards(openedCards); + + clearInterval(intervalId); + + setTimeout(() => { + setCards(initialCardsState); + const newIntervalId = setInterval(() => { + setSecondsElapsed(prev => prev + 1); + }, 1000); + setIntervalId(newIntervalId); + }, 5000); + } + }; + const isGameEnded = status === STATUS_LOST || status === STATUS_WON; const navigate = useNavigate(); - // Инициализация игры + useEffect(() => { if (status !== STATUS_PREVIEW) { return; @@ -185,9 +168,9 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { return; } - setCards(() => { - return shuffle(generateDeck(pairsCount)); - }); + const deck = shuffle(generateDeck(pairsCount)); + setCards(deck); + setInitialCardsState(deck); const timerId = setTimeout(() => { startGame(); @@ -198,16 +181,6 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { }; }, [status, pairsCount, previewSeconds]); - // Обновление таймера - useEffect(() => { - const intervalId = setInterval(() => { - setTimer(getTimerValue(gameStartDate, gameEndDate)); - }, 300); - return () => { - clearInterval(intervalId); - }; - }, [gameStartDate, gameEndDate]); - const handleStartGame = () => { navigate("/"); // Выполняем переход на главную страницу }; @@ -243,6 +216,9 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
)} + ) : null}
@@ -252,7 +228,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { openCard(card)} - open={status !== STATUS_IN_PROGRESS ? true : card.open} + open={status !== STATUS_IN_PROGRESS || card.open} suit={card.suit} rank={card.rank} /> diff --git a/src/pages/GamePage/GamePage.jsx b/src/pages/GamePage/GamePage.jsx index 61f7b102e..b77f1d5e3 100644 --- a/src/pages/GamePage/GamePage.jsx +++ b/src/pages/GamePage/GamePage.jsx @@ -1,11 +1,12 @@ import { useParams } from "react-router-dom"; import { Cards } from "../../components/Cards/Cards"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; // Добавляем useState для супер-силы export function GamePage() { const { pairsCount } = useParams(); + const [isSuperPowerUsed, setIsSuperPowerUsed] = useState(false); // Добавляем состояние для отслеживания использования супер-силы - // Пример функции для отправки результата в API + // Функция для отправки результата в API const sendResultToLeaderboard = (name, time) => { const result = { name, @@ -15,7 +16,10 @@ export function GamePage() { fetch("https://wedev-api.sky.pro/api/leaderboard", { method: "POST", - body: JSON.stringify(result), // JSON всё ещё отправляется, но без указания заголовка + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(result), // JSON всё ещё отправляется, но теперь с заголовком }) .then(response => response.json()) .then(data => { @@ -38,9 +42,37 @@ export function GamePage() { } }, [pairsCount]); // Этот эффект будет срабатывать при изменении количества пар + // Функция для активации супер-силы + const handleSuperPowerClick = () => { + if (!isSuperPowerUsed) { + setIsSuperPowerUsed(true); // Ставим флаг, что супер-сила была использована + } + }; + return ( <> - +
+
+ + ); } From 34334d8c79e286b6df861b0320ae33e361db38c6 Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Mon, 23 Sep 2024 06:05:06 +0500 Subject: [PATCH 15/20] =?UTF-8?q?=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D0=B0=20?= =?UTF-8?q?=D0=B2=D1=91=D1=80=D1=81=D1=82=D0=BA=D0=B0=20=D1=81=D1=83=D0=BF?= =?UTF-8?q?=D0=B5=D1=80-=D1=81=D0=B8=D0=BB=D1=8B=20=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D0=B5=20cards.jsx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Cards/Cards.jsx | 13 +++- src/components/Cards/Cards.module.css | 63 ++++++++++++++++++++ src/components/EndGameModal/images/power.svg | 34 +++++++++++ src/pages/GamePage/GamePage.jsx | 38 ++---------- 4 files changed, 111 insertions(+), 37 deletions(-) create mode 100644 src/components/EndGameModal/images/power.svg diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index bccce5cbf..e3eaf4eb5 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -7,6 +7,7 @@ import { EndGameModal } from "../../components/EndGameModal/EndGameModal"; import { Button } from "../../components/Button/Button"; import { Card } from "../../components/Card/Card"; import { GameModeContext } from "../../context/GameModeContext"; +import { ReactComponent as PowerIcon } from "../../components/EndGameModal/images/power.svg"; // Константы статусов игры const STATUS_LOST = "STATUS_LOST"; @@ -216,9 +217,15 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { )} - +
+ +
+
Прозрение
+

На 5 секунд показываются все карты. Таймер длительности игры на это время останавливается.

+
+
) : null} diff --git a/src/components/Cards/Cards.module.css b/src/components/Cards/Cards.module.css index 71e8e3847..cbd7dcf09 100644 --- a/src/components/Cards/Cards.module.css +++ b/src/components/Cards/Cards.module.css @@ -105,4 +105,67 @@ .buttonEsc:hover { background-color: #45a049; +} + +.superPowerButton { + background: none; + border: none; + cursor: pointer; + padding: 0; +} + +.superPowerButton:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.superPowerContainer { + position: relative; + display: inline-block; +} + +.tooltip { + display: none; + /* Скрываем по умолчанию */ + position: absolute; + width: 161px; + height: 195px; + background: #C2F5FF; + /* Задний фон окна */ + color: #004980; + /* Цвет текста */ + border-radius: 12px; + font-family: 'Inter', sans-serif; + font-size: 18px; + font-weight: 400; + line-height: 24px; + text-align: center; + padding: 10px; + /* Немного отступов для текста */ + z-index: 10; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); + /* Тень для эффекта */ + top: 100%; + /* Ставим под кнопку */ + right: -80%; + /* Выравнивание по горизонтали */ +} + +.superPowerContainer:hover .tooltip { + display: block; + /* Показываем при наведении */ +} + + /* Цвет слова "Прозрение" */ +.superPowerContainer:hover .tooltip .title { + height: 24px; + color: #004980; + font-weight: bold; + text-align: center; +} + +/* Определим стиль для "Прозрение" */ +.title { + font-size: 18px; + padding-bottom: 18px; } \ No newline at end of file diff --git a/src/components/EndGameModal/images/power.svg b/src/components/EndGameModal/images/power.svg new file mode 100644 index 000000000..d88a5843e --- /dev/null +++ b/src/components/EndGameModal/images/power.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/GamePage/GamePage.jsx b/src/pages/GamePage/GamePage.jsx index b77f1d5e3..5c18e1116 100644 --- a/src/pages/GamePage/GamePage.jsx +++ b/src/pages/GamePage/GamePage.jsx @@ -1,10 +1,9 @@ import { useParams } from "react-router-dom"; import { Cards } from "../../components/Cards/Cards"; -import { useEffect, useState } from "react"; // Добавляем useState для супер-силы +import { useEffect } from "react"; export function GamePage() { const { pairsCount } = useParams(); - const [isSuperPowerUsed, setIsSuperPowerUsed] = useState(false); // Добавляем состояние для отслеживания использования супер-силы // Функция для отправки результата в API const sendResultToLeaderboard = (name, time) => { @@ -19,7 +18,7 @@ export function GamePage() { headers: { "Content-Type": "application/json", }, - body: JSON.stringify(result), // JSON всё ещё отправляется, но теперь с заголовком + body: JSON.stringify(result), }) .then(response => response.json()) .then(data => { @@ -32,7 +31,6 @@ export function GamePage() { // Пример эффекта для отправки данных при завершении игры useEffect(() => { - // Это может быть любая логика завершения игры const gameEnded = true; // Предположим, что игра завершена const playerName = "Игрок"; // Можно получить откуда-то имя игрока const gameTime = 120; // Время игры в секундах @@ -40,39 +38,11 @@ export function GamePage() { if (gameEnded) { sendResultToLeaderboard(playerName, gameTime); } - }, [pairsCount]); // Этот эффект будет срабатывать при изменении количества пар - - // Функция для активации супер-силы - const handleSuperPowerClick = () => { - if (!isSuperPowerUsed) { - setIsSuperPowerUsed(true); // Ставим флаг, что супер-сила была использована - } - }; + }, [pairsCount]); return ( <> -
-
- - + ); } From 7f6b2aa8d3462ef41bd12bc7f2c94373bb2ff1de Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Tue, 24 Sep 2024 03:02:56 +0500 Subject: [PATCH 16/20] =?UTF-8?q?=D0=B1=D0=B0=D0=B3=D0=BE=D1=84=D0=B8?= =?UTF-8?q?=D0=BA=D1=81:=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE=D0=B2?= =?UTF-8?q?=D0=BE=D0=BA=20'Content-Type'=20=D0=B2=20cardpage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++--- src/components/Cards/Cards.module.css | 10 ---------- src/pages/GamePage/GamePage.jsx | 4 +--- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 44e31d627..390683abb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -## Оценка времени выполнения работы "Внедрение лидерборда" +## Оценка времени выполнения работы "Курсовая работа 4" -- **Ожидаемое время:** 6 часов -- **Фактическое время:** 5 часов +- **Ожидаемое время:** 12 часов +- **Фактическое время:** n часов # MVP Карточная игра "Мемо" diff --git a/src/components/Cards/Cards.module.css b/src/components/Cards/Cards.module.css index cbd7dcf09..37ef75264 100644 --- a/src/components/Cards/Cards.module.css +++ b/src/components/Cards/Cards.module.css @@ -126,14 +126,11 @@ .tooltip { display: none; - /* Скрываем по умолчанию */ position: absolute; width: 161px; height: 195px; background: #C2F5FF; - /* Задний фон окна */ color: #004980; - /* Цвет текста */ border-radius: 12px; font-family: 'Inter', sans-serif; font-size: 18px; @@ -141,22 +138,16 @@ line-height: 24px; text-align: center; padding: 10px; - /* Немного отступов для текста */ z-index: 10; box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); - /* Тень для эффекта */ top: 100%; - /* Ставим под кнопку */ right: -80%; - /* Выравнивание по горизонтали */ } .superPowerContainer:hover .tooltip { display: block; - /* Показываем при наведении */ } - /* Цвет слова "Прозрение" */ .superPowerContainer:hover .tooltip .title { height: 24px; color: #004980; @@ -164,7 +155,6 @@ text-align: center; } -/* Определим стиль для "Прозрение" */ .title { font-size: 18px; padding-bottom: 18px; diff --git a/src/pages/GamePage/GamePage.jsx b/src/pages/GamePage/GamePage.jsx index 5c18e1116..d882c0334 100644 --- a/src/pages/GamePage/GamePage.jsx +++ b/src/pages/GamePage/GamePage.jsx @@ -13,11 +13,9 @@ export function GamePage() { level: pairsCount / 3, // предположим, что level = количество пар / 3 }; + // Убираем заголовок "Content-Type" fetch("https://wedev-api.sky.pro/api/leaderboard", { method: "POST", - headers: { - "Content-Type": "application/json", - }, body: JSON.stringify(result), }) .then(response => response.json()) From 7b9e53915014fde1f30048ad7282b528ea7132ed Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Tue, 24 Sep 2024 04:37:43 +0500 Subject: [PATCH 17/20] =?UTF-8?q?=D0=B2=20=D0=BB=D0=B8=D0=B4=D0=B5=D1=80?= =?UTF-8?q?=D0=B1=D0=BE=D1=80=D0=B4=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D1=81=D1=83=D0=BF=D0=B5=D1=80=D1=81=D0=B8?= =?UTF-8?q?=D0=BB=D0=B0=20=D0=B8=20hardmode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Cards/Cards.jsx | 1 + src/components/EndGameModal/EndGameModal.jsx | 28 +++++++---- .../EndGameModal/images/hardlvloff.png | Bin 0 -> 1070 bytes .../EndGameModal/images/hardlvlon.png | Bin 0 -> 1340 bytes .../EndGameModal/images/superpoweroff.png | Bin 0 -> 891 bytes .../EndGameModal/images/superpoweron.png | Bin 0 -> 1318 bytes src/context/GameModeContext.js | 11 ++++- src/pages/GamePage/GamePage.jsx | 45 ++++++++++++++---- src/pages/LeaderboardPage/LeaderboardPage.jsx | 39 ++++++++------- .../LeaderboardPage.module.css | 13 ++++- 10 files changed, 100 insertions(+), 37 deletions(-) create mode 100644 src/components/EndGameModal/images/hardlvloff.png create mode 100644 src/components/EndGameModal/images/hardlvlon.png create mode 100644 src/components/EndGameModal/images/superpoweroff.png create mode 100644 src/components/EndGameModal/images/superpoweron.png diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index e3eaf4eb5..756f23315 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -251,6 +251,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { gameDurationSeconds={timer.seconds} gameDurationMinutes={timer.minutes} onClick={resetGame} + isSuperPowerUsed={isSuperPowerUsed} // Передаем состояние использования суперсилы /> ) : null} diff --git a/src/components/EndGameModal/EndGameModal.jsx b/src/components/EndGameModal/EndGameModal.jsx index 900afb3b9..9da5a48f4 100644 --- a/src/components/EndGameModal/EndGameModal.jsx +++ b/src/components/EndGameModal/EndGameModal.jsx @@ -1,12 +1,15 @@ -import { useState } from "react"; +import { useState, useContext } from "react"; import styles from "./EndGameModal.module.css"; import { Button } from "../Button/Button"; import deadImageUrl from "./images/dead.png"; import celebrationImageUrl from "./images/celebration.png"; import { Link } from "react-router-dom"; +import { GameModeContext } from "../../context/GameModeContext"; // Импортируем контекст -export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick }) { +export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick, isSuperPowerUsed }) { const [playerName, setPlayerName] = useState(""); + const { livesMode } = useContext(GameModeContext); // Получаем данные из контекста + const title = isWon ? "Вы попали\nна Лидерборд!" : "Вы проиграли!"; const imgSrc = isWon ? celebrationImageUrl : deadImageUrl; const imgAlt = isWon ? "celebration emodji" : "dead emodji"; @@ -15,16 +18,21 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, const name = playerName || "Пользователь"; const totalTime = gameDurationMinutes * 60 + gameDurationSeconds; - console.log("Отправляем данные:", { name, time: totalTime }); + // Определяем достижения + const achievements = []; + if (!livesMode) achievements.push(1); // Сложный уровень (без трех жизней) + if (!isSuperPowerUsed) achievements.push(2); // Не использовалась суперсила + + console.log("Отправляем данные:", { name, time: totalTime, achievements }); - // Отправляем данные в формате JSON - fetch("https://wedev-api.sky.pro/api/leaderboard", { + // Отправляем данные в формате JSON без заголовка Content-Type + fetch("https://wedev-api.sky.pro/api/v2/leaderboard", { method: "POST", - headers: { - // Убираем Content-Type, так как API требует это убрать - // Не указываем заголовок Content-Type - }, - body: JSON.stringify({ name, time: totalTime }), // Преобразуем данные в JSON + body: JSON.stringify({ + name, + time: totalTime, + achievements, // Передаем массив достижений + }), }) .then(response => { if (!response.ok) { diff --git a/src/components/EndGameModal/images/hardlvloff.png b/src/components/EndGameModal/images/hardlvloff.png new file mode 100644 index 0000000000000000000000000000000000000000..ab6ebf3d1d43281f4ccecbc76cab414e9c4d5349 GIT binary patch literal 1070 zcmV+}1kwA6P)yp#K6CD%QHJpt4@7y0l)q%zx^w29-@WI<2jIKDMG->U-Cpm_Q$ijrvVHP| z)X=^(?>PgvRF@D#mX#e*l3+X@f%9={v|5e5wB6AWX$tqa$1KN{APm6^g6-cMzioUG zK$jeMWJTW8bscz~2iJ9>-|r*Dcbt}0io7Xux zIWdyXsRTiQVzCGXN!%rgaEPMVR5h)J-;ph~lVur1Su)c5<_1s|t&wyd>obIm5XTXO zD9a7Ho)pJ%AW4#$-Zys$E-x=j6W6`ZvFw8on0ER(4aWe(F?F1My!xh&H^4AV%EmWV zmaBVr0!ZrZ`S~M;<^D~t`@RR$sT18w@&?d}6$H@vxmx)z{mxu~qb@O67Q#PTt-VZ$ z_ZI{Y*o2#90>|E8DwmJv8D!=F&2~kRcd*t?gKz?^`!H&jg^(RZZP2 zm&;Ldsk-`C+UE56^KF)8Yp@8wb(_bh&pJ2RFlpzSu5GHS8f7i5U)#2=p>^3pnNdgM zg$^dzKZ>G2I?2)sF!1(p2=;ii)mmHYEChJ@`Zdusy-A(Na$N^3t8Zd*^>~hLV||zM zIFIG~J~kj5>_LAE57D_1;Oy+I#t4PSxL8ZB(-NbfxT)lIo`e_<2DEi=v>J`=xd1T) zix7~4_)<}b0YZGVhwFF~hw=JM5-u+d@l0gm6~3S2u+ z#?yMAjO>G%b&okf|4l#e4ziqIfu+fP#(l`RbTqbThHSQ=4PgiDo%kqHf@?GZ_$c1jEo-2(Slt z1&{=uI@eDh`taC~x=#$uV%m*K&iLg-wp&h85ie~Sk>tB#m@Kr0m%F_CvIEAjv zwbJQQ!NFu~Nrq2?^oux9y1FPVP4fNTb3FInv}S42wG{?~9k{vooA3Fa&*%KQ_X0eS z`%xClgPFzZ;L1XE@a-Mwrsm4YWWVn)WZOKU|X z0NN)oB0N3%?4Og}dJ1+S=c|JS18&v^-5Nlg>5fFHoJwk?=R_-Mz>Q|a_};ndf&88c zEM6DS0X7X7?>C4jgkmR&1~k$#rOcWHokW#bVMvhMGXZ2A`tOO^q@a_GJZ_Tkl+|T& z1GM~vqODm4-FAK0p(H3VC7xB$hqst8YhgPH3I+MAxN!ZEv4}WLma`#%9E$f^-fH5R zEsnt+WTE`%DNy`j$M=>D*`h?l21p*X1lRye{{JR|!W&0^|9Hm)KD+vO&S&J;kNh^< z>-{&C17!wI+in1t2D31^(TwffLm<=G#Niv&emFgA;FF)8EHueG-2x~UuN=(u(g^{R z5?W{pSR}Js%OE$JGSpIxg*vJHZLLsdsc3(sTj%quPfRzoN(neOMX%ui zxyc@wbwmDNm$eWW`beDFj=&u$c&GGCVZhh<1QHA=hPccz#Wf^7Jo$VO6s5mI4F!++ zT~l+=ZbbrD1g_moM5N+n9m?89LgUm2FZ@!tD}gsJ4wVOx(hn&^fD8eSOD+3~1BG0X z^@wja7j{M8DdYxg3vSYYXd-0NVn|3t-~TlGb75No6N~ATjzcK{oH7IkJRtytG12lR z8lKd+3<<8lBe64Jyk@&I7_Vh;B;ZmbZ0e&$9@>;hs@5Yld2aODbYB8SMukMoQ>C~9 zOCn~YU`Vi#kWB{6&L?oT*<9#t_-)lI8~(@u+k%u7aJwcX3U%_a9{JhI%ResnC1518 z^jumTznD<8KOw-51fVCg5J0a@0L*Ua4kX*}X1g`uf*VM2rke6fx(OY);<~KsT2*e0 z{V=?N)w2zkk6o$MSRK9@`N3-B^UWxRvW7L8*R;=TQ5({9jN6@xEPf&`&f?(!_VuuuIuu z5WhZpP4=GQUdrO>Epy93*MVTW-`;rKEY@faumjoJ1qA!K>#TR^Jj1Un zJ*P&AI2|KXLITemTY)b!DR?0Fqx=inGNoD^zy~V;00001BCpEP)K~#7F?N&`r z+dve4Gvj|l;%q9Yx^OrEi6u5&k)EI&A)EkiKzah;1i%SOPe9lrRStrRRfQEwOQA@v zK>2YzGo48Vsv?^>sik(oCs~$e-p}{m%)Ivip7D=?CK2|1--#Kl6GD~|0+$20!wt?` zob!MK-(T{)y$MWaBOo0ERvJVhYr3w3rfHxk3Lx}bol**M97ELaL%$ydF{O2o#DwDKm+S*&mev-7O zL0(#2(`x?dX%(^(P&Be4pIKgzcgWPtWynfEB}z4q=pUVkqfk|nJp-5!2XcfAhNRfU zX$pB00F?aT(g*~QGjNK{WLgL{AxFp(jR|T&Rsw{xeW{cufkCf#2w4eqyZWI}da{X3 zNMW~JE(eg6K)GBBIpgmjAA~(hQ*EX8f|qkIwix3R4Hrq^3>xM6`5t` zSy*s)BgPJZLlvg-4vnv0+ItM}S_EwSwdVz+B8m0ZvdkS>y@HF2inqAvUEkhrihHyU z86lzy#YoWjc6t4Q==y=B>24fTS%_9gWfPA;^l@+;NBoQR>Apfp%e}Det>S}i&F4L>IxmB@P zLx@xm;=}UFISLg8H*do5_&y9>(MxtLd!{x!I|~o<1V$t~fyanM36*VG8W96$z0>X1 zzu(*jl*Xc{pcZB9m<)4>D?+$V2(Haowqpn#*#Tt`d002ovPDHLkV1gENgBJh* literal 0 HcmV?d00001 diff --git a/src/components/EndGameModal/images/superpoweron.png b/src/components/EndGameModal/images/superpoweron.png new file mode 100644 index 0000000000000000000000000000000000000000..b6a94ab90e3e08648aba446cdc3467745a059ff7 GIT binary patch literal 1318 zcmV+>1=;$EP)^ftbG`ZTl^SX1s?|kQRhv>z4&Vw(qxO*Ti z>eD)hbd!V<+)DhT4(1^Tw6pLQ|40`YwFp??0c~LMXVrVmBqHPEiNS!nIBeD>@=OBV z-(?9PKYNcShj=1jpeJVtG3`i7cdsmS?|&2M{vin)I>4&SB&XMUP-nz}p4gjEr6U+IB27!W>icGl7;B`h+ zWQI+y@~aZ!!(cNH2jMUT=Kyp9`~Vg<0GeYBj;dg!Bw|{kx*^C@X{qP=$dVwPs)iDg z(yHLXZ*Z;7D>gj03czBR8xCV7YdG?%fkdk|fnuans7N73N?fEurFvAb%Yz(wAudZ? zH>zR}Pa`~UkXut?nQ=DxR24sqz>p5}loGFk5`hXJVMF1JO6i-AD>ZnJT#Vpp4BXtw z?n~765QO5eHUWU{LH0E$!I%OyxbUG+yIkutv0jdW@g zFh#IajLOw~NUV8aNub;VPwSs_(*poi9y@9m^w{P5U9!|rW+KQL&b1xg3 zO_U;8&s`WK8wzCu!`U%Wc8u#j367NGI>r5Bpk!?oK`ju?Sl{pEqkNNQjB|Ix-7nJZ ze88*u03ZO*tLpG&sU?Yl%!dfB=}LH5V+%(Z8PBkfxSv~P%UCE7#RdvM&B zs~>%w9HA>5P@}nknAo_~S0HY>7GuA+?59|F?aMc|vG08S;M2eF6}f4bS+EEdTIw;X z0KnniwOil*b^Z2t34-$jYO!t0g2;V6*jD)OQ&3Y`$)9if53K5guc~Of2Ry-k-GSB} zD(Q(^CyPkSgbZWHlUL3lxOYXi`T9S1>nYH< {}, + setUsedSuperpower: () => {}, }); export function GameModeProvider({ children }) { @@ -11,9 +14,15 @@ export function GameModeProvider({ children }) { return savedMode === "true"; }); + const [usedSuperpower, setUsedSuperpower] = useState(false); + useEffect(() => { localStorage.setItem("livesMode", livesMode); }, [livesMode]); - return {children}; + return ( + + {children} + + ); } diff --git a/src/pages/GamePage/GamePage.jsx b/src/pages/GamePage/GamePage.jsx index d882c0334..940738952 100644 --- a/src/pages/GamePage/GamePage.jsx +++ b/src/pages/GamePage/GamePage.jsx @@ -1,24 +1,53 @@ import { useParams } from "react-router-dom"; import { Cards } from "../../components/Cards/Cards"; -import { useEffect } from "react"; +import { useEffect, useContext } from "react"; +import { GameModeContext } from "../../context/GameModeContext"; // Импортируем контекст для режимов игры export function GamePage() { const { pairsCount } = useParams(); + const { livesMode, usedSuperpower } = useContext(GameModeContext); // Получаем режимы игры и использование суперсилы - // Функция для отправки результата в API + // Функция для отправки результата в API с ачивками const sendResultToLeaderboard = (name, time) => { - const result = { + const achievements = []; + + // Проверяем, играл ли пользователь в режиме без жизней + if (!livesMode) achievements.push(1); // Если сложный режим (без трех жизней) + + // Проверяем, использовалась ли суперсила + if (!usedSuperpower) achievements.push(2); // Если суперсила не использовалась + + const level = Math.floor(pairsCount / 3); // Рассчитываем уровень на основе количества пар + + // Проверим, что все значения корректны перед отправкой + console.log("Отправляем результат:", { name, time, - level: pairsCount / 3, // предположим, что level = количество пар / 3 + level, + achievements, + }); + + const result = { + name: name || "Пользователь", // Если имя не указано, используем "Пользователь" + time: parseInt(time, 10), // Убедимся, что время передано как число + level, // Уровень игры + achievements, // Добавляем массив ачивок в запрос }; - // Убираем заголовок "Content-Type" - fetch("https://wedev-api.sky.pro/api/leaderboard", { + // Отправляем POST-запрос без заголовка Content-Type + fetch("https://wedev-api.sky.pro/api/v2/leaderboard", { method: "POST", - body: JSON.stringify(result), + body: JSON.stringify(result), // Преобразуем объект в строку JSON }) - .then(response => response.json()) + .then(response => { + if (!response.ok) { + return response.json().then(errorData => { + console.error("Ошибка при добавлении результата:", errorData); + throw new Error(`Ошибка HTTP: ${response.status}`); + }); + } + return response.json(); + }) .then(data => { console.log("Результат успешно добавлен:", data); }) diff --git a/src/pages/LeaderboardPage/LeaderboardPage.jsx b/src/pages/LeaderboardPage/LeaderboardPage.jsx index 5cc84a341..a16cdcb41 100644 --- a/src/pages/LeaderboardPage/LeaderboardPage.jsx +++ b/src/pages/LeaderboardPage/LeaderboardPage.jsx @@ -1,39 +1,43 @@ import { useState, useEffect } from "react"; import styles from "./LeaderboardPage.module.css"; import { useNavigate } from "react-router-dom"; +import hardlvlon from "../../components/EndGameModal/images/hardlvlon.png"; +import superpoweron from "../../components/EndGameModal/images/superpoweron.png"; export function LeaderboardPage() { const [leaders, setLeaders] = useState([]); const navigate = useNavigate(); + // Убедитесь, что сортировка корректная и вся информация выводится useEffect(() => { - fetch("https://wedev-api.sky.pro/api/leaderboard") + fetch("https://wedev-api.sky.pro/api/v2/leaderboard") .then(response => response.json()) .then(data => { - // Поскольку API не поддерживает level, фильтруем по времени или другому признаку - const thresholdForHardLevel = 30; // Порог для сложного уровня (время в секундах) + const sortedLeaders = data.leaders.sort((a, b) => a.time - b.time).slice(0, 10); // Оставляем только топ-10 лидеров + console.log("Топ-10 лидеров:", sortedLeaders); // Вывод топ-10 лидеров - // Фильтрация по порогу времени - const filteredLeaders = data.leaders.filter(leader => leader.time >= thresholdForHardLevel); - - // Сортируем лидеров по времени (чем меньше время, тем выше позиция) - const sortedLeaders = filteredLeaders.sort((a, b) => a.time - b.time); - - // Ограничиваем список до топ 10 игроков - const topLeaders = sortedLeaders.slice(0, 10); - - setLeaders(topLeaders); + setLeaders(sortedLeaders); // Устанавливаем только топ-10 лидеров }) .catch(error => { console.error("Ошибка при получении списка лидеров:", error); }); }, []); - // Обработчик нажатия на кнопку const handleStartGame = () => { - navigate("/"); // Выполняем переход на главную страницу + navigate("/"); }; + const renderAchievements = (achievements = []) => ( +
+ {achievements.includes(1) && ( + Hard level achievement + )} + {achievements.includes(2) && ( + Superpower achievement + )} +
+ ); + return (
@@ -50,14 +54,16 @@ export function LeaderboardPage() {
+ {leaders.map((leader, index) => ( - + + ))} @@ -66,7 +72,6 @@ export function LeaderboardPage() { ); } -// Функция для форматирования времени в минуты и секунды function formatTime(timeInSeconds) { const minutes = Math.floor(timeInSeconds / 60) .toString() diff --git a/src/pages/LeaderboardPage/LeaderboardPage.module.css b/src/pages/LeaderboardPage/LeaderboardPage.module.css index 91bba5664..92ad199f0 100644 --- a/src/pages/LeaderboardPage/LeaderboardPage.module.css +++ b/src/pages/LeaderboardPage/LeaderboardPage.module.css @@ -65,4 +65,15 @@ display: flex; justify-content: space-between; align-items: baseline; -} \ No newline at end of file +} +/* Добавляем стили для иконок достижений */ +.achievements { + display: flex; + gap: 10px; + } + + .achievementIcon { + width: 24px; + height: 24px; + } + \ No newline at end of file From abe2849d11550f00239d3274f2feee912ca891fa Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Tue, 24 Sep 2024 04:41:25 +0500 Subject: [PATCH 18/20] =?UTF-8?q?=D0=B2=D0=B5=D1=81=D1=80=D1=81=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BB=D0=B8=D0=B4=D0=B5=D1=80=D0=B1=D0=BE=D1=80?= =?UTF-8?q?=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/LeaderboardPage/LeaderboardPage.jsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pages/LeaderboardPage/LeaderboardPage.jsx b/src/pages/LeaderboardPage/LeaderboardPage.jsx index a16cdcb41..c27ac3e19 100644 --- a/src/pages/LeaderboardPage/LeaderboardPage.jsx +++ b/src/pages/LeaderboardPage/LeaderboardPage.jsx @@ -2,7 +2,9 @@ import { useState, useEffect } from "react"; import styles from "./LeaderboardPage.module.css"; import { useNavigate } from "react-router-dom"; import hardlvlon from "../../components/EndGameModal/images/hardlvlon.png"; +import hardlvloff from "../../components/EndGameModal/images/hardlvloff.png"; import superpoweron from "../../components/EndGameModal/images/superpoweron.png"; +import superpoweroff from "../../components/EndGameModal/images/superpoweroff.png"; export function LeaderboardPage() { const [leaders, setLeaders] = useState([]); @@ -29,11 +31,15 @@ export function LeaderboardPage() { const renderAchievements = (achievements = []) => (
- {achievements.includes(1) && ( + {achievements.includes(1) ? ( Hard level achievement + ) : ( + Hard level not achieved )} - {achievements.includes(2) && ( + {achievements.includes(2) ? ( Superpower achievement + ) : ( + Superpower not achieved )}
); From 6a2135c6246cf5d41314b38c153cd2096c94a0e0 Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Tue, 24 Sep 2024 04:52:06 +0500 Subject: [PATCH 19/20] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D0=B1=D0=B0=D0=B3.=20fix:=20=D1=81=D0=B1?= =?UTF-8?q?=D1=80=D0=BE=D1=81=20=D1=81=D1=87=D0=B5=D1=82=D1=87=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B6=D0=B8=D0=B7=D0=BD=D0=B5=D0=B9=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B5=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=83=D1=81=D0=BA=D0=B0=20=D0=B8=D0=B3=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Cards/Cards.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index 756f23315..f73acf059 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -83,6 +83,9 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { setIsProcessing(false); setIsSuperPowerUsed(false); clearInterval(intervalId); + + // Добавляем сброс жизней на начальное количество + setLives(initialLives); } const openCard = clickedCard => { From 28c3831d26919862da10832f6ab93855e6792a3e Mon Sep 17 00:00:00 2001 From: HydroponKing <155968943+HydroponKing@users.noreply.github.com> Date: Tue, 24 Sep 2024 04:59:05 +0500 Subject: [PATCH 20/20] =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=20+=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 390683abb..a9d1bbae4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## Оценка времени выполнения работы "Курсовая работа 4" - **Ожидаемое время:** 12 часов -- **Фактическое время:** n часов +- **Фактическое время:** 10 часов # MVP Карточная игра "Мемо"
Позиция Пользователь ВремяДостижения
{`# ${index + 1}`}{`#${index + 1}`} {leader.name} {formatTime(leader.time)}{renderAchievements(leader.achievements)}