import React from 'react'; import Grid from './grid/grid'; import GridControl from './grid/grid-control'; class MineSeeker extends React.Component { constructor(props) { super(props); let gameAssoc = '' !== props.gameId ? props.gameId : this.makeGameAssoc(50); let channel = 'mineseeker/channel/' + gameAssoc; this.state = { env: props.env, ssl: props.ssl, gameInherited: '' !== props.gameId, gameAssoc: gameAssoc, channel: channel, session: null, createGrid: false, stepCache: [], connectionLost: false, end: false, }; } makeGameAssoc(len) { let text = ''; let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for (let i = 0; i < len; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; } currectGridSize() { let $field = $('#mine-wrapper .grid'); $field.height($field.width()); $field = $('#mine-wrapper .grid .field-wrapper'); $field.height($field.width()); $('#mine-wrapper .grid .field-wrapper .field') .height($field.width()) .css('line-height', ($field.width() - 2) + 'px'); } /** * STEP * * @param coords * @returns {{red: *, blue: *}} */ makePointsCalcAndStep(coords) { let users = this.refs.gridControl.refs.userControl, activePlayer = users.state.activePlayer ? 'blue' : 'red', inactivePlayer = users.state.activePlayer ? 'red' : 'blue', redPoints = 'red' === activePlayer ? users.refs[activePlayer].state.mines : users.refs[inactivePlayer].state.mines, bluePoints = 'blue' === activePlayer ? users.refs[activePlayer].state.mines : users.refs[inactivePlayer].state.mines; this.refs.gridControl.stepEvent(coords); let mineCache = this.refs.gridControl.state.foundUserMineCache; redPoints += 'red' === activePlayer ? mineCache : 0; bluePoints += 'blue' === activePlayer ? mineCache : 0; return { red: redPoints, blue: bluePoints }; } /** * START * * @param payload */ makeGameStart(payload) { /** every time the blue starts */ this.refs.gridControl.refs.userControl.setState({ activePlayer: 1 }); /** Set up player names w/ server data */ this.refs.gridControl.refs.userControl.refs.red.setState({ name: '' !== payload.users.red ? payload.users.red : payload.users.redAnon, }); this.refs.gridControl.refs.userControl.refs.blue.setState({ name: '' !== payload.users.blue ? payload.users.blue : payload.users.blueAnon, desc: 'blue' === this.refs.gridControl.state.webPlayer ? this.refs.gridControl.state.desc.you : this.refs.gridControl.state.desc.buddy, active: true, }); this.refs.gridControl.setState({ overlay: false }); } /** * THE END * * @param bluePoints * @param redPoints * @param resign */ makeGameEndIfItEnds(bluePoints, redPoints, resign = false) { let redWins = 25 < redPoints, blueWins = 25 < bluePoints; if (redWins || blueWins || resign) { this.refs.gridControl.state.sound.won.play(); if (false === resign) { this.refs.gridControl.setState({ overlay: true, overlayTitle: (redWins ? 'Red' : 'Blue') + ' wins the game!', overlaySubTitle: 'Play again!', }); } this.refs.gridControl.showLeftMines(); this.refs.gridControl.refs.userControl.setState({ activePlayer: false }); this.refs.gridControl.refs.userControl.refs.red.setState({ desc: '' }); this.refs.gridControl.refs.userControl.refs.blue.setState({ desc: '' }); } } resignProcess(color) { this.refs.gridControl.setState({ overlay: true, overlayTitle: color === this.refs.gridControl.state.webPlayer ? 'You have been give up' : 'Your opponent has been resigned', overlaySubTitle: color === this.refs.gridControl.state.webPlayer ? 'You LOSE!' : 'You WIN!', }); this.setState({ end: true }); this.makeGameEndIfItEnds(0, 0, true); } clickResign() { /** PUBLISH */ this.state.session.publish(this.state.channel, { 'resign': this.refs.gridControl.refs.userControl.state.activePlayer ? 'blue' : 'red', }); this.resignProcess(this.refs.gridControl.state.webPlayer); } clickResignCancel() { this.refs.gridControl.setState({ overlay: false, }); } /** RESIGN */ resign() { let users = this.refs.gridControl.refs.userControl, activePlayer = users.state.activePlayer ? 'blue' : 'red'; if (this.refs.gridControl.state.webPlayer === activePlayer) { this.refs.gridControl.setState({ overlay: true, overlayTitle: 'Are u sure u want to resign?!', overlaySubTitle: (
Yes No!
), }); } } /** * @see https://developers.facebook.com/docs/reference/javascript/FB.getLoginStatus * @see https://developers.facebook.com/docs/sharing/reference/send-dialog * @see https://developers.facebook.com/docs/plugins/share-button/ */ clickFBShare() { let display = 'popup'; FB.getLoginStatus(function(response) { display = 'connected' === response.status ? 'dialog' : 'popup'; }); FB.ui({ method: 'send', display: display, link: window.location.href + '/' + this.state.gameAssoc, }); } wInit(session, gridServer, gridClient) { this.setState({ session: session }); /** save session to GridControl */ /** render grid fields - @see #12 */ this.refs.gridControl.setState({ grid: this.state.gameInherited ? gridServer : gridClient, channel: this.state.channel, desc: { buddy: (
Your buddy is
making a
move.
), you: (
It is your turn!
Make a move.
), }, overlay: true, overlayTitle: 'We are waiting for your opponent...', overlaySubTitle: this.state.gameAssoc ? (

Share this unique link w/ your opponent

{'dev' !== this.state.env && Share in Facebook message } {'dev' === this.state.env && Play w/ me! }
) : '', renderGridFields: this.state.gameAssoc, }); } wSubscribe(payload, rpcUsers = null) { 'dev' === this.state.env && console.info( ('undefined' !== typeof payload.user ? payload.user : 'user') + ' has been subscribed to the channel!', ); let firstUser = !rpcUsers; null === this.refs.gridControl.state.webPlayer && this.refs.gridControl.setState({ webPlayer: payload.user === payload.users.blue || ( firstUser && '' !== payload.users.blueAnon || !firstUser && ('' === rpcUsers.blueAnon && '' === rpcUsers.blue) ) ? 'blue' : 'red', }); /** rwd */ (900 > $(document).width()) && this.currectGridSize(); /** every user has been came */ if ( 2 === payload.userCnt && ( !this.state.connectionLost || this.state.connectionLost && false === this.refs.gridControl.refs.userControl.state.activePlayer && !this.state.end ) ) { this.makeGameStart(payload); } } wUnsubscribe(payload) { 'dev' === this.state.env && console.info(payload.msg); this.refs.gridControl.setState({ overlay: true, overlayTitle: 'The connection has been lost w/ your friend...', overlaySubTitle: 'Please, restart the game!', }); } wTopic(payload) { /** Auto-Step if this player is not the current user */ if (this.refs.gridControl.state.webPlayer !== payload.data.player) { if (null === payload.data.resign) { 'dev' === this.state.env && console.warn(payload.user + ' has been stepped to coords: ' + payload.data.coords[0] + ', ' + payload.data.coords[1]); 'dev' === this.state.env && console.warn('Opponent stepped: Auto-Step process'); this.refs.gridControl.refs.userControl.setState({ bombSelected: payload.data.bomb }); /** STEP */ let points = this.makePointsCalcAndStep(payload.data.coords); /** THE END */ this.makeGameEndIfItEnds(points.blue, points.red); } else { /** RESIGN */ /** THE END */ this.resignProcess(payload.data.resign); } } } /** Connect - Subscribe */ subscribe(rpcUsers = null) { this.state.session.subscribe( this.state.channel, (uri, payload) => { let isTopicEvent = 'undefined' !== typeof payload.data, isNotUnsubscribe = 'undefined' === typeof payload.msg; /** CONNECTION */ if (isTopicEvent) { this.wTopic(payload); } else { if (isNotUnsubscribe) { this.wSubscribe(payload, rpcUsers); } else { this.wUnsubscribe(payload); } } /** RECONNECTION */ if (2 === payload.userCnt && this.state.connectionLost) { 'dev' === this.state.env && console.info('Reconnection process'); /** PUBLISH */ let cache = this.state.stepCache; cache.forEach(item => this.state.session.publish(this.state.channel, item)); this.setState({ connectionLost: false, stepCache: [] }); } }); } connectWithWebsocket() { /** Create Websocket w/ Bahnhof.js */ let websocket = WS.connect( ('true' === this.state.ssl ? 'wss' : 'ws') + '://' + window.location.hostname + '/ws/', ); /** * Connect * Session is an Autobahn JS WAMP session. */ websocket.on('socket/connect', session => { 'dev' === this.state.env && console.info('Successfully connected to the Server!'); if (!this.state.connectionLost) { let gridClient = this.state.gameInherited || new Grid().state.grid; /** * Connect - RPC * Send grid information to the server */ session .call( this.state.gameInherited ? 'mineseeker-rpc/connectGame' : 'mineseeker-rpc/startGame', this.state.gameInherited ? this.state.gameAssoc : [window.btoa(JSON.stringify(gridClient)), this.state.gameAssoc], ) .then( data => { 'dev' === this.state.env && console.info('RPC has been called'); let serverData = true !== data[0] ? JSON.parse(window.atob(data)) : data; /** Check the grid if the user is inherited @see #30 */ if ((this.state.gameInherited && 'undefined' !== typeof serverData.grid) || !this.state.gameInherited) { this.wInit(session, serverData.grid, gridClient); this.subscribe(this.state.gameInherited && serverData.users); } else { this.refs.gridControl.setState({ overlay: true, overlayTitle: 'This channel does not exists!', overlaySubTitle: Restart game!, }); console.error('This channel does not exists!'); } }, (error, desc) => 'dev' === this.state.env && console.error(['RPC Error', error, desc]), ); } else { this.setState({ session: session }); this.subscribe(); } }); /** * DisConnect * Error provides us with some insight into the disconnection: error.reason and error.code */ websocket.on('socket/disconnect', error => { 'dev' === this.state.env && console.error('Disconnected for ' + error.reason + ' with code ' + error.code); 6 === error.code && this.setState({ connectionLost: true }); 3 === error.code && setTimeout(this.componentDidMount.bind(this), 500); }); } /** After rendering */ componentDidMount() { this.connectWithWebsocket(); } /** * Cache the steps unless reconnection * * @param dataPack */ cachePublish(dataPack) { let cache = this.state.stepCache; cache.push(dataPack); this.setState({ stepCache: cache }); } onClick(coords) { let activePlayer = this.refs.gridControl.refs.userControl.state.activePlayer ? 'blue' : 'red'; /** if the clicked field is NEVER CLICKED */ if (this.refs.gridControl.checkFieldHasBeenNeverClicked(coords[0], coords[1])) { /** Player step and it is the current player */ if (activePlayer === this.refs.gridControl.state.webPlayer) { /** STEP */ let points = this.makePointsCalcAndStep(coords); /** THE END */ this.makeGameEndIfItEnds(points.blue, points.red); let dataPack = { 'coords': coords, 'player': activePlayer, 'bomb': this.refs.gridControl.refs.userControl.state.bombSelected, 'redPoints': points.red, 'bluePoints': points.blue, 'resign': null, 'redExplodedBomb': 'red' === activePlayer && this.refs.gridControl.refs.userControl.state.bombSelected, 'blueExplodedBomb': 'blue' === activePlayer && this.refs.gridControl.refs.userControl.state.bombSelected, }; /** PUBLISH */ !this.state.connectionLost ? this.state.session.publish(this.state.channel, dataPack) : this.cachePublish(dataPack); } } } render() { return ( ); } } export default MineSeeker;