Skip to content
Open
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
4 changes: 2 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"plugins": ["prettier"],
"rules": {
"camelcase": ["error", { "properties": "never" }],
"prettier/prettier": "error",
"prettier/prettier": ["error", { "endOfLine": "auto" }],
"eqeqeq": ["error", "always"],
"no-unused-vars": ["error"]
}
}
}
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,47 @@
# Домашняя работа 1"

Добавление нового режима: реализовать режим, при котором игра завершается после трех ошибок.

Включение режима: режим должен включаться с помощью чек-бокса на странице выбора уровня.

Счетчик ошибок: нужно добавить вывод количества оставшихся попыток на игровой экран («Осталось 3 попытки» и так далее).

Отображение: внешний вид и расположение элементов остаются на ваше усмотрение, но выбор должен быть логически обоснован.

Конец игры: при достижении трех ошибок должно появляться модальное окно о завершении игры, его можно взять из макета.

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




# Домашняя работа 2"
Создание страницы "Лидерборд": Требуется разработать новую страницу в игре, которая будет носить название "Лидерборд". Формат отображения нужно взять из макета.

Отображение “Лидерборда”: На странице должны отображаться результаты пользователей в порядке возрастания по времени, затраченному на успешное прохождение игры.

Результат игры: Если результат игры проходит в "Лидерборд по ТОП-3, то в модальном окне нужно запросить имя пользователя и указать затраченное на игру время. Формат отображения нужно взять из макета.

Если пользователь не проходит в “Лидерборд”, однако он выиграл, то показываем ему модальное окно победы (уже реализовано в приложении).
Отображение модального окна проигрыша остается без изменений.
Страница выбора сложности: На страницу выбора сложности нужно добавить ссылку, которая ведет на страницу лидерборда. Отображение нужно взять из макета.

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



# Курсовая работа"
Часть 1. Создание суперсил
Нужно создать и внедрить систему суперсил, которые будут действовать как упрощения или подсказки для игрока. Например, если игрок открывает неправильную карту, суперсила будет автоматически открывать соседнюю правильную пару. Подробная информация — в техническом задании. Для зачтения работы достаточно реализовать минимум одну из суперсил.

Часть 2. Доработка лидерборда
Добавьте к каждой позиции в лидерборде ачивки. Например, если пользователь прошел игру без использования суперсил или в сложном режиме. Подробная информация — в техническом задании.

Ожидаемое время выполнения работы: 8 часа
Фактическое время выполнения работы: часов

# MVP Карточная игра "Мемо"

В этом репозитории реализован MVP карточкой игры "Мемо" по [тех.заданию](./docs/mvp-spec.md)
Expand Down
7 changes: 4 additions & 3 deletions docs/mvp-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
Количество карточек для каждого уровня сложности можете назначать и свои или выбрать готовый пресет.

Предлагаем следующее пресеты:
- Легкий уровень - 6 карточек (3 пары)
- Средний уровень - 12 карточек (6 пар)
- Сложный уровень - 18 карточек (9 пар)

- Легкий уровень - 6 карточек (3 пары)
- Средний уровень - 12 карточек (6 пар)
- Сложный уровень - 18 карточек (9 пар)

Как только уровень сложности выбран, игроку показывается на игровой поле.

Expand Down
26 changes: 26 additions & 0 deletions src/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const baseHost = "https://wedev-api.sky.pro/api/v2/leaderboard";
//Получаем список лидеров
export async function getLeaders() {
const response = await fetch(baseHost);
if (!response.status === 200) {
throw new Error("Ошибка");
}
const data = await response.json();
return data;
}
//Добавляем лидера в список
export async function postLeader({ name, time, achievements }) {
const response = await fetch(baseHost, {
method: "POST",
body: JSON.stringify({
name: name,
time: time,
achievements: achievements,
}),
});
if (response.status === 400) {
throw new Error("Полученные данные не в формате JSON");
}
const data = await response.json();
return data;
}
9 changes: 9 additions & 0 deletions src/components/Achievment/Achievment.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import unsuper1 from "./unsuper1.svg";
import super1 from "./super1.svg";

export default function Achievment(isAchievment) {
console.log(Object.values(isAchievment)[2].isAchievment);
return (
<>{Object.values(isAchievment)[2].isAchievment ? <img src={super1} alt="" /> : <img src={unsuper1} alt="" />}</>
);
}
17 changes: 17 additions & 0 deletions src/components/Achievment/super1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions src/components/Achievment/unsuper1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 46 additions & 10 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 { useEasyMode } from "../../context/hooks/useEasyMode";
import superImg from "./super.png";

// Игра закончилась
const STATUS_LOST = "STATUS_LOST";
Expand Down Expand Up @@ -41,6 +43,9 @@ function getTimerValue(startDate, endDate) {
* previewSeconds - сколько секунд пользователь будет видеть все карты открытыми до начала игры
*/
export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
const [achievements, setAchievements] = useState([]);
const { easyMode } = useEasyMode();
const [countTry, setCountTry] = useState(null);
// В cards лежит игровое поле - массив карт и их состояние открыта\закрыта
const [cards, setCards] = useState([]);
// Текущий статус игры
Expand All @@ -63,6 +68,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
}
function startGame() {
const startDate = new Date();
setCountTry(easyMode ? 3 : 1);
setGameEndDate(null);
setGameStartDate(startDate);
setTimer(getTimerValue(startDate, null));
Expand All @@ -82,17 +88,32 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
* - "Игрок проиграл", если на поле есть две открытые карты без пары
* - "Игра продолжается", если не случилось первых двух условий
*/
function alohomora() {
if (achievements.includes(1)) {
alert("Подсказкой можно воспользоваться только 1 раз");
return;
}
let closedCards = cards.filter(card => !card.open);
closedCards = shuffle(closedCards);
closedCards[0].open = true;
closedCards.forEach(card => {
if (card.suit === closedCards[0].suit && card.rank === closedCards[0].rank) {
card.open = true;
}
});
setAchievements([1]);
}

const openCard = clickedCard => {
// Если карта уже открыта, то ничего не делаем
if (clickedCard.open) {
return;
}
// Игровое поле после открытия кликнутой карты
const nextCards = cards.map(card => {
let nextCards = cards.map(card => {
if (card.id !== clickedCard.id) {
return card;
}

return {
...card,
open: true,
Expand Down Expand Up @@ -123,15 +144,23 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
return false;
});

const playerLost = openCardsWithoutPair.length >= 2;
if (openCardsWithoutPair.length >= 2) {
setTimeout(() => {
openCardsWithoutPair[1].open = false;
openCardsWithoutPair[0].open = false;
}, 1000);
setCountTry(countTry - 1);

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

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

// ... игра продолжается
}
};

const isGameEnded = status === STATUS_LOST || status === STATUS_WON;
Expand Down Expand Up @@ -195,7 +224,13 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
</>
)}
</div>
{status === STATUS_IN_PROGRESS ? <Button onClick={resetGame}>Начать заново</Button> : null}
{status === STATUS_IN_PROGRESS ? (
<>
<div className={styles.count}>{easyMode && `Осталось попыток ${countTry}`}</div>
<img onClick={alohomora} src={superImg} alt="super" />
<Button onClick={resetGame}>Начать заново</Button>
</>
) : null}
</div>

<div className={styles.cards}>
Expand All @@ -216,6 +251,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) {
isWon={status === STATUS_WON}
gameDurationSeconds={timer.seconds}
gameDurationMinutes={timer.minutes}
achievements={achievements}
onClick={resetGame}
/>
</div>
Expand Down
12 changes: 11 additions & 1 deletion src/components/Cards/Cards.module.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.container {
width: 672px;
width: 700px;
margin: 0 auto;
padding: 26px;
padding-top: 22px;
Expand All @@ -11,6 +11,7 @@
flex-direction: row;
gap: 10px;
flex-wrap: wrap;
justify-content: space-between;
}

.modalContainer {
Expand Down Expand Up @@ -70,3 +71,12 @@

margin-bottom: -12px;
}

.count {
color: #fff;
font-variant-numeric: lining-nums proportional-nums;
font-family: StratosSkyeng;
font-size: 16px;
font-style: normal;
margin-right: 10px;
}
Binary file added src/components/Cards/super.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 66 additions & 5 deletions src/components/EndGameModal/EndGameModal.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,51 @@
import styles from "./EndGameModal.module.css";

import { Button } from "../Button/Button";

import deadImageUrl from "./images/dead.png";
import celebrationImageUrl from "./images/celebration.png";
import { useState, useEffect } from "react";
import { postLeader } from "../../api";
import { useLeaders } from "../../context/hooks/useLeaders";
import { Link } from "react-router-dom";

export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, achievements, onClick }) {
const [newLeader, setNewLeader] = useState({
name: "",
time: gameDurationSeconds,
achievements: achievements,
});
const { setLeaders, leaders } = useLeaders();
const [btnDisabled, setBtnDisabled] = useState(false);

export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes, onClick }) {
const title = isWon ? "Вы победили!" : "Вы проиграли!";
useEffect(() => {
setLeaders(leaders.sort((a, b) => +a.time - +b.time));
}, [leaders, setLeaders]);

const handleInputChange = e => {
const { name, value } = e.target;
setNewLeader({
...newLeader,
[name]: value,
});
};
const handleFormSubmit = e => {
e.preventDefault();
postLeader({ name: newLeader.name, time: newLeader.time, achievements: newLeader.achievements })
.then(leaders => {
setLeaders(leaders.leaders);
setBtnDisabled(!btnDisabled);
// Добавьте код для очистки поля ввода
setNewLeader({ name: "", time: "", achievements: "" });
})
.catch(error => {
console.log(error);
alert(error);
});
};
let title = isWon ? "Вы победили!" : "Вы проиграли!";
if (gameDurationMinutes * 60 + gameDurationSeconds < leaders[2].time && isWon) {
title = "Вы попали на лидерборд!";
}
let isLeader = gameDurationMinutes * 60 + gameDurationSeconds < leaders[2].time && isWon;

const imgSrc = isWon ? celebrationImageUrl : deadImageUrl;

Expand All @@ -16,12 +55,34 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes,
<div className={styles.modal}>
<img className={styles.image} src={imgSrc} alt={imgAlt} />
<h2 className={styles.title}>{title}</h2>
{isLeader && (
<div className={styles.inputDiv}>
<input
className={styles.inputSave}
type="text"
name="name"
value={newLeader.name}
onChange={handleInputChange}
id="formTitle"
placeholder="Пользователь"
autoFocus=""
/>
<button className={styles.btnsave} onClick={handleFormSubmit} disabled={btnDisabled}>
Сохранить
</button>
</div>
)}
<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>
<div>
<Link to="/leaderboard">
<p>Перейти к лидерборду</p>
</Link>
</div>
</div>
);
}
Loading