Skip to content
Open

ф #74

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
1 change: 1 addition & 0 deletions .prettierrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ module.exports = {
bracketSpacing: true,
arrowParens: "avoid",
htmlWhitespaceSensitivity: "ignore",
endOfLine: 'auto'
};
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 проверку кода, эта же команда запускается перед каждым коммитом.
Если не получается закоммитить, попробуйте запустить эту команду и исправить все ошибки и предупреждения.

### Оценочное время выполнения

20 часов

### Фактическое время выполнения
67 changes: 58 additions & 9 deletions src/components/Cards/Cards.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
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 { LightContext } from "../../context/easyMode";
// import { useNavigate } from "react-router-dom";

// Игра закончилась
const STATUS_LOST = "STATUS_LOST";
Expand Down Expand Up @@ -41,8 +43,12 @@ function getTimerValue(startDate, endDate) {
* previewSeconds - сколько секунд пользователь будет видеть все карты открытыми до начала игры
*/
export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
const { isLight, tries, setTries } = useContext(LightContext);
// В cards лежит игровое поле - массив карт и их состояние открыта\закрыта
const [cards, setCards] = useState([]);

const [playerLost, setPlayerLost] = useState(false);

// Текущий статус игры
const [status, setStatus] = useState(STATUS_PREVIEW);

Expand All @@ -68,7 +74,12 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
setTimer(getTimerValue(startDate, null));
setStatus(STATUS_IN_PROGRESS);
}
// const navigate = useNavigate();

function resetGame() {
// navigate("/");
setTries(isLight ? 3 : 1);
setPlayerLost(false);
setGameStartDate(null);
setGameEndDate(null);
setTimer(getTimerValue(null, null));
Expand All @@ -77,16 +88,26 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {

/**
* Обработка основного действия в игре - открытие карты.
* После открытия карты игра может пепереходит в следующие состояния
* После открытия карты игра может переходить в следующие состояния
* - "Игрок выиграл", если на поле открыты все карты
* - "Игрок проиграл", если на поле есть две открытые карты без пары
* - "Игра продолжается", если не случилось первых двух условий
*/

useEffect(() => {
if (tries === 0) setPlayerLost(true);
}, [tries, playerLost]);

useEffect(() => {
if (playerLost) finishGame(STATUS_LOST);
}, [playerLost]);

const openCard = clickedCard => {
// Если карта уже открыта, то ничего не делаем
if (clickedCard.open) {
return;
}

// Игровое поле после открытия кликнутой карты
const nextCards = cards.map(card => {
if (card.id !== clickedCard.id) {
Expand Down Expand Up @@ -123,13 +144,37 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
return false;
});

const playerLost = openCardsWithoutPair.length >= 2;
function tryLost() {
if (openCardsWithoutPair.length === 2) {
setTries(tries - 1);
setTimeout(() => {
setCards(
cards.reduce((acc, card) => {
if (card.id === clickedCard.id) {
return [...acc, { ...card, open: false }];
}
return [...acc, card];
}, []),
);
setCards(
cards.reduce((acc, card) => {
const previousCard = openCardsWithoutPair.find(item => item.id !== clickedCard.id);
if (card.id === previousCard.id) {
return [...acc, { ...card, open: false }];
}
return [...acc, card];
}, []),
);
}, 1000);
}
}
tryLost();

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

// ... игра продолжается
};
Expand Down Expand Up @@ -195,9 +240,13 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
</>
)}
</div>
{status === STATUS_IN_PROGRESS ? <Button onClick={resetGame}>Начать заново</Button> : null}
{status === STATUS_IN_PROGRESS ? (
<div className="tries">
<Button onClick={resetGame}>Начать заново</Button>
{isLight && <p className={styles.tries}>Осталось попыток: {tries}</p>}
</div>
) : null}
</div>

<div className={styles.cards}>
{cards.map(card => (
<Card
Expand Down
10 changes: 7 additions & 3 deletions src/components/Cards/Cards.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,13 @@
.header {
display: flex;
justify-content: space-between;
align-items: end;
align-items: flex-end;
margin-bottom: 35px;
}

.timer {
display: flex;
align-items: end;

align-items: flex-end;
color: #fff;
font-family: StratosSkyeng;
font-size: 64px;
Expand Down Expand Up @@ -70,3 +69,8 @@

margin-bottom: -12px;
}

.tries {
color: #fff;
margin-left: 50px;
}
14 changes: 14 additions & 0 deletions src/context/easyMode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createContext, useEffect, useState } from "react";

export const LightContext = createContext();

export const LightProvider = ({ children }) => {
const [isLight, setIsLight] = useState(true);
const [tries, setTries] = useState(3);

useEffect(() => {
isLight ? setTries(3) : setTries(1);
}, [isLight]);

return <LightContext.Provider value={{ isLight, setIsLight, tries, setTries }}>{children}</LightContext.Provider>;
};
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 { LightProvider } from "./context/easyMode";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<RouterProvider router={router}></RouterProvider>
<LightProvider>
<RouterProvider router={router}></RouterProvider>
</LightProvider>
</React.StrictMode>,
);
13 changes: 13 additions & 0 deletions src/pages/SelectLevelPage/SelectLevelPage.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Link } from "react-router-dom";
import styles from "./SelectLevelPage.module.css";
import { useContext } from "react";
import { LightContext } from "../../context/easyMode";

export function SelectLevelPage() {
const { isLight, setIsLight } = useContext(LightContext);
return (
<div className={styles.container}>
<div className={styles.modal}>
Expand All @@ -23,6 +26,16 @@ export function SelectLevelPage() {
</Link>
</li>
</ul>
<label className={styles.checkboxLabel}>
<input
type="checkbox"
className={styles.checkbox}
name="checkbox"
checked={isLight}
onChange={() => setIsLight(!isLight)}
/>
Easy mode
</label>
</div>
</div>
);
Expand Down
42 changes: 42 additions & 0 deletions src/pages/SelectLevelPage/SelectLevelPage.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,45 @@
.levelLink:visited {
color: #0080c1;
}

.checkbox {
position: relative;
width: 24px;
height: 13px;
border-radius: 100px;
background: #0080c1;
outline: none;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;

&::before {
content: "";
position: absolute;
top: 1px;
left: 1px;
width: 11px;
height: 11px;
border-radius: 50%;
background-color: #fff;
transition: 0.5s;
}
&:checked::before {
left: 12px;
background: #fff;
}
&:checked {
background-color: #046d2c;
}
}

.checkboxLabel {
color: #004980;
text-align: center;
font-variant-numeric: lining-nums proportional-nums;
font-family: StratosSkyeng;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: 48px;
}