add match menu, continue/new game mods
This commit is contained in:
parent
69c1ca8359
commit
1a778e680d
@ -1,13 +1,13 @@
|
||||
import AppRoutes from 'components/AppRoutes';
|
||||
import { MatchGameProvider } from 'context/MatchGameContext';
|
||||
import React from 'react';
|
||||
import cls from './App.module.scss';
|
||||
import MatchGame from 'components/MatchGame/MatchGame';
|
||||
import { MatchGameProvider } from 'context/MatchGameContext';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className={cls.App}>
|
||||
<MatchGameProvider>
|
||||
<MatchGame/>
|
||||
<AppRoutes/>
|
||||
</MatchGameProvider>
|
||||
</div>
|
||||
);
|
||||
|
||||
29
src/components/AppRoutes.tsx
Normal file
29
src/components/AppRoutes.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import MatchGame from 'components/MatchGame/MatchGame';
|
||||
import MatchMenu from 'components/MatchMenu/MatchMenu';
|
||||
import { useMatchGame } from 'context/MatchGameContext';
|
||||
import React from 'react';
|
||||
|
||||
const AppRoutes = () => {
|
||||
const {currentMenuItem} = useMatchGame();
|
||||
|
||||
function getRootComponent () {
|
||||
switch (currentMenuItem) {
|
||||
case 'menu':
|
||||
return <MatchMenu/>;
|
||||
|
||||
case 'continue-game':
|
||||
return <MatchGame isNewGame={false}/>;
|
||||
|
||||
case 'new-game':
|
||||
return <MatchGame isNewGame={true}/>;
|
||||
|
||||
|
||||
default: return <MatchMenu/>;
|
||||
}
|
||||
}
|
||||
|
||||
return getRootComponent();
|
||||
|
||||
};
|
||||
|
||||
export default AppRoutes;
|
||||
@ -11,6 +11,7 @@ type BottomPanelProps = {
|
||||
retryGame: () => void;
|
||||
isFinishedGame?: boolean;
|
||||
shuffleCards: () => void;
|
||||
isNewGame: boolean;
|
||||
}
|
||||
|
||||
type PanelState = 'start' | 'retry';
|
||||
@ -19,7 +20,7 @@ const BottomPanel: React.FC<BottomPanelProps> = (props) => {
|
||||
const [isTimerStarted, setIsTimerStarted] = useState(false);
|
||||
const [isTimerStopped, setIsTimerStopped] = useState(false);
|
||||
const [panelState, setPanelState] = useState<PanelState>('start');
|
||||
const {changeGameTime} = useMatchGame();
|
||||
const {gameTime, changeGameTime, setCurrentMenuItem, setIsGameStarted} = useMatchGame();
|
||||
|
||||
function onStartBtnClickHandler(event: React.MouseEvent<HTMLButtonElement>) {
|
||||
setIsTimerStarted(true);
|
||||
@ -27,12 +28,16 @@ const BottomPanel: React.FC<BottomPanelProps> = (props) => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setIsTimerStopped(true)
|
||||
setPanelState(props.isFinishedGame ? 'retry' : 'start')
|
||||
|
||||
if (props.isFinishedGame) {
|
||||
setIsTimerStopped(true);
|
||||
localStorage.removeItem('match-game:cards');
|
||||
}
|
||||
setPanelState(props.isFinishedGame ? 'retry' : 'start');
|
||||
}, [props.isFinishedGame])
|
||||
|
||||
function onRetryBtnClickHandler() {
|
||||
setCurrentMenuItem?.('new-game');
|
||||
|
||||
setIsTimerStarted(false);
|
||||
setIsTimerStopped(false);
|
||||
setPanelState('start');
|
||||
@ -43,6 +48,7 @@ const BottomPanel: React.FC<BottomPanelProps> = (props) => {
|
||||
function renderRetryStatePanel() {
|
||||
return (
|
||||
<>
|
||||
<Button onClick={goToMenu}>⇦</Button>
|
||||
<Button onClick={onRetryBtnClickHandler}>RETRY</Button>
|
||||
</>
|
||||
)
|
||||
@ -52,12 +58,21 @@ const BottomPanel: React.FC<BottomPanelProps> = (props) => {
|
||||
props.shuffleCards?.();
|
||||
}
|
||||
|
||||
function goToMenu() {
|
||||
setIsGameStarted?.(false);
|
||||
setCurrentMenuItem?.('menu');
|
||||
}
|
||||
|
||||
function renderStartStatePanel() {
|
||||
return isTimerStarted
|
||||
? <Timer isStarted={isTimerStarted} startTime={0} isFinished={isTimerStopped} changeTime={changeGameTime}/>
|
||||
? <>
|
||||
<Button onClick={goToMenu}>⇦</Button>
|
||||
<Timer isStarted={isTimerStarted} startTime={gameTime || 0} isFinished={isTimerStopped} changeTime={changeGameTime}/>
|
||||
</>
|
||||
: <>
|
||||
<Button onClick={onStartBtnClickHandler}>START</Button>
|
||||
<Button onClick={onShuffleBtnClickHandler}>SHUFFLE</Button>
|
||||
<Button onClick={goToMenu}>⇦</Button>
|
||||
<Button onClick={onStartBtnClickHandler}>{props.isNewGame ? 'START' : 'Continue'}</Button>
|
||||
{props.isNewGame && <Button onClick={onShuffleBtnClickHandler}>SHUFFLE</Button>}
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@ -13,8 +13,9 @@ type CardProps = {
|
||||
|
||||
type CardsFieldProps = {
|
||||
cards: CardProps[];
|
||||
isGameStarted: boolean;
|
||||
isGameStarted?: boolean;
|
||||
finishGameCallback: () => void;
|
||||
isNewGame: boolean;
|
||||
}
|
||||
|
||||
type CardHandlerType =
|
||||
@ -28,14 +29,23 @@ type CurrentCardPareType = {
|
||||
second: CardComponentProps | null;
|
||||
}
|
||||
|
||||
const CardsField: React.FC<CardsFieldProps> = ({cards, isGameStarted, finishGameCallback}) => {
|
||||
const CardsField: React.FC<CardsFieldProps> = ({cards, isGameStarted = false, finishGameCallback, isNewGame}) => {
|
||||
// TODO use useReducer
|
||||
const [cardsState, setCardsState] = useState(cards.map(el => createCard(el)));
|
||||
const [currentCard, setCurrentCard] = useState<CardComponentProps | null>(null)
|
||||
const [cardsState, setCardsState] = useState<CardComponentProps[]>(getInitialConfig(isNewGame));
|
||||
const [currentCardPare, setCurrentCardPare] = useState<CurrentCardPareType>({first: null, second: null})
|
||||
|
||||
const {addMistakesCount} = useMatchGame();
|
||||
|
||||
function getInitialConfig(isNewGame: boolean): CardComponentProps[] {
|
||||
if (!isNewGame) {
|
||||
const config = localStorage.getItem('match-game:cards');
|
||||
|
||||
if (config) return (JSON.parse(config) as CardComponentProps[]).map(el => ({...el, disabled: true, toggled: false}));
|
||||
}
|
||||
|
||||
return cards.map(el => createCard(el))
|
||||
}
|
||||
|
||||
function matchHandler(prev: CardComponentProps, card: CardComponentProps) {
|
||||
setCardsState(prevState => prevState.map(el => {
|
||||
if (el.id === card.id || el.id === prev.id) return {...el, disabled: true, isMatched: true}
|
||||
@ -89,7 +99,9 @@ const CardsField: React.FC<CardsFieldProps> = ({cards, isGameStarted, finishGame
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isAllCardsMatched(cardsState)) setTimeout(finishGameCallback, 800);
|
||||
if (isAllCardsMatched(cardsState)) {
|
||||
setTimeout(finishGameCallback, 800);
|
||||
}
|
||||
}, [cardsState])
|
||||
|
||||
useEffect(() => {
|
||||
@ -101,12 +113,12 @@ const CardsField: React.FC<CardsFieldProps> = ({cards, isGameStarted, finishGame
|
||||
}, [currentCardPare])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isGameStarted) return;
|
||||
setCardsState(prev => (prev.map(el => ({...el, disabled: false, toggled: false}))));
|
||||
setCardsState(prev => (prev.map(el => ({...el, disabled: !isGameStarted, toggled: !isGameStarted && isNewGame}))));
|
||||
if (isGameStarted) localStorage.setItem('match-game:cards', JSON.stringify(cardsState));
|
||||
}, [isGameStarted])
|
||||
|
||||
useEffect(() => {
|
||||
setCardsState(cards.map(el => createCard(el)))
|
||||
if (isNewGame) setCardsState(cards.map(el => createCard(el)))
|
||||
}, [cards])
|
||||
|
||||
function createCard (card: CardProps): CardComponentProps {
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
import React from 'react';
|
||||
import { getTimeString } from 'src/utils';
|
||||
|
||||
import cls from './FinishedField.module.scss';
|
||||
|
||||
type FinishedFieldProps = {
|
||||
mistakesCount?: number;
|
||||
gameTime?: string;
|
||||
gameTime?: number;
|
||||
}
|
||||
|
||||
const FinishedField: React.FC<FinishedFieldProps> = ({mistakesCount, gameTime}) => {
|
||||
return (
|
||||
<div className={cls.FinishedField}>
|
||||
<h1>GAME IS FINISHED !</h1>
|
||||
<p>Время: <span>{gameTime}</span></p>
|
||||
<p>Время: <span>{getTimeString(new Date(gameTime || 0))}</span></p>
|
||||
<p>Количество ошибок: <span>{mistakesCount}</span></p>
|
||||
<p>Сложность: <span>difficultyLevel</span></p>
|
||||
<p>Размер поля: <span>fieldSize</span></p>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import BottomPanel from 'components/BottomPanel/BottomPanel';
|
||||
import CardsField from 'components/CardsField/CardsField';
|
||||
@ -9,7 +9,11 @@ import { useMatchGame } from 'context/MatchGameContext';
|
||||
|
||||
import cls from './MatchGame.module.scss'
|
||||
|
||||
const MatchGame: React.FC = () => {
|
||||
interface MatchGameProps {
|
||||
isNewGame: boolean;
|
||||
}
|
||||
|
||||
const MatchGame: React.FC<MatchGameProps> = ({isNewGame}) => {
|
||||
// TODO replace to CardsField
|
||||
const [cards, setCards] = useState<any[]>(randomizeElementInArray([
|
||||
{code: 'react', imageURL: cardImages.react},
|
||||
@ -20,25 +24,34 @@ const MatchGame: React.FC = () => {
|
||||
{code: 'js', imageURL: cardImages.js},
|
||||
]));
|
||||
|
||||
const [isGameFinished, setGameFinish] = useState<boolean>(false);
|
||||
const [isGameStarted, setGameStart] = useState<boolean>(false);
|
||||
const {gameTime,
|
||||
mistakesCount,
|
||||
resetGameProgress,
|
||||
setIsGameStarted,
|
||||
setIsGameFinished,
|
||||
isGameFinished,
|
||||
isGameStarted
|
||||
} = useMatchGame();
|
||||
|
||||
const {gameTime, mistakesCount, resetGameProgress} = useMatchGame();
|
||||
useEffect(() => {
|
||||
if (isNewGame) resetGameProgress?.();
|
||||
}, [isNewGame])
|
||||
|
||||
function startGame() {
|
||||
setGameStart(true);
|
||||
setIsGameStarted?.(true);
|
||||
}
|
||||
|
||||
function retryGame() {
|
||||
setGameFinish(false);
|
||||
setGameStart(false);
|
||||
setCards(prev => randomizeElementInArray(prev.map(el => ({...el, disabled: false, isMatched: false, toggled: true}))))
|
||||
setIsGameFinished?.(false);
|
||||
setIsGameStarted?.(false);
|
||||
setCards(prev => randomizeElementInArray(prev.map(el => ({...el, disabled: true, isMatched: false, toggled: true}))))
|
||||
|
||||
resetGameProgress?.();
|
||||
}
|
||||
|
||||
function finishGame() {
|
||||
setGameFinish(true);
|
||||
setIsGameFinished?.(true);
|
||||
localStorage.removeItem('match-game:cards');
|
||||
}
|
||||
|
||||
// TODO replace to CardsField
|
||||
@ -60,10 +73,10 @@ const MatchGame: React.FC = () => {
|
||||
{
|
||||
isGameFinished
|
||||
? <FinishedField gameTime={gameTime} mistakesCount={mistakesCount}/>
|
||||
: <CardsField cards={cards} isGameStarted={isGameStarted} finishGameCallback={finishGame}/>
|
||||
: <CardsField cards={cards} isGameStarted={isGameStarted} finishGameCallback={finishGame} isNewGame={isNewGame}/>
|
||||
}
|
||||
</div>
|
||||
<BottomPanel startGame={startGame} isFinishedGame={isGameFinished} retryGame={retryGame} shuffleCards={shuffleCards}/>
|
||||
<BottomPanel startGame={startGame} isFinishedGame={isGameFinished} retryGame={retryGame} shuffleCards={shuffleCards} isNewGame={isNewGame}/>
|
||||
</>
|
||||
|
||||
);
|
||||
|
||||
0
src/components/MatchMenu/MatchMenu.module.scss
Normal file
0
src/components/MatchMenu/MatchMenu.module.scss
Normal file
33
src/components/MatchMenu/MatchMenu.tsx
Normal file
33
src/components/MatchMenu/MatchMenu.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { MenuItem, useMatchGame } from 'context/MatchGameContext';
|
||||
import React from 'react';
|
||||
|
||||
type GameMenuProps = {
|
||||
|
||||
}
|
||||
|
||||
const MatchMenu: React.FC<GameMenuProps> = () => {
|
||||
const {setCurrentMenuItem} = useMatchGame();
|
||||
|
||||
function clickItemHandler(item: MenuItem) {
|
||||
setCurrentMenuItem?.(item);
|
||||
}
|
||||
|
||||
function canContinueGame() {
|
||||
return !!localStorage.getItem('match-game:cards');
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ul>
|
||||
{
|
||||
canContinueGame() ? <li onClick={() => clickItemHandler('continue-game')}>Continue</li> : null
|
||||
}
|
||||
<li onClick={() => clickItemHandler('new-game')}>New game</li>
|
||||
<li onClick={() => clickItemHandler('options')}>Options</li>
|
||||
<li onClick={() => clickItemHandler('exit')}>Exit</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MatchMenu;
|
||||
@ -1,31 +1,44 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { getTimeString } from 'src/utils';
|
||||
|
||||
type MatchGameContextType = {
|
||||
export type MenuItem = 'menu' | 'new-game' | 'continue-game' | 'options' | 'exit';
|
||||
|
||||
interface MatchGameContextType {
|
||||
mistakesCount?: number;
|
||||
addMistakesCount?: () => void;
|
||||
gameTime?: string;
|
||||
gameTime?: number;
|
||||
changeGameTime?: (time: number) => void;
|
||||
resetGameProgress?: () => void;
|
||||
currentMenuItem?: MenuItem;
|
||||
setCurrentMenuItem?: (item: MenuItem) => void;
|
||||
isGameStarted?: boolean;
|
||||
setIsGameStarted?: (status: boolean) => void;
|
||||
isGameFinished?: boolean;
|
||||
setIsGameFinished?: (status: boolean) => void;
|
||||
}
|
||||
const MatchGameContext = React.createContext<MatchGameContextType>({});
|
||||
|
||||
export const useMatchGame = () => useContext(MatchGameContext);
|
||||
|
||||
export const MatchGameProvider: React.FC = ({children}) => {
|
||||
const [mistakesCount, setMistakesCount] = useState(0)
|
||||
const [gameTime, setGameTime] = useState('');
|
||||
const [gameTime, setGameTime] = useState(0);
|
||||
const [currentMenuItem, setCurrentMenuItem] = useState<MenuItem>('menu');
|
||||
const [isGameStarted, setIsGameStarted] = useState(false);
|
||||
const [isGameFinished, setIsGameFinished] = useState(false);
|
||||
|
||||
const addMistakesCount = () => {
|
||||
return setMistakesCount(prev => prev + 1)
|
||||
};
|
||||
|
||||
const changeGameTime = (time: number) => {
|
||||
setGameTime(getTimeString(new Date(time)));
|
||||
setGameTime(time);
|
||||
}
|
||||
|
||||
const resetGameProgress = () => {
|
||||
setMistakesCount(0);
|
||||
setGameTime(getTimeString(new Date(0)));
|
||||
setGameTime(0);
|
||||
setIsGameStarted(false);
|
||||
setIsGameFinished(false);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -34,7 +47,13 @@ export const MatchGameProvider: React.FC = ({children}) => {
|
||||
addMistakesCount,
|
||||
gameTime,
|
||||
changeGameTime,
|
||||
resetGameProgress
|
||||
resetGameProgress,
|
||||
currentMenuItem,
|
||||
setCurrentMenuItem,
|
||||
isGameStarted,
|
||||
setIsGameStarted,
|
||||
isGameFinished,
|
||||
setIsGameFinished
|
||||
}}>
|
||||
{children}
|
||||
</MatchGameContext.Provider>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user