diff --git a/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/app.js b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/app.js index 0569670..483f67f 100644 --- a/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/app.js +++ b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/app.js @@ -6,8 +6,8 @@ class MineSeeker extends React.Component { constructor(props) { super(props); - var gameAssoc = props.gameId !== '' ? props.gameId : this.makeGameAssoc(50); - var channel = "mineseeker/channel/" + gameAssoc; + let gameAssoc = props.gameId !== '' ? props.gameId : this.makeGameAssoc(50); + let channel = "mineseeker/channel/" + gameAssoc; this.state = { env: props.env, @@ -16,14 +16,15 @@ class MineSeeker extends React.Component { channel: channel, session: null, createGrid: false, - stepCache: null + stepCache: [], + disconnect: false } } makeGameAssoc(len) { - var text = ""; - var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - for (var i = 0; i < len; i++) { + let text = ""; + let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (let i = 0; i < len; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; @@ -52,7 +53,7 @@ class MineSeeker extends React.Component { /** THE END */ makeGameEndIfItEnds(bluePoints, redPoints, resign = false) { - var redWins = redPoints > 25, + let redWins = redPoints > 25, blueWins = bluePoints > 25; if (redWins || blueWins || resign) { @@ -89,10 +90,9 @@ class MineSeeker extends React.Component { } clickResign() { - this.state.session - .publish(this.state.channel, { - 'resign': this.refs.gridControl.refs.userControl.state.activePlayer ? 'blue' : 'red' - }); + this.state.session.publish(this.state.channel, { + 'resign': this.refs.gridControl.refs.userControl.state.activePlayer ? 'blue' : 'red' + }); this.resignProcess(this.refs.gridControl.state.webPlayer); } @@ -119,10 +119,146 @@ class MineSeeker extends React.Component { } } + wInit(session, gridServer, gridClient) { + this.setState({session: session}); + + /** save session to GridControl */ + /** render grid fields - #12 */ + this.refs.gridControl.setState({ + grid: this.state.gameInherited ? JSON.parse(Base64.decode(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 + ? +
+
+ + +
+ Play w/ me! +
+ : '', + renderGridFields: this.state.gameAssoc + }); + } + + wSubscribe(payload) { + this.state.env === 'dev' && console.info( + (typeof payload.user !== 'undefined' ? payload.user : 'user') + " has been subscribed to the channel!" + ); + + if (!this.state.disconnect) { + /** setup the web player */ + null === this.refs.gridControl.state.webPlayer && this.refs.gridControl.setState({ + webPlayer: payload.user === payload.users.blue || payload.user === payload.users.blueAnon + ? 'blue' + : 'red' + }); + + /** every user has been came */ + if (payload.userCnt === 2) { + /** 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: this.refs.gridControl.state.webPlayer === 'blue' + ? this.refs.gridControl.state.desc.you + : this.refs.gridControl.state.desc.buddy, + active: true, + }); + + this.refs.gridControl.setState({overlay: false}); + } + } + } + + wUnsubscribe(payload) { + this.state.env === 'dev' && 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) { + this.state.env === 'dev' && console.warn(payload.user + " has been stepped to coords: " + payload.data.coords[0] + ', ' + payload.data.coords[1]); + this.state.env === 'dev' && 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() { + this.state.session.subscribe( + this.state.channel, + (uri, payload, log) => { + let isTopicEvent = typeof payload.data !== 'undefined', + isNotUnsubscribe = typeof payload.msg === 'undefined'; + + /** CONNECTION */ + if (isTopicEvent) { + this.wTopic(payload); + } else { + if (isNotUnsubscribe) { + this.wSubscribe(payload); + } else { + this.wUnsubscribe(payload); + } + } + + /** RECONNECTION */ + if (payload.userCnt === 2 && this.state.disconnect) { + this.state.env === 'dev' && console.info('Reconnection process'); + + let cache = this.state.stepCache; + cache.forEach((item) => this.state.session.publish(this.state.channel, item)); + this.setState({disconnect: false, stepCache: []}); + } + }); + } + /** after rendering */ componentDidMount() { /** Create Websocket w/ Bahnhof.js */ - var websocket = WS.connect( + let websocket = WS.connect( this.state.env === 'dev' ? "ws://mine.dev:6450" : "ws://www.mineseeker.ninja:6450" @@ -133,149 +269,58 @@ class MineSeeker extends React.Component { * Session is an Autobahn JS WAMP session. */ websocket.on("socket/connect", (session) => { - console.info("Successfully connected to the Server!"); + this.state.env === 'dev' && console.info("Successfully connected to the Server!"); - var gridClient = this.state.gameInherited || new Grid().state.grid; + if (!this.state.disconnect) { + 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 : [Base64.encode(JSON.stringify(gridClient)), this.state.gameAssoc] - ) - .then( - (gridServer) => { - console.info("Grid has been created! Return w/ gameAssoc."); + /** + * 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 : [Base64.encode(JSON.stringify(gridClient)), this.state.gameAssoc] + ) + .then( + (gridServer) => { + this.state.env === 'dev' && console.info("Grid has been created! Return w/ gameAssoc."); - this.setState({session: session}); - - /** save session to GridControl */ - /** render grid fields - #12 */ - this.refs.gridControl.setState({ - grid: this.state.gameInherited ? JSON.parse(Base64.decode(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 - ? -
-
- - -
- Play w/ me! -
- : '', - renderGridFields: this.state.gameAssoc - }); - - /** Connect - Subscribe */ - this.state.session.subscribe( - this.state.channel, - (uri, payload, log) => { - - var isTopicEvent = typeof payload.data !== 'undefined', - isNotUnsubscribe = typeof payload.msg === 'undefined'; - - if (isTopicEvent) { - - /** Auto-Step if this player is not the current user */ - if (this.refs.gridControl.state.webPlayer !== payload.data.player) { - if (null === payload.data.resign) { - console.warn(payload.user + " has been stepped to coords: " + payload.data.coords[0] + ', ' + payload.data.coords[1]); - 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); - } - } - } else { - - /** It is subscribe or unsubscribe */ - if (isNotUnsubscribe) { - console.info( - (typeof payload.user !== 'undefined' ? payload.user : 'user') + " has been subscribed to the channel!" - ); - - /** setup the web player */ - null === this.refs.gridControl.state.webPlayer && this.refs.gridControl.setState({ - webPlayer: payload.user === payload.users.blue || payload.user === payload.users.blueAnon - ? 'blue' - : 'red' - }); - - /** every user has been came */ - if (payload.userCnt === 2) { - /** 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: this.refs.gridControl.state.webPlayer === 'blue' - ? this.refs.gridControl.state.desc.you - : this.refs.gridControl.state.desc.buddy, - active: true, - }); - - this.refs.gridControl.setState({overlay: false}); - } - } else { - console.info(payload.msg); - - this.refs.gridControl.setState({ - overlay: true, - overlayTitle: "The connection has been lost w/ your friend...", - overlaySubTitle: "Please, restart the game!" - }); - } - } - } - ); - }, - (error, desc) => console.error(["RPC Error", error, desc]) - ); + this.wInit(session, gridServer, gridClient); + this.subscribe(); + }, + (error, desc) => this.state.env === 'dev' && 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) => console.error("Disconnected for " + error.reason + " with code " + error.code)); + websocket.on("socket/disconnect", (error) => { + this.state.env === 'dev' && console.error("Disconnected for " + error.reason + " with code " + error.code); + this.setState({disconnect: true}); + }); + } + + /** + * Cache the steps unless reconnection + * + * @param dataPack + */ + cachePublish(dataPack) { + let cache = this.state.stepCache; + cache.push(dataPack); + this.setState({stepCache: cache}); } onClick(coords) { - var activePlayer = this.refs.gridControl.refs.userControl.state.activePlayer ? 'blue' : 'red'; + 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])) { @@ -287,17 +332,21 @@ class MineSeeker extends React.Component { /** THE END */ this.makeGameEndIfItEnds(points.blue, points.red); - this.state.session - .publish(this.state.channel, { - 'coords': coords, - 'player': activePlayer, - 'bomb': this.refs.gridControl.refs.userControl.state.bombSelected, - 'redPoints': points.red, - 'bluePoints': points.blue, - 'resign': null, - 'redExplodedBomb': activePlayer === 'red' && this.refs.gridControl.refs.userControl.state.bombSelected, - 'blueExplodedBomb': activePlayer === 'blue' && this.refs.gridControl.refs.userControl.state.bombSelected - }); + let dataPack = { + 'coords': coords, + 'player': activePlayer, + 'bomb': this.refs.gridControl.refs.userControl.state.bombSelected, + 'redPoints': points.red, + 'bluePoints': points.blue, + 'resign': null, + 'redExplodedBomb': activePlayer === 'red' && this.refs.gridControl.refs.userControl.state.bombSelected, + 'blueExplodedBomb': activePlayer === 'blue' && this.refs.gridControl.refs.userControl.state.bombSelected + }; + + /** PUBLISH */ + !this.state.disconnect + ? this.state.session.publish(this.state.channel, dataPack) + : this.cachePublish(dataPack); } } } diff --git a/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/grid/grid-control.js b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/grid/grid-control.js index c9619f7..c5b860b 100644 --- a/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/grid/grid-control.js +++ b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/grid/grid-control.js @@ -96,7 +96,7 @@ class GridControl extends React.Component { * It must be cached because the GridField.state not updated until * all showAppropriateFields() method runned out!! */ - if (this.state.updatedFieldCache.indexOf(this.refString(row, col)) < 0 && !currentField.state.active) { + if (this.checkFieldHasBeenNeverClicked(row, col)) { this.state.updatedFieldCache.push(this.refString(row, col)); currentField.setState({ @@ -229,77 +229,80 @@ class GridControl extends React.Component { * Player control method * * @param currentObject {int|string} Current object from Grid class - * @param x {int} - * @param y {int} + * @param row {int} + * @param col {int} * @param justOnFirstIteration {int} When bomb is being used check the whole explosion area */ - handleGridField(currentObject, x, y, justOnFirstIteration = 0) { + handleGridField(currentObject, row, col, justOnFirstIteration = 0) { var userControl = this.refs.userControl, - gridFieldControl = this.refs[this.refString(x, y)], + gridFieldControl = this.refs[this.refString(row, col)], activePlayer = userControl.state.activePlayer ? 'blue' : 'red', inactivePlayer = userControl.state.activePlayer ? 'red' : 'blue'; - /** 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] = [x, y]; - - /** if you found mine */ - if (currentObject === 'm') { - this.state.foundUserMineCache++; - + /** if the clicked field is NEVER CLICKED */ + if (this.checkFieldHasBeenNeverClicked(row, col)) { + /** update LAST CLICKED grid field */ if (!justOnFirstIteration) { - /** set last clicked field w/ color */ - this.state.lastClicked[activePlayer] = [x, y]; - - this.state.sound[ - (userControl.refs[activePlayer].state.mines + this.state.foundUserMineCache) > 20 - ? 'warning' - : 'mine' - ].play(); + 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 + }); + } } - /** set current image in field */ - gridFieldControl.setState({ - currentImage: gridFieldControl.state.icons.root + gridFieldControl.state.icons.flag[activePlayer] - }); - } else { - this.state.sound.click.play(); + this.state.lastClicked[activePlayer] = [row, col]; - /** set current image in field - WHEN it is a number */ - if (!isNaN(currentObject)) { + /** 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: currentObject + 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' }); } } - - /** - * 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' - }); - } } /** diff --git a/src/Mine/SeekerBundle/Topic/MineseekerTopic.php b/src/Mine/SeekerBundle/Topic/MineseekerTopic.php index 577eab7..08bb61e 100644 --- a/src/Mine/SeekerBundle/Topic/MineseekerTopic.php +++ b/src/Mine/SeekerBundle/Topic/MineseekerTopic.php @@ -44,7 +44,7 @@ class MineseekerTopic implements TopicInterface * @param ConnectionInterface $connection * @param Topic $topic * @param WampRequest $request - * @return void4 + * @return void */ public function onSubscribe(ConnectionInterface $connection, Topic $topic, WampRequest $request) { @@ -56,8 +56,7 @@ class MineseekerTopic implements TopicInterface if ($topic->count() > 2) { $topic->remove($connection); } else { - /** @var $users array Save users to database */ - $users = $this->saveUserToDb($topic, $userName, $user, $topic->count()); + $users = $this->controlUsers($topic, $userName, $user); $topic->broadcast([ 'userTopicId' => $connection->resourceId, @@ -129,6 +128,9 @@ class MineseekerTopic implements TopicInterface /** * Save Resign event to database + * + * @param $topic + * @param $color */ private function saveResignToDb($topic, $color) { @@ -176,6 +178,36 @@ class MineseekerTopic implements TopicInterface $this->em->flush(); } + /** + * Control all users in a channel + * + * @param $topic + * @param $userName + * @param $user + * @return array + */ + private function controlUsers($topic, $userName, $user) + { + $gameAssoc = explode('/', $topic->getId())[2]; + + $playedGame = $this->em + ->getRepository('MineSeekerBundle:PlayedGame') + ->findOneByGameAssoc($gameAssoc); + + $users = $this->getUserCollection($playedGame); + + /** This checks it is a reconnection */ + if ( + (null !== $users['red'] || null !== $users['redAnon']) && + (null !== $users['blue'] || null !== $users['blueAnon']) + ) { + /** @var $users array Save users to database */ + $users = $this->saveUserToDb($topic, $userName, $user, $topic->count()); + } + + return $users; + } + /** * Save user data to database * @@ -231,6 +263,17 @@ class MineseekerTopic implements TopicInterface $this->em->persist($playedGame); $this->em->flush(); + return $this->getUserCollection($playedGame); + } + + /** + * Get user collection from PlayedGame entity + * + * @param $playedGame + * @return array + */ + private function getUserCollection($playedGame) + { return array( 'red' => null !== $playedGame->getRed() ? $playedGame->getRed()->getUsername() : '', 'blue' => null !== $playedGame->getBlue() ? $playedGame->getBlue()->getUsername() : '', @@ -239,7 +282,9 @@ class MineseekerTopic implements TopicInterface ); } - /** Handle prod MySQL timeout */ + /** + * Handle prod MySQL timeout + */ private function reConnect() { try { diff --git a/webpack.config.js b/webpack.config.js index 488f993..0abfda3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,6 @@ 'use strict'; -module.exports = { +const config = module.exports = { entry: './web/bundles/mineseeker/js/mine-seeker.js', output: { path: './src/Mine/SeekerBundle/Resources/public/js', @@ -19,3 +19,5 @@ module.exports = { ] } }; + +module.exports = config;