add match menu, continue/new game mods

This commit is contained in:
Sergey Krylov 2022-07-25 11:45:39 +05:00
parent 69c1ca8359
commit 1a778e680d
9 changed files with 161 additions and 39 deletions

View File

@ -1,13 +1,13 @@
import AppRoutes from 'components/AppRoutes';
import { MatchGameProvider } from 'context/MatchGameContext';
import React from 'react'; import React from 'react';
import cls from './App.module.scss'; import cls from './App.module.scss';
import MatchGame from 'components/MatchGame/MatchGame';
import { MatchGameProvider } from 'context/MatchGameContext';
function App() { function App() {
return ( return (
<div className={cls.App}> <div className={cls.App}>
<MatchGameProvider> <MatchGameProvider>
<MatchGame/> <AppRoutes/>
</MatchGameProvider> </MatchGameProvider>
</div> </div>
); );

View 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;

View File

@ -11,6 +11,7 @@ type BottomPanelProps = {
retryGame: () => void; retryGame: () => void;
isFinishedGame?: boolean; isFinishedGame?: boolean;
shuffleCards: () => void; shuffleCards: () => void;
isNewGame: boolean;
} }
type PanelState = 'start' | 'retry'; type PanelState = 'start' | 'retry';
@ -19,7 +20,7 @@ const BottomPanel: React.FC<BottomPanelProps> = (props) => {
const [isTimerStarted, setIsTimerStarted] = useState(false); const [isTimerStarted, setIsTimerStarted] = useState(false);
const [isTimerStopped, setIsTimerStopped] = useState(false); const [isTimerStopped, setIsTimerStopped] = useState(false);
const [panelState, setPanelState] = useState<PanelState>('start'); const [panelState, setPanelState] = useState<PanelState>('start');
const {changeGameTime} = useMatchGame(); const {gameTime, changeGameTime, setCurrentMenuItem, setIsGameStarted} = useMatchGame();
function onStartBtnClickHandler(event: React.MouseEvent<HTMLButtonElement>) { function onStartBtnClickHandler(event: React.MouseEvent<HTMLButtonElement>) {
setIsTimerStarted(true); setIsTimerStarted(true);
@ -27,12 +28,16 @@ const BottomPanel: React.FC<BottomPanelProps> = (props) => {
} }
useEffect(() => { useEffect(() => {
setIsTimerStopped(true) if (props.isFinishedGame) {
setPanelState(props.isFinishedGame ? 'retry' : 'start') setIsTimerStopped(true);
localStorage.removeItem('match-game:cards');
}
setPanelState(props.isFinishedGame ? 'retry' : 'start');
}, [props.isFinishedGame]) }, [props.isFinishedGame])
function onRetryBtnClickHandler() { function onRetryBtnClickHandler() {
setCurrentMenuItem?.('new-game');
setIsTimerStarted(false); setIsTimerStarted(false);
setIsTimerStopped(false); setIsTimerStopped(false);
setPanelState('start'); setPanelState('start');
@ -43,6 +48,7 @@ const BottomPanel: React.FC<BottomPanelProps> = (props) => {
function renderRetryStatePanel() { function renderRetryStatePanel() {
return ( return (
<> <>
<Button onClick={goToMenu}>&#8678;</Button>
<Button onClick={onRetryBtnClickHandler}>RETRY</Button> <Button onClick={onRetryBtnClickHandler}>RETRY</Button>
</> </>
) )
@ -52,12 +58,21 @@ const BottomPanel: React.FC<BottomPanelProps> = (props) => {
props.shuffleCards?.(); props.shuffleCards?.();
} }
function goToMenu() {
setIsGameStarted?.(false);
setCurrentMenuItem?.('menu');
}
function renderStartStatePanel() { function renderStartStatePanel() {
return isTimerStarted return isTimerStarted
? <Timer isStarted={isTimerStarted} startTime={0} isFinished={isTimerStopped} changeTime={changeGameTime}/> ? <>
<Button onClick={goToMenu}>&#8678;</Button>
<Timer isStarted={isTimerStarted} startTime={gameTime || 0} isFinished={isTimerStopped} changeTime={changeGameTime}/>
</>
: <> : <>
<Button onClick={onStartBtnClickHandler}>START</Button> <Button onClick={goToMenu}>&#8678;</Button>
<Button onClick={onShuffleBtnClickHandler}>SHUFFLE</Button> <Button onClick={onStartBtnClickHandler}>{props.isNewGame ? 'START' : 'Continue'}</Button>
{props.isNewGame && <Button onClick={onShuffleBtnClickHandler}>SHUFFLE</Button>}
</> </>
} }

View File

@ -13,8 +13,9 @@ type CardProps = {
type CardsFieldProps = { type CardsFieldProps = {
cards: CardProps[]; cards: CardProps[];
isGameStarted: boolean; isGameStarted?: boolean;
finishGameCallback: () => void; finishGameCallback: () => void;
isNewGame: boolean;
} }
type CardHandlerType = type CardHandlerType =
@ -28,14 +29,23 @@ type CurrentCardPareType = {
second: CardComponentProps | null; second: CardComponentProps | null;
} }
const CardsField: React.FC<CardsFieldProps> = ({cards, isGameStarted, finishGameCallback}) => { const CardsField: React.FC<CardsFieldProps> = ({cards, isGameStarted = false, finishGameCallback, isNewGame}) => {
// TODO use useReducer // TODO use useReducer
const [cardsState, setCardsState] = useState(cards.map(el => createCard(el))); const [cardsState, setCardsState] = useState<CardComponentProps[]>(getInitialConfig(isNewGame));
const [currentCard, setCurrentCard] = useState<CardComponentProps | null>(null)
const [currentCardPare, setCurrentCardPare] = useState<CurrentCardPareType>({first: null, second: null}) const [currentCardPare, setCurrentCardPare] = useState<CurrentCardPareType>({first: null, second: null})
const {addMistakesCount} = useMatchGame(); 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) { function matchHandler(prev: CardComponentProps, card: CardComponentProps) {
setCardsState(prevState => prevState.map(el => { setCardsState(prevState => prevState.map(el => {
if (el.id === card.id || el.id === prev.id) return {...el, disabled: true, isMatched: true} 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(() => { useEffect(() => {
if (isAllCardsMatched(cardsState)) setTimeout(finishGameCallback, 800); if (isAllCardsMatched(cardsState)) {
setTimeout(finishGameCallback, 800);
}
}, [cardsState]) }, [cardsState])
useEffect(() => { useEffect(() => {
@ -101,12 +113,12 @@ const CardsField: React.FC<CardsFieldProps> = ({cards, isGameStarted, finishGame
}, [currentCardPare]) }, [currentCardPare])
useEffect(() => { useEffect(() => {
if (!isGameStarted) return; setCardsState(prev => (prev.map(el => ({...el, disabled: !isGameStarted, toggled: !isGameStarted && isNewGame}))));
setCardsState(prev => (prev.map(el => ({...el, disabled: false, toggled: false})))); if (isGameStarted) localStorage.setItem('match-game:cards', JSON.stringify(cardsState));
}, [isGameStarted]) }, [isGameStarted])
useEffect(() => { useEffect(() => {
setCardsState(cards.map(el => createCard(el))) if (isNewGame) setCardsState(cards.map(el => createCard(el)))
}, [cards]) }, [cards])
function createCard (card: CardProps): CardComponentProps { function createCard (card: CardProps): CardComponentProps {

View File

@ -1,17 +1,18 @@
import React from 'react'; import React from 'react';
import { getTimeString } from 'src/utils';
import cls from './FinishedField.module.scss'; import cls from './FinishedField.module.scss';
type FinishedFieldProps = { type FinishedFieldProps = {
mistakesCount?: number; mistakesCount?: number;
gameTime?: string; gameTime?: number;
} }
const FinishedField: React.FC<FinishedFieldProps> = ({mistakesCount, gameTime}) => { const FinishedField: React.FC<FinishedFieldProps> = ({mistakesCount, gameTime}) => {
return ( return (
<div className={cls.FinishedField}> <div className={cls.FinishedField}>
<h1>GAME IS FINISHED !</h1> <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>{mistakesCount}</span></p>
<p>Сложность: <span>difficultyLevel</span></p> <p>Сложность: <span>difficultyLevel</span></p>
<p>Размер поля: <span>fieldSize</span></p> <p>Размер поля: <span>fieldSize</span></p>

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useEffect, useState } from 'react';
import BottomPanel from 'components/BottomPanel/BottomPanel'; import BottomPanel from 'components/BottomPanel/BottomPanel';
import CardsField from 'components/CardsField/CardsField'; import CardsField from 'components/CardsField/CardsField';
@ -9,7 +9,11 @@ import { useMatchGame } from 'context/MatchGameContext';
import cls from './MatchGame.module.scss' import cls from './MatchGame.module.scss'
const MatchGame: React.FC = () => { interface MatchGameProps {
isNewGame: boolean;
}
const MatchGame: React.FC<MatchGameProps> = ({isNewGame}) => {
// TODO replace to CardsField // TODO replace to CardsField
const [cards, setCards] = useState<any[]>(randomizeElementInArray([ const [cards, setCards] = useState<any[]>(randomizeElementInArray([
{code: 'react', imageURL: cardImages.react}, {code: 'react', imageURL: cardImages.react},
@ -20,25 +24,34 @@ const MatchGame: React.FC = () => {
{code: 'js', imageURL: cardImages.js}, {code: 'js', imageURL: cardImages.js},
])); ]));
const [isGameFinished, setGameFinish] = useState<boolean>(false); const {gameTime,
const [isGameStarted, setGameStart] = useState<boolean>(false); mistakesCount,
resetGameProgress,
setIsGameStarted,
setIsGameFinished,
isGameFinished,
isGameStarted
} = useMatchGame();
const {gameTime, mistakesCount, resetGameProgress} = useMatchGame(); useEffect(() => {
if (isNewGame) resetGameProgress?.();
}, [isNewGame])
function startGame() { function startGame() {
setGameStart(true); setIsGameStarted?.(true);
} }
function retryGame() { function retryGame() {
setGameFinish(false); setIsGameFinished?.(false);
setGameStart(false); setIsGameStarted?.(false);
setCards(prev => randomizeElementInArray(prev.map(el => ({...el, disabled: false, isMatched: false, toggled: true})))) setCards(prev => randomizeElementInArray(prev.map(el => ({...el, disabled: true, isMatched: false, toggled: true}))))
resetGameProgress?.(); resetGameProgress?.();
} }
function finishGame() { function finishGame() {
setGameFinish(true); setIsGameFinished?.(true);
localStorage.removeItem('match-game:cards');
} }
// TODO replace to CardsField // TODO replace to CardsField
@ -60,10 +73,10 @@ const MatchGame: React.FC = () => {
{ {
isGameFinished isGameFinished
? <FinishedField gameTime={gameTime} mistakesCount={mistakesCount}/> ? <FinishedField gameTime={gameTime} mistakesCount={mistakesCount}/>
: <CardsField cards={cards} isGameStarted={isGameStarted} finishGameCallback={finishGame}/> : <CardsField cards={cards} isGameStarted={isGameStarted} finishGameCallback={finishGame} isNewGame={isNewGame}/>
} }
</div> </div>
<BottomPanel startGame={startGame} isFinishedGame={isGameFinished} retryGame={retryGame} shuffleCards={shuffleCards}/> <BottomPanel startGame={startGame} isFinishedGame={isGameFinished} retryGame={retryGame} shuffleCards={shuffleCards} isNewGame={isNewGame}/>
</> </>
); );

View 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;

View File

@ -1,31 +1,44 @@
import React, { useContext, useState } from 'react'; import React, { useContext, useState } from 'react';
import { getTimeString } from 'src/utils'; import { getTimeString } from 'src/utils';
type MatchGameContextType = { export type MenuItem = 'menu' | 'new-game' | 'continue-game' | 'options' | 'exit';
interface MatchGameContextType {
mistakesCount?: number; mistakesCount?: number;
addMistakesCount?: () => void; addMistakesCount?: () => void;
gameTime?: string; gameTime?: number;
changeGameTime?: (time: number) => void; changeGameTime?: (time: number) => void;
resetGameProgress?: () => 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>({}); const MatchGameContext = React.createContext<MatchGameContextType>({});
export const useMatchGame = () => useContext(MatchGameContext); export const useMatchGame = () => useContext(MatchGameContext);
export const MatchGameProvider: React.FC = ({children}) => { export const MatchGameProvider: React.FC = ({children}) => {
const [mistakesCount, setMistakesCount] = useState(0) 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 = () => { const addMistakesCount = () => {
return setMistakesCount(prev => prev + 1) return setMistakesCount(prev => prev + 1)
}; };
const changeGameTime = (time: number) => { const changeGameTime = (time: number) => {
setGameTime(getTimeString(new Date(time))); setGameTime(time);
} }
const resetGameProgress = () => { const resetGameProgress = () => {
setMistakesCount(0); setMistakesCount(0);
setGameTime(getTimeString(new Date(0))); setGameTime(0);
setIsGameStarted(false);
setIsGameFinished(false);
} }
return ( return (
@ -34,7 +47,13 @@ export const MatchGameProvider: React.FC = ({children}) => {
addMistakesCount, addMistakesCount,
gameTime, gameTime,
changeGameTime, changeGameTime,
resetGameProgress resetGameProgress,
currentMenuItem,
setCurrentMenuItem,
isGameStarted,
setIsGameStarted,
isGameFinished,
setIsGameFinished
}}> }}>
{children} {children}
</MatchGameContext.Provider> </MatchGameContext.Provider>