HTML5テクニカルノート
Create React App 入門 03: マルバツで勝ち負けを決める
- ID: FN2001002
- Technique: ECMAScript 2015
- Library: React 16.12.0
Create React Appのひな形からチュートリアルと同じマルバツゲームをつくる「Create React App 入門」シリーズ第3回は、いよいよゲームとして成り立たせます。つまり、XとOを交互につけ、勝ち負けを決めるということです。
01 XとOを交互に描く
XとOは交互につけなければなりません。つぎがXかどうかかを、クラスApp
のstate
にプロパティ(xIsNext
)でブール(論理)値としてもたせます。そのうえで、マス目クリックのハンドラ(handleClick()
)で、加える文字を切り替えればよいでしょう。また、つぎの番がどちらかを、render()
メソッドに書き加えてテキストで示すようにしました(図001)。
src/components/App.jsclass App extends React.Component { constructor(props) { this.state = { xIsNext: true }; } handleClick(i) { // squares[i] = 'X'; squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({ xIsNext: !this.state.xIsNext }); }; render() { const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); return ( <div className="game"> <div className="game-info"> <div>{status}</div> </div> </div> ); } }
ただこのままでは、つけてあった印が書き替えられてしまいます。マス目に印がすでにあったら、処理を進めないようにしなければなりません。
src/components/App.jsclass App extends React.Component { handleClick(i) { const squares = [...this.state.squares]; if (squares[i]) { return; } }; }
図001■XとOが交互に加えられる
02 勝ちを調べる
いよいよ、どちらが勝ったか調べる関数(calculateWinner()
)を定めます。引数(squares
)は、印の配置を要素にした配列です。関数本体では、勝ちの3マスの並びのインデックスが収められた配列8つを、予め変数(lines
)の配列にもっておきます。揃った並びがあるかfor
ループで調べて、あれば戻り値は勝ちの印(player
)です。なければnull
が返されます。
src/components/App.jsfunction calculateWinner(squares) { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ]; const length = lines.length; for (let i = 0; i < length; i++) { const [a, b, c] = lines[i]; const player = squares[a]; if (player && player === squares[b] && player === squares[c]) { return player; } } return null; }
クラスApp
のstate
に新たに加えるのは、勝ちが決まったかどうかをブール値で示すプロパティ(finished
)です。マス目をクリックしたら(handleClick()
)、今定めた関数(calculateWinner()
)で勝ちを調べます。勝者が決まったら、プロパティをtrue
にしてゲームは終わりです。
src/components/App.jsclass App extends React.Component { constructor(props) { this.state = { finished: false }; } handleClick(i) { if (this.state.finished) { return; } const winner = calculateWinner(this.state.squares); if (winner) { this.setState({finished: true}); return; } }; }
勝者をテキストで示すように、render()
メソッドもつぎのように書き替えましょう(図002)。
src/components/App.jsclass App extends React.Component { render() { const winner = calculateWinner(this.state.squares); // const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); const status = (winner) ? 'Winner: ' + winner : 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); } }
図002■勝者が決まるとテキストで示される
無駄な処理を断捨離する
クラスApp
には勝負決着のプロパティ(finished
)を設けて、クリックイベントのハンドラメソッド(handleClick()
)は、勝負が着いた処理は進めないようにしました。React公式サイトのチュートリアル作例には、このプロパティがありません。つまり、マス目をクリックするたびに、勝者判定の関数(calculateWinner()
)が呼び出されてしまうのです。重い処理ではないものの、無駄は省くに越したことはないでしょう。
そう考えると、勝負がつかないまま9つのマス目が埋まった場合も、決着のプロパティを設定した方がよさそうです。ただ、次回このゲームにはさらに機能を加えます。断捨離は、そのあとまとめて行うつもりです。
書き上がったモジュールsrc/components/App.js
のスクリプト全体は、つぎのコード001にまとめたとおりです。また、CodeSandboxに以下のサンプル001を掲げました。
コード001■マルバツの勝ち負けを判定して表示する
src/components/App.js
import React from 'react';
import Board from './Board';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
squares: Array(9).fill(null),
xIsNext: true,
finished: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(i) {
const squares = [...this.state.squares];
if (squares[i]) { return; }
if (this.state.finished) { return; }
const winner = calculateWinner(this.state.squares);
if (winner) {
this.setState({finished: true});
return;
}
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext
});
};
render() {
const winner = calculateWinner(this.state.squares);
const status = (winner) ?
'Winner: ' + winner :
'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
return (
<div className="game">
<Board
squares={this.state.squares}
onClick={(i) => this.handleClick(i)}
/>
<div className="game-info">
<div>{status}</div>
</div>
</div>
);
}
}
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
const length = lines.length;
for (let i = 0; i < length; i++) {
const [a, b, c] = lines[i];
const player = squares[a];
if (player && player === squares[b] && player === squares[c]) {
return player;
}
}
return null;
}
export default App;
サンプル001■Create React App: Tic Tac Toe 03
Create React App 入門
- Create React App 入門 01: 3×3のマス目をつくる
- Create React App 入門 02: クリックしたマス目にXをつける
- Create React App 入門 03: マルバツで勝ち負けを決める
- Create React App 入門 04: ゲームの履歴をさかのぼる
- Create React App 入門 05: 無駄な処理を省く
作成者: 野中文雄
作成日: 2020年01月13日
Copyright © 2001-2020 Fumio Nonaka. All rights reserved.