HTML5テクニカルノート
Create React App 入門 03: マルバツで勝ち負けを決める
- ID: FN2101003
- Technique: ECMAScript 2015
- Library: React 17.0.1
Create React Appのひな形からチュートリアルと同じマルバツゲームをつくる「Create React App 入門」シリーズ第3回は、いよいよゲームとして成り立たせます。つまり、XとOを交互につけ、勝ち負けを決めるということです。
01 XとOを交互に描く
XとOは交互につけなければなりません。つぎがXかどうかかを、クラスApp
のstate
にプロパティ(xIsNext
)でブール(論理)値としてもたせます。そのうえで、マス目クリックのハンドラ(handleClick()
)で、加える文字を切り替えればよいでしょう。また、つぎの番がどちらかを、render()
メソッドに書き加えてテキスト(status
)で示すようにしました(図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({ squares }); this.setState({ squares, 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) { 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(squares); if (winner) { this.setState({finished: true}); } }; }
勝者をテキストで示すように、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■勝者が決まるとテキストで示される
ノート01■無駄な処理を断捨離する
クラス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; }
squares[i] = this.state.xIsNext ? 'X' : 'O';
this.setState({
squares,
xIsNext: !this.state.xIsNext
});
const winner = calculateWinner(squares);
if (winner) {
this.setState({finished: true});
}
};
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: クラスのコンポーネントをuseState()で関数に書き替える
- Create React App 入門 05: useContextで状態をコンポーネントツリー内に共有する
- Create React App 入門 06: アプリケーションのロジックをコンテクストに切り出す
- Create React App 入門 07: ゲームの履歴をさかのぼる
- Create React App 入門 08: useMemoフックで無駄な再計算を省く
- Create React App 入門 09: useCallbackフックで無駄な処理を省く
- Create React App 入門 10: 条件によってハンドラは無効にする ー useMemoを使って
作成者: 野中文雄
作成日: 2021年01月22日 FN2001002「Create React App 入門 03: マルバツで勝ち負けを決める」を大幅に改訂。
Copyright © 2001-2021 Fumio Nonaka. All rights reserved.