-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* [Feature] Add game board. Now we can to start and restart game. I use mock data yet * [Feature][Modules] Add Ant Design for base stylization * [Bugfix][Environment] Hot reload works incorrectly after version 2.24.9 ( gatsbyjs/gatsby#26192 ) * [Bugfix] Stub for 'Error: The result of this StaticQuery could not be fetched' (gatsbyjs/gatsby#24902)
- Loading branch information
1 parent
ea3fafb
commit a4bc865
Showing
21 changed files
with
1,125 additions
and
131 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
!.yarn/plugins | ||
!.yarn/sdks | ||
!.yarn/versions | ||
yarn-error.log | ||
.pnp.* | ||
|
||
/cypress | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import React from 'react' | ||
import { css } from 'styled-components' | ||
import { useSelector } from 'react-redux' | ||
import { flowRight } from 'lodash-es' | ||
import { withErrorBoundaries } from 'helpers/decorators' | ||
import { battleshipGameSelectors } from 'store/battleshipGame/selectors' | ||
import { GameBoardCell } from './GameBoardCell' | ||
|
||
function GameBoard() { | ||
const gameBoardSize = useSelector(battleshipGameSelectors.getGameBoardSize), | ||
gameBoardMap = useSelector(battleshipGameSelectors.getGameBoardData), | ||
[rowsSize, columnsSize] = [gameBoardSize, gameBoardSize], | ||
Cells = React.useMemo(() => { | ||
const cells = [] | ||
for (let rowIndex = 0; rowIndex < rowsSize; rowIndex += 1) { | ||
for (let columnIndex = 0; columnIndex < columnsSize; columnIndex += 1) { | ||
const cell = gameBoardMap[rowIndex][columnIndex] | ||
cells.push( | ||
<GameBoardCell | ||
rowIndex={rowIndex} | ||
columnIndex={columnIndex} | ||
shipId={cell.shipId} | ||
isShot={cell.isShot} | ||
key={`cell-${rowIndex}-${columnIndex}`} | ||
/> | ||
) | ||
} | ||
} | ||
return cells | ||
}, [gameBoardMap, rowsSize, columnsSize]) | ||
|
||
return ( | ||
<section css={boardStyles.wrapper as any} {...{ rowsSize, columnsSize }} data-testid="gameBoard-wrapper"> | ||
{Cells} | ||
</section> | ||
) | ||
} | ||
|
||
const boardStyles = { | ||
wrapper: css<{ rowsSize: number; columnsSize: number }>` | ||
display: grid; | ||
grid-area: game-board; | ||
${(props) => { | ||
let dynamicStyles = '' | ||
const cellSize = '2rem' | ||
dynamicStyles += ` | ||
grid-template-columns: repeat(${props.columnsSize}, ${cellSize}); | ||
grid-template-rows: repeat(${props.rowsSize}, ${cellSize}); | ||
` | ||
return dynamicStyles | ||
}}; | ||
`, | ||
} | ||
|
||
const withDecorators = flowRight(withErrorBoundaries(), React.memo)(GameBoard) | ||
export { withDecorators as GameBoard } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import React from 'react' | ||
import { css } from 'styled-components' | ||
import { Checkbox } from 'antd' | ||
import { useSelector, useDispatch } from 'react-redux' | ||
import { battleshipGameSelectors } from 'store/battleshipGame/selectors' | ||
import { battleshipGameAPI } from 'store/battleshipGame/actions' | ||
import { NGameBoard } from 'store/battleshipGame/@types' | ||
|
||
interface IGameBoardCell extends NGameBoard.ICell { | ||
rowIndex: NGameBoard.rowIndex | ||
columnIndex: NGameBoard.columnIndex | ||
} | ||
|
||
export const GameBoardCell: React.FC<IGameBoardCell> = React.memo((props) => { | ||
const { rowIndex, columnIndex, shipId, isShot } = props | ||
|
||
const dispatch = useDispatch(), | ||
onCellClick = React.useCallback( | ||
(event: any) => { | ||
event.stopPropagation() | ||
dispatch(battleshipGameAPI.shotCell({ payload: { rowIndex, columnIndex } })) | ||
}, | ||
[dispatch, rowIndex, columnIndex] | ||
) | ||
|
||
const ship = useSelector(battleshipGameSelectors.getShip({ shipId })), | ||
cell = useSelector(battleshipGameSelectors.getShipCell({ shipId, rowIndex, columnIndex })), | ||
cellBorders = cell?.borders ?? Object.prototype, | ||
isShowHints = useSelector(battleshipGameSelectors.isShowHints({ shipId })) | ||
|
||
const styles = React.useMemo( | ||
() => [ | ||
cellStyles.cell, | ||
shipId && | ||
css` | ||
--cellsAlive: ${ship?.cellsAlive}; | ||
`, | ||
isShowHints && cellStyles.cellWithHints, | ||
], | ||
[shipId, isShowHints] | ||
), | ||
className = React.useMemo( | ||
() => | ||
[ | ||
shipId && | ||
Object.keys(cellBorders) | ||
.map((borderPosition) => `border-${borderPosition}`) | ||
.join(' '), | ||
] | ||
.filter(Boolean) | ||
.join(''), | ||
[shipId, cellBorders] | ||
) | ||
|
||
return ( | ||
<Checkbox css={styles as any} className={className} onChange={onCellClick} disabled={isShot} checked={isShot} /> | ||
) | ||
}) | ||
|
||
const cellStyles = { | ||
cell: css` | ||
&.ant-checkbox-wrapper { | ||
margin: 0; | ||
} | ||
.ant-checkbox { | ||
display: flex; | ||
} | ||
.ant-checkbox, | ||
.ant-checkbox-inner { | ||
width: 100%; | ||
height: 100%; | ||
} | ||
.ant-checkbox-disabled .ant-checkbox-inner { | ||
background-color: hsl(0 70% calc(var(--cellsAlive) * 20%)); | ||
} | ||
&[class*='border-'] .ant-checkbox-disabled .ant-checkbox-inner { | ||
border: initial !important; | ||
} | ||
${['left', 'right', 'top', 'bottom'].reduce((dynamicStyles, currentBorderPosition) => { | ||
dynamicStyles += ` | ||
&.border-${currentBorderPosition} .ant-checkbox-disabled .ant-checkbox-inner { | ||
border-${currentBorderPosition}: 2px solid hsl(0deg 0% 0%) !important; | ||
}` | ||
return dynamicStyles | ||
}, '')} | ||
`, | ||
cellWithHints: css` | ||
.ant-checkbox-inner { | ||
background-color: hsl(271 76% 53% / 0.4); | ||
} | ||
`, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import React from 'react' | ||
import { css } from 'styled-components' | ||
import { Modal, Button } from 'antd' | ||
import { useSelector, useDispatch } from 'react-redux' | ||
import { flowRight } from 'lodash-es' | ||
import { withErrorBoundaries } from 'helpers/decorators' | ||
import { battleshipGameSelectors } from 'store/battleshipGame/selectors' | ||
import { battleshipGameAPI } from 'store/battleshipGame/actions' | ||
|
||
function GameInfo() { | ||
const dispatch = useDispatch(), | ||
aliveShips = useSelector(battleshipGameSelectors.getAliveShips), | ||
onShowHints = React.useCallback(() => { | ||
dispatch(battleshipGameAPI.showHints()) | ||
setTimeout(() => dispatch(battleshipGameAPI.showHints()), 1000) | ||
}, [dispatch]) | ||
|
||
const [isModalVisible, setModalVisible] = React.useState(false), | ||
onClickCancel = React.useCallback(() => setModalVisible(false), []), | ||
onClickOk = React.useCallback(() => { | ||
setModalVisible(false) | ||
dispatch(battleshipGameAPI.restartGame()) | ||
}, [dispatch]) | ||
|
||
React.useEffect(() => { | ||
if (!aliveShips) setTimeout(() => setModalVisible(true)) | ||
}, [aliveShips]) | ||
|
||
return ( | ||
<aside css={infoStyles.wrapper}> | ||
<p> | ||
Alive: <span>{aliveShips}</span> | ||
</p> | ||
<Button children="Show hints" onClick={onShowHints} /> | ||
<Modal | ||
title="You win!" | ||
visible={isModalVisible} | ||
onOk={onClickOk} | ||
onCancel={onClickCancel} | ||
centered | ||
children="Do you want to start new game?" | ||
/> | ||
</aside> | ||
) | ||
} | ||
|
||
const infoStyles = { | ||
wrapper: css` | ||
grid-area: info; | ||
`, | ||
} | ||
|
||
const withDecorators = flowRight(withErrorBoundaries(), React.memo)(GameInfo) | ||
export { withDecorators as GameInfo } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { LoadableComponent } from '@loadable/component' | ||
import { getModuleAsync } from 'modules/optimizations' | ||
|
||
export const GameBoard: LoadableComponent<{}> = getModuleAsync({ | ||
moduleName: 'GameBoard', | ||
moduleImport: () => import(/* webpackChunkName: "GameBoard", webpackPrefetch: true */ `./GameBoard`), | ||
}), | ||
GameInfo: LoadableComponent<{}> = getModuleAsync({ | ||
moduleName: 'GameInfo', | ||
moduleImport: () => import(/* webpackChunkName: "GameInfo", webpackPrefetch: true */ `./GameInfo`), | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import React from 'react' | ||
|
||
interface IWithErrorBoundariesSettings { | ||
isWrapperHandler?: boolean | ||
customError?: null | ((errorInfo: IWrapperState) => React.ReactNode) | ||
} | ||
|
||
interface IWithErrorBoundaries { | ||
stateKey: string | ||
(settings?: IWithErrorBoundariesSettings): (Component: any) => React.ComponentType<any> | ||
} | ||
|
||
interface IWrapperState { | ||
hasError: boolean | ||
} | ||
|
||
/** After error: | ||
* withErrorBoundaries()(SomeComponent) | ||
wrapper will replace children to renderDefaultError | ||
* withErrorBoundaries({customError: OtherComp})(SomeComponent) | ||
wrapper will replace children to OtherComp | ||
* withErrorBoundaries({isWrapperHandler: null})(SomeComponent) | ||
wrapper will ignore error, SomeComponent must use props[withErrorBoundaries.stateKey] for custom error handling | ||
*/ | ||
|
||
export const withErrorBoundaries: IWithErrorBoundaries = (settings = Object.prototype) => (Component) => | ||
class Wrapper extends React.Component<any, IWrapperState> { | ||
static displayName = `withErrorBoundaries(${displayNameCreate(Component)})` | ||
|
||
state = { hasError: false } | ||
|
||
static getDerivedStateFromError(error: any) { | ||
return { hasError: true, error } | ||
} | ||
|
||
renderDefaultError = () => <h1>Something went wrong</h1> | ||
|
||
/*componentDidCatch(error, errorInfo) { | ||
showNotification() | ||
sendErrorToLog(error, errorInfo); | ||
}*/ | ||
|
||
render() { | ||
const { isWrapperHandler = true, customError = this.renderDefaultError } = settings, | ||
{ hasError } = this.state | ||
|
||
if (isWrapperHandler && hasError) return customError ? customError(this.state) : null | ||
|
||
return <Component {...this.props} {...{ [`${withErrorBoundaries.stateKey}`]: this.state }} /> | ||
} | ||
} | ||
|
||
withErrorBoundaries.stateKey = 'withErrorBoundaries' | ||
|
||
const displayNameCreate = (Component: any) => Component.displayName || Component.name || 'Component' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.