Skip to content
Open

Cw #58

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
"plugins": ["prettier"],
"rules": {
"camelcase": ["error", { "properties": "never" }],
"prettier/prettier": "error",
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
}
],
"eqeqeq": ["error", "always"],
"no-unused-vars": ["error"]
}
Expand Down
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"cSpell.words": [
"закрыта"
]
}
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,9 @@ https://skypro-web-developer.github.io/react-memo/

Запускает eslint проверку кода, эта же команда запускается перед каждым коммитом.
Если не получается закоммитить, попробуйте запустить эту команду и исправить все ошибки и предупреждения.

Предплагаемое время на выполнение ДЗ №1 5 часов
фактическое время 6.5 часов

Предплагаемое время на выполнение ДЗ №2 5 часов
фактическое время 5 часов
28 changes: 28 additions & 0 deletions src/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//Получить список лидеров

export async function getLeaders() {
const response = await fetch(`https://wedev-api.sky.pro/api/v2/leaderboard`, { method: "GET" });
if (!response.status === 200) {
throw new Error("Не удалось получить список лидеров");
}
const data = await response.json();
return data;
}

export async function addLeaders({ name, time, achievements }) {
const response = await fetch(`https://wedev-api.sky.pro/api/v2/leaderboard`, {
method: "POST",
body: JSON.stringify({
name,
time,
achievements,
}),
});
if (!response.status === 201) {
throw new Error("Не удалось добавить в список лидеров");
} else if (response.status === 400) {
throw new Error("Введите Ваше имя");
}
const data = await response.json();
return data;
}
143 changes: 136 additions & 7 deletions src/components/Cards/Cards.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ 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 { ToolTipComponent } from "../Tooltip/Tooltip";
import { superPowerData } from "../../lib";

// Игра закончилась
const STATUS_LOST = "STATUS_LOST";
Expand Down Expand Up @@ -40,7 +42,7 @@ function getTimerValue(startDate, endDate) {
* pairsCount - сколько пар будет в игре
* previewSeconds - сколько секунд пользователь будет видеть все карты открытыми до начала игры
*/
export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
export function Cards({ pairsCount = 3, previewSeconds = 5, isGameMode }) {
// В cards лежит игровое поле - массив карт и их состояние открыта\закрыта
const [cards, setCards] = useState([]);
// Текущий статус игры
Expand All @@ -57,9 +59,28 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
minutes: 0,
});

// Стейт для счетчика попыток
const [numberOfAttempts, setNumberOfAttempts] = useState(2);
const takeAwayTheAttempt = () => {
setNumberOfAttempts(numberOfAttempts - 1);
};
// стейт для массива достижений
const [achievementsArr, setAchievementsArr] = useState([]);

// стейт чтобы определить были ли использованы суперсилы
const [superPowersUsed, setSuperpowersUsed] = useState(false);

function finishGame(status = STATUS_LOST) {
setGameEndDate(new Date());
setStatus(status);
if (pairsCount > 8) {
achievementsArr.push(1);
setAchievementsArr(achievementsArr);
}
if (!superPowersUsed) {
achievementsArr.push(2);
setAchievementsArr(achievementsArr);
}
}
function startGame() {
const startDate = new Date();
Expand All @@ -73,11 +94,14 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
setGameEndDate(null);
setTimer(getTimerValue(null, null));
setStatus(STATUS_PREVIEW);
setNumberOfAttempts(2);
setSuperpowersUsed(false);
setAchievementsArr([]);
}

/**
* Обработка основного действия в игре - открытие карты.
* После открытия карты игра может пепереходит в следующие состояния
* После открытия карты игра может переходит в следующие состояния
* - "Игрок выиграл", если на поле открыты все карты
* - "Игрок проиграл", если на поле есть две открытые карты без пары
* - "Игра продолжается", если не случилось первых двух условий
Expand Down Expand Up @@ -126,11 +150,25 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
const playerLost = openCardsWithoutPair.length >= 2;

// "Игрок проиграл", т.к на поле есть две открытые карты без пары
if (playerLost) {
finishGame(STATUS_LOST);
return;
}

if (isGameMode === "true") {
if (playerLost) {
takeAwayTheAttempt();
if (numberOfAttempts < 1) {
finishGame(STATUS_LOST);
return;
} else {
setTimeout(() => {
setCards(cards.map(card => (openCardsWithoutPair.includes(card) ? { ...card, open: false } : card)));
}, 1000);
}
}
} else {
if (playerLost) {
finishGame(STATUS_LOST);
return;
}
}
// ... игра продолжается
};

Expand Down Expand Up @@ -172,6 +210,23 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
};
}, [gameStartDate, gameEndDate]);

// Закрытые карты на игровом поле
const reversedCards = cards.filter(card => !card.open);

// Cуперсила "алохомора"
function alohomora() {
if (!superPowersUsed & (reversedCards.length > 2)) {
const randomCard = shuffle(reversedCards)[0];
const randomPair = reversedCards.filter(
sameCard => randomCard.suit === sameCard.suit && randomCard.rank === sameCard.rank,
);
randomPair[0].open = true;
randomPair[1].open = true;
setSuperpowersUsed(true);
}
return;
}

return (
<div className={styles.container}>
<div className={styles.header}>
Expand All @@ -195,7 +250,79 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
</>
)}
</div>
{status === STATUS_IN_PROGRESS ? <Button onClick={resetGame}>Начать заново</Button> : null}

{status === STATUS_IN_PROGRESS ? (
<>
<div className={styles.superpowersBox}>
<ToolTipComponent text={superPowerData.alohomora.text} title={superPowerData.alohomora.title}>
<div onClick={alohomora}>
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="68" height="68" rx="34" fill="#C2F5FF" />
<rect
x="31.7295"
y="9"
width="24.9566"
height="34.761"
rx="2"
transform="rotate(15 31.7295 9)"
fill="url(#paint0_linear_0_1)"
stroke="#E4E4E4"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M38.5302 33.4852C38.4143 33.3327 38.2972 33.1797 38.1797 33.0262C36.5408 30.8843 34.8288 28.6471 35.3027 26.6581C35.8981 24.6731 37.4389 24.3711 38.6234 24.6885C39.3167 24.8742 40.0048 25.4818 40.4186 26.3021C41.1243 25.7818 41.9569 25.5817 42.6502 25.7675C43.8346 26.0849 45.1365 27.1485 44.6596 29.1653C44.0534 31.1989 41.3849 32.3165 38.8199 33.3908C38.7345 33.4265 38.6492 33.4622 38.5641 33.4979C38.558 33.5006 38.552 33.5032 38.5459 33.5058C38.5458 33.5057 38.5458 33.5057 38.5458 33.5056C38.5457 33.5057 38.5456 33.5057 38.5455 33.5057C38.5404 33.4989 38.5352 33.492 38.5302 33.4852Z"
fill="#FF4545"
/>
<rect
x="9.80664"
y="24.6251"
width="24.9566"
height="34.761"
rx="2"
transform="rotate(-15 9.80664 24.6251)"
fill="white"
stroke="#E4E4E4"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M25.1215 35.4952C24.353 34.9918 23.4534 34.8097 22.7601 34.9955C21.5757 35.3129 20.3923 36.3448 20.8692 38.3615C21.4533 40.321 24.0545 41.4026 26.5447 42.438C26.7244 42.5127 26.9035 42.5872 27.0812 42.6617C27.0879 42.6646 27.0945 42.6675 27.1012 42.6703C27.1013 42.6702 27.1013 42.6702 27.1013 42.6701C27.1014 42.6702 27.1015 42.6702 27.1016 42.6703C27.1076 42.6624 27.1135 42.6545 27.1193 42.6466C27.1732 42.5756 27.2272 42.5046 27.2813 42.4335C28.9656 40.2207 30.7177 37.9186 30.2259 35.8543C29.6306 33.8693 27.9713 33.5991 26.7868 33.9164C26.0935 34.1022 25.4725 34.6918 25.1215 35.4952Z"
fill="#FF4545"
/>
<defs>
<linearGradient
id="paint0_linear_0_1"
x1="41.9795"
y1="17.0218"
x2="32.1751"
y2="25.0435"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" />
<stop offset="1" stop-color="#F0F0F0" />
</linearGradient>
</defs>
</svg>
</div>
</ToolTipComponent>
</div>
{isGameMode === "true" ? (
<div className={styles.attemptСounter}>
<svg width="30" height="26" viewBox="0 0 30 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14.53 3.27849C12.6964 1.22519 10.2335 0.000124665 8.08277 0.000124664C4.40879 0.000124663 0.183699 2.06893 -8.96305e-08 8.27535C0.170897 14.3992 6.86026 19.5464 13.2644 24.474C13.7441 24.8432 14.2223 25.2111 14.696 25.5781C14.696 25.5781 14.696 25.5781 14.696 25.578C14.6961 25.5781 14.6961 25.5781 14.6961 25.5781C14.9305 25.3965 15.1655 25.2147 15.4009 25.0326C21.9912 19.9345 28.8473 14.6309 29.0247 8.27535C28.841 2.06893 24.2485 0.00012207 20.5745 0.00012207C18.4238 0.00012207 16.169 1.22519 14.53 3.27849Z"
fill="#FF4545"
/>
</svg>
<div>{numberOfAttempts + 1}</div>
</div>
) : null}
<Button onClick={resetGame}>Начать заново</Button>
</>
) : null}
</div>

<div className={styles.cards}>
Expand All @@ -217,6 +344,8 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
gameDurationSeconds={timer.seconds}
gameDurationMinutes={timer.minutes}
onClick={resetGame}
isGameMode
achievementsArr={achievementsArr}
/>
</div>
) : null}
Expand Down
16 changes: 15 additions & 1 deletion src/components/Cards/Cards.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@
font-style: normal;
font-weight: 400;
line-height: 32px;

margin-bottom: -12px;
}


.attemptСounter {
display: flex;
gap: 10px;
color: red;
font-variant-numeric: lining-nums proportional-nums;
font-family: StratosSkyeng;
font-size: 30px;
font-style: normal;
font-weight: 400;
line-height: 30px;
}


3 changes: 3 additions & 0 deletions src/components/Cards/images/hearts.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
76 changes: 72 additions & 4 deletions src/components/EndGameModal/EndGameModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,60 @@ import { Button } from "../Button/Button";

import deadImageUrl from "./images/dead.png";
import celebrationImageUrl from "./images/celebration.png";
import { useNavigate, useParams } from "react-router-dom";
import { useState } from "react";
import { addLeaders } from "../../api";

export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick }) {
const title = isWon ? "Вы победили!" : "Вы проиграли!";
export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick, achievementsArr }) {
console.log(achievementsArr);
isWon = true;
const { isGameMode } = useParams();
const { pairsCount } = useParams();
const navigate = useNavigate();
const gameSeconds = gameDurationMinutes * 60 + gameDurationSeconds;

const [userData, setuserData] = useState({
name: " ",
time: gameSeconds,
achievements: achievementsArr,
});

const handleInputChange = e => {
const { name, value } = e.target; // Извлекаем имя поля и его значение

setuserData({
...userData, // Копируем текущие данные из состояния
[name]: value, // Обновляем нужное поле
});
};

async function handleAddUser(e) {
e.preventDefault();
try {
await addLeaders(userData).then(data => {
navigate(`/leaderboard`);
});
} catch (error) {
alert(error.message);
}
}
async function handleAddUserButton(e) {
e.preventDefault();
try {
await addLeaders(userData).then(data => {
onClick();
});
} catch (error) {
alert(error.message);
}
}

let title = "";
if (pairsCount === "9") {
title = isWon ? "Вы попали на лидерборд!" : "Вы проиграли!";
} else {
title = isWon ? "Вы победили!" : "Вы проиграли!";
}

const imgSrc = isWon ? celebrationImageUrl : deadImageUrl;

Expand All @@ -16,12 +67,29 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes,
<div className={styles.modal}>
<img className={styles.image} src={imgSrc} alt={imgAlt} />
<h2 className={styles.title}>{title}</h2>
{isGameMode === "false" && pairsCount === "9" && isWon ? (
<form className={styles.form}>
<input
className={styles.nameInput}
value={userData.name}
onChange={handleInputChange}
type="text"
name="name"
id="formUser"
placeholder="Пользователь"
/>
</form>
) : null}
<p className={styles.description}>Затраченное время:</p>
<div className={styles.time}>
{gameDurationMinutes.toString().padStart("2", "0")}.{gameDurationSeconds.toString().padStart("2", "0")}
</div>

<Button onClick={onClick}>Начать сначала</Button>
<Button onClick={handleAddUserButton}>Начать сначала</Button>
{isGameMode === "false" && pairsCount === "9" && isWon ? (
<div onClick={handleAddUser} className={styles.leaderboardLink}>
Перейти к лидерборду
</div>
) : null}
</div>
);
}
Loading