websocket basic setup FE & BE && working basic game w/ react && webpack & babel config
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -33,3 +33,5 @@ phpunit-report/*
|
|||||||
/node_modules/
|
/node_modules/
|
||||||
/src/Mine/SeekerBundle/Resources/public/js/node/
|
/src/Mine/SeekerBundle/Resources/public/js/node/
|
||||||
/src/Mine/SeekerBundle/Resources/public/js/src/
|
/src/Mine/SeekerBundle/Resources/public/js/src/
|
||||||
|
|
||||||
|
nohup.out
|
||||||
|
|||||||
24
README.md
24
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
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ class AppKernel extends Kernel
|
|||||||
new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(),
|
new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(),
|
||||||
new Symfony\Bundle\AsseticBundle\AsseticBundle(),
|
new Symfony\Bundle\AsseticBundle\AsseticBundle(),
|
||||||
new FOS\UserBundle\FOSUserBundle(),
|
new FOS\UserBundle\FOSUserBundle(),
|
||||||
|
new Gos\Bundle\WebSocketBundle\GosWebSocketBundle(),
|
||||||
|
new Gos\Bundle\PubSubRouterBundle\GosPubSubRouterBundle(),
|
||||||
|
|
||||||
new Jotunheimr\AdminBundle\JotunheimrAdminBundle(),
|
new Jotunheimr\AdminBundle\JotunheimrAdminBundle(),
|
||||||
new Jotunheimr\UserBundle\JotunheimrUserBundle(),
|
new Jotunheimr\UserBundle\JotunheimrUserBundle(),
|
||||||
|
|||||||
@@ -74,3 +74,9 @@ fos_user:
|
|||||||
db_driver: orm # other valid values are 'mongodb', 'couchdb' and 'propel'
|
db_driver: orm # other valid values are 'mongodb', 'couchdb' and 'propel'
|
||||||
firewall_name: main
|
firewall_name: main
|
||||||
user_class: Jotunheimr\UserBundle\Entity\User
|
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
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
"name": "mine-seeker",
|
"name": "mine-seeker",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Mine Seeker Game by system7",
|
"description": "Mine Seeker Game by system7",
|
||||||
"main": "index.js",
|
|
||||||
"directories": {
|
"directories": {
|
||||||
"test": "tests"
|
"test": "tests"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-core": "^6.14.0",
|
"babel-core": "^6.14.0",
|
||||||
"babel-loader": "^6.2.5",
|
"babel-loader": "^6.2.5",
|
||||||
|
"babel-preset-es2015": "^6.14.0",
|
||||||
"babel-preset-react": "^6.11.1",
|
"babel-preset-react": "^6.11.1",
|
||||||
"babelify": "^7.3.0",
|
|
||||||
"react": "^15.3.2",
|
"react": "^15.3.2",
|
||||||
"react-dom": "^15.3.2"
|
"react-dom": "^15.3.2",
|
||||||
|
"react-websocket": "^1.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {},
|
"devDependencies": {},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
8
src/Mine/SeekerBundle/Resources/public/js/mine-seeker.js
Normal file
8
src/Mine/SeekerBundle/Resources/public/js/mine-seeker.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import MineSeeker from './mine-seeker/app';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<MineSeeker />,
|
||||||
|
document.getElementById('mine-wrapper')
|
||||||
|
);
|
||||||
21
src/Mine/SeekerBundle/Resources/public/js/mine-seeker/app.js
Normal file
21
src/Mine/SeekerBundle/Resources/public/js/mine-seeker/app.js
Normal file
@@ -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 (
|
||||||
|
<GridControl ref="gridControl"/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MineSeeker;
|
||||||
@@ -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(
|
||||||
|
<GridField row={i}
|
||||||
|
col={k}
|
||||||
|
obj={this.state.grid[i][k]}
|
||||||
|
ref={this.refString(i, k)}
|
||||||
|
key={i + k * Math.random() * 0.5}
|
||||||
|
onClick={this.onClick.bind(this, [i, k])}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="game-wrapper">
|
||||||
|
<div className="users">
|
||||||
|
<UserControl ref="userControl" blue="Olcsó János" red="Eszet Lenke"/>
|
||||||
|
</div>
|
||||||
|
<div className="grid">
|
||||||
|
{this.renderGrid()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GridControl;
|
||||||
@@ -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 (
|
||||||
|
<div className={this.isActive()} onClick={this.props.onClick}>
|
||||||
|
{this.state.currentObj}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GridField;
|
||||||
@@ -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;
|
||||||
@@ -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 (
|
||||||
|
<div>
|
||||||
|
<User ref="blue" name={this.props.blue}/>
|
||||||
|
<User ref="red" name={this.props.red}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserControl;
|
||||||
@@ -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 (
|
||||||
|
<div>
|
||||||
|
{this.state.name}: {this.state.mines}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default User;
|
||||||
@@ -1,24 +1,18 @@
|
|||||||
{% extends '::base.html.twig' %}
|
{% extends '::base.html.twig' %}
|
||||||
|
|
||||||
|
{% block stylesheets %}
|
||||||
|
{% stylesheets filter='cssrewrite'
|
||||||
|
'@MineSeekerBundle/Resources/public/css/style.mineseeker.css' %}
|
||||||
|
<link rel="stylesheet" media="screen" href="{{ asset_url }}" type="text/css"/>
|
||||||
|
{% endstylesheets %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div id="example"></div>
|
<div id="mine-wrapper"></div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block javascripts %}
|
{% block javascripts %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
|
|
||||||
<script type="text/babel">
|
<script type="text/javascript" src="{{ asset('js/index.js') }}"></script>
|
||||||
ReactDOM.render(
|
|
||||||
<h1>Hello, world!</h1>,
|
|
||||||
document.getElementById('example')
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% javascripts
|
|
||||||
'@MineSeekerBundle/Resources/public/js/node/react/dist/react.js'
|
|
||||||
'@MineSeekerBundle/Resources/public/js/src/build/react-dom.js'%}
|
|
||||||
<script type="text/javascript" src="{{ asset_url }}"></script>
|
|
||||||
{% endjavascripts %}
|
|
||||||
|
|
||||||
<script src="https://unpkg.com/babel-core@5.8.38/browser.min.js"></script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
21
webpack.config.js
Normal file
21
webpack.config.js
Normal file
@@ -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']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user