diff --git a/.prettierrc.js b/.prettierrc.js
index 65e18f5ff..28033a2b6 100644
--- a/.prettierrc.js
+++ b/.prettierrc.js
@@ -7,4 +7,5 @@ module.exports = {
bracketSpacing: true,
arrowParens: "avoid",
htmlWhitespaceSensitivity: "ignore",
+ endOfLine: "auto",
};
diff --git a/README.md b/README.md
index 9b90842c4..f9812dd99 100644
--- a/README.md
+++ b/README.md
@@ -44,3 +44,43 @@ https://skypro-web-developer.github.io/react-memo/
Запускает eslint проверку кода, эта же команда запускается перед каждым коммитом.
Если не получается закоммитить, попробуйте запустить эту команду и исправить все ошибки и предупреждения.
+
+## Счётчик ошибок
+
+В игре реализован счётчик ошибок, который показывает количество оставшихся попыток.
+
+### Формат отображения
+
+- "Осталось попыток: X"
+
+### Обоснование выбора
+
+Этот формат позволяет игрокам легко понять, сколько попыток у них осталось, без отвлечения от игры. Использование красного цвета привлекает внимание к этой важной информации.
+
+## Оценка времени работы
+
+- **Инициализация игры**: O(n)
+ - Игра инициализируется на основе количества карт. Чем больше карт, тем больше времени потребуется для их инициализации.
+- **Обработка клика по карте**: O(1)
+ - Время на обработку клика по карте фиксированное, так как это простое действие — переворот карты.
+- **Проверка совпадений**: O(1)
+ - Проверка совпадения двух карт осуществляется мгновенно, так как это сравнение двух значений.
+- **Проверка выигрыша**: O(n)
+ - Для проверки выигрыша необходимо пройти по всем картам, чтобы убедиться, что все пары найдены.
+- **Перемешивание карт**: O(n log n)
+ - Перемешивание карт реализовано с использованием алгоритма, временная сложность которого составляет O(n log n), что характерно для эффективных алгоритмов сортировки.
+
+ Время на выполнение ДЗ №1:
+
+ - пларируемое - 12 часов
+ - фактическое - 15 часов
+
+Время на выполнение ДЗ №2:
+
+ - пларируемое - 12 часов
+ - фактическое - 14 часов
+
+Время на выполнение ДЗ №3:
+
+ - пларируемое - 12 часов
+ - фактическое - 13 часов
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index edaf5083f..662339d85 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"classnames": "^2.3.2",
+ "date-fns": "^3.6.0",
"gh-pages": "^6.0.0",
"lodash": "^4.17.21",
"react": "^18.2.0",
@@ -6785,6 +6786,15 @@
"node": ">=10"
}
},
+ "node_modules/date-fns": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
+ "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -23136,6 +23146,11 @@
"whatwg-url": "^8.0.0"
}
},
+ "date-fns": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
+ "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="
+ },
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
diff --git a/package.json b/package.json
index e9b7a089e..061aa6e4b 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"classnames": "^2.3.2",
+ "date-fns": "^3.6.0",
"gh-pages": "^6.0.0",
"lodash": "^4.17.21",
"react": "^18.2.0",
diff --git a/public/1682744403_papik-pro-p-smail-chekboks-png-32.png b/public/1682744403_papik-pro-p-smail-chekboks-png-32.png
new file mode 100644
index 000000000..9f9e0f561
Binary files /dev/null and b/public/1682744403_papik-pro-p-smail-chekboks-png-32.png differ
diff --git a/public/card_insight.png b/public/card_insight.png
new file mode 100644
index 000000000..aeddd2607
Binary files /dev/null and b/public/card_insight.png differ
diff --git a/public/index.html b/public/index.html
index c0103cf10..f8f3a611a 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2,6 +2,9 @@
+
+
+
diff --git a/public/magic_ball.svg b/public/magic_ball.svg
new file mode 100644
index 000000000..78d4894ef
--- /dev/null
+++ b/public/magic_ball.svg
@@ -0,0 +1,17 @@
+
diff --git a/public/magic_ball_empty.svg b/public/magic_ball_empty.svg
new file mode 100644
index 000000000..8de6a592f
--- /dev/null
+++ b/public/magic_ball_empty.svg
@@ -0,0 +1,33 @@
+
diff --git a/public/puzzle.svg b/public/puzzle.svg
new file mode 100644
index 000000000..0b1729123
--- /dev/null
+++ b/public/puzzle.svg
@@ -0,0 +1,18 @@
+
diff --git a/public/puzzle_empty.svg b/public/puzzle_empty.svg
new file mode 100644
index 000000000..b49cb5c68
--- /dev/null
+++ b/public/puzzle_empty.svg
@@ -0,0 +1,20 @@
+
diff --git a/src/api.js b/src/api.js
new file mode 100644
index 000000000..d26ba048a
--- /dev/null
+++ b/src/api.js
@@ -0,0 +1,47 @@
+const API_URL = "https://wedev-api.sky.pro/api/v2/leaderboard";
+
+export async function getPlayersList() {
+ const response = await fetch(API_URL, {
+ method: "GET",
+ });
+
+ const data = await response.json();
+ return data.leaders;
+}
+
+// Получить ID достижений
+// function getAchievementIds(achievements) {
+// if (!Array.isArray(achievements)) {
+// return [];
+// }
+// return achievements;
+// }
+
+export async function updateLeaderboard(name, time, achievements) {
+ // const achievementIds = getAchievementIds(achievements);
+ // console.log("Achievements IDs in updateLeaderboard:", achievementIds);
+
+ const requestBody = JSON.stringify({
+ name: name || "Пользователь",
+ time,
+ achievements,
+ });
+
+ try {
+ const response = await fetch(API_URL, {
+ method: "POST",
+ body: requestBody,
+ });
+
+ const responseBody = await response.text();
+ console.log("Ответ от сервера:", responseBody);
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ throw new Error(`Не удалось обновить лидерборд: ${response.status} ${errorText}`);
+ }
+ } catch (error) {
+ console.error("Ошибка сети или другая ошибка:", error);
+ throw new Error("Не удалось обновить лидерборд");
+ }
+}
diff --git a/src/components/Ball/Ball.jsx b/src/components/Ball/Ball.jsx
new file mode 100644
index 000000000..c67978f70
--- /dev/null
+++ b/src/components/Ball/Ball.jsx
@@ -0,0 +1,11 @@
+import styles from "./Ball.module.css";
+
+export function Ball() {
+ return (
+
+
+
Игра пройдена в сложном режиме
+
+
+ );
+}
diff --git a/src/components/Ball/Ball.module.css b/src/components/Ball/Ball.module.css
new file mode 100644
index 000000000..93850aee7
--- /dev/null
+++ b/src/components/Ball/Ball.module.css
@@ -0,0 +1,34 @@
+.popup {
+ position: absolute;
+ top: -100px;
+}
+
+.popupContent {
+ position: relative;
+ background-color: #C2F5FF;
+ border-radius: 8px;
+ padding: 15px 20px;
+ width: 212px;
+ text-align: center;
+ box-sizing: border-box;
+}
+
+.popupContent::after {
+ content: "";
+ position: absolute;
+ bottom: -20px;
+ left: 20px;
+ width: 0;
+ height: 0;
+ border-right: 25px solid transparent;
+ border-top: 20px solid #C2F5FF;
+ border-bottom: 5px solid transparent;
+}
+
+.popupText {
+ color: rgb(0, 73, 128);
+ font-family: StratosSkyeng;
+ font-size: 18px;
+ font-weight: 400;
+ text-align: center;
+}
\ No newline at end of file
diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx
index 7526a56c8..a89697f56 100644
--- a/src/components/Cards/Cards.jsx
+++ b/src/components/Cards/Cards.jsx
@@ -1,10 +1,11 @@
import { shuffle } from "lodash";
-import { useEffect, useState } from "react";
+import { useContext, useEffect, useState } 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 { EasyContext } from "../../context/context";
// Игра закончилась
const STATUS_LOST = "STATUS_LOST";
@@ -21,11 +22,9 @@ function getTimerValue(startDate, endDate) {
seconds: 0,
};
}
-
if (endDate === null) {
endDate = new Date();
}
-
const diffInSecconds = Math.floor((endDate.getTime() - startDate.getTime()) / 1000);
const minutes = Math.floor(diffInSecconds / 60);
const seconds = diffInSecconds % 60;
@@ -35,28 +34,25 @@ function getTimerValue(startDate, endDate) {
};
}
-/**
- * Основной компонент игры, внутри него находится вся игровая механика и логика.
- * pairsCount - сколько пар будет в игре
- * previewSeconds - сколько секунд пользователь будет видеть все карты открытыми до начала игры
- */
export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
- // В cards лежит игровое поле - массив карт и их состояние открыта\закрыта
+ const isHardMode = pairsCount === 9;
+ const { tries, setTries, isEasyMode } = useContext(EasyContext);
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 [achievements, setAchievements] = useState([]);
+
+ const [isRevealed, setIsRevealed] = useState(false); // Состояние для отображения всех карт
+ const [canUsePower, setCanUsePower] = useState(true); // Состояние для использования суперсилы
+ const [isPaused, setIsPaused] = useState(false); // Состояние паузы таймера
+ const [usedSuperpower, setUsedSuperpower] = useState(false);
+
function finishGame(status = STATUS_LOST) {
setGameEndDate(new Date());
setStatus(status);
@@ -73,15 +69,12 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
setGameEndDate(null);
setTimer(getTimerValue(null, null));
setStatus(STATUS_PREVIEW);
+ setTries(3);
+ setAchievements([]);
+ setCanUsePower(true); // Сброс возможности использования суперсилы
+ setUsedSuperpower(false);
}
- /**
- * Обработка основного действия в игре - открытие карты.
- * После открытия карты игра может пепереходит в следующие состояния
- * - "Игрок выиграл", если на поле открыты все карты
- * - "Игрок проиграл", если на поле есть две открытые карты без пары
- * - "Игра продолжается", если не случилось первых двух условий
- */
const openCard = clickedCard => {
// Если карта уже открыта, то ничего не делаем
if (clickedCard.open) {
@@ -127,15 +120,43 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
// "Игрок проиграл", т.к на поле есть две открытые карты без пары
if (playerLost) {
- finishGame(STATUS_LOST);
+ if (isEasyMode) {
+ setTries(prevTries => prevTries - 1);
+ if (tries - 1 <= 0) {
+ finishGame(STATUS_LOST);
+ } else {
+ setTimeout(() => {
+ setCards(
+ nextCards.map(card =>
+ openCardsWithoutPair.some(openCard => openCard.id === card.id) ? { ...card, open: false } : card,
+ ),
+ );
+ }, 1000);
+ }
+ } else {
+ finishGame(STATUS_LOST);
+ }
return;
}
-
- // ... игра продолжается
};
const isGameEnded = status === STATUS_LOST || status === STATUS_WON;
+ // Логика для активации суперсилы "Прозрение"
+ const handleReveal = () => {
+ if (canUsePower) {
+ setIsRevealed(true);
+ setIsPaused(true); // Останавливаем таймер
+ setCanUsePower(false); // Суперсила используется только один раз
+ setUsedSuperpower(true);
+
+ setTimeout(() => {
+ setIsRevealed(false);
+ setIsPaused(false); // Возобновляем таймер через 5 секунд
+ }, 5000); // 5 секундное действие
+ }
+ };
+
// Игровой цикл
useEffect(() => {
// В статусах кроме превью доп логики не требуется
@@ -164,13 +185,15 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
// Обновляем значение таймера в интервале
useEffect(() => {
+ if (isPaused) return;
+
const intervalId = setInterval(() => {
setTimer(getTimerValue(gameStartDate, gameEndDate));
}, 300);
return () => {
clearInterval(intervalId);
};
- }, [gameStartDate, gameEndDate]);
+ }, [gameStartDate, gameEndDate, isPaused]);
return (