React.jsでシンプルなゲームを作る

[前回](http://qiita.com/1-AizawaSatoshi/items/a38666ddb8025d975167)の続き

```index.html
<!DOCTYPE html>
<html>

  <head>
    <script data-require="jquery@*" data-semver="2.2.0" src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
    <link data-require="bootstrap-css@3.3.6" data-semver="3.3.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />
    <link rel="stylesheet" href="style.css" />
  </head>

  <body>
    <div id="container" class="container"></div>
    <script src="https://fb.me/react-0.13.3.js"></script>
    <script src="https://fb.me/JSXTransformer-0.13.3.js"></script>
    <script type="text/jsx" src="script.jsx"></script>
  </body>

</html>
```

```style.css
#stars-frame .glyphicon{
  margin: 0.3em;
  font-size: 1.75em;
}

#stars-frame, #answer-frame{
  width: 40%;
  float:left;
}

#button-frame{
  width:20%;
  float:left;
  text-align:center;
  margin-top: 70px;
}

#stars-frame .well, #answer-frame .well{
  height:200px;
}

#numbers-frame .number, #answer-frame .well span{
  display:inline-block;
  margin: 0.5em;
  background-color:#bbb;
  width:30px;
  text-align:center;
  font-size:22px;
  border-radius:50%;
  cursor:pointer;
}

#numbers-frame .selected-true{
  background-color:#e8e8e8;
  color:#ddd;
  cursor:not-allowed;
}

#numbers-frame .used-true{
  background-color:#aaddaa;
  color:#99bb99;
  cursor:not-allowed;
}
```

```script.jsx
//bit.ly/s-pcs
var possibleCombinationSum = function(arr, n) {
  if (arr.indexOf(n) >= 0) { return true; }
  if (arr[0] > n) { return false; }
  if (arr[arr.length - 1] > n) {
    arr.pop();
    return possibleCombinationSum(arr, n);
  }
  var listSize = arr.length, combinationsCount = (1 << listSize)
  for (var i = 1; i < combinationsCount ; i++ ) {
    var combinationSum = 0;
    for (var j=0 ; j < listSize ; j++) {
      if (i & (1 << j)) { combinationSum += arr[j]; }
    }
    if (n === combinationSum) { return true; }
  }
  return false;
};

var StarsFrame = React.createClass({
  render: function(){
    var stars = [];
    for(var i = 0; i < this.props.numberOfStars; i++){
      stars.push(
        <span className="glyphicon glyphicon-star"></span>
      );
    }
    
    return (
      <div id="stars-frame">
        <div className="well">
          {stars}
        </div>
      </div>
    );
  }
});

var ButtonFrame = React.createClass({
  render: function(){
    var disabled, button, correct = this.props.correct;
    
    switch(correct){
      case true:
        button = (
          <button className="btn btn-success btn-lg" onClick={this.props.acceptAnswer}>
            <span className="glyphicon glyphicon-ok"></span>
          </button>
        );
        break;
      case false:
        button = (
          <button className="btn btn-danger btn-lg">
            <span className="glyphicon glyphicon-remove"></span>
          </button>
        );
        break;
      default:
        disabled = (this.props.selectedNumbers.length === 0);
        button = (
          <button className="btn btn-primary btn-lg" disabled={disabled}
                  onClick={this.props.checkAnswer}>
            =
          </button>
        );
    }
    
    disabled = (this.props.selectedNumbers.length === 0);
    return (
      <div id="button-frame">
        {button}
        <br /><br />
        <button className="btn btn-warning btn-xs" onClick={this.props.redraw}
                disabled={this.props.redraws === 0}>
          <span className="glyphicon glyphicon-refresh"></span>
          &nbsp;
          {this.props.redraws}
        </button>
      </div>
    );
  }
});

var AnswerFrame = React.createClass({
  render: function(){
    var props = this.props;
    var selectedNumbers = props.selectedNumbers.map(function(i){
      return (
        <span onClick={props.unselectNumber.bind(null, i)}>
          {i}
        </span>
      )
    });
    
    return (
      <div id="answer-frame">
        <div className="well">
          {selectedNumbers}
        </div>
      </div>
    );
  }
});

var NumbersFrame = React.createClass({
  render: function(){
    var numbers = [], className,
        selectNumber = this.props.selectNumber,
        usedNumbers = this.props.usedNumbers,
        selectedNumbers = this.props.selectedNumbers;
        
    for( var i = 1; i <= 9; i++){
      className = "number selected-" + (selectedNumbers.indexOf(i) >= 0);
      className += " used-" + (usedNumbers.indexOf(i) >= 0);
      numbers.push(
        <div className={className} onClick={selectNumber.bind(null, i)}>
          {i}
        </div>
      );
    }
    return (
      <div id="numbers-frame">
        <div className="well">
          {numbers}
        </div>
      </div>
    );
  }
});

var DoneFrame = React.createClass({
  render: function(){
    return (
      <div className="well text-center">
        <h2>{this.props.doneStatus}</h2>
        <button className="btn btn-default"
                onClick={this.props.resetGame}>
          Play again
        </button>
      </div>
    );
  }
});

var Game = React.createClass({
  getInitialState: function(){
    return {numberOfStars: this.randomNumber(),
            selectedNumbers: [],
            usedNumbers: [],
            redraws: 5,
            correct: null,
            doneStatus: null
    };
  },
  resetGame: function(){
    this.replaceState(this.getInitialState());
  },
  randomNumber: function(){
    return Math.floor(Math.random() * 9) + 1
  },
  selectNumber: function(clickedNumber){
    if(this.state.selectedNumbers.indexOf(clickedNumber) < 0){
      this.setState(
        {selectedNumbers: this.state.selectedNumbers.concat(clickedNumber),
          correct: null
        }
      );
    }
  },
  unselectNumber: function(clickedNumber){
    var selectedNumbers = this.state.selectedNumbers,
        indexOfNumber = selectedNumbers.indexOf(clickedNumber);
        
    selectedNumbers.splice(indexOfNumber, 1);
    
    this.setState({selectedNumbers: selectedNumbers, correct: null});
  },
  sumOfSelectedNumbers: function(){
    return this.state.selectedNumbers.reduce(function(p, n){
      return p+n;
    }, 0)
  },
  checkAnswer: function(){
    var correct = (this.state.numberOfStars === this.sumOfSelectedNumbers());
    this.setState({correct: correct});
  },
  acceptAnswer: function(){
    var usedNumbers = this.state.usedNumbers.concat(this.state.selectedNumbers);
    this.setState({
      selectedNumbers: [],
      usedNumbers: usedNumbers,
      correct: null,
      numberOfStars: this.randomNumber()
    }, function(){
      this.updateDoneStatus();
    });
  },
  redraw: function(){
    if(this.state.redraws > 0){
      this.setState({
        numberOfStars: this.randomNumber(),
        correct: null,
        selectedNumbers: [],
        redraws: this.state.redraws - 1
      }, function(){
        this.updateDoneStatus();
      });
    }
  },
  possibleSolution: function(){
    var numberOfStars = this.state.numberOfStars,
        possibleNumbers = [],
        usedNumbers = this.state.usedNumbers;
        
    for(var i = 1; i <= 9; i++){
      if(usedNumbers.indexOf(i) < 0){
        possibleNumbers.push(i);
      }
    }
    
    return possibleCombinationSum(possibleNumbers, numberOfStars);
  },
  updateDoneStatus: function(){
    if(this.state.usedNumbers.length === 9){
      this.setState({doneStatus: 'Done. Nice!'});
      return;
    }
    if(this.state.redraws === 0 && !this.possibleSolution()){
      this.setState({doneStatus: 'Game Pver!'});
    }
    
  },
  render: function(){
    var selectedNumbers = this.state.selectedNumbers,
        usedNumbers = this.state.usedNumbers,
        numberOfStars = this.state.numberOfStars,
        redraws = this.state.redraws,
        correct = this.state.correct,
        doneStatus = this.state.doneStatus,
        bottomFrame;
        
        if(doneStatus){
          bottomFrame = <DoneFrame doneStatus={doneStatus}
                                   resetGame={this.resetGame} />;
        }else{
          bottomFrame = <NumbersFrame selectedNumbers={selectedNumbers}
                      usedNumbers={usedNumbers}
                      selectNumber={this.selectNumber} />;
        }
        
    return (
      <div id="game">
        <h2>Play Nine</h2>
        <hr />
        <div className="clearfix">
          <StarsFrame numberOfStars={numberOfStars} />
          <ButtonFrame selectedNumbers={selectedNumbers}
                       correct={correct}
                       redraws={redraws}
                       checkAnswer={this.checkAnswer} 
                       acceptAnswer={this.acceptAnswer}
                       redraw={this.redraw} />
          <AnswerFrame selectedNumbers={selectedNumbers} 
                       unselectNumber={this.unselectNumber} />
        </div>
        
        {bottomFrame} 
        
      </div>
    );
  }
});

React.render(
  <Game />,
  document.getElementById('container')
);
```