はじめに
この記事では、Reactチュートリアルの三目並べ(tic-tac-toe)ゲームを、
Aureliaで書いてみます。
完成するのはReactチュートリアルで作成する三目並べと同じものです。
Chapter0 事前準備
まず、Aureliaのプロジェクト作成と、CSSなどの準備を行います。
プロジェクト作成
aurelia-cliを用いて、Aureliaのプロジェクトを作成します。
作成方法は、こちらの記事を参照ください。
不要な記述の削除
以降のチュートリアルをスムーズに行うために、今回作成する三目並べアプリには必要のない記述を修正・削除しましょう。
まず、下記ファイルは削除してください。
src/resources/index.ts
次に、下記ファイルを以下のように修正してください。
import {Aurelia} from 'aurelia-framework'
-import * as environment from '../config/environment.json';
import {PLATFORM} from 'aurelia-pal';
export function configure(aurelia: Aurelia) {
- aurelia.use
- .standardConfiguration()
- .feature(PLATFORM.moduleName('resources/index'));
-
- aurelia.use.developmentLogging(environment.debug ? 'debug' : 'warn');
-
- if (environment.testing) {
- aurelia.use.plugin(PLATFORM.moduleName('aurelia-testing'));
- }
-
- aurelia.start().then(() => aurelia.setRoot(PLATFORM.moduleName('app')));
+ aurelia.use.basicConfiguration();
+ aurelia.start().then(() =>
aurelia.setRoot(PLATFORM.moduleName("app")));
}
次に、画面に初期表示されている Hello World!
を表示しないようにします。
<template>
- <h1>${message}</h1>
</template>
-export class App {
- public message: string = 'Hello World!';
-}
+export class App {}
CSSとJS追加
下記のCSSファイルを追加してください。
内容は、Reactチュートリアルで提供されているものと同じです。
body {
font: 14px "Century Gothic", Futura, sans-serif;
margin: 20px;
}
ol,
ul {
padding-left: 30px;
}
.board-row:after {
clear: both;
content: "";
display: table;
}
.status {
margin-bottom: 10px;
}
.square {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
margin-right: -1px;
margin-top: -1px;
padding: 0;
text-align: center;
width: 34px;
}
.square:focus {
outline: none;
}
.kbd-navigation .square:focus {
background: #ddd;
}
.game {
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
}
また、下記のJSファイルを追加してください。こちらも、Reactチュートリアルで提供されているものです。
三目並べの勝者を判定するためのメソッドを定義しています。
export function calculateWinner(squares: Array<"X" | "O" | null>) {
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]
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
三目並べアプリの枠組み作成
画面上に三目並べの枠組みを表示します。
三目並べのそれぞれのマス
まず、三目並べの一つ一つのマスにあたる部分を表すファイルを作成します。
この三目並べのマスをクリックすると、マス内に「O」や「X」が表示されるようにします。
下記のファイルを追加してください。
<template>
<button class="square">
<!-- /* TODO */ -->
</button>
</template>
上記のHTMLファイルに対応する、TypeScriptファイルも追加します。
export class Square {}
三目並べの盤
次に、三目並べの9個のマスが集合した盤にあたるファイルを作成します。
下記のファイルを追加してください。
<template>
<require from="./square"></require>
<div>
<div class="status">${status}</div>
<div class="board-row">
<square></square>
<square></square>
<square></square>
</div>
<div class="board-row">
<square></square>
<square></square>
<square></square>
</div>
<div class="board-row">
<square></square>
<square></square>
<square></square>
</div>
</div>
</template>
上記ファイルについて少し説明をします。
<require from="./square"></require>
の部分ですが、
こちらは、 ES2015における import と同様の役割を持つものだと思ってください。
importすることによって、ファイル内で square
コンポーネントを利用できるようになります。
<square></square>
の部分には、三目並べのそれぞれのマスが表示されます。
また、対応するTypeScriptファイルも追加します。
export class Board {
status = "Next player: X";
}
この status
は、プロジェクト作成時に画面に表示されていたHello World!
と同様に、画面で表示することができます。
アプリ全体
アプリケーションのルートコンポーネントである src/app.html
ファイルを修正します。
(ルートコンポーネントの指定についての軽い説明は、こちらを参照ください。)
<template>
+ <require from="./styles.css"></require>
+ <require from="./board"></require>
+
+ <div class="game">
+ <div class="game-board">
+ <board></board>
+ </div>
+ <div class="game-info">
+ <div><!-- /* status */ --></div>
+ <ol>
+ <!-- /* TODO */ -->
+ </ol>
+ </div>
+ </div>
</template>
先ほど追加したboard
コンポーネントを表示します。
事前準備は以上です。
現在、 http://localhost:8080/ にアクセスすると、このような表示になっていると思います。
Chapter1 square
コンポーネントへデータを渡す
マスの中に表示する値をvalue
とします。
<template>
<button class="square">
- <!-- /* TODO */ -->
+ ${value}
</button>
</template>
value
の値は、squareコンポーネントの呼び出し元で指定します。
<div>
<div class="status">${status}</div>
<div class="board-row">
- <square></square>
- <square></square>
- <square></square>
+ <square value.bind="1"></square>
+ <square value.bind="2"></square>
+ <square value.bind="3"></square>
</div>
<div class="board-row">
- <square></square>
- <square></square>
- <square></square>
+ <square value.bind="4"></square>
+ <square value.bind="5"></square>
+ <square value.bind="6"></square>
</div>
<div class="board-row">
- <square></square>
- <square></square>
- <square></square>
+ <square value.bind="7"></square>
+ <square value.bind="8"></square>
+ <square value.bind="9"></square>
</div>
</div>
</template>
この時点では、まだマスには数字が表示されていません。
上記のbind
を機能させるために、src/square.ts
を下記のように修正します。
-export class Square {}
+import { bindable } from "aurelia-framework";
+
+export class Square {
+ @bindable value: number;
+}
これで、マス目に数字が表示されます。
現在は"1"〜"9"の固定値がbindされています。
マスをクリックすることで、このbindされる値が「O」「X」に切り替わるようにすると、
クリック時に、変更された値がsquare
コンポーネントにbindされることで、それぞれのマスの表示内容を切り替えることができます。
Chapter2 ボタンクリック時にalertを出す
次は、ボタンをクリックしてイベントを発火させてみましょう。
まずは、alert
メソッドを追加します。
export class Square {
@bindable value: number;
+
+ alert() {
+ alert("click");
+ }
}
<template>
- <button class="square">
+ <button class="square" click.trigger="alert()">
${value}
</button>
</template>
上記は、click
イベントに、.trigger
を追加しています。
click
イベントが起きると、alert
メソッドが実行されるようになっています。
各マスをクリックすると、アラートが表示されるようになります。
Chapter3 マスの値を変更してみよう
次は、マスに表示される値を変更してみましょう。
Chapter1で行った値のバインドを一旦やめます。
そして、setValue
メソッドを追加します。
setValue
-import { bindable } from "aurelia-framework";
-
export class Square {
- @bindable value: number;
+ value: string;
+
- alert() {
- alert("click");
+ setValue(value: string) {
+ this.value = value;
+ }
}
マスのクリック時に実行するメソッドを、setValue
メソッドに変更します。
- <button class="square" click.trigger="alert()">
+ <button class="square" click.trigger="setValue('X')">
これで、マスをクリックするとマスに「X」が表示されるようになります。
Chapter4 boardコンポーネントで値を管理する
現在、マスに表示されるvalue
の値は、それぞれのsquare
コンポーネント内で管理されています。
しかし、「3つのマスが揃っている」や「次はOとXのどちらの順番である」といった情報を得るためには、
すべてのマスの情報をboardコンポーネントで管理する必要があります。
9つのマスの初期値
まず、9つのマスの初期値を設定しましょう。
9つのマスには、最初何も入力されていない状態です。
export class Board {
status = "Next player: X";
+ squares = Array(9).fill(null);
}
値をsquares
コンポーネントにbind
作成したsquares
の値を、それぞれのsquare
コンポーネントに渡しましょう。
<div>
<div class="status">${status}</div>
<div class="board-row">
- <square value.bind="1"></square>
- <square value.bind="2"></square>
- <square value.bind="3"></square>
+ <square
+ value.bind="squares[0]"
+ ></square>
+ <square
+ value.bind="squares[1]"
+ ></square>
+ <square
+ value.bind="squares[2]"
+ ></square>
</div>
<div class="board-row">
- <square value.bind="4"></square>
- <square value.bind="5"></square>
- <square value.bind="6"></square>
+ <square
+ value.bind="squares[3]"
+ ></square>
+ <square
+ value.bind="squares[4]"
+ ></square>
+ <square
+ value.bind="squares[5]"
+ ></square>
</div>
<div class="board-row">
- <square value.bind="7"></square>
- <square value.bind="8"></square>
- <square value.bind="9"></square>
+ <square
+ value.bind="squares[6]"
+ ></square>
+ <square
+ value.bind="squares[7]"
+ ></square>
+ <square
+ value.bind="squares[8]"
+ ></square>
</div>
</div>
</template>
再度、value
の値をbindするようにします。
+import { bindable } from "aurelia-framework";
export class Square {
- value: string;
+ @bindable value: string;
画面の見た目は変わりありませんが、square
コンポーネントには、board
コンポーネントで管理している値が渡されていることになります。
メソッドをsquares
コンポーネントにbind
squares
の値を変更するメソッドを追加します。
指定したマスの値を"X"に変更するメソッドです。
export class Board {
status = "Next player: X";
squares = Array(9).fill(null);
+ setSquares(i: number) {
+ const squares = this.squares.slice();
+ squares[i] = "X";
+ this.squares = squares;
+ }
}
このメソッドを、square
コンポーネントに渡します。
<div>
<div class="status">${status}</div>
<div class="board-row">
<square
value.bind="squares[0]"
+ handle-click.call="setSquares(0)"
></square>
<square
value.bind="squares[1]"
+ handle-click.call="setSquares(1)"
></square>
<square
value.bind="squares[2]"
+ handle-click.call="setSquares(2)"
></square>
</div>
<div class="board-row">
<square
value.bind="squares[3]"
+ handle-click.call="setSquares(3)"
></square>
<square
value.bind="squares[4]"
+ handle-click.call="setSquares(4)"
></square>
<square
value.bind="squares[5]"
+ handle-click.call="setSquares(5)"
></square>
</div>
<div class="board-row">
<square
value.bind="squares[6]"
+ handle-click.call="setSquares(6)"
></square>
<square
value.bind="squares[7]"
+ handle-click.call="setSquares(7)"
></square>
<square
value.bind="squares[8]"
+ handle-click.call="setSquares(8)"
></square>
</div>
</div>
</template>
上記のように記述することで、square
コンポーネントでhandleClick
ファンクションをbindすることができるようになります。
call
コマンドは、メソッドを渡す際にbind
コマンドよりも有用です。
下記のファイルでは、this.handleClick()
と記述しています。
call
コマンドを使ってメソッドを渡すことによって、
正しいthis
のコンテキストにおいてメソッドが実行されます。
詳細は、下記ページをご確認ください。
import { bindable } from "aurelia-framework";
export class Square {
@bindable value: string;
+ @bindable handleClick: Function;
- setValue(value: string) {
- this.value = value;
+ onClick() {
+ this.handleClick();
}
}
あとは、定義したonClick
メソッドをボタンクリック時にtriggerすることで、
board
コンポーネントの持つ値が更新されるようになります。
- <button class="square" click.trigger="setValue('X')">
+ <button class="square" click.trigger="onClick()">
ここまで完了したら、アプリの画面を操作してみてください。
見た目や動きはChapter3から変化していませんが、
マスに表示されている値は、board
コンポーネントが管理する値になっています。
Chpater5 "X"と"O"で順番にマスを埋めていく
三目並べは、2人が交代してマスを埋めていきます。
なので、「次がどちらの番か」という情報が必要です。
export class Board {
- status = "Next player: X";
squares = Array(9).fill(null);
+ xIsNext = true;
+ status = "Next player: " + (this.xIsNext ? "X" : "O");
setSquares(i: number) {
次のプレイヤーが"X"である場合、xIsNext
はtrueになります。
画面に表示される「NextPlayer: X」もxIsNext
の値によって変化するようにしましょう。
また、"X"と"O"が順番に切り替わるように、setSquares
メソッドを更新しましょう。
setSquares(i: number) {
const squares = this.squares.slice();
- squares[i] = "X";
+ squares[i] = this.xIsNext ? "X" : "O";
this.squares = squares;
+ this.xIsNext = !this.xIsNext;
}
これで、"X"と"O"が交互にマスに表示されるようになります。
Chapter6 勝者を判定する
事前準備で作成したcalculateWinner
メソッドを利用して、
三目並べの勝者を判定するようにしましょう。
ソースコードは下記のようになります。
+ import { calculateWinner } from "./function"
export class Board {
squares = Array(9).fill(null);
xIsNext = true;
status = "Next player: " + (this.xIsNext ? "X" : "O");
setSquares(i: number) {
const squares = this.squares.slice();
squares[i] = this.xIsNext ? "X" : "O";
this.squares = squares;
this.xIsNext = !this.xIsNext;
+ const winner = calculateWinner(this.squares);
+ if (winner) {
+ this.status = "Winner: " + winner;
+ } else {
+ this.status = "Next player: " + (this.xIsNext ? "X" : "O");
+ }
}
}
勝者が決まっている場合は、下記のように、勝者が表示されるようになります。
ですが、今のままだと下記のようなことが可能です。
- すでに"X"や"O"がついているマスをクリックして、値を変更できる
- 勝者が決まっても、"X"や"O"を押し続けることができる
勝者が決まっている場合や、すでに値が設定されているマスがクリックされた場合は、
squares
の値を更新しないように、下記の記述を追加しましょう。
setSquares(i: number) {
const squares = this.squares.slice();
+ if (calculateWinner(squares) || squares[i]) {
+ return;
+ }
this.squares = squares;
これで三目並べアプリの完成です。
おわりに(後半の予告)
三目並べという簡単な内容ということもあり、随分見やすくコードが書ける感じがしました。
Reactだと、HTML部分とJS部分が混在してどうも読みづらくなってしまうように思うのですが(匙加減の問題かもしれませんが)、
Aureliaだとはっきり分かれているので、画面のレイアウトとロジック部分を分けて考えやすいですね。