diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..86c445f --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015", "react"] +} diff --git a/.gitignore b/.gitignore index f967762..0fdded9 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ phpunit-report/* /node_modules/ /src/Mine/SeekerBundle/Resources/public/js/node/ /src/Mine/SeekerBundle/Resources/public/js/src/ + +nohup.out diff --git a/README.md b/README.md index 627569e..6b77adf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,22 @@ -mine -==== +This is a Symfony 3 project w/ React JS in standalone mode and w/ WebSocket. -A Symfony project created on September 21, 2016, 10:11 am. + 0.) Must installed modules w/ npm are in package.json + to global: + + $ npm install webpack -g + + You will need a + .babelrc file w/ the presets + webpack.config.js - https://webpack.github.io/docs/webpack-for-browserify-users.html + same as dir where the package.json!! + + (!) Tutorial: https://egghead.io/lessons/react-introduction-to-properties + + + + 1.) Backend WebSocket server start as daemon - GeniusesOfSymfony/WebSocketBundle + + $ nohup bin/console gos:websocket:server & + + 2.) React JS WebPack watch generator w/ babel presets: es2015 + + $ webpack --progress --colors --watch diff --git a/app/AppKernel.php b/app/AppKernel.php index 9f3d8a6..e26e942 100644 --- a/app/AppKernel.php +++ b/app/AppKernel.php @@ -19,6 +19,8 @@ class AppKernel extends Kernel new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(), new Symfony\Bundle\AsseticBundle\AsseticBundle(), new FOS\UserBundle\FOSUserBundle(), + new Gos\Bundle\WebSocketBundle\GosWebSocketBundle(), + new Gos\Bundle\PubSubRouterBundle\GosPubSubRouterBundle(), new Jotunheimr\AdminBundle\JotunheimrAdminBundle(), new Jotunheimr\UserBundle\JotunheimrUserBundle(), diff --git a/app/config/config.yml b/app/config/config.yml index 4787fc0..48d8fcc 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -74,3 +74,9 @@ fos_user: db_driver: orm # other valid values are 'mongodb', 'couchdb' and 'propel' firewall_name: main user_class: Jotunheimr\UserBundle\Entity\User + +# Web Socket Configuration +gos_web_socket: + server: + port: 8080 #The port the socket server will listen on + host: 127.0.0.1 #The host ip to bind to diff --git a/package.json b/package.json index efbb05a..84c2278 100644 --- a/package.json +++ b/package.json @@ -2,17 +2,17 @@ "name": "mine-seeker", "version": "1.0.0", "description": "Mine Seeker Game by system7", - "main": "index.js", "directories": { "test": "tests" }, "dependencies": { "babel-core": "^6.14.0", "babel-loader": "^6.2.5", + "babel-preset-es2015": "^6.14.0", "babel-preset-react": "^6.11.1", - "babelify": "^7.3.0", "react": "^15.3.2", - "react-dom": "^15.3.2" + "react-dom": "^15.3.2", + "react-websocket": "^1.1.6" }, "devDependencies": {}, "scripts": { diff --git a/src/Mine/SeekerBundle/Resources/public/css/style.mineseeker.css b/src/Mine/SeekerBundle/Resources/public/css/style.mineseeker.css new file mode 100644 index 0000000..34a3da8 --- /dev/null +++ b/src/Mine/SeekerBundle/Resources/public/css/style.mineseeker.css @@ -0,0 +1,32 @@ +#mine-wrapper, +#mine-wrapper * { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +#mine-wrapper .grid { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + flex-wrap: wrap; + width: 642px; + border: 1px solid red; +} + +#mine-wrapper .grid .field { + background: #fff; + width: 40px; + height: 40px; + border: 1px solid red; +} + +#mine-wrapper .grid .field.active { + background: blue; + color: #FFFFFF; +} + +#mine-wrapper .grid .field.active.mine { + background: red; + color: #FFFFFF; +} \ No newline at end of file diff --git a/src/Mine/SeekerBundle/Resources/public/js/mine-seeker.js b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker.js new file mode 100644 index 0000000..d4dab14 --- /dev/null +++ b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker.js @@ -0,0 +1,8 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import MineSeeker from './mine-seeker/app'; + +ReactDOM.render( + , + document.getElementById('mine-wrapper') +); diff --git a/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/app.js b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/app.js new file mode 100644 index 0000000..ff61d2d --- /dev/null +++ b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/app.js @@ -0,0 +1,21 @@ +import React from 'react'; +import GridControl from './grid/grid-control'; + +class MineSeeker extends React.Component { + // /** after rendering */ + // componentDidMount() { + // this.connection = new WebSocket('ws://127.0.0.1:8080'); + // + // this.connection.onmessage = evt => { + // // console.log(evt.data); + // }; + // } + + render() { + return ( + + ); + } +} + +export default MineSeeker; 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 new file mode 100644 index 0000000..6a4781d --- /dev/null +++ b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/grid/grid-control.js @@ -0,0 +1,160 @@ +import React from 'react'; +import Grid from './grid'; +import GridField from './grid-field'; +import UserControl from '../user/user-control'; + +class GridControl extends React.Component { + constructor() { + super(); + + var grid = new Grid(); + + this.state = { + grid: grid.state.grid, + updated: [] + }; + } + + refString(row, col) { + return 'gridField_' + row + '_' + col; + } + + checkMine(field, i, j) { + return typeof field[i] !== 'undefined' && typeof field[i][j] !== 'undefined' && field[i][j] !== 'm'; + } + + checkNeighbourItem(row, col) { + if (this.checkMine(this.state.grid, row, col)) { + var currentField = this.refs[this.refString(row, col)]; + + currentField.setState({ + currentObj: this.state.grid[row][col], + active: true + }); + + /** + * ez azért kell, mert amíg nem fut le a showAppropriateFields(), addig nem updatelődik a GridField.state + */ + if ( + this.state.grid[row][col] !== 0 && + this.state.updated.indexOf(this.refString(row, col)) < 0 && !currentField.state.active + ) { + this.state.updated.push(this.refString(row, col)); + } + + if ( + this.state.grid[row][col] === 0 && + this.state.updated.indexOf(this.refString(row, col)) < 0 && !currentField.state.active + ) { + this.state.updated.push(this.refString(row, col)); + + return { + row: row, + col: col + }; + } + } + return false; + } + + checkNeighbours(row, col) { + var anotherFields = []; + + anotherFields.push(this.checkNeighbourItem(row - 1, col)); + anotherFields.push(this.checkNeighbourItem(row - 1, col - 1)); + anotherFields.push(this.checkNeighbourItem(row - 1, col + 1)); + anotherFields.push(this.checkNeighbourItem(row, col - 1)); + anotherFields.push(this.checkNeighbourItem(row, col + 1)); + anotherFields.push(this.checkNeighbourItem(row + 1, col)); + anotherFields.push(this.checkNeighbourItem(row + 1, col + 1)); + anotherFields.push(this.checkNeighbourItem(row + 1, col - 1)); + + return anotherFields; + } + + showAppropriateFields(currentField, row, col) { + currentField.setState({ + currentObj: this.state.grid[row][col], + active: true + }); + + if (this.state.updated.indexOf(this.refString(row, col)) < 0) { + this.state.updated.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 + */ + handlePlayers(currentObject) { + var userControl = this.refs.userControl, + activePlayer = userControl.state.activePlayer ? 'blue' : 'red'; + + if (currentObject === 'm') { + userControl.refs[activePlayer].setState({ + mines: userControl.refs[activePlayer].state.mines + 1 + }); + } else { + userControl.state.activePlayer = userControl.state.activePlayer ? 0 : 1; + } + } + + /** + * Most important event!! + * @param coords + */ + onClick(coords) { + var currentField = this.refs[this.refString(coords[0], coords[1])]; + this.showAppropriateFields(currentField, coords[0], coords[1]); + this.handlePlayers(this.state.grid[coords[0]][coords[1]]); + } + + renderGrid() { + var grid = []; + + for (var i = 0, j = this.state.grid.length; i < j; i++) { + for (var k = 0, l = this.state.grid[i].length; k < l; k++) { + grid.push( + + ); + } + } + + return grid; + } + + render() { + return ( +
+
+ +
+
+ {this.renderGrid()} +
+
+ ); + } +} + +export default GridControl; diff --git a/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/grid/grid-field.js b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/grid/grid-field.js new file mode 100644 index 0000000..66810bf --- /dev/null +++ b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/grid/grid-field.js @@ -0,0 +1,29 @@ +import React from 'react'; + +class GridField extends React.Component { + constructor(props) { + super(props); + + this.state = { + currentObj: 'w', + obj: this.props.obj, + active: false + }; + } + + isActive() { + return 'field' + + (this.state.active === true ? ' active' : '') + + (this.state.active === true && this.state.obj === 'm' ? ' mine' : ''); + } + + render() { + return ( +
+ {this.state.currentObj} +
+ ); + } +} + +export default GridField; diff --git a/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/grid/grid.js b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/grid/grid.js new file mode 100644 index 0000000..66857c7 --- /dev/null +++ b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/grid/grid.js @@ -0,0 +1,101 @@ +import React from 'react'; + +class Grid extends React.Component { + constructor() { + super(); + + this.state = { + row: 16, + col: 16, + mines: 51, + set: [] + }; + + this.state.grid = this.numberingGrid( + this.createGrid( + this.shuffleSet( + this.createSet( + this.state.set + ) + ) + ) + ); + } + + createSet(obj) { + for (var i = 0, j = this.state.row * this.state.col; i < j; i++) { + obj.push( + this.state.mines > 0 + ? "m" + : "w" + ); + this.state.mines--; + } + + return obj; + } + + shuffleSet(obj) { + return obj.sort(function () { + return Math.round(Math.random()) - .5; + }); + } + + createGrid(obj) { + var grid = [[]], + row = 0, + col = 0; + + for (var i = 0, j = obj.length; i < j; i++) { + grid[row][col] = obj[i]; + + if (col === 15 && row !== 15) { + row++; + col = 0; + grid.push([]); + } else { + col++; + } + } + + return grid; + } + + checkMine(field, i, j) { + return typeof field[i] !== 'undefined' && typeof field[i][j] !== 'undefined' && field[i][j] === 'm'; + } + + isThereMine(obj, row, col) { + if (this.checkMine(obj, row, col)) { + return 1; + } + return 0; + } + + numberingGrid(obj) { + var nbr = 0; + + for (var i = 0; i < this.state.row; i++) { + for (var j = 0; j < this.state.col; j++) { + if (obj[i][j] === 'w') { + nbr = 0; + + nbr += this.isThereMine(obj, i - 1, j); + nbr += this.isThereMine(obj, i - 1, j - 1); + nbr += this.isThereMine(obj, i - 1, j + 1); + nbr += this.isThereMine(obj, i, j - 1); + nbr += this.isThereMine(obj, i, j + 1); + nbr += this.isThereMine(obj, i + 1, j); + nbr += this.isThereMine(obj, i + 1, j + 1); + nbr += this.isThereMine(obj, i + 1, j - 1); + + obj[i][j] = nbr; + } + } + } + + return obj; + } +} + +export default Grid; diff --git a/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/user/user-control.js b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/user/user-control.js new file mode 100644 index 0000000..3cb5167 --- /dev/null +++ b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/user/user-control.js @@ -0,0 +1,28 @@ +import React from 'react'; +import User from './user'; + +class UserControl extends React.Component { + constructor() { + super(); + + /** + * activePlayer - red: 0, blue: 1 + * @type {{activePlayer: number, mines: number}} + */ + this.state = { + activePlayer: 0, + mines: 51 + }; + } + + render() { + return ( +
+ + +
+ ); + } +} + +export default UserControl; diff --git a/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/user/user.js b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/user/user.js new file mode 100644 index 0000000..dded393 --- /dev/null +++ b/src/Mine/SeekerBundle/Resources/public/js/mine-seeker/user/user.js @@ -0,0 +1,23 @@ +import React from 'react'; + +class User extends React.Component { + constructor(props) { + super(props); + + this.state = { + name: this.props.name, + bomb: 1, + mines: 0 + } + } + + render() { + return ( +
+ {this.state.name}: {this.state.mines} +
+ ); + } +} + +export default User; diff --git a/src/Mine/SeekerBundle/Resources/views/Game/play.html.twig b/src/Mine/SeekerBundle/Resources/views/Game/play.html.twig index 3682642..78f5b48 100644 --- a/src/Mine/SeekerBundle/Resources/views/Game/play.html.twig +++ b/src/Mine/SeekerBundle/Resources/views/Game/play.html.twig @@ -1,24 +1,18 @@ {% extends '::base.html.twig' %} +{% block stylesheets %} + {% stylesheets filter='cssrewrite' + '@MineSeekerBundle/Resources/public/css/style.mineseeker.css' %} + + {% endstylesheets %} +{% endblock %} + {% block body %} -
+
{% endblock %} {% block javascripts %} {{ parent() }} - - - {% javascripts - '@MineSeekerBundle/Resources/public/js/node/react/dist/react.js' - '@MineSeekerBundle/Resources/public/js/src/build/react-dom.js'%} - - {% endjavascripts %} - - + {% endblock %} diff --git a/web/node b/web/node new file mode 120000 index 0000000..61c06aa --- /dev/null +++ b/web/node @@ -0,0 +1 @@ +bundles/mineseeker/js/node/ \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..8bd9b41 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,21 @@ +'use strict'; + +module.exports = { + entry: './web/bundles/mineseeker/js/mine-seeker.js', + output: { + path: './web/js', + filename: 'index.js' + }, + module: { + loaders: [ + { + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel', + query: { + presets: ['es2015', 'react'] + } + } + ] + } +};