import React from 'react'; import GridField from './grid-field'; import UserControl from '../user/user-control'; import {Howl, Howler} 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 typeof this.state.grid[row] !== 'undefined' && typeof this.state.grid[row][col] !== 'undefined' && this.state.grid[row][col] !== 'm'; } checkFieldHasBeenNeverClicked(row, col) { return this.state.updatedFieldCache.indexOf(this.refString(row, col)) < 0 && !this.refs[this.refString(row, col)].state.active; } getBombRadius(row, col) { let isBombTargetCenter = row > 1 && row < this.state.grid.length - 2 && col > 1 && col < this.state.grid[row].length - 2; /** if the (5x5) target not fits the grid */ if (!isBombTargetCenter) { col = col < 2 ? 2 : col; row = row < 2 ? 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 (this.state.grid[row][col] === 0) { 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 (this.state.grid[i][k] === 'm' && 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 && currentObject !== 'm' ) { 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 (this.state.grid[row][col] === 0) { var neighbours = this.checkNeighbours(row, col); neighbours .filter((i) => { return i !== false; }) .forEach((element, index, array) => { var 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 (this.state.lastClicked[activePlayer] !== null) { 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 (currentObject === 'm') { this.state.foundUserMineCache++; if (!justOnFirstIteration) { /** set last clicked field w/ color */ this.state.lastClicked[activePlayer] = [row, col]; this.state.sound[ (userControl.refs[activePlayer].state.mines + this.state.foundUserMineCache) > 20 ? '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 + (currentObject === 'm' ? 1 : 0) <= userControl.refs[inactivePlayer].state.mines }); userControl.refs[inactivePlayer].setState({ enabledBomb: userControl.refs[activePlayer].state.mines + (currentObject === 'm' ? 1 : 0) >= userControl.refs[inactivePlayer].state.mines }); /** set-up last clicked */ if (!justOnFirstIteration) { gridFieldControl.setState({ lastClickedRed: activePlayer === 'red', lastClickedBlue: activePlayer === 'blue' }); } } } /** * 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}); } } } overlayClass() { return 'game-overlay' + (this.state.overlay ? '' : ' hide'); } 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;