Skip to content
Open

Hw2 #80

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
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"plugins": ["prettier"],
"rules": {
"camelcase": ["error", { "properties": "never" }],
"prettier/prettier": "error",
"prettier/prettier": "off",
"eqeqeq": ["error", "always"],
"no-unused-vars": ["error"]
}
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,7 @@ https://skypro-web-developer.github.io/react-memo/

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

## Ожидаемое время выполнения работы: 6 часов.

## Фактическое время выполнения: больше 20 часов.
27 changes: 27 additions & 0 deletions src/api/Api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const URL = "https://wedev-api.sky.pro/api/leaderboard";

export const getLeadersPage = async () => {
const response = await fetch(URL, {
method: "GET",
});
if (!response.ok) {
throw new Error("Не удалось получить данные");
}
const data = await response.json()
return data.leaders;
};

export const createLeader = async (name, time) => {
const response = await fetch(URL, {
method: "POST",
body: JSON.stringify ({
name,
time,
})
})
if (response.status === 400) {
throw new Error ("Не удалось загрузить данные")
}
const data = await response.json()
return data.leaders;
}
33 changes: 32 additions & 1 deletion src/components/Cards/Cards.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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 { useEasyContext } from "../../context/useContext";

// Игра закончилась
const STATUS_LOST = "STATUS_LOST";
Expand Down Expand Up @@ -41,6 +42,9 @@ function getTimerValue(startDate, endDate) {
* previewSeconds - сколько секунд пользователь будет видеть все карты открытыми до начала игры
*/
export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
const isHardMode = pairsCount === 3;
const { isEasyMode } = useEasyContext();
const [tries, setTries] = useState(3);
// В cards лежит игровое поле - массив карт и их состояние открыта\закрыта
const [cards, setCards] = useState([]);
// Текущий статус игры
Expand Down Expand Up @@ -69,6 +73,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
setStatus(STATUS_IN_PROGRESS);
}
function resetGame() {
setTries(3);
setGameStartDate(null);
setGameEndDate(null);
setTimer(getTimerValue(null, null));
Expand All @@ -82,6 +87,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
* - "Игрок проиграл", если на поле есть две открытые карты без пары
* - "Игра продолжается", если не случилось первых двух условий
*/

const openCard = clickedCard => {
// Если карта уже открыта, то ничего не делаем
if (clickedCard.open) {
Expand Down Expand Up @@ -126,8 +132,31 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
const playerLost = openCardsWithoutPair.length >= 2;

// "Игрок проиграл", т.к на поле есть две открытые карты без пары

if (playerLost) {
finishGame(STATUS_LOST);
if (isEasyMode) {
setTries(tries - 1);
setTimeout(() => {
setCards(
cards.reduce((accum, card) => {
if (card.id === clickedCard.id) {
return [...accum, { ...card, open: false }];
}
return [...accum, card];
}, []),
);
setCards(
cards.reduce((accum, card) => {
const firstCard = openCardsWithoutPair.find(el => el.id !== clickedCard.id);
if (card.id === firstCard.id) {
return [...accum, { ...card, open: false }];
}
return [...accum, card];
}, []),
);
}, 1000);
}
if (!isEasyMode || tries === 1) finishGame(STATUS_LOST);
return;
}

Expand Down Expand Up @@ -196,6 +225,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
)}
</div>
{status === STATUS_IN_PROGRESS ? <Button onClick={resetGame}>Начать заново</Button> : null}
{isEasyMode ? <span className={styles.quantity}>Количество попыток: {tries}</span> : ""}
</div>

<div className={styles.cards}>
Expand All @@ -214,6 +244,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
<div className={styles.modalContainer}>
<EndGameModal
isWon={status === STATUS_WON}
isHardMode = {isHardMode}
gameDurationSeconds={timer.seconds}
gameDurationMinutes={timer.minutes}
onClick={resetGame}
Expand Down
6 changes: 6 additions & 0 deletions src/components/Cards/Cards.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,9 @@

margin-bottom: -12px;
}

.quantity {
font-size: 16px;
font-family: StratosSkyeng;
color: white
}
69 changes: 63 additions & 6 deletions src/components/EndGameModal/EndGameModal.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,44 @@
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 { useEasyContext } from "../../context/useContext";
import { useState } from "react";
import { createLeader } from "../../api/Api";

export function EndGameModal({ isWon, isHardMode, gameDurationSeconds, gameDurationMinutes, onClick }) {
const { isEasyMode } = useEasyContext();
const isLeader = isWon && isHardMode && !isEasyMode;
const timeLeader = gameDurationMinutes * 60 + gameDurationSeconds;

const [error, setError] = useState(null);
const [leaderName, setLeaderName] = useState("");
const [isDisabled, setIsDisabled] = useState(false);

function changeDisable() {
setIsDisabled(!isDisabled);
}

function handleInputChange(e) {
setLeaderName(e.target.value);
}

async function HandleNewLeader() {
if (leaderName.trim() === "") {
setError("Введите имя");
return;
}
await createLeader(leaderName, timeLeader);
changeDisable();
}

function onKeyDown(e) {
if (e.key === "Enter") {
HandleNewLeader();
}
}

export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick }) {
const title = isWon ? "Вы победили!" : "Вы проиграли!";

const imgSrc = isWon ? celebrationImageUrl : deadImageUrl;
Expand All @@ -15,13 +48,37 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes,
return (
<div className={styles.modal}>
<img className={styles.image} src={imgSrc} alt={imgAlt} />
<h2 className={styles.title}>{title}</h2>
{!isLeader && <h2 className={styles.title}>{title}</h2>}

{isLeader && (
<div className={styles.leaderText}>
<h2 className={styles.title}>Вы попали на Лидерборд!</h2>
<input
className={styles.placeholder}
type="text"
value={leaderName.name}
onChange={handleInputChange}
name="name"
placeholder="Пользователь"
onKeyDown={onKeyDown}
/>
</div>
)}
{error && <p className={styles.error}>{error}</p>}

<button className={styles.buttonLeader} onClick={HandleNewLeader} disabled={isDisabled}>
Добавить
</button>
<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={onClick}>Играть снова</Button>
{isLeader && (
<Link className={styles.toLeaderboard} to="/leaderboard">
Перейти к лидерборду
</Link>
)}
</div>
);
}
64 changes: 62 additions & 2 deletions src/components/EndGameModal/EndGameModal.module.css
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
.modal {
width: 480px;
height: 459px;
max-height: 634px;
border-radius: 12px;
background: #c2f5ff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding-bottom: 48px;
padding-top: 40px;
}

.image {
Expand All @@ -27,6 +29,27 @@
margin-bottom: 28px;
}

.leaderText {
text-align: center
}

.placeholder {
border-radius: 10px;
border: none;
background: rgb(255, 255, 255);
width: 276px;
height: 45px;
left: 374px;
top: 334px;
/* margin-bottom: 28px; */
color: black;
font-size: 24px;
font-weight: 400;
line-height: 32px;
letter-spacing: 0%;
text-align: center;
}

.description {
color: #000;
font-variant-numeric: lining-nums proportional-nums;
Expand All @@ -35,7 +58,7 @@
font-style: normal;
font-weight: 400;
line-height: 32px;

margin-top: 28px;
margin-bottom: 10px;
}

Expand All @@ -49,3 +72,40 @@

margin-bottom: 40px;
}

.toLeaderboard {
font-size: 18px;
color: #565EEF;
font-family: StratosSkyeng;
margin-top: 18px;
text-decoration-line: underline;
line-height: 32px;
}

.error {
margin-bottom: 20px;
font-size: 18px;
color: red;
font-family: StratosSkyeng;
}

.buttonLeader {
border-radius: 8px;
margin-top: 10px;
border: none;
width: 150px;
height: 30px;
font-family: StratosSkyeng;
font-size: 18px;
background: rgb(255, 255, 255);
color: rgba(80, 75, 75, 0.675);
}

.buttonLeader:hover {
color: #000;
cursor: pointer;
}

.buttonLeader:disabled {
color: lightgrey;
}
9 changes: 9 additions & 0 deletions src/context/context.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createContext, useState } from "react";

export const EasyContext = createContext(true);

export const EasyProvider = ({ children }) => {
const [isEasyMode, setEasyMode] = useState(false);
return <EasyContext.Provider value={{ isEasyMode, setEasyMode }}>{children}</EasyContext.Provider>;
};

6 changes: 6 additions & 0 deletions src/context/useContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { useContext } from "react"
import { EasyContext } from "./context"

export const useEasyContext = () => {
return useContext(EasyContext);
}
5 changes: 4 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import ReactDOM from "react-dom/client";
import "./index.css";
import { RouterProvider } from "react-router-dom";
import { router } from "./router";
import { EasyProvider } from "./context/context";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<RouterProvider router={router}></RouterProvider>
<EasyProvider>
<RouterProvider router={router}></RouterProvider>
</EasyProvider>
</React.StrictMode>,
);
Loading