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
+ ?
+
+ : '',
+ 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
- ?
-
- : '',
- 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;