0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AureliaAdvent Calendar 2019

Day 1

Aurelia 三目並べ (tic-tac-toe)アプリの作成(前半)

Last updated at Posted at 2019-12-01

はじめに

この記事では、Reactチュートリアルの三目並べ(tic-tac-toe)ゲームを、
Aureliaで書いてみます。

完成するのはReactチュートリアルで作成する三目並べと同じものです。

Chapter0 事前準備

まず、Aureliaのプロジェクト作成と、CSSなどの準備を行います。

プロジェクト作成

aurelia-cliを用いて、Aureliaのプロジェクトを作成します。
作成方法は、こちらの記事を参照ください。

不要な記述の削除

以降のチュートリアルをスムーズに行うために、今回作成する三目並べアプリには必要のない記述を修正・削除しましょう。
まず、下記ファイルは削除してください。
src/resources/index.ts

次に、下記ファイルを以下のように修正してください。

src/main.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!を表示しないようにします。

src/app.html
 <template>
-  <h1>${message}</h1>
 </template>
src/app.ts
-export class App {
-  public message: string = 'Hello World!';
-}
+export class App {}

CSSとJS追加

下記のCSSファイルを追加してください。
内容は、Reactチュートリアルで提供されているものと同じです。

src/style.css
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チュートリアルで提供されているものです。
三目並べの勝者を判定するためのメソッドを定義しています。

src/function.ts
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」が表示されるようにします。

下記のファイルを追加してください。

src/square.html
<template>
  <button class="square">
    <!-- /* TODO */ -->
  </button>
</template>

上記のHTMLファイルに対応する、TypeScriptファイルも追加します。

src/square.ts
export class Square {}

三目並べの盤

次に、三目並べの9個のマスが集合した盤にあたるファイルを作成します。
下記のファイルを追加してください。

src/board.html
<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ファイルも追加します。

src/board.ts
export class Board {
  status = "Next player: X";
}

この statusは、プロジェクト作成時に画面に表示されていたHello World!と同様に、画面で表示することができます。

アプリ全体

アプリケーションのルートコンポーネントである src/app.htmlファイルを修正します。
(ルートコンポーネントの指定についての軽い説明は、こちらを参照ください。)

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/ にアクセスすると、このような表示になっていると思います。
三目並べ_事前準備.png

Chapter1 squareコンポーネントへデータを渡す

はじめに、それぞれのマスに数字を表示してみましょう。
三目並べ_bind.png

マスの中に表示する値をvalueとします。

src/square.html
 <template>
   <button class="square">
-    <!-- /* TODO */ -->
+    ${value}
   </button>
 </template>

valueの値は、squareコンポーネントの呼び出し元で指定します。

src/board.html
   <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を下記のように修正します。

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メソッドを追加します。

src/square.ts
 export class Square {
   @bindable value: number;
+
+  alert() {
+    alert("click");
+  }
 }
src/square.html
 <template>
-  <button class="square">
+  <button class="square" click.trigger="alert()">
     ${value}
   </button>
 </template>

上記は、clickイベントに、.triggerを追加しています。
clickイベントが起きると、alertメソッドが実行されるようになっています。

各マスをクリックすると、アラートが表示されるようになります。

Chapter3 マスの値を変更してみよう

次は、マスに表示される値を変更してみましょう。
Chapter1で行った値のバインドを一旦やめます。
そして、setValueメソッドを追加します。

setValue

src/square.ts
-import { bindable } from "aurelia-framework";
-
 export class Square {
-  @bindable value: number;
+  value: string;
+
-  alert() {
-    alert("click");
+  setValue(value: string) {
+    this.value = value;
+  }
 }

マスのクリック時に実行するメソッドを、setValueメソッドに変更します。

src/square.html
-  <button class="square" click.trigger="alert()">
+  <button class="square" click.trigger="setValue('X')">

これで、マスをクリックするとマスに「X」が表示されるようになります。
三目並べ_trigger.png

Chapter4 boardコンポーネントで値を管理する

現在、マスに表示されるvalueの値は、それぞれのsquareコンポーネント内で管理されています。
しかし、「3つのマスが揃っている」や「次はOとXのどちらの順番である」といった情報を得るためには、
すべてのマスの情報をboardコンポーネントで管理する必要があります。

9つのマスの初期値

まず、9つのマスの初期値を設定しましょう。
9つのマスには、最初何も入力されていない状態です。

src/board.ts
 export class Board {
   status = "Next player: X";
+  squares = Array(9).fill(null);
 }

値をsquaresコンポーネントにbind

作成したsquaresの値を、それぞれのsquareコンポーネントに渡しましょう。

src/board.html
   <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するようにします。

src/square.ts
+import { bindable } from "aurelia-framework";

 export class Square {
-  value: string;
+  @bindable value: string;

画面の見た目は変わりありませんが、squareコンポーネントには、boardコンポーネントで管理している値が渡されていることになります。

メソッドをsquaresコンポーネントにbind

squaresの値を変更するメソッドを追加します。
指定したマスの値を"X"に変更するメソッドです。

src/board.ts
 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コンポーネントに渡します。

board.html
   <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のコンテキストにおいてメソッドが実行されます。
詳細は、下記ページをご確認ください。

src/square.ts
 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コンポーネントの持つ値が更新されるようになります。

src/square.html
-  <button class="square" click.trigger="setValue('X')">
+  <button class="square" click.trigger="onClick()">

ここまで完了したら、アプリの画面を操作してみてください。
見た目や動きはChapter3から変化していませんが、
マスに表示されている値は、boardコンポーネントが管理する値になっています。

Chpater5 "X"と"O"で順番にマスを埋めていく

三目並べは、2人が交代してマスを埋めていきます。
なので、「次がどちらの番か」という情報が必要です。

src/board.ts
 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メソッドを更新しましょう。

src/board.ts
   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"が交互にマスに表示されるようになります。
三目並べ_x_o.png

Chapter6 勝者を判定する

事前準備で作成したcalculateWinnerメソッドを利用して、
三目並べの勝者を判定するようにしましょう。
ソースコードは下記のようになります。

src/board.ts
+ 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");
+    }
   }
 }

勝者が決まっている場合は、下記のように、勝者が表示されるようになります。

三目並べ_winner.png

ですが、今のままだと下記のようなことが可能です。

  • すでに"X"や"O"がついているマスをクリックして、値を変更できる
  • 勝者が決まっても、"X"や"O"を押し続けることができる

勝者が決まっている場合や、すでに値が設定されているマスがクリックされた場合は、
squaresの値を更新しないように、下記の記述を追加しましょう。

src/board.ts
   setSquares(i: number) {
     const squares = this.squares.slice();
+    if (calculateWinner(squares) || squares[i]) {
+      return;
+    }
     this.squares = squares;

これで三目並べアプリの完成です。

おわりに(後半の予告)

三目並べという簡単な内容ということもあり、随分見やすくコードが書ける感じがしました。

Reactだと、HTML部分とJS部分が混在してどうも読みづらくなってしまうように思うのですが(匙加減の問題かもしれませんが)、
Aureliaだとはっきり分かれているので、画面のレイアウトとロジック部分を分けて考えやすいですね。

思ったより長くなってしまったので、前後半に分けることにしました。
後半では、三目並べの履歴を管理できるようにします。
三目並べ_history.png

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?