Re-architecting to implement redux.

master
Ben Burlingham 9 years ago
parent 0296107742
commit be7f760ccd
  1. 5
      .babelrc
  2. 33
      js/State.js
  3. 27
      js/app/App.js
  4. 8
      js/app/Constants.js
  5. 4
      js/app/Creators.js
  6. 15
      js/app/Reducers.js
  7. 14
      js/board/Board.js
  8. 86
      js/board/Grid.js
  9. 9
      js/board/GridCell.js
  10. 74
      js/board/Message.js
  11. 25
      js/board/Muncher.js
  12. 58
      js/board/Scorebar.js
  13. 26
      js/board/Titlebar.js
  14. 21
      js/index.js
  15. 36
      js/main.js
  16. 13
      js/welcome/HighScoreEntry.js
  17. 36
      js/welcome/HighScores.js
  18. 31
      js/welcome/NewGame.js
  19. 57
      js/welcome/Welcome.js
  20. 24
      npm-debug.log
  21. 12
      package.json
  22. 2
      readme.md
  23. 7
      webpack.config.js

@ -1,5 +0,0 @@
{
"presets": [
"react"
]
}

@ -1,33 +0,0 @@
var State = require('./State');
var actions = Object.create(null);
/**
*
*/
module.exports = {
level: 0,
subscribe(event, callback) {
if (actions[event] === undefined) {
actions[event] = [];
}
actions[event].push(callback);
},
publish(event) {
if (actions[event] === undefined) {
return;
}
var args = Array.prototype.slice.call(arguments, 1);
actions[event].forEach(function(callback) {
callback.apply(null, args);
});
}
};
module.exports.subscribe('level/complete', function() {
module.exports.level++;
});

@ -0,0 +1,27 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Board from '../board/Board';
import Welcome from '../welcome/Welcome';
import { MODE } from './Constants';
export class App extends Component {
render() {
const { mode } = this.props;
switch (mode) {
case MODE.WELCOME:
return <Welcome />;
case MODE.BOARD:
return <Board />;
}
};
};
const select = (state) => {
return {
mode: state.mode
}
};
export default connect(select)(App);

@ -0,0 +1,8 @@
export const MODE = {
WELCOME: 'WELCOME',
BOARD: 'BOARD'
};
export const ACTIONS = {
NEXT: 'NEXT'
};

@ -0,0 +1,4 @@
import { ACTIONS } from './Constants';
export const nextMode =
() => ({ type: ACTIONS.NEXT });

@ -0,0 +1,15 @@
import { MODE, ACTIONS } from './Constants.js';
export const modeReducer = (state = MODE.WELCOME, action) => {
if (action.type === ACTIONS.NEXT) {
switch (state) {
case MODE.BOARD:
return MODE.WELCOME;
case MODE.WELCOME:
return MODE.BOARD;
};
}
else {
return MODE.WELCOME;
}
};

@ -1,12 +1,12 @@
require('../../sass/board/board.scss'); require('../../sass/board/board.scss');
var React = require('react'); import { Component } from 'react';
var Scorebar = require('./Scorebar'); import Scorebar from './Scorebar';
var Titlebar = require('./Titlebar'); import Titlebar from './Titlebar';
var Grid = require('./Grid'); import Grid from './Grid';
var Message = require('./Message'); import Message from './Message';
var Muncher = require('./Muncher'); import Muncher from './Muncher';
var Input = require('./Input'); import Input from './Input';
module.exports = React.createClass({ module.exports = React.createClass({
componentDidMount() { componentDidMount() {

@ -1,68 +1,60 @@
require('../../sass/board/grid.scss'); require('../../sass/board/grid.scss');
var React = require('react'); import { Component } from 'react';
var Values = require('./Values'); import GridCell from './GridCell';
var State = require('../State'); // var Values = require('./Values');
var Cell = React.createClass({ export default class Grid extends Component {
render() {
var classname = ['cell', 'x' + this.props.x, 'y' + this.props.y];
return (<div className={classname.join(' ')}>{this.props.value}</div>);
}
});
module.exports = React.createClass({
generateValues() { generateValues() {
return Values.generate(this.props.width * this.props.height, State.level); // return Values.generate(this.props.width * this.props.height, State.level);
}, };
getInitialState() { // getInitialState() {
return { values: this.generateValues() }; // return { values: this.generateValues() };
}, // },
componentDidMount() { componentDidMount() {
State.subscribe('level/next', this.levelNext); // State.subscribe('level/next', this.levelNext);
}, };
checkComplete() { checkComplete() {
if (Values.checkComplete(this.state.values, State.level) === true) { // if (Values.checkComplete(this.state.values, State.level) === true) {
State.publish('level/complete'); // State.publish('level/complete');
} // }
}, };
levelNext() { levelNext() {
this.setState({ values: this.generateValues() }); // this.setState({ values: this.generateValues() });
}, };
munch(x, y) { munch(x, y) {
var i = y * this.props.width + x; // var i = y * this.props.width + x;
//
if (this.state.values[i] === "") { // if (this.state.values[i] === "") {
return; // return;
} // }
//
if (Values.validate(this.state.values[i], State.level)) { // if (Values.validate(this.state.values[i], State.level)) {
this.state.values[i] = ""; // this.state.values[i] = "";
this.setState({ values: this.state.values }, this.checkComplete); // this.setState({ values: this.state.values }, this.checkComplete);
State.publish('munch/successful'); // State.publish('munch/successful');
} // }
else { // else {
State.publish('munch/failed', this.state.values[i]); // State.publish('munch/failed', this.state.values[i]);
} // }
}, };
render() { render() {
var cells = []; const cells = [];
var i; let i;
for (var x = 0; x < this.props.width; x++) { for (let x = 0; x < this.props.width; x++) {
for (var y = 0; y < this.props.height; y++) { for (let y = 0; y < this.props.height; y++) {
i = y * this.props.width + x; i = y * this.props.width + x;
cells.push(<Cell value={this.state.values[i]} x={x} y={y} key={i} />); // cells.push(<GridCell value={this.state.values[i]} x={x} y={y} key={i} />);
} }
} }
return (<div className='grid'>{cells}</div>); return (<div className='grid'>{cells}</div>);
} };
}); };

@ -0,0 +1,9 @@
import { Component } from 'react';
export default class GridCell extends Component {
render() {
const classname = ['cell', 'x' + this.props.x, 'y' + this.props.y];
return (<div className={classname.join(' ')}>{this.props.value}</div>);
}
};

@ -1,8 +1,7 @@
require('../../sass/board/message.scss'); require('../../sass/board/message.scss');
var React = require('react'); import { Component } from 'react';
var State = require('../State'); // var Values = require('./Values');
var Values = require('./Values');
var exclamations = [ var exclamations = [
'Congratulations!', 'Congratulations!',
@ -15,20 +14,20 @@ var exclamations = [
'Shazam!' 'Shazam!'
]; ];
module.exports = React.createClass({ export default class Message extends Component {
getInitialState() { // getInitialState() {
return { // return {
message1: 'Congratulations!', // message1: 'Congratulations!',
message2: 'Press spacebar to continue.', // message2: 'Press spacebar to continue.',
hidden: true // hidden: true
}; // };
}, // },
componentDidMount() { componentDidMount() {
State.subscribe('level/complete', this.levelComplete); // State.subscribe('level/complete', this.levelComplete);
State.subscribe('level/next', this.levelNext); // State.subscribe('level/next', this.levelNext);
State.subscribe('munch/failed', this.munchFailed); // State.subscribe('munch/failed', this.munchFailed);
}, };
munchFailed(value) { munchFailed(value) {
var self = this; var self = this;
@ -36,45 +35,48 @@ module.exports = React.createClass({
function keydown(e) { function keydown(e) {
if (e.keyCode === 32) { if (e.keyCode === 32) {
window.removeEventListener('keydown', keydown); window.removeEventListener('keydown', keydown);
self.setState({ hidden: true }); // self.setState({ hidden: true });
} }
}; };
var msg = Values.getError(value, State.level); // var msg = Values.getError(value, State.level);
this.setState({ hidden: false, message1: msg }); // this.setState({ hidden: false, message1: msg });
window.addEventListener('keydown', keydown); window.addEventListener('keydown', keydown);
}, };
levelComplete() { levelComplete() {
function keydown(e) { function keydown(e) {
if (e.keyCode === 32) { if (e.keyCode === 32) {
window.removeEventListener('keydown', keydown); window.removeEventListener('keydown', keydown);
State.publish('level/next'); // State.publish('level/next');
} }
}; };
var msg = exclamations[Math.floor(Math.random() * exclamations.length)]; var msg = exclamations[Math.floor(Math.random() * exclamations.length)];
this.setState({ hidden: false, message1: msg }); // this.setState({ hidden: false, message1: msg });
window.addEventListener('keydown', keydown); window.addEventListener('keydown', keydown);
}, };
levelNext() { levelNext() {
this.setState({ hidden: true }); // this.setState({ hidden: true });
}, };
render() { render() {
var classname = ['message']; var classname = ['message'];
if (this.state.hidden === true) { // if (this.state.hidden === true) {
classname.push('hidden'); // classname.push('hidden');
} // }
return ( return (<div>message</div>);
<div className={classname.join(' ')}>
{this.state.message1}
<br /> // return (
{this.state.message2} // <div className={classname.join(' ')}>
</div> // {this.state.message1}
); // <br />
} // {this.state.message2}
}); // </div>
// );
};
};

@ -1,20 +1,21 @@
require('../../sass/board/muncher.scss'); require('../../sass/board/muncher.scss');
var React = require('react'); import { Component } from 'react';
module.exports = React.createClass({ export default class Muncher extends Component{
getInitialState() { // getInitialState() {
return { // return {
x: 0, // x: 0,
y: 0 // y: 0
}; // };
}, // },
render() { render() {
var classname = ['muncher', 'x' + this.state.x, 'y' + this.state.y]; // const classname = ['muncher', 'x' + this.state.x, 'y' + this.state.y];
return ( return (
<div className={classname.join(' ')}></div> <div className='muncher'></div>
// <div className={classname.join(' ')}></div>
); );
} };
}); };

@ -1,42 +1,42 @@
require('../../sass/board/scorebar.scss'); require('../../sass/board/scorebar.scss');
var React = require('react'); import { Component } from 'react';
var State = require('../State');
module.exports = React.createClass({ export default class Scorebar extends Component {
getInitialState() { // getInitialState() {
return { // return {
currentScore: 0, // currentScore: 0,
highScore: 0, // highScore: 0,
lives: 3 // lives: 3
}; // };
}, // },
componentDidMount() { componentDidMount() {
State.subscribe('munch/successful', this.updateScore); // // State.subscribe('munch/successful', this.updateScore);
State.subscribe('munch/failed', this.updateLives); // State.subscribe('munch/failed', this.updateLives);
}, };
updateScore() { updateScore() {
var score = this.state.currentScore; // var score = this.state.currentScore;
this.setState({ currentScore: score + 10 }); // this.setState({ currentScore: score + 10 });
}, };
updateLives() { updateLives() {
var lives = this.state.lives; // var lives = this.state.lives;
this.setState({ lives: lives - 1 }); // this.setState({ lives: lives - 1 });
}, };
render() { render() {
var lives = []; var lives = [];
for (var i = 0; i < this.state.lives; i++) { // for (var i = 0; i < this.state.lives; i++) {
lives.push(<div className='life' key={i}></div>); // lives.push(<div className='life' key={i}></div>);
} // }
return (<div className='scorebar'> // return (<div className='scorebar'>
<div className='item current-score'>{this.state.currentScore}</div> // <div className='item current-score'>{this.state.currentScore}</div>
<div className='item high-score'>{this.state.highScore}</div> // <div className='item high-score'>{this.state.highScore}</div>
<div className='item lives'>{lives}</div> // <div className='item lives'>{lives}</div>
</div>); // </div>);
} return <h1>Scorebar</h1>;
}); };
};

@ -1,25 +1,17 @@
require('../../sass/board/titlebar.scss'); require('../../sass/board/titlebar.scss');
var React = require('react'); import { Component } from 'react';
var State = require('../State');
var Values = require('./Values');
module.exports = React.createClass({
getInitialState() {
return {
title: Values.getDescription(State.level)
};
},
export default class Titlebar extends Component {
componentDidMount() { componentDidMount() {
State.subscribe('level/next', this.levelNext); // State.subscribe('level/next', this.levelNext);
}, };
levelNext() { levelNext() {
this.setState({ title: Values.getDescription(State.level) }); // this.setState({ title: Values.getDescription(State.level) });
}, };
render() { render() {
return (<div className='titlebar'>{this.state.title}</div>); return (<div className='titlebar'>DYNAMIC TITLE</div>);
} };
}); };

@ -0,0 +1,21 @@
require('../sass/reset.scss');
import { render } from 'react-dom';
import { createStore, combineReducers } from 'redux'
import { Provider } from 'react-redux';
import App from './app/App';
import * as reducers from './app/Reducers';
const combinedReducers = combineReducers({
mode: reducers.modeReducer
});
const store = createStore(combinedReducers);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
);

@ -1,36 +0,0 @@
require('../sass/reset.scss');
var React = require('react');
var ReactDom = require('react-dom');
var Board = require('./board/Board');
var Welcome = require('./welcome/Welcome');
var State = require('./State');
var App = React.createClass({
getInitialState() {
return {
screen: 'board'
};
},
handleKeydown(e) {
if (this.state.screen === 'welcome' && e.keyCode === 32) {
this.setState({ screen: 'board' });
}
},
componentDidMount() {
window.addEventListener('keydown', this.handleKeydown);
},
render() {
if (this.state.screen === 'welcome') {
return (<Welcome />);
}
else if (this.state.screen === 'board') {
return (<Board />);
}
}
});
ReactDom.render(<App />, document.getElementById('app'));

@ -0,0 +1,13 @@
import { Component } from 'react';
export default class HighScoreEntry extends Component {
render() {
return (
<div className='entry'>
<div className='rank'>{this.props.rank}</div>
<div className='initials'>{this.props.initials}</div>
<div className='score'>{this.props.score}</div>
</div>
);
}
};

@ -1,32 +1,26 @@
require('../../sass/welcome/highscores.scss'); require('../../sass/welcome/highscores.scss');
var React = require('react'); import { Component } from 'react';
import HighScoreEntry from './HighScoreEntry';
var Entry = React.createClass({ export default class HighScores extends Component {
render() { render() {
return ( const vals = [
<div className='entry'> { initials: "ABA", score: "219283", rank: "1" },
<div className='rank'>{this.props.rank}</div> { initials: "ABA", score: "107112", rank: "2" },
<div className='initials'>{this.props.initials}</div> { initials: "ABA", score: "81091", rank: "3" },
<div className='score'>{this.props.score}</div> { initials: "ABA", score: "67747", rank: "4" },
</div> { initials: "ABA", score: "9283", rank: "5" },
); { initials: "ABA", score: "928", rank: "6" }
} ];
});
module.exports = React.createClass({
render() {
var entries = []; var entries = [];
entries.push(<Entry initials="ABA" score="219283" rank="1" key="0" />); vals.map(function(v, i) {
entries.push(<Entry initials="ABA" score="107112" rank="2" key="1" />); entries.push(<HighScoreEntry initials={v.initials} score={v.score} rank={v.rank} key={i} />);
entries.push(<Entry initials="ABA" score="81091" rank="3" key="2" />); });
entries.push(<Entry initials="ABA" score="67747" rank="4" key="3" />);
entries.push(<Entry initials="ABA" score="9283" rank="5" key="4" />);
entries.push(<Entry initials="ABA" score="928" rank="6" key="5" />);
return ( return (
<div className='highscores'>{entries}</div> <div className='highscores'>{entries}</div>
); );
} }
}); };

@ -0,0 +1,31 @@
import { Component } from 'react';
const blinkTimer = null;
const toggleTimeout = function() {
//var hidden = this.state.hidden; which was false
//this.setState({ hidden: !hidden });
//blinkTimer = setTimeout(toggleTimeout.bind(this), 600);
};
export default class NewGame extends Component {
componentDidMount() {
//toggleTimeout.call(this);
};
componentWillUnmount() {
clearTimeout(blinkTimer);
};
render() {
const classname = ['newgame'];
//if (this.state.hidden === true) {
// classname.push('hidden');
//}
return (
<div className={classname.join(' ')}>Press Space Bar To Play</div>
);
};
};

@ -1,46 +1,29 @@
require('../../sass/welcome/welcome.scss'); require('../../sass/welcome/welcome.scss');
var React = require('react'); import { Component } from 'react';
var HighScores = require('./HighScores'); import { connect } from 'react-redux';
import * as creators from '../app/Creators.js';
var blinkTimer = null;
import NewGame from './NewGame';
var toggleTimeout = function() { import HighScores from './HighScores';
var hidden = this.state.hidden;
this.setState({ hidden: !hidden }); export class Welcome extends Component {
handleKeydown(e) {
blinkTimer = setTimeout(toggleTimeout.bind(this), 600); if (e.keyCode === 32) {
}; this.props.dispatch(creators.nextMode());
//this.setState({ screen: 'board' });
var NewGame = React.createClass({
getInitialState() {
return {
hidden: false
} }
}, };
componentDidMount() { componentDidMount() {
toggleTimeout.call(this); window.addEventListener('keydown', this.handleKeydown.bind(this));
}, };
componentWillUnmount() { componentWillUnmount() {
clearTimeout(blinkTimer); console.log('need to remove listener')
}, // remove keydown listener
};
render() {
var classname = ['newgame'];
if (this.state.hidden === true) {
classname.push('hidden');
}
return (
<div className={classname.join(' ')}>Press Space Bar To Play</div>
);
}
});
module.exports = React.createClass({
render() { render() {
return ( return (
<div className='welcome'> <div className='welcome'>
@ -50,4 +33,6 @@ module.exports = React.createClass({
</div> </div>
); );
} }
}); };
export default connect(() => ({}))(Welcome);

@ -0,0 +1,24 @@
0 info it worked if it ends with ok
1 verbose cli [ '/usr/local/bin/node', '/usr/local/bin/npm', 'run', 'start' ]
2 info using npm@3.7.3
3 info using node@v5.8.0
4 verbose stack Error: missing script: start
4 verbose stack at run (/usr/local/lib/node_modules/npm/lib/run-script.js:147:19)
4 verbose stack at /usr/local/lib/node_modules/npm/lib/run-script.js:57:5
4 verbose stack at /usr/local/lib/node_modules/npm/node_modules/read-package-json/read-json.js:345:5
4 verbose stack at checkBinReferences_ (/usr/local/lib/node_modules/npm/node_modules/read-package-json/read-json.js:309:45)
4 verbose stack at final (/usr/local/lib/node_modules/npm/node_modules/read-package-json/read-json.js:343:3)
4 verbose stack at then (/usr/local/lib/node_modules/npm/node_modules/read-package-json/read-json.js:113:5)
4 verbose stack at /usr/local/lib/node_modules/npm/node_modules/read-package-json/read-json.js:300:12
4 verbose stack at /usr/local/lib/node_modules/npm/node_modules/graceful-fs/graceful-fs.js:78:16
4 verbose stack at tryToString (fs.js:414:3)
4 verbose stack at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:401:12)
5 verbose cwd /Users/tonklin/Development/bb/number-munchers
6 error Darwin 15.3.0
7 error argv "/usr/local/bin/node" "/usr/local/bin/npm" "run" "start"
8 error node v5.8.0
9 error npm v3.7.3
10 error missing script: start
11 error If you need help, you may report this error at:
11 error <https://github.com/npm/npm/issues>
12 verbose exit [ 1, true ]

@ -4,15 +4,15 @@
"description": "MECC's classic Number Munchers game rebuilt with React and Webpack.", "description": "MECC's classic Number Munchers game rebuilt with React and Webpack.",
"main": "./js/main.js", "main": "./js/main.js",
"scripts": { "scripts": {
"start": "./node_modules/webpack-dev-server/bin/webpack-dev-server.js --progress --colors" "dev": "./node_modules/webpack-dev-server/bin/webpack-dev-server.js --progress --colors"
}, },
"author": "Ben Burlingham", "author": "Ben Burlingham",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {},
"babel-loader": "^6.2.4",
"babel-preset-react": "^6.5.0"
},
"devDependencies": { "devDependencies": {
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"css-loader": "^0.23.1", "css-loader": "^0.23.1",
"express": "^4.13.4", "express": "^4.13.4",
"extract-text-webpack-plugin": "^1.0.1", "extract-text-webpack-plugin": "^1.0.1",
@ -21,6 +21,8 @@
"react": "^0.14.7", "react": "^0.14.7",
"react-dom": "^0.14.7", "react-dom": "^0.14.7",
"react-hot-loader": "^1.3.0", "react-hot-loader": "^1.3.0",
"react-redux": "^4.4.4",
"redux": "^3.4.0",
"sass-loader": "^3.2.0", "sass-loader": "^3.2.0",
"style-loader": "^0.13.1", "style-loader": "^0.13.1",
"webpack": "^1.12.14", "webpack": "^1.12.14",

@ -8,6 +8,6 @@ Number Munchers uses Webpack for live reloading and ES6 transpilation.
Run `npm install` to install the required Node packages. Run `npm install` to install the required Node packages.
Run `npm run start` in the project root to start the [webpack dev server](https://webpack.github.io/docs/webpack-dev-server.html). Run `npm run dev` in the project root to start the [webpack dev server](https://webpack.github.io/docs/webpack-dev-server.html).
Navigate to http://localhost:8080/. Navigate to http://localhost:8080/.

@ -3,7 +3,7 @@ var ExtractTextPlugin = require('extract-text-webpack-plugin');
function getEntrySources() { function getEntrySources() {
var sources = [ var sources = [
'./js/main.js' './js/index.js'
]; ];
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
@ -22,7 +22,10 @@ module.exports = {
{ {
test: /\.js$/, test: /\.js$/,
include: __dirname + '/js', include: __dirname + '/js',
loaders: ['babel'] loader: 'babel',
query: {
presets: ['react', 'es2015']
}
}, },
{ {
test: /\.scss$/, test: /\.scss$/,

Loading…
Cancel
Save