import React from 'react'; import GridField from './grid-field'; import UserControl from '../user/user-control'; import { Howl } from 'howler'; class GridControl extends React.Component { constructor(props) { super(props); let click = new Howl({ src: ['/sound/click.mp3'] }), bomb = new Howl({ src: ['/sound/bomb.mp3'] }), mine = new Howl({ src: ['/sound/mine.mp3'] }), warning = new Howl({ src: ['/sound/warning.mp3'] }), won = new Howl({ src: ['/sound/won.mp3'] }); this.state = { env: props.env, webPlayer: null, grid: null, desc: null, renderGridFields: false, gridFields: [], updatedFieldCache: [], bombFieldCache: [], foundUserMineCache: 0, playBomb: false, overlay: false, overlayTitle: '', overlaySubTitle: '', sound: { click: click, bomb: bomb, mine: mine, warning: warning, won: won, }, lastClicked: { red: null, blue: null, }, }; } refString(row, col) { return 'gridField_' + row + '_' + col; } checkMine(row, col) { return 'undefined' !== typeof this.state.grid[row] && 'undefined' !== typeof this.state.grid[row][col] && 'm' !== this.state.grid[row][col]; } checkFieldHasBeenNeverClicked(row, col) { return 0 > this.state.updatedFieldCache.indexOf(this.refString(row, col)) && !this.refs[this.refString(row, col)].state.active; } getBombRadius(row, col) { let isBombTargetCenter = 1 < row && row < this.state.grid.length - 2 && 1 < col && col < this.state.grid[row].length - 2; /** if the (5x5) target not fits the grid */ if (!isBombTargetCenter) { col = 2 > col ? 2 : col; row = 2 > row ? 2 : row; row = row > this.state.grid.length - 3 ? this.state.grid.length - 3 : row; col = col > this.state.grid[0].length - 3 ? this.state.grid[0].length - 3 : col; } return [ [row, col], [row - 2, col - 2], [row - 2, col], [row - 2, col + 2], [row, col - 2], [row, col + 2], [row + 2, col - 2], [row + 2, col], [row + 2, col + 2], [row - 2, col + 1], [row - 2, col - 1], [row - 1, col - 2], [row - 1, col - 1], [row - 1, col], [row - 1, col + 1], [row - 1, col + 2], [row, col - 1], [row, col + 1], [row + 1, col - 2], [row + 1, col - 1], [row + 1, col], [row + 1, col + 1], [row + 1, col + 2], [row + 2, col - 1], [row + 2, col + 1], ]; } getBombFieldRadius() { return [ [null, null], [0, 0], [1, 0], [2, 0], [0, 1], [2, 1], [0, 2], [1, 2], [2, 2], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], [null, null], ]; } getNeighbourRadius(row, col) { return [ [row - 1, col], [row - 1, col - 1], [row - 1, col + 1], [row, col - 1], [row, col + 1], [row + 1, col], [row + 1, col + 1], [row + 1, col - 1], ]; } checkNeighbourItem(row, col) { if (this.checkMine(row, col)) { var currentField = this.refs[this.refString(row, col)]; /** * It must be cached because the GridField.state not updated until * all showAppropriateFields() method runned out!! */ if (this.checkFieldHasBeenNeverClicked(row, col)) { this.state.updatedFieldCache.push(this.refString(row, col)); currentField.setState({ currentImage: this.state.grid[row][col], currentObj: this.state.grid[row][col], active: true, }); if (0 === this.state.grid[row][col]) { return { row: row, col: col, }; } } } return false; } checkNeighbours(row, col) { let anotherFields = [], neighbours = this.getNeighbourRadius(row, col); for (let i = 0, j = neighbours.length; i < j; i++) { anotherFields.push(this.checkNeighbourItem(neighbours[i][0], neighbours[i][1])); } return anotherFields; } bombClear() { if (this.state.bombFieldCache.length) { for (var i = 0, j = this.state.bombFieldCache.length; i < j; i++) { var cacheItem = this.state.bombFieldCache[i]; this.refs[this.refString(cacheItem[0], cacheItem[1])] .setState({ bombTargetArea: null }); } this.state.bombFieldCache = []; } } bombCreate(row, col) { var bombFieldSymbols = this.getBombFieldRadius(), bombFields = this.getBombRadius(row, col); for (var i = 0, j = bombFields.length; i < j; i++) { this.state.bombFieldCache.push(bombFields[i]); this.refs[this.refString(bombFields[i][0], bombFields[i][1])] .setState({ bombTargetArea: bombFieldSymbols[i] }); } } showLeftMines() { for (let i = 0, j = this.state.grid.length; i < j; i++) { for (let k = 0, l = this.state.grid[i].length; k < l; k++) { let currentField = this.refs[this.refString(i, k)]; if ('m' === this.state.grid[i][k] && this.checkFieldHasBeenNeverClicked(i, k)) { currentField.setState({ currentImage: currentField.state.icons.root + currentField.state.icons.left, }); } } } } /** set __ACTIVE__ player in the UserControl !!!! */ changePlayer(idx, max, currentObject) { var userControl = this.refs.userControl, activePlayer = userControl.state.activePlayer ? 'blue' : 'red', inactivePlayer = userControl.state.activePlayer ? 'red' : 'blue'; if ( userControl.state.bombSelected && idx === (max - 1) || !idx && !userControl.state.bombSelected && 'm' !== currentObject ) { userControl.setState({ activePlayer: userControl.state.activePlayer ? 0 : 1, }); /** the desc is inversely because the user.active is not changed yet !!! */ userControl.refs[activePlayer].setState({ active: false, desc: '', }); userControl.refs[inactivePlayer].setState({ active: true, desc: activePlayer === this.state.webPlayer ? this.state.desc.buddy : this.state.desc.you, }); } } /** * Show all fields that needed after click * * @param currentField * @param row * @param col */ showAppropriateFields(currentField, row, col) { currentField.setState({ currentObj: this.state.grid[row][col], active: true, }); if (this.checkFieldHasBeenNeverClicked(row, col)) { this.state.updatedFieldCache.push(this.refString(row, col)); } if (0 === this.state.grid[row][col]) { let neighbours = this.checkNeighbours(row, col); neighbours .filter(i => false !== i) .forEach(element => { let currentField = this.refs[this.refString(element.row, element.col)]; this.showAppropriateFields(currentField, element.row, element.col); }); } } /** * Player control method * * @param currentObject {int|string} Current object from Grid class * @param row {int} * @param col {int} * @param justOnFirstIteration {int} When bomb is being used check the whole explosion area */ handleGridField(currentObject, row, col, justOnFirstIteration = 0) { var userControl = this.refs.userControl, gridFieldControl = this.refs[this.refString(row, col)], activePlayer = userControl.state.activePlayer ? 'blue' : 'red', inactivePlayer = userControl.state.activePlayer ? 'red' : 'blue'; /** if the clicked field is NEVER CLICKED */ if (this.checkFieldHasBeenNeverClicked(row, col)) { /** update LAST CLICKED grid field */ if (!justOnFirstIteration) { if (null !== this.state.lastClicked[activePlayer]) { this.refs[this.refString(this.state.lastClicked[activePlayer][0], this.state.lastClicked[activePlayer][1])].setState({ lastClickedRed: false, lastClickedBlue: false, }); } } this.state.lastClicked[activePlayer] = [row, col]; /** if you found mine */ if ('m' === currentObject) { this.state.foundUserMineCache++; if (!justOnFirstIteration) { /** set last clicked field w/ color */ this.state.lastClicked[activePlayer] = [row, col]; this.state.sound[ 20 < (userControl.refs[activePlayer].state.mines + this.state.foundUserMineCache) ? 'warning' : 'mine' ].play(); } /** set current image in field */ gridFieldControl.setState({ currentImage: gridFieldControl.state.icons.root + gridFieldControl.state.icons.flag[activePlayer], }); } else { this.state.sound.click.play(); /** set current image in field - WHEN it is a number */ if (!isNaN(currentObject)) { gridFieldControl.setState({ currentImage: currentObject, }); } } /** * set bombs status - we must add one mine (currentObject === 'm' ? 1 : 0) to current mine * when it found NOW because the status is not refreshed unless the handleGridField() ends */ userControl.refs[activePlayer].setState({ enabledBomb: userControl.refs[activePlayer].state.mines + ('m' === currentObject ? 1 : 0) <= userControl.refs[inactivePlayer].state.mines, }); userControl.refs[inactivePlayer].setState({ enabledBomb: userControl.refs[activePlayer].state.mines + ('m' === currentObject ? 1 : 0) >= userControl.refs[inactivePlayer].state.mines, }); /** set-up last clicked */ if (!justOnFirstIteration) { gridFieldControl.setState({ lastClickedRed: 'red' === activePlayer, lastClickedBlue: 'blue' === activePlayer, }); } } } /** * Show elems w/ conditions * * @param row * @param col * @param idx * @param max */ show(row, col, idx = 0, max = 0) { this.handleGridField(this.state.grid[row][col], row, col, idx); this.showAppropriateFields(this.refs[this.refString(row, col)], row, col); this.changePlayer(idx, max, this.state.grid[row][col]); } /** * STEP one * * @param coords */ stepEvent(coords) { /** if the clicked field is NEVER CLICKED */ if (this.checkFieldHasBeenNeverClicked(coords[0], coords[1])) { var activePlayer = this.refs.userControl.state.activePlayer ? 'blue' : 'red'; this.state.foundUserMineCache = 0; this.state.playBomb = true; /** Show elements */ if (this.refs.userControl.state.bombSelected) { this.state.sound.bomb.play(); var bombRadius = this.getBombRadius(coords[0], coords[1]); for (var i = 0, j = bombRadius.length; i < j; i++) { this.show(bombRadius[i][0], bombRadius[i][1], i, j); } /** remove BOMB from activePlayer */ this.refs.userControl.refs[activePlayer].setState({ haveBomb: false, }); } else { this.show(coords[0], coords[1]); } /** Mine score handling */ if (this.state.foundUserMineCache) { this.refs.userControl.setState({ mines: this.refs.userControl.state.mines - this.state.foundUserMineCache, foundMines: true, }, () => { /** because of CSS animation in .found-mine */ setTimeout(() => this.refs.userControl.setState({ foundMines: false }), 500); /** add the found mines to the active Player */ this.refs.userControl.refs[activePlayer].setState({ mines: this.refs.userControl.refs[activePlayer].state.mines + this.state.foundUserMineCache, }); }); } /** Reset BOMB status */ if (this.refs.userControl.state.bombSelected) { /** reset bomb selected status */ this.refs.userControl.setState({ bombSelected: false }); /** clear cache, reset symbols */ this.bombClear(); } } } /** * On Hover when you want to drop BOMB * Target grid field * @param coords */ onHoverGridField(coords) { if (this.refs.userControl.state.bombSelected) { var activePlayer = this.refs.userControl.state.activePlayer ? 'blue' : 'red'; if (activePlayer === this.state.webPlayer) { /** clear cache, reset symbols */ this.bombClear(); /** new cache && field activate */ this.bombCreate(coords[0], coords[1]); } else { this.refs.userControl.setState({ bombSelected: false }); } } } renderGridFields() { for (let i = 0, j = this.state.grid.length; i < j; i++) { for (let k = 0, l = this.state.grid[i].length; k < l; k++) { this.state.gridFields.push( , ); } } } render() { /** Render the grid fields just one time in one party #12 */ this.state.renderGridFields && this.renderGridFields(); this.state.renderGridFields = false; return (

{this.state.overlayTitle}

{this.state.overlaySubTitle}

<> {this.state.gridFields}
); } } export default GridControl;