Browse Source

add chessboard interface component and some of its children

romanmatiasko 9 years ago
parent
commit
4bda68a830

+ 1 - 0
package.json

@@ -23,6 +23,7 @@
     "babelify": "^5.0.3",
     "browserify": "^9.0.3",
     "chess.js": "^0.1.0",
+    "classnames": "^1.1.4",
     "es6-shim": "^0.27.1",
     "flux": "^2.0.1",
     "gulp": "^3.8.11",

+ 2 - 2
routes/routes.js

@@ -10,7 +10,7 @@ let router = express.Router();
 
 router.get('/', (req, res) => {
   res.render('index', {
-    content: React.renderToString(<Index />)
+    content: React.renderToString(<Index io={{}} />)
   });
 });
 
@@ -26,7 +26,7 @@ router.get('/play/:token/:time/:inc', (req, res) => {
   ];
 
   res.render('play', {
-    content: React.renderToString(<GameInterface params={params} />)
+    content: React.renderToString(<GameInterface params={params} io={{}} />)
   });
 });
 

+ 6 - 0
src/js/actions/GameActions.js

@@ -12,6 +12,12 @@ const GameActions = {
       actionType: GameConstants.GAME_OVER,
       options: options
     });
+  },
+  changePromotion(promotion) {
+    AppDispatcher.handleViewAction({
+      actionType: GameConstants.CHANGE_PROMOTION,
+      promotion: promotion
+    });
   }
 };
 

+ 36 - 0
src/js/components/CapturedPieces.js

@@ -0,0 +1,36 @@
+'use strict';
+
+const React = require('react/addons');
+const GameStore = require('../stores/GameStore');
+const onGameChange = require('../mixins/onGameChange');
+
+const CapturedPieces = React.createClass({
+  
+  mixins: [React.addons.PureRenderMixin, onGameChange],
+
+  getInitialState() {
+    return {
+      capturedPieces: GameStore.getCapturedPieces()
+    };
+  },
+  render() {
+    const cp = this.state.capturedPieces;
+
+    return (
+      <div id="captured-pieces">
+        {cp.map((pieces, color) => (
+          <ul key={color}>
+            {pieces.map((piece, i) => <li key={i}>{piece}</li>).toArray()}
+          </ul>
+        )).toArray()}
+      </div>
+    );
+  },
+  _onGameChange() {
+    this.setState({
+      capturedPieces: GameStore.getCapturedPieces()
+    });
+  }
+});
+
+module.exports = CapturedPieces;

+ 6 - 6
src/js/components/Chat.js

@@ -7,7 +7,7 @@ const ChatActions = require('../actions/ChatActions');
 const Chat = React.createClass({
   
   propTypes: {
-    io: React.PropTypes.object,
+    io: React.PropTypes.object.isRequired,
     token: React.PropTypes.string,
     color: React.PropTypes.string,
     soundsEnabled: React.PropTypes.bool.isRequired
@@ -15,7 +15,7 @@ const Chat = React.createClass({
   mixins: [React.addons.PureRenderMixin],
 
   getInitialState() {
-    let state = ChatStore.getState();
+    const state = ChatStore.getState();
     return {
       isChatHidden: state.isChatHidden,
       messages: state.messages,
@@ -23,7 +23,7 @@ const Chat = React.createClass({
     };
   },
   componentDidMount() {
-    let {io, soundsEnabled} = this.props;
+    const {io, soundsEnabled} = this.props;
 
     io.on('receive-message', data => {
       ChatActions.submitMessage(data.message, data.color + ' left');
@@ -84,8 +84,8 @@ const Chat = React.createClass({
   },
   _submitMessage(e) {
     e.preventDefault();
-    let {io, token, color} = this.props;
-    let message = this.state.message;
+    const {io, token, color} = this.props;
+    const message = this.state.message;
 
     ChatActions.submitMessage(message, color + ' right');
     this.setState({message: ''});
@@ -97,7 +97,7 @@ const Chat = React.createClass({
     });
   },
   _scrollChat() {
-    let chatNode = this.refs.chat.getDOMNode();
+    const chatNode = this.refs.chat.getDOMNode();
     chatNode.scrollTop = chatNode.scrollHeight;
   }
 });

+ 33 - 0
src/js/components/Chessboard.js

@@ -0,0 +1,33 @@
+'use strict';
+
+const React = require('react');
+const GameStore = require('../stores/GameStore');
+const GameActions = require('../actions/GameActions');
+const onGameChange = require('../mixins/onGameChange');
+
+const Chessboard = React.createClass({
+  
+  propTypes: {
+    io: React.PropTypes.object.isRequired
+  },
+  mixins: [React.addons.PureRenderMixin, onGameChange],
+
+  getInitialState() {
+    return {
+      fen: GameStore.getFEN()
+    };
+  },
+  render() {
+    return (
+      <table className="chessboard">
+      </table>
+    );
+  },
+  _onGameChange() {
+    this.setState({
+      fen: GameStore.getFEN()
+    });
+  }
+});
+
+module.exports = Chessboard;

+ 93 - 0
src/js/components/ChessboardInterface.js

@@ -0,0 +1,93 @@
+'use strict';
+
+const React = require('react/addons');
+const GameStore = require('../stores/GameStore');
+const GameActions = require('../actions/GameActions');
+const onGameChange = require('../mixins/onGameChange');
+const Chessboard = require('./Chessboard');
+const CapturedPieces = require('./CapturedPieces');
+const TableOfMoves = require('./TableOfMoves');
+const cx = require('classnames');
+
+const ChessboardInterface = React.createClass({
+  
+  propTypes: {
+    io: React.PropTypes.object.isRequired
+  },
+  mixins: [React.addons.PureRenderMixin, onGameChange],
+
+  getInitialState() {
+    return GameStore.getState();
+  },
+  render() {
+    const {promotion, turn, gameOver} = this.state;
+    const cxFeedback = cx({
+      feedback: true,
+      whitefeedback: turn === 'w',
+      blackfeedback: turn === 'b'
+    });
+    console.log(turn);
+    console.log(cxFeedback);
+    const goType = gameOver.get('type');
+    const loser = gameOver.get('winner') === 'White' ? 'Black' : 'White';
+
+    return (
+      <div id="board-moves-wrapper" className="clearfix">
+        
+        <div id="board-wrapper">
+          <CapturedPieces />
+          <Chessboard io={this.props.io} />
+        </div>
+
+        <TableOfMoves />
+
+        <span className="promotion">
+          <label>
+            <span>Promotion: </span>
+            <select value={promotion}
+                    onChange={this._onPromotionChange}>
+              <option value="q">Queen</option>
+              <option value="r">Rook</option>
+              <option value="n">Knight</option>
+              <option value="b">Bishop</option>
+            </select>
+          </label>
+        </span>
+
+        <span className={cxFeedback}>
+          {!gameOver.get('status') ? 
+            <span className="feedback-move">
+              {`${turn === 'w' ? 'White' : 'Black'} to move.`}
+            </span> :
+
+            <span className="feedback-status">
+              {goType === 'checkmate' ?
+                `Checkmate. ${gameOver.get('winner')} wins!`
+              :goType === 'timeout' ?
+                `${loser}โ€˜s time is out. ${gameOver.get('winner')} wins!`
+              :goType === 'resign' ?
+                `${loser} has resigned. ${gameOver.get('winner')} wins!`
+              :goType === 'draw' ?
+                'Draw.'
+              :goType === 'stalemate' ?
+                'Draw (Stalemate).'
+              :goType === 'threefoldRepetition' ?
+                'Draw (Threefold Repetition).'
+              :goType === 'insufficientMaterial' ?
+                'Draw (Insufficient Material)'
+              :null}
+            </span>
+          }
+        </span>
+      </div>
+    );
+  },
+  _onGameChange() {
+    this.setState(GameStore.getState());
+  },
+  _onPromotionChange(e) {
+    GameActions.changePromotion(e.target.value);
+  }
+});
+
+module.exports = ChessboardInterface;

+ 7 - 7
src/js/components/Clock.js

@@ -7,13 +7,13 @@ const PureRenderMixin = React.addons.PureRenderMixin;
 const Clock = React.createClass({
   
   propTypes: {
-    io: React.PropTypes.object,
+    io: React.PropTypes.object.isRequired,
     params: React.PropTypes.array.isRequired
   },
   mixins: [PureRenderMixin],
 
   getInitialState() {
-    let [_, time, inc] = this.props.params;
+    const [_, time, inc] = this.props.params;
     
     return {
       white: time * 60,
@@ -23,7 +23,7 @@ const Clock = React.createClass({
     };
   },
   componentDidMount() {
-    let io = this.props.io;
+    const io = this.props.io;
 
     io.on('countdown', data => this.setState({
       [data.color]: data.time,
@@ -59,10 +59,10 @@ const Timer = React.createClass({
   mixins: [PureRenderMixin],
 
   render() {
-    let {time, color, countdown} = this.props;
-    let min = Math.floor(time / 60);
-    let sec = time % 60;
-    let timeLeft = `${min}:${sec < 10 ? '0' + sec : sec}`;
+    const {time, color, countdown} = this.props;
+    const min = Math.floor(time / 60);
+    const sec = time % 60;
+    const timeLeft = `${min}:${sec < 10 ? '0' + sec : sec}`;
 
     return (
       <li className={color + (color === countdown ? ' ticking' : '')}>

+ 5 - 5
src/js/components/GameHeader.js

@@ -8,7 +8,7 @@ const ChatActions = require('../actions/ChatActions');
 const GameHeader = React.createClass({
   
   propTypes: {
-    io: React.PropTypes.object,
+    io: React.PropTypes.object.isRequired,
     params: React.PropTypes.array.isRequired,
     color: React.PropTypes.string,
     openModal: React.PropTypes.func.isRequired,
@@ -23,7 +23,7 @@ const GameHeader = React.createClass({
     };
   },
   componentDidMount() {
-    let io = this.props.io;
+    const io = this.props.io;
 
     io.on('receive-message', () => {
       if (this.state.isChatHidden) {
@@ -36,7 +36,7 @@ const GameHeader = React.createClass({
     ChatStore.off('change', this._onChatStoreChange);
   },
   render() {
-    let [_, time, inc] = this.props.params;
+    const [_, time, inc] = this.props.params;
 
     return (
       <header className="clearfix">
@@ -86,7 +86,7 @@ const GameHeader = React.createClass({
     ChatActions.toggleChat();
   },
   _onResign() {
-    let {io, params, color} = this.props;
+    const {io, params, color} = this.props;
 
     io.emit('resign', {
       token: params[0],
@@ -94,7 +94,7 @@ const GameHeader = React.createClass({
     });
   },
   _onRematch() {
-    let {io, params, openModal} = this.props;
+    const {io, params, openModal} = this.props;
 
     io.emit('rematch-offer', {
       token: params[0]

+ 10 - 8
src/js/components/GameInterface.js

@@ -6,13 +6,14 @@ const Chat = require('./Chat');
 const Modal = require('./Modal');
 const GameActions = require('../actions/GameActions');
 const GameStore = require('../stores/GameStore');
+const ChessboardInterface = require('./ChessboardInterface');
 const Immutable = require('immutable');
 const {Map} = Immutable;
 
 const GameInterface = React.createClass({
   
   propTypes: {
-    io: React.PropTypes.object,
+    io: React.PropTypes.object.isRequired,
     params: React.PropTypes.array.isRequired
   },
 
@@ -34,7 +35,7 @@ const GameInterface = React.createClass({
     };
   },
   componentDidMount() {
-    let {io, params} = this.props;
+    const {io, params} = this.props;
 
     io.emit('join', {
       token: params[0],
@@ -66,8 +67,8 @@ const GameInterface = React.createClass({
     });
 
     io.on('player-resigned', data => {
-      let winner = data.color === 'black' ? 'White' : 'Black';
-      let loser = winner === 'Black' ? 'White' : 'Black';
+      const winner = data.color === 'black' ? 'White' : 'Black';
+      const loser = winner === 'Black' ? 'White' : 'Black';
 
       GameActions.gameOver({
         type: 'resign',
@@ -103,8 +104,8 @@ const GameInterface = React.createClass({
     GameStore.off('change', this._onGameChange);
   },
   render() {
-    let {io, params} = this.props;
-    let {color, soundsEnabled, gameOver} = this.state;
+    const {io, params} = this.props;
+    const {color, soundsEnabled, gameOver} = this.state;
 
     return (
       <div>
@@ -132,6 +133,7 @@ const GameInterface = React.createClass({
           color={color}
           soundsEnabled={soundsEnabled} />
 
+        <ChessboardInterface io={io} />
         <Modal data={this.state.modal} />
       </div>
     );
@@ -151,7 +153,7 @@ const GameInterface = React.createClass({
     this.setState({modal: this.state.modal.set('open', false)});
   },
   _acceptRematch() {
-    let {io, params} = this.props;
+    const {io, params} = this.props;
 
     io.emit('rematch-confirm', {
       token: params[0],
@@ -161,7 +163,7 @@ const GameInterface = React.createClass({
     this._hideModal();
   },
   _declineRematch() {
-    let {io, params} = this.props;
+    const {io, params} = this.props;
 
     io.emit('rematch-decline', {
       token: params[0]

+ 4 - 4
src/js/components/Index.js

@@ -19,13 +19,13 @@ const Index = React.createClass({
     };
   },
   componentDidMount() {
-    let io = this.props.io;
+    const io = this.props.io;
 
     /**
      * Socket.IO events
      */
     io.on('created', data => {
-      let {time, inc} = this.state;
+      const {time, inc} = this.state;
 
       this.setState({
         link: `${document.location.origin}/play/${data.token}/${time}/${inc}`
@@ -82,8 +82,8 @@ const Index = React.createClass({
   },
   _createGame(e) {
     e.preventDefault();
-    let {time, inc} = this.state;
-    let isInvalid = [time, inc].some(val => {
+    const {time, inc} = this.state;
+    const isInvalid = [time, inc].some(val => {
       val = parseInt(val, 10);
       return isNaN(val) || val < 0 || val > 50;
     });

+ 7 - 7
src/js/components/Modal.js

@@ -10,7 +10,7 @@ const Modal = React.createClass({
   mixins: [React.addons.PureRenderMixin],
 
   componentDidUpdate() {
-    let isOpen = this.props.data.get('open');
+    const isOpen = this.props.data.get('open');
 
     if (isOpen)
       document.addEventListener('keydown', this._onKeydown);
@@ -18,9 +18,9 @@ const Modal = React.createClass({
       document.removeEventListener('keydown', this._onKeydown);
   },
   render() {
-    let data = this.props.data;
-    let type = data.get('type');
-    let callbacks = data.get('callbacks');
+    const data = this.props.data;
+    const type = data.get('type');
+    const callbacks = data.get('callbacks');
 
     return (
       <div className={'modal-mask' + (data.get('open') ? '' : ' hidden')}>
@@ -36,7 +36,7 @@ const Modal = React.createClass({
           <p>{data.get('message')}</p>
 
           {type === 'info' ? 
-            <a className="btn"
+            <a className="btn ok"
                onClick={callbacks.hide}>
               OK
             </a> : [
@@ -59,8 +59,8 @@ const Modal = React.createClass({
     );
   },
   _onKeydown(e) {
-    let type = this.props.data.get('type');
-    let callbacks = this.props.data.get('callbacks');
+    const type = this.props.data.get('type');
+    const callbacks = this.props.data.get('callbacks');
 
     if (type === 'info') {
       if (e.which === 13 || e.which === 27) {

+ 46 - 0
src/js/components/TableOfMoves.js

@@ -0,0 +1,46 @@
+'use strict';
+
+const React = require('react/addons');
+const GameStore = require('../stores/GameStore');
+const onGameChange = require('../mixins/onGameChange');
+
+const TableOfMoves = React.createClass({
+  
+  mixins: [React.addons.PureRenderMixin, onGameChange],
+
+  getInitialState() {
+    return {
+      moves: GameStore.getMoves()
+    };
+  },
+  render() {
+    return (
+      <table id="moves" className="clearfix">
+        <thead>
+          <tr>
+            <th>Table of moves</th>
+          </tr>
+        </thead>
+        <tbody>
+          {this.state.moves.map((row, i) => (
+            <tr key={i}>
+              {row.map((move, j) => (
+                <td key={j}>
+                  <strong>{i + 1}</strong>
+                  <span>{`. ${move}`}</span>
+                </td>
+              )).toArray()}
+            </tr>
+          )).toArray()}
+        </tbody>
+      </table>
+    );
+  },
+  _onGameChange() {
+    this.setState({
+      moves: GameStore.getMoves()
+    });
+  }
+});
+
+module.exports = TableOfMoves;

+ 2 - 1
src/js/constants/GameConstants.js

@@ -2,5 +2,6 @@ const keyMirror = require('react/lib/keyMirror');
 
 module.exports = keyMirror({
   REMATCH: null,
-  GAME_OVER: null
+  GAME_OVER: null,
+  CHANGE_PROMOTION: null
 });

+ 10 - 0
src/js/mixins/onGameChange.js

@@ -0,0 +1,10 @@
+const GameStore = require('../stores/GameStore');
+
+module.exports = {
+  componentDidMount() {
+    GameStore.on('change', this._onGameChange);
+  },
+  componentWillUnmount() {
+    GameStore.off('change', this._onGameChange);
+  }
+};

+ 24 - 3
src/js/stores/GameStore.js

@@ -4,7 +4,7 @@ const AppDispatcher = require('../dispatcher/AppDispatcher');
 const EventEmitter = require('eventemitter2').EventEmitter2; 
 const GameConstants = require('../constants/GameConstants');
 const Immutable = require('immutable');
-const {List, Map, Set} = Immutable;
+const {List, Map, OrderedMap, Set} = Immutable;
 const CHANGE_EVENT = 'change';
   
 var _gameOver = Map({
@@ -12,14 +12,31 @@ var _gameOver = Map({
   type: null,
   winner: null
 });
+var _capturedPieces = OrderedMap([
+  ['white', List()],
+  ['black', List()]
+]);
 var _moves = List();
+var _promotion = 'q';
+var _turn = 'w';
+var _fen = null;
 
-var GameStore = Object.assign({}, EventEmitter.prototype, {
+const GameStore = Object.assign({}, EventEmitter.prototype, {
   getState() {
     return {
       gameOver: _gameOver,
-      moves: _moves
+      promotion: _promotion,
+      turn: _turn
     };
+  },
+  getCapturedPieces() {
+    return _capturedPieces;
+  },
+  getMoves() {
+    return _moves;
+  },
+  getFEN() {
+    return _fen;
   }
 });
 
@@ -50,6 +67,10 @@ AppDispatcher.register(payload => {
       gameOver(action.options);
       break;
 
+    case GameConstants.CHANGE_PROMOTION:
+      _promotion = action.promotion;
+      break;
+
     default:
       return true;
   }