add OPTIONS page, add size, difficult level mode
This commit is contained in:
parent
1a778e680d
commit
e2c70835e2
@ -4,4 +4,5 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
font-family: "Pacifico", sans-serif;
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import MatchGame from 'components/MatchGame/MatchGame';
|
||||
import MatchMenu from 'components/MatchMenu/MatchMenu';
|
||||
import MatchGame from 'components/../pages/MatchGame/MatchGame';
|
||||
import MatchMenu from 'components/../pages/MatchMenu/MatchMenu';
|
||||
import { useMatchGame } from 'context/MatchGameContext';
|
||||
import React from 'react';
|
||||
import Options from 'src/pages/Options/Options';
|
||||
|
||||
const AppRoutes = () => {
|
||||
const {currentMenuItem} = useMatchGame();
|
||||
@ -17,6 +18,9 @@ const AppRoutes = () => {
|
||||
case 'new-game':
|
||||
return <MatchGame isNewGame={true}/>;
|
||||
|
||||
case 'options':
|
||||
return <Options/>
|
||||
|
||||
|
||||
default: return <MatchMenu/>;
|
||||
}
|
||||
|
||||
@ -1,99 +0,0 @@
|
||||
import { useMatchGame } from 'context/MatchGameContext';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import Button from 'components/UI/Button/Button';
|
||||
import Timer from 'components/UI/Timer/Timer';
|
||||
|
||||
import cls from './BottomPanel.module.scss';
|
||||
|
||||
type BottomPanelProps = {
|
||||
startGame: () => void;
|
||||
retryGame: () => void;
|
||||
isFinishedGame?: boolean;
|
||||
shuffleCards: () => void;
|
||||
isNewGame: boolean;
|
||||
}
|
||||
|
||||
type PanelState = 'start' | 'retry';
|
||||
|
||||
const BottomPanel: React.FC<BottomPanelProps> = (props) => {
|
||||
const [isTimerStarted, setIsTimerStarted] = useState(false);
|
||||
const [isTimerStopped, setIsTimerStopped] = useState(false);
|
||||
const [panelState, setPanelState] = useState<PanelState>('start');
|
||||
const {gameTime, changeGameTime, setCurrentMenuItem, setIsGameStarted} = useMatchGame();
|
||||
|
||||
function onStartBtnClickHandler(event: React.MouseEvent<HTMLButtonElement>) {
|
||||
setIsTimerStarted(true);
|
||||
props.startGame();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
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');
|
||||
|
||||
props.retryGame?.();
|
||||
}
|
||||
|
||||
function renderRetryStatePanel() {
|
||||
return (
|
||||
<>
|
||||
<Button onClick={goToMenu}>⇦</Button>
|
||||
<Button onClick={onRetryBtnClickHandler}>RETRY</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function onShuffleBtnClickHandler() {
|
||||
props.shuffleCards?.();
|
||||
}
|
||||
|
||||
function goToMenu() {
|
||||
setIsGameStarted?.(false);
|
||||
setCurrentMenuItem?.('menu');
|
||||
}
|
||||
|
||||
function renderStartStatePanel() {
|
||||
return isTimerStarted
|
||||
? <>
|
||||
<Button onClick={goToMenu}>⇦</Button>
|
||||
<Timer isStarted={isTimerStarted} startTime={gameTime || 0} isFinished={isTimerStopped} changeTime={changeGameTime}/>
|
||||
</>
|
||||
: <>
|
||||
<Button onClick={goToMenu}>⇦</Button>
|
||||
<Button onClick={onStartBtnClickHandler}>{props.isNewGame ? 'START' : 'Continue'}</Button>
|
||||
{props.isNewGame && <Button onClick={onShuffleBtnClickHandler}>SHUFFLE</Button>}
|
||||
</>
|
||||
}
|
||||
|
||||
function renderPanelByState(state: PanelState) {
|
||||
if (!state) {
|
||||
console.error('Нет состояния у панели')
|
||||
return;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case 'start': return renderStartStatePanel();
|
||||
case 'retry': return renderRetryStatePanel();
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cls.BottomPanel}>
|
||||
{ renderPanelByState(panelState) }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BottomPanel;
|
||||
@ -115,7 +115,7 @@ const CardsField: React.FC<CardsFieldProps> = ({cards, isGameStarted = false, fi
|
||||
useEffect(() => {
|
||||
setCardsState(prev => (prev.map(el => ({...el, disabled: !isGameStarted, toggled: !isGameStarted && isNewGame}))));
|
||||
if (isGameStarted) localStorage.setItem('match-game:cards', JSON.stringify(cardsState));
|
||||
}, [isGameStarted])
|
||||
}, [isGameStarted, isNewGame])
|
||||
|
||||
useEffect(() => {
|
||||
if (isNewGame) setCardsState(cards.map(el => createCard(el)))
|
||||
|
||||
@ -1,6 +1,29 @@
|
||||
.FinishedField {
|
||||
background: #283593;
|
||||
border-radius: 10px;
|
||||
border: 2px solid #fff;
|
||||
box-sizing: border-box;
|
||||
color: #ffffff;
|
||||
max-width: 320px;
|
||||
margin: 0 auto;
|
||||
font-family: 'Pacifico', sans-serif;
|
||||
height: 500px;
|
||||
margin: 0 auto;
|
||||
padding: 10px 50px;
|
||||
position: relative;
|
||||
width: 500px;
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 40px;
|
||||
}
|
||||
p {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 24px;
|
||||
|
||||
|
||||
span {
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { DiffLevel, FieldSize } from 'src/pages/Options/Options';
|
||||
import { getTimeString } from 'src/utils';
|
||||
|
||||
import cls from './FinishedField.module.scss';
|
||||
@ -6,16 +7,18 @@ import cls from './FinishedField.module.scss';
|
||||
type FinishedFieldProps = {
|
||||
mistakesCount?: number;
|
||||
gameTime?: number;
|
||||
difficultyLevel?: number;
|
||||
fieldSize?: number;
|
||||
}
|
||||
|
||||
const FinishedField: React.FC<FinishedFieldProps> = ({mistakesCount, gameTime}) => {
|
||||
const FinishedField: React.FC<FinishedFieldProps> = ({mistakesCount, gameTime, difficultyLevel, fieldSize}) => {
|
||||
return (
|
||||
<div className={cls.FinishedField}>
|
||||
<h1>GAME IS FINISHED !</h1>
|
||||
<p>Время: <span>{getTimeString(new Date(gameTime || 0))}</span></p>
|
||||
<p>Количество ошибок: <span>{mistakesCount}</span></p>
|
||||
<p>Сложность: <span>difficultyLevel</span></p>
|
||||
<p>Размер поля: <span>fieldSize</span></p>
|
||||
<p>Сложность: <span>{DiffLevel[difficultyLevel as any]}</span></p>
|
||||
<p>Размер поля: <span>{FieldSize[fieldSize as any]}</span></p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
.FieldFinish {
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
background: #283593;
|
||||
position: relative;
|
||||
border: 2px solid #fff;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 40px;
|
||||
}
|
||||
p {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 24px;
|
||||
|
||||
|
||||
span {
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,85 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import BottomPanel from 'components/BottomPanel/BottomPanel';
|
||||
import CardsField from 'components/CardsField/CardsField';
|
||||
import FinishedField from 'components/FinishedField/FinishedField';
|
||||
import { cardImages } from 'src/constants';
|
||||
import { randomizeElementInArray } from 'src/utils';
|
||||
import { useMatchGame } from 'context/MatchGameContext';
|
||||
|
||||
import cls from './MatchGame.module.scss'
|
||||
|
||||
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},
|
||||
{code: 'angular', imageURL: cardImages.angular},
|
||||
{code: 'js', imageURL: cardImages.js},
|
||||
{code: 'react', imageURL: cardImages.react},
|
||||
{code: 'angular', imageURL: cardImages.angular},
|
||||
{code: 'js', imageURL: cardImages.js},
|
||||
]));
|
||||
|
||||
const {gameTime,
|
||||
mistakesCount,
|
||||
resetGameProgress,
|
||||
setIsGameStarted,
|
||||
setIsGameFinished,
|
||||
isGameFinished,
|
||||
isGameStarted
|
||||
} = useMatchGame();
|
||||
|
||||
useEffect(() => {
|
||||
if (isNewGame) resetGameProgress?.();
|
||||
}, [isNewGame])
|
||||
|
||||
function startGame() {
|
||||
setIsGameStarted?.(true);
|
||||
}
|
||||
|
||||
function retryGame() {
|
||||
setIsGameFinished?.(false);
|
||||
setIsGameStarted?.(false);
|
||||
setCards(prev => randomizeElementInArray(prev.map(el => ({...el, disabled: true, isMatched: false, toggled: true}))))
|
||||
|
||||
resetGameProgress?.();
|
||||
}
|
||||
|
||||
function finishGame() {
|
||||
setIsGameFinished?.(true);
|
||||
localStorage.removeItem('match-game:cards');
|
||||
}
|
||||
|
||||
// TODO replace to CardsField
|
||||
function shuffleCards() {
|
||||
setCards(randomizeElementInArray([...cards]));
|
||||
}
|
||||
|
||||
function getWrapperClassList() {
|
||||
let classes = [cls.Field];
|
||||
|
||||
if (isGameFinished) classes = [cls.FieldFinish];
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={getWrapperClassList().join(' ')} style={{marginBottom: 20}}>
|
||||
{
|
||||
isGameFinished
|
||||
? <FinishedField gameTime={gameTime} mistakesCount={mistakesCount}/>
|
||||
: <CardsField cards={cards} isGameStarted={isGameStarted} finishGameCallback={finishGame} isNewGame={isNewGame}/>
|
||||
}
|
||||
</div>
|
||||
<BottomPanel startGame={startGame} isFinishedGame={isGameFinished} retryGame={retryGame} shuffleCards={shuffleCards} isNewGame={isNewGame}/>
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default MatchGame;
|
||||
15
src/components/UI/BottomPanel/BottomPanel.tsx
Normal file
15
src/components/UI/BottomPanel/BottomPanel.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
|
||||
import cls from './BottomPanel.module.scss';
|
||||
|
||||
type BottomPanelProps = {}
|
||||
|
||||
const BottomPanel: React.FC<BottomPanelProps> = ({children}) => {
|
||||
return (
|
||||
<div className={cls.BottomPanel}>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BottomPanel;
|
||||
@ -8,7 +8,7 @@ type ButtonProps = {
|
||||
|
||||
const Button: React.FC<ButtonProps> = ({children, className, onClick}) => {
|
||||
return (
|
||||
<button className={[cls.Button, className].join('')} onClick={onClick}>
|
||||
<button className={[cls.Button, className].join(' ')} onClick={onClick}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
@ -2,4 +2,6 @@ export const cardImages = {
|
||||
react: 'https://w7.pngwing.com/pngs/79/518/png-transparent-js-react-js-logo-react-react-native-logos-icon-thumbnail.png',
|
||||
js: 'https://www.freepnglogos.com/uploads/javascript-png/javascript-logo-transparent-logo-javascript-images-3.png',
|
||||
angular: 'https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Angular_full_color_logo.svg/1200px-Angular_full_color_logo.svg.png',
|
||||
typescript: 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Typescript_logo_2020.svg/1024px-Typescript_logo_2020.svg.png',
|
||||
webpack: 'https://cdn.iconscout.com/icon/free/png-256/webpack-3629741-3030792.png',
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { getTimeString } from 'src/utils';
|
||||
import { DiffLevel, FieldSize, OptionsType } from 'src/pages/Options/Options';
|
||||
|
||||
export type MenuItem = 'menu' | 'new-game' | 'continue-game' | 'options' | 'exit';
|
||||
|
||||
@ -15,6 +15,9 @@ interface MatchGameContextType {
|
||||
setIsGameStarted?: (status: boolean) => void;
|
||||
isGameFinished?: boolean;
|
||||
setIsGameFinished?: (status: boolean) => void;
|
||||
filedSize?: number;
|
||||
diffLevel?: number;
|
||||
saveOptions?: (options: OptionsType) => void;
|
||||
}
|
||||
const MatchGameContext = React.createContext<MatchGameContextType>({});
|
||||
export const useMatchGame = () => useContext(MatchGameContext);
|
||||
@ -25,6 +28,8 @@ export const MatchGameProvider: React.FC = ({children}) => {
|
||||
const [currentMenuItem, setCurrentMenuItem] = useState<MenuItem>('menu');
|
||||
const [isGameStarted, setIsGameStarted] = useState(false);
|
||||
const [isGameFinished, setIsGameFinished] = useState(false);
|
||||
const [filedSize, setFieldSize] = useState(FieldSize.Small);
|
||||
const [diffLevel, setDiffLevel] = useState(DiffLevel.Easy);
|
||||
|
||||
const addMistakesCount = () => {
|
||||
return setMistakesCount(prev => prev + 1)
|
||||
@ -39,6 +44,12 @@ export const MatchGameProvider: React.FC = ({children}) => {
|
||||
setGameTime(0);
|
||||
setIsGameStarted(false);
|
||||
setIsGameFinished(false);
|
||||
localStorage.removeItem('match-game:cards');
|
||||
}
|
||||
|
||||
const saveOptions = (options: OptionsType) => {
|
||||
setFieldSize(options.size);
|
||||
setDiffLevel(options.diffLevel);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -53,7 +64,10 @@ export const MatchGameProvider: React.FC = ({children}) => {
|
||||
isGameStarted,
|
||||
setIsGameStarted,
|
||||
isGameFinished,
|
||||
setIsGameFinished
|
||||
setIsGameFinished,
|
||||
filedSize,
|
||||
diffLevel,
|
||||
saveOptions
|
||||
}}>
|
||||
{children}
|
||||
</MatchGameContext.Provider>
|
||||
|
||||
129
src/pages/MatchGame/MatchGame.tsx
Normal file
129
src/pages/MatchGame/MatchGame.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
import CardsField from 'components/CardsField/CardsField';
|
||||
import FinishedField from 'components/FinishedField/FinishedField';
|
||||
|
||||
import BottomPanel from 'components/UI/BottomPanel/BottomPanel';
|
||||
import Button from 'components/UI/Button/Button';
|
||||
import Timer from 'components/UI/Timer/Timer';
|
||||
import { useMatchGame } from 'context/MatchGameContext';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { createCards, randomizeElementInArray } from 'src/utils';
|
||||
|
||||
interface MatchGameProps {
|
||||
isNewGame: boolean;
|
||||
}
|
||||
type PanelState = 'start' | 'retry';
|
||||
|
||||
const MatchGame: React.FC<MatchGameProps> = ({isNewGame}) => {
|
||||
const {
|
||||
gameTime,
|
||||
mistakesCount,
|
||||
resetGameProgress,
|
||||
setIsGameStarted,
|
||||
setIsGameFinished,
|
||||
isGameFinished,
|
||||
isGameStarted,
|
||||
changeGameTime,
|
||||
setCurrentMenuItem,
|
||||
diffLevel,
|
||||
filedSize
|
||||
} = useMatchGame();
|
||||
|
||||
// TODO replace to CardsField
|
||||
const [cards, setCards] = useState<any[]>(randomizeElementInArray(createCards(diffLevel, filedSize)));
|
||||
const [isTimerStarted, setIsTimerStarted] = useState(false);
|
||||
const [isTimerStopped, setIsTimerStopped] = useState(false);
|
||||
const [panelState, setPanelState] = useState<PanelState>('start');
|
||||
|
||||
useEffect(() => {
|
||||
if (isNewGame) resetGameProgress?.();
|
||||
}, [isNewGame, resetGameProgress])
|
||||
|
||||
useEffect(() => {
|
||||
if (isGameFinished) {
|
||||
setIsTimerStopped(true);
|
||||
localStorage.removeItem('match-game:cards');
|
||||
}
|
||||
setPanelState(isGameFinished ? 'retry' : 'start');
|
||||
}, [isGameFinished])
|
||||
|
||||
function startGame() {
|
||||
setIsGameStarted?.(true);
|
||||
setIsTimerStarted(true);
|
||||
}
|
||||
|
||||
function retryGame() {
|
||||
setIsGameFinished?.(false);
|
||||
setIsGameStarted?.(false);
|
||||
setIsTimerStarted?.(false);
|
||||
setCards(prev => randomizeElementInArray(prev.map(el => ({...el, disabled: true, isMatched: false, toggled: true}))))
|
||||
|
||||
resetGameProgress?.();
|
||||
}
|
||||
|
||||
function finishGame() {
|
||||
setIsGameFinished?.(true);
|
||||
localStorage.removeItem('match-game:cards');
|
||||
}
|
||||
|
||||
function shuffleCards() {
|
||||
setCards(randomizeElementInArray([...cards]));
|
||||
}
|
||||
|
||||
function goToMenu() {
|
||||
setIsGameStarted?.(false);
|
||||
setCurrentMenuItem?.('menu');
|
||||
}
|
||||
|
||||
function renderRetryStatePanel() {
|
||||
return (
|
||||
<>
|
||||
<Button onClick={goToMenu}>⇦</Button>
|
||||
<Button onClick={retryGame}>RETRY</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function renderStartStatePanel() {
|
||||
return isTimerStarted
|
||||
? <>
|
||||
<Button onClick={goToMenu}>⇦</Button>
|
||||
<Timer isStarted={isTimerStarted} startTime={gameTime || 0} isFinished={isTimerStopped} changeTime={changeGameTime}/>
|
||||
</>
|
||||
: <>
|
||||
<Button onClick={goToMenu}>⇦</Button>
|
||||
<Button onClick={startGame}>{isNewGame ? 'START' : 'Continue'}</Button>
|
||||
{isNewGame && <Button onClick={shuffleCards}>SHUFFLE</Button>}
|
||||
</>
|
||||
}
|
||||
|
||||
function renderPanelByState(state: PanelState) {
|
||||
if (!state) {
|
||||
console.error('Нет состояния у панели')
|
||||
return;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case 'start': return renderStartStatePanel();
|
||||
case 'retry': return renderRetryStatePanel();
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{marginBottom: 20}}>
|
||||
{
|
||||
isGameFinished
|
||||
? <FinishedField gameTime={gameTime} mistakesCount={mistakesCount} difficultyLevel={diffLevel} fieldSize={filedSize}/>
|
||||
: <CardsField cards={cards} isGameStarted={isGameStarted} finishGameCallback={finishGame} isNewGame={isNewGame}/>
|
||||
}
|
||||
</div>
|
||||
<BottomPanel>
|
||||
{ renderPanelByState(panelState) }
|
||||
</BottomPanel>
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default MatchGame;
|
||||
0
src/pages/MatchMenu/MatchMenu.module.scss
Normal file
0
src/pages/MatchMenu/MatchMenu.module.scss
Normal file
@ -18,6 +18,7 @@ const MatchMenu: React.FC<GameMenuProps> = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Match game menu</h2>
|
||||
<ul>
|
||||
{
|
||||
canContinueGame() ? <li onClick={() => clickItemHandler('continue-game')}>Continue</li> : null
|
||||
28
src/pages/Options/Option.tsx
Normal file
28
src/pages/Options/Option.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import Button from 'components/UI/Button/Button';
|
||||
import React from 'react';
|
||||
|
||||
import cls from './Options.module.scss'
|
||||
|
||||
interface OptionProps {
|
||||
text: string;
|
||||
value: string;
|
||||
onChange: (type: 'next' | 'prev') => void;
|
||||
}
|
||||
|
||||
const Option:React.FC<OptionProps> = ({value, text, onChange}) => {
|
||||
return (
|
||||
<div className={cls.Option}>
|
||||
<div>
|
||||
{text}
|
||||
</div>
|
||||
<div className={cls.selectGroup}>
|
||||
<Button className={cls.button} onClick={() => onChange('prev')}>❮</Button>
|
||||
<span>{value}</span>
|
||||
<Button className={cls.button} onClick={() => onChange('next')}>❯</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default Option;
|
||||
48
src/pages/Options/Options.module.scss
Normal file
48
src/pages/Options/Options.module.scss
Normal file
@ -0,0 +1,48 @@
|
||||
.Options {
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
background: #283593;
|
||||
position: relative;
|
||||
border: 2px solid #fff;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
color: #ffffff;
|
||||
margin-bottom: 20px;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.Option {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 30px;
|
||||
|
||||
.button {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
margin: 10px 5px;
|
||||
}
|
||||
|
||||
.selectGroup {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
72
src/pages/Options/Options.tsx
Normal file
72
src/pages/Options/Options.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import BottomPanel from 'components/UI/BottomPanel/BottomPanel';
|
||||
import Button from 'components/UI/Button/Button';
|
||||
import { useMatchGame } from 'context/MatchGameContext';
|
||||
import React, { useState } from 'react';
|
||||
import Option from 'src/pages/Options/Option';
|
||||
|
||||
import cls from './Options.module.scss'
|
||||
|
||||
export type OptionsType = {
|
||||
diffLevel: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export enum DiffLevel {
|
||||
Easy,
|
||||
Normal,
|
||||
Hard
|
||||
}
|
||||
|
||||
export enum FieldSize {
|
||||
Small,
|
||||
Normal,
|
||||
Big
|
||||
}
|
||||
|
||||
const Options: React.FC = () => {
|
||||
const {saveOptions, setCurrentMenuItem, diffLevel, resetGameProgress, filedSize} = useMatchGame();
|
||||
|
||||
const [size, setSize] = useState(filedSize || FieldSize.Small);
|
||||
const [difficultLevel, setDifficultLevel] = useState(diffLevel || DiffLevel.Easy);
|
||||
|
||||
function saveOptionHandler() {
|
||||
saveOptions?.({
|
||||
diffLevel: difficultLevel,
|
||||
size
|
||||
});
|
||||
resetGameProgress?.();
|
||||
setCurrentMenuItem?.('menu');
|
||||
}
|
||||
|
||||
function changeFieldSize(type: 'prev' | 'next') {
|
||||
const size = Object.values(FieldSize).length/2;
|
||||
|
||||
if (type === 'next') setSize(prev => prev + 1 === size ? 0 : prev + 1);
|
||||
if (type === 'prev') setSize(prev => prev === 0 ? size - 1 : prev - 1);
|
||||
}
|
||||
|
||||
function changeDiffLevel(type: 'prev' | 'next') {
|
||||
const size = Object.values(DiffLevel).length/2;
|
||||
|
||||
if (type === 'next') setDifficultLevel(prev => prev + 1 === size ? 0 : prev + 1);
|
||||
if (type === 'prev') setDifficultLevel(prev => prev === 0 ? size - 1 : prev - 1);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cls.Options}>
|
||||
<h2>Options</h2>
|
||||
<ul>
|
||||
<li><Option text={'Size'} value={FieldSize[size]} onChange={changeFieldSize}/></li>
|
||||
<li><Option text={'Difficult level'} value={DiffLevel[difficultLevel]} onChange={changeDiffLevel}/></li>
|
||||
</ul>
|
||||
</div>
|
||||
<BottomPanel>
|
||||
<Button onClick={() => setCurrentMenuItem?.('menu')}>⇦</Button>
|
||||
<Button onClick={saveOptionHandler}>Save</Button>
|
||||
</BottomPanel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Options;
|
||||
81
src/utils.ts
81
src/utils.ts
@ -1,5 +1,10 @@
|
||||
import { cardImages } from 'src/constants';
|
||||
import { DiffLevel, FieldSize } from 'src/pages/Options/Options';
|
||||
|
||||
export type CardType = {code: string, imageURL: string};
|
||||
|
||||
export function randomizeElementInArray (arr: any[]) {
|
||||
return arr.sort(() => {
|
||||
return [...arr].sort(() => {
|
||||
return Math.random() > 0.5 ? 1 : -1;
|
||||
})
|
||||
}
|
||||
@ -16,3 +21,77 @@ export function getTimeString(time?: Date): string {
|
||||
return `${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
export function createCards(diffLevel: DiffLevel = DiffLevel.Easy, size: FieldSize = FieldSize.Small): CardType[] {
|
||||
let arrSize = 6;
|
||||
switch (diffLevel) {
|
||||
case DiffLevel.Easy:
|
||||
switch (size) {
|
||||
case FieldSize.Small: {
|
||||
arrSize = 6;
|
||||
break;
|
||||
}
|
||||
case FieldSize.Normal:{
|
||||
arrSize = 12;
|
||||
break;
|
||||
}
|
||||
case FieldSize.Big:{
|
||||
arrSize = 18;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Array(arrSize).fill('').map((_, i) => {
|
||||
if (i < arrSize / 3) return {code: 'js', imageURL: cardImages.js};
|
||||
if (i >= arrSize / 3 && i < arrSize * 2/3) return {code: 'react', imageURL: cardImages.react};
|
||||
return {code: 'angular', imageURL: cardImages.angular}
|
||||
});
|
||||
|
||||
case DiffLevel.Normal:
|
||||
switch (size) {
|
||||
case FieldSize.Small: {
|
||||
arrSize = 8;
|
||||
break;
|
||||
}
|
||||
case FieldSize.Normal:{
|
||||
arrSize = 16;
|
||||
break;
|
||||
}
|
||||
case FieldSize.Big:{
|
||||
arrSize = 24;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Array(arrSize).fill('').map((_, i) => {
|
||||
if (i < arrSize / 4) return {code: 'js', imageURL: cardImages.js};
|
||||
if (i >= arrSize / 4 && i < arrSize / 2) return {code: 'react', imageURL: cardImages.react};
|
||||
if (i >= arrSize / 2 && i < arrSize * 3/4) return {code: 'angular', imageURL: cardImages.angular};
|
||||
return {code: 'typescript', imageURL: cardImages.typescript}
|
||||
});
|
||||
|
||||
case DiffLevel.Hard:
|
||||
switch (size) {
|
||||
case FieldSize.Small: {
|
||||
arrSize = 10;
|
||||
break;
|
||||
}
|
||||
case FieldSize.Normal:{
|
||||
arrSize = 20;
|
||||
break;
|
||||
}
|
||||
case FieldSize.Big:{
|
||||
arrSize = 30;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Array(arrSize).fill('').map((_, i) => {
|
||||
if (i < arrSize / 5) return {code: 'js', imageURL: cardImages.js};
|
||||
if (i >= arrSize / 5 && i < arrSize * 2/5) return {code: 'react', imageURL: cardImages.react};
|
||||
if (i >= arrSize * 2/5 && i < arrSize * 3/5) return {code: 'angular', imageURL: cardImages.angular};
|
||||
if (i >= arrSize * 3/5 && i < arrSize * 4/5) return {code: 'typescript', imageURL: cardImages.typescript};
|
||||
return {code: 'webpack', imageURL: cardImages.webpack}
|
||||
});
|
||||
}
|
||||
|
||||
return Array(6).fill({code: 'angular', imageURL: cardImages.angular});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user