Chessboard.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. 'use strict';
  2. const React = require('react');
  3. const GameStore = require('../stores/GameStore');
  4. const GameActions = require('../actions/GameActions');
  5. const ChessPieces = require('../constants/ChessPieces');
  6. const onGameChange = require('../mixins/onGameChange');
  7. const maybeReverse = require('../mixins/maybeReverse');
  8. const omit = require('lodash.omit');
  9. const cx = require('classnames');
  10. const Immutable = require('immutable');
  11. const {Seq, Repeat, List} = Immutable;
  12. const FILES = Seq.Indexed('abcdefgh');
  13. const RANKS = Seq.Indexed('12345678');
  14. const Chessboard = React.createClass({
  15. propTypes: {
  16. io: React.PropTypes.object.isRequired,
  17. token: React.PropTypes.string.isRequired,
  18. maybePlaySound: React.PropTypes.func.isRequired,
  19. color: React.PropTypes.oneOf(['white', 'black']).isRequired
  20. },
  21. mixins: [React.addons.PureRenderMixin, maybeReverse],
  22. getInitialState() {
  23. const state = GameStore.getChessboardState();
  24. return {
  25. fen: state.fen,
  26. moveFrom: null,
  27. lastMove: state.lastMove
  28. };
  29. },
  30. componentDidMount() {
  31. GameStore.on('change', this._onGameChange);
  32. GameStore.on('new-move', this._onNewMove);
  33. this.props.io.on('move', data => {
  34. console.log(data);
  35. GameActions.makeMove(data.from, data.to, data.capture, false);
  36. });
  37. },
  38. componentWillUnmount() {
  39. GameStore.off('change', this._onGameChange);
  40. GameStore.on('new-move', this._onNewMove);
  41. },
  42. render() {
  43. const fenArray = this.state.fen.split(' ');
  44. const placement = fenArray[0];
  45. const isItMyTurn = fenArray[1] === this.props.color.charAt(0);
  46. const rows = this._maybeReverse(placement.split('/'));
  47. const ranks = this._maybeReverse(RANKS, 'white');
  48. return (
  49. <table className="chessboard">
  50. {rows.map((placement, i) =>
  51. <Row
  52. key={i}
  53. rank={ranks.get(i)}
  54. placement={placement}
  55. color={this.props.color}
  56. isItMyTurn={isItMyTurn}
  57. moveFrom={this.state.moveFrom}
  58. lastMove={this.state.lastMove}
  59. setMoveFrom={this._setMoveFrom} />)}
  60. </table>
  61. );
  62. },
  63. _onGameChange() {
  64. const state = GameStore.getChessboardState();
  65. this.setState({
  66. fen: state.fen,
  67. lastMove: state.lastMove
  68. });
  69. },
  70. _setMoveFrom(square) {
  71. this.setState({
  72. moveFrom: square
  73. });
  74. },
  75. _onNewMove(move) {
  76. const {io, token} = this.props;
  77. io.emit('new-move', {
  78. token: token,
  79. move: omit(move, 'turn')
  80. });
  81. if (move.gameOver) return;
  82. io.emit(move.turn === 'b' ? 'timer-black' : 'timer-white', {
  83. token: token
  84. });
  85. }
  86. });
  87. const Row = React.createClass({
  88. propTypes: {
  89. rank: React.PropTypes.oneOf(['1','2','3','4','5','6','7','8']).isRequired,
  90. placement: React.PropTypes.string.isRequired,
  91. color: React.PropTypes.oneOf(['white', 'black']).isRequired,
  92. isItMyTurn: React.PropTypes.bool.isRequired,
  93. moveFrom: React.PropTypes.string,
  94. lastMove: React.PropTypes.object,
  95. setMoveFrom: React.PropTypes.func.isRequired
  96. },
  97. mixins: [maybeReverse],
  98. render() {
  99. const {rank, placement, color} = this.props;
  100. const files = this._maybeReverse(FILES);
  101. const pieces = this._maybeReverse(placement.length < 8 ?
  102. Seq(placement).flatMap(piece => (
  103. /^\d$/.test(piece) ? Repeat('-', parseInt(piece, 10)) : piece
  104. )).toArray() :
  105. placement.split('')
  106. );
  107. return (
  108. <tr>
  109. {pieces.map((piece, i) =>
  110. <Column
  111. key={i}
  112. square={files.get(i) + rank}
  113. piece={piece}
  114. {...omit(this.props, 'rank', 'placement')} />)}
  115. </tr>
  116. );
  117. }
  118. });
  119. const Column = React.createClass({
  120. propTypes: {
  121. square: React.PropTypes.string.isRequired,
  122. piece: React.PropTypes.string.isRequired,
  123. color: React.PropTypes.oneOf(['white', 'black']).isRequired,
  124. isItMyTurn: React.PropTypes.bool.isRequired,
  125. moveFrom: React.PropTypes.string,
  126. lastMove: React.PropTypes.object,
  127. setMoveFrom: React.PropTypes.func.isRequired
  128. },
  129. render() {
  130. const {moveFrom, lastMove, square} = this.props;
  131. const piece = ChessPieces[this.props.piece];
  132. return piece ?
  133. <td className={cx({
  134. selected: moveFrom === square,
  135. to: lastMove.get('to') === square
  136. })}>
  137. <a onClick={this._onClickSquare}>
  138. {piece}
  139. </a>
  140. </td> :
  141. <td className={lastMove.get('from') === square ? 'from' : null}
  142. onClick={this._onClickSquare} />;
  143. },
  144. _onClickSquare() {
  145. const {isItMyTurn, color, moveFrom, square, piece} = this.props;
  146. const rgx = color === 'white' ? /^[KQRBNP]$/ : /^[kqrbnp]$/;
  147. if (!isItMyTurn || (!moveFrom && !rgx.test(piece)))
  148. return;
  149. else if (moveFrom && moveFrom === square)
  150. this.props.setMoveFrom(null);
  151. else if (rgx.test(piece))
  152. this.props.setMoveFrom(square);
  153. else
  154. GameActions.makeMove(moveFrom, square, ChessPieces[piece], true);
  155. }
  156. });
  157. module.exports = Chessboard;