diff --git a/src/App.tsx b/src/App.tsx index 53cff46..9327c40 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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 (
- +
); diff --git a/src/components/AppRoutes.tsx b/src/components/AppRoutes.tsx new file mode 100644 index 0000000..89bafc1 --- /dev/null +++ b/src/components/AppRoutes.tsx @@ -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 ; + + case 'continue-game': + return ; + + case 'new-game': + return ; + + + default: return ; + } + } + + return getRootComponent(); + +}; + +export default AppRoutes; diff --git a/src/components/BottomPanel/BottomPanel.tsx b/src/components/BottomPanel/BottomPanel.tsx index f95eecd..f39892e 100644 --- a/src/components/BottomPanel/BottomPanel.tsx +++ b/src/components/BottomPanel/BottomPanel.tsx @@ -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 = (props) => { const [isTimerStarted, setIsTimerStarted] = useState(false); const [isTimerStopped, setIsTimerStopped] = useState(false); const [panelState, setPanelState] = useState('start'); - const {changeGameTime} = useMatchGame(); + const {gameTime, changeGameTime, setCurrentMenuItem, setIsGameStarted} = useMatchGame(); function onStartBtnClickHandler(event: React.MouseEvent) { setIsTimerStarted(true); @@ -27,12 +28,16 @@ const BottomPanel: React.FC = (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 = (props) => { function renderRetryStatePanel() { return ( <> + ) @@ -52,12 +58,21 @@ const BottomPanel: React.FC = (props) => { props.shuffleCards?.(); } + function goToMenu() { + setIsGameStarted?.(false); + setCurrentMenuItem?.('menu'); + } + function renderStartStatePanel() { return isTimerStarted - ? + ? <> + + + : <> - - + + + {props.isNewGame && } } diff --git a/src/components/CardsField/CardsField.tsx b/src/components/CardsField/CardsField.tsx index 38175bb..8acd132 100644 --- a/src/components/CardsField/CardsField.tsx +++ b/src/components/CardsField/CardsField.tsx @@ -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 = ({cards, isGameStarted, finishGameCallback}) => { +const CardsField: React.FC = ({cards, isGameStarted = false, finishGameCallback, isNewGame}) => { // TODO use useReducer - const [cardsState, setCardsState] = useState(cards.map(el => createCard(el))); - const [currentCard, setCurrentCard] = useState(null) + const [cardsState, setCardsState] = useState(getInitialConfig(isNewGame)); const [currentCardPare, setCurrentCardPare] = useState({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 = ({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 = ({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 { diff --git a/src/components/FinishedField/FinishedField.tsx b/src/components/FinishedField/FinishedField.tsx index 1754a43..d49af30 100644 --- a/src/components/FinishedField/FinishedField.tsx +++ b/src/components/FinishedField/FinishedField.tsx @@ -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 = ({mistakesCount, gameTime}) => { return (

GAME IS FINISHED !

-

Время: {gameTime}

+

Время: {getTimeString(new Date(gameTime || 0))}

Количество ошибок: {mistakesCount}

Сложность: difficultyLevel

Размер поля: fieldSize

diff --git a/src/components/MatchGame/MatchGame.tsx b/src/components/MatchGame/MatchGame.tsx index 9e29ffc..c1a724f 100644 --- a/src/components/MatchGame/MatchGame.tsx +++ b/src/components/MatchGame/MatchGame.tsx @@ -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 = ({isNewGame}) => { // TODO replace to CardsField const [cards, setCards] = useState(randomizeElementInArray([ {code: 'react', imageURL: cardImages.react}, @@ -20,25 +24,34 @@ const MatchGame: React.FC = () => { {code: 'js', imageURL: cardImages.js}, ])); - const [isGameFinished, setGameFinish] = useState(false); - const [isGameStarted, setGameStart] = useState(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 ? - : + : }
- + ); diff --git a/src/components/MatchMenu/MatchMenu.module.scss b/src/components/MatchMenu/MatchMenu.module.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/components/MatchMenu/MatchMenu.tsx b/src/components/MatchMenu/MatchMenu.tsx new file mode 100644 index 0000000..268f540 --- /dev/null +++ b/src/components/MatchMenu/MatchMenu.tsx @@ -0,0 +1,33 @@ +import { MenuItem, useMatchGame } from 'context/MatchGameContext'; +import React from 'react'; + +type GameMenuProps = { + +} + +const MatchMenu: React.FC = () => { + const {setCurrentMenuItem} = useMatchGame(); + + function clickItemHandler(item: MenuItem) { + setCurrentMenuItem?.(item); + } + + function canContinueGame() { + return !!localStorage.getItem('match-game:cards'); + } + + return ( +
+
    + { + canContinueGame() ?
  • clickItemHandler('continue-game')}>Continue
  • : null + } +
  • clickItemHandler('new-game')}>New game
  • +
  • clickItemHandler('options')}>Options
  • +
  • clickItemHandler('exit')}>Exit
  • +
+
+ ); +}; + +export default MatchMenu; diff --git a/src/context/MatchGameContext.tsx b/src/context/MatchGameContext.tsx index 19833ff..20f717d 100644 --- a/src/context/MatchGameContext.tsx +++ b/src/context/MatchGameContext.tsx @@ -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({}); - 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('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}