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 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>
|
||||||
);
|
);
|
||||||
|
|||||||
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;
|
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}>⇦</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}>⇦</Button>
|
||||||
|
<Timer isStarted={isTimerStarted} startTime={gameTime || 0} isFinished={isTimerStopped} changeTime={changeGameTime}/>
|
||||||
|
</>
|
||||||
: <>
|
: <>
|
||||||
<Button onClick={onStartBtnClickHandler}>START</Button>
|
<Button onClick={goToMenu}>⇦</Button>
|
||||||
<Button onClick={onShuffleBtnClickHandler}>SHUFFLE</Button>
|
<Button onClick={onStartBtnClickHandler}>{props.isNewGame ? 'START' : 'Continue'}</Button>
|
||||||
|
{props.isNewGame && <Button onClick={onShuffleBtnClickHandler}>SHUFFLE</Button>}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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}/>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|||||||
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 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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user