サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

Create React App 入門 03: マルバツで勝ち負けを決める


Create React Appのひな形からチュートリアルと同じマルバツゲームをつくる「Create React App 入門」シリーズ第3回は、いよいよゲームとして成り立たせます。つまり、XとOを交互につけ、勝ち負けを決めるということです。

01 XとOを交互に描く

XとOは交互につけなければなりません。つぎがXかどうかかを、クラスAppstateにプロパティ(xIsNext)でブール(論理)値としてもたせます。そのうえで、マス目クリックのハンドラ(handleClick())で、加える文字を切り替えればよいでしょう。また、つぎの番がどちらかを、render()メソッドに書き加えてテキスト(status)で示すようにしました(図001)。テキストを生成するのに用いた構文はテンプレートリテラルです。

src/components/App.js

class 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.js

class App extends React.Component {

	handleClick(i) {

		if (squares[i]) { return; }

	};

}

図001■XとOが交互に加えられる

図001

02 勝ちを調べる

いよいよ、どちらが勝ったか調べる関数(calculateWinner())を定めます。引数(squares)は、印の配置を要素にした配列です。関数本体では、勝ち手3マスの並びのインデックスが収められた配列8つを、予め変数(lines)に入れ子配列でもっておきます。揃った並びがあるかforループで調べて、あれば戻り値は勝ちの印(player)です。なければnullが返されます。

src/components/App.js

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;
}

クラスAppstateに新たに加えるのは、勝ちが決まったかどうかをブール値で示すプロパティ(finished)です。マス目をクリックしたら(handleClick())、今定めた関数(calculateWinner())で勝ちを調べます。勝者が決まったら、プロパティをtrueにしてゲームは終わりです。

src/components/App.js

class 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.js

class 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■勝者が決まるとテキストで示される

図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 入門


作成者: 野中文雄
作成日: 2021年01月22日 FN2001002「Create React App 入門 03: マルバツで勝ち負けを決める」を大幅に改訂。


Copyright © 2001-2021 Fumio Nonaka.  All rights reserved.