diff --git a/package-lock.json b/package-lock.json index c20f8c388..19baf6a3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12633,7 +12633,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" }, "node_modules/lodash.debounce": { "version": "4.0.8", diff --git a/public/index.html b/public/index.html index c0103cf10..5e46aec0e 100644 --- a/public/index.html +++ b/public/index.html @@ -37,16 +37,8 @@ -
- +
diff --git a/src/API/leaders.js b/src/API/leaders.js index 0846ecf15..0753d0654 100644 --- a/src/API/leaders.js +++ b/src/API/leaders.js @@ -1,6 +1,6 @@ export async function getLeaders() { try { - const response = await fetch("https://wedev-api.sky.pro/api/leaderboard/", { + const response = await fetch("https://wedev-api.sky.pro/api/v2/leaderboard/", { method: "GET", }); const isResponseOk = response.ok; @@ -17,7 +17,7 @@ export async function getLeaders() { //добавление лидера в список export const addLeader = async data => { - const response = await fetch("https://wedev-api.sky.pro/api/leaderboard/", { + const response = await fetch("https://wedev-api.sky.pro/api/v2/leaderboard/", { method: "POST", body: JSON.stringify(data), }); diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index 23b2125a4..eabbaf1c3 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -6,6 +6,8 @@ import { EndGameModal } from "../../components/EndGameModal/EndGameModal"; import { Button } from "../../components/Button/Button"; import { Card } from "../../components/Card/Card"; import { EasyContext } from "../../context/Context"; +import alohomora from "./images/alohomora.png"; +import Modal from "../modal/Modal"; // Игра закончилась const STATUS_LOST = "STATUS_LOST"; @@ -15,6 +17,11 @@ const STATUS_IN_PROGRESS = "STATUS_IN_PROGRESS"; // Начало игры: игрок видит все карты в течении нескольких секунд const STATUS_PREVIEW = "STATUS_PREVIEW"; +const SECOND_HINT = { + title: "Алохомора", + description: " Открывается случайная пара карт.", +}; + function getTimerValue(startDate, endDate) { if (!startDate && !endDate) { return { @@ -42,7 +49,7 @@ function getTimerValue(startDate, endDate) { * previewSeconds - сколько секунд пользователь будет видеть все карты открытыми до начала игры */ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { - const { isEasyMode, tries, setTries } = useContext(EasyContext); + const { isEasyMode, tries, setTries, setUsedHints } = useContext(EasyContext); // В cards лежит игровое поле - массив карт и их состояние открыта\закрыта const [cards, setCards] = useState([]); // Текущий статус игры @@ -59,6 +66,11 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { minutes: 0, }); + const [isHovered, setIsHovered] = useState(false); + + const handleMouseOver = () => setIsHovered(true); + const handleMouseOut = () => setIsHovered(false); + function finishGame(status = STATUS_LOST) { setGameEndDate(new Date()); setStatus(status); @@ -198,6 +210,35 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { }; }, [gameStartDate, gameEndDate]); + const [isUsedHint, setIsUsedHint] = useState(0); + + const handleClickAlohomora = () => { + if (isUsedHint > 2 || gameStartDate - gameEndDate === 0) return; + setUsedHints(true); + const obj = {}; + const filtredCards = cards.filter(element => !element.open); + for (let i = 0; i < filtredCards.length; i++) { + if (obj[filtredCards[i].suit + " " + filtredCards[i].rank]) { + obj[filtredCards[i].suit + " " + filtredCards[i].rank] += 1; + } else { + obj[filtredCards[i].suit + " " + filtredCards[i].rank] = 1; + } + } + const onlyPairsCards = Object.entries(obj).filter(([key, value]) => value === 2); + const randomPair = onlyPairsCards[Math.floor(Math.random() * onlyPairsCards.length)][0]; + const suitOfRandomPair = randomPair.split(" ")[0]; + const rankOfRandomPair = randomPair.split(" ")[1]; + + const newCards = cards.map(el => { + if (el.suit === suitOfRandomPair && el.rank === rankOfRandomPair) { + return { ...el, open: true }; + } else { + return el; + } + }); + setIsUsedHint(prev => prev + 1); + setCards(newCards); + }; return (
@@ -221,6 +262,21 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { )}
+
+ +
+ {status === STATUS_IN_PROGRESS ? : null} {isEasyMode && Колличество жизней: {tries}}
@@ -247,6 +303,11 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { /> ) : null} + {isHovered && ( + +
+
+ )} ); } diff --git a/src/components/Cards/Cards.module.css b/src/components/Cards/Cards.module.css index 000c5006c..18e97bb90 100644 --- a/src/components/Cards/Cards.module.css +++ b/src/components/Cards/Cards.module.css @@ -70,3 +70,95 @@ margin-bottom: -12px; } + +.cheatBox { + width: 151px; + height: 68px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.popUpHint { + opacity: 0.6; + background-color: #004980; + width: 100%; + height: 100vh; + z-index: 5000; + position: fixed; +} + +.showAllCard { + display: flex; + justify-content: center; + align-items: center; + width: 68px; + height: 68px; + border-radius: 50%; + background-color: #c2f5ff; + position: relative; + cursor: pointer; + border: none; + z-index: 7000; +} + +.alohomora { + display: flex; + justify-content: center; + align-items: center; + width: 68px; + height: 68px; + border-radius: 50%; + background-color: #c2f5ff; + position: relative; + cursor: pointer; + border: none; + z-index: 7000; +} + +.popUpHintContent { + position: absolute; + width: 222px; + height: 223px; + background-color: #c2f5ff; + padding: 25px 20px 20px 20px; + bottom: -230px; + z-index: 6000; + border-radius: 12px; +} + +.popUpHintContenTitle { + font-size: 18px; + font-weight: 700; + line-height: 24px; + color: #004980; +} + +.popUpHintContentDescription { + font-size: 18px; + font-weight: 400; + line-height: 24px; + margin-top: 10px; +} + +.popUpHintActiveFirst { + display: none; +} +.showAllCard:active .popUpHint { + display: block; +} +.showAllCard:hover .popUpHintActiveFirst { + display: block; +} + +.popUpHintActiveSecond { + display: none; +} + +.alohomora:hover .popUpHint { + display: block; +} + +.alohomora:hover .popUpHintActiveSecond { + display: block; +} diff --git a/src/components/Cards/images/alohomora.png b/src/components/Cards/images/alohomora.png new file mode 100644 index 000000000..e323e39c0 Binary files /dev/null and b/src/components/Cards/images/alohomora.png differ diff --git a/src/components/Cards/images/showallcard.png b/src/components/Cards/images/showallcard.png new file mode 100644 index 000000000..7c910dfe9 Binary files /dev/null and b/src/components/Cards/images/showallcard.png differ diff --git a/src/components/EndGameModal/EndGameModal.jsx b/src/components/EndGameModal/EndGameModal.jsx index 4ce815b1c..bb24b36b3 100644 --- a/src/components/EndGameModal/EndGameModal.jsx +++ b/src/components/EndGameModal/EndGameModal.jsx @@ -5,10 +5,12 @@ import { Button } from "../Button/Button"; import deadImageUrl from "./images/dead.png"; import celebrationImageUrl from "./images/celebration.png"; import { Link, useNavigate, useParams } from "react-router-dom"; -import { useState } from "react"; +import { useContext, useState } from "react"; import { addLeader } from "../../API/leaders.js"; +import { EasyContext } from "../../context/Context.jsx"; export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick }) { + const { achievements, setAchievements, usedHints, setUsedHints, setEasyMode } = useContext(EasyContext); const { pairsCount } = useParams(); const [error, setError] = useState(); const nav = useNavigate(); @@ -17,8 +19,6 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, const isLeader = isWon && Number(pairsCount) === thirdLevelPairs; const title = isLeader ? "Вы попали на лидерборд!" : isWon ? "Вы выйграли!" : "Вы проиграли!"; - //const title = isWon ? "Вы выйграли!" : "Вы проиграли!"; - const imgSrc = isWon ? celebrationImageUrl : deadImageUrl; const imgAlt = isWon ? "celebration emodji" : "dead emodji"; @@ -35,9 +35,16 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, return; } try { - await addLeader({ name: leader.name, time: leader.time }).then(res => { + await addLeader({ + name: leader.name, + time: leader.time, + achievements: usedHints ? achievements : [...achievements, 2], + }).then(res => { setAddLeader(res.leaders); nav("/leaderBoard"); + setAchievements([]); + setUsedHints(false); + setEasyMode(false); }); } catch (error) { setError(error.message); diff --git a/src/components/modal/Modal.jsx b/src/components/modal/Modal.jsx new file mode 100644 index 000000000..ef034e15a --- /dev/null +++ b/src/components/modal/Modal.jsx @@ -0,0 +1,9 @@ +import React from "react"; +import ReactDOM from "react-dom"; + +const Modal = ({ children }) => { + const modalRoot = document.getElementById("modal-root"); + return ReactDOM.createPortal(<>{children}, modalRoot); +}; + +export default Modal; diff --git a/src/context/Context.jsx b/src/context/Context.jsx index ff14509f5..303d9a259 100644 --- a/src/context/Context.jsx +++ b/src/context/Context.jsx @@ -3,7 +3,15 @@ import { createContext, useState } from "react"; export const EasyContext = createContext(false); export const EasyProvider = ({ children }) => { + const [usedHints, setUsedHints] = useState(false); const [tries, setTries] = useState(3); const [isEasyMode, setEasyMode] = useState(false); - return {children}; + const [achievements, setAchievements] = useState([]); + return ( + + {children} + + ); }; diff --git a/src/pages/LeaderBoard/Image/magicBall.png b/src/pages/LeaderBoard/Image/magicBall.png new file mode 100644 index 000000000..b59b52e7d Binary files /dev/null and b/src/pages/LeaderBoard/Image/magicBall.png differ diff --git a/src/pages/LeaderBoard/Image/magicBallHollow.png b/src/pages/LeaderBoard/Image/magicBallHollow.png new file mode 100644 index 000000000..5af4ec3c7 Binary files /dev/null and b/src/pages/LeaderBoard/Image/magicBallHollow.png differ diff --git a/src/pages/LeaderBoard/Image/puzzleFull.png b/src/pages/LeaderBoard/Image/puzzleFull.png new file mode 100644 index 000000000..49c30e1d1 Binary files /dev/null and b/src/pages/LeaderBoard/Image/puzzleFull.png differ diff --git a/src/pages/LeaderBoard/Image/puzzleHollow.png b/src/pages/LeaderBoard/Image/puzzleHollow.png new file mode 100644 index 000000000..5a1568407 Binary files /dev/null and b/src/pages/LeaderBoard/Image/puzzleHollow.png differ diff --git a/src/pages/LeaderBoard/LeaderBoard.jsx b/src/pages/LeaderBoard/LeaderBoard.jsx index 476cda8f6..cd5b1b602 100644 --- a/src/pages/LeaderBoard/LeaderBoard.jsx +++ b/src/pages/LeaderBoard/LeaderBoard.jsx @@ -3,9 +3,15 @@ import { Button } from "../../components/Button/Button"; import styles from "./LeaderBoard.module.css"; import { getLeaders } from "../../API/leaders"; import { Link } from "react-router-dom"; +import puzzleFull from "./Image/puzzleFull.png"; +import puzzleHollow from "./Image/puzzleHollow.png"; +import magicBall from "./Image/magicBall.png"; +import magicBallHollow from "./Image/magicBallHollow.png"; const LeaderBoard = () => { - const [leaders, setLeaders] = useState([{ position: "Позиция", name: "Пользователь", time: "Время" }]); + const [leaders, setLeaders] = useState([ + { position: "Позиция", name: "Пользователь", time: "Время", achievements: "Достижения" }, + ]); useEffect(() => { getLeaders().then(data => { setLeaders([...leaders, ...data.sort((a, b) => a.time - b.time)]); @@ -31,9 +37,27 @@ const LeaderBoard = () => { {leaders.map((player, index) => (
  • ))} diff --git a/src/pages/LeaderBoard/LeaderBoard.module.css b/src/pages/LeaderBoard/LeaderBoard.module.css index bac7ded00..98e2a81bc 100644 --- a/src/pages/LeaderBoard/LeaderBoard.module.css +++ b/src/pages/LeaderBoard/LeaderBoard.module.css @@ -39,6 +39,11 @@ color: #999999; } +.achievementsBox{ + display: flex; + gap: 6px; +} + .firstColumn { width: 244px; } @@ -48,7 +53,13 @@ } .thirdColumn { - width: 102px; + width: 194px; + margin-right: 66px; + +} + +.fourthColumn { + width: 104px; } .leaderBoardTitle { diff --git a/src/pages/SelectLevelPage/SelectLevelPage.jsx b/src/pages/SelectLevelPage/SelectLevelPage.jsx index 10f4f451d..06801fed3 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.jsx +++ b/src/pages/SelectLevelPage/SelectLevelPage.jsx @@ -19,13 +19,17 @@ const levels = [ }, ]; export function SelectLevelPage() { - const { isEasyMode, setEasyMode } = useContext(EasyContext); + const { isEasyMode, setEasyMode, achievements, setAchievements } = useContext(EasyContext); const [level, setLevel] = useState(3); const navigate = useNavigate(); function onClick(value) { setLevel(value); } function onStart() { + if (level === 9 && !isEasyMode) { + setAchievements([...new Set([...achievements, 1])]); + } + navigate(`/game/${level}`); } return ( @@ -34,7 +38,11 @@ export function SelectLevelPage() {

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