JavaScript

JavaScriptでリバーシを作ろう (part.2)

前回までの記事はこちら

JavaScriptでリバーシを作ろう (part.1) JavaやC#といった言語だと、はじめての人が使うには環境構築やらなにやらで結構とりかかるまでに時間を要しますよね。その点JavaSc...

本稿では、JavaScriptによる処理の実装について解説します。

ゲーム盤中央に黒白2つの石を置く(JavaScript)

以下のコードが実行されると、画面表示時に中央に黒白2つの石が置かれます。

<script>
    window.onload = function () {    //①
        var squares = document.querySelectorAll("#myBoard td");  //②
        squares[27].classList.add("shiro");  //③
        squares[28].classList.add("kuro");
        squares[35].classList.add("kuro");
        squares[36].classList.add("shiro");
    }
</script>

一つずつ解説します。

  1. window.onloadでローディング工程の終了時に呼び出す処理を定義します。ここでは、初期処理である中央への石を配置する処理を定義します。
  2. idが”myBoard”の要素(今回はtable要素)の子要素のtdをすべて取得します。全部で8×8=64の要素を取得する想定です。
  3. 取得したtdの27番目と36番目をshiroに、28番目と35番目をkuroに設定します。配列は0から数えます。取得したtdの順序は下図を参照してください。

これで初期処理は完了です。

tdに以下のようなidを定義すると、何番目のマスかなど気にすることなく、document.getElementById("hoge").classList.add("kuro")のように直接指定することもできます。今回は前後左右ナナメのマスを見つける処理を後半に行いたいため、配列のインデックスでアクセスする方法で進めます。


<td id="d4"></td>
<td id="e4"></td>

document.getElementById("d4").classList.add("shiro");
document.getElementById("e4").classList.add("kuro");

黒石・白石を交互に打つ(JavaScript)

ゲームの要である”石を打つ”を実装します。

黒のターンにマスをクリックすると黒石を打ち、白のターンにマスをクリックすると白石を打つ処理としましょう。

マスをクリックした時の振る舞いを定義する

    let squares = document.querySelectorAll("#myBoard td");

    window.onload = function () {
        squares[27].classList.add("shiro");
        squares[28].classList.add("kuro");
        squares[35].classList.add("kuro");
        squares[36].classList.add("shiro");

        for (let i = 0, len = squares.length; i < len; i++) {
            squares[i].addEventListener("click", function () {
                placement(i);
            }, false);
        }
    }

    function placement(index) {
        squares[index].classList.add("kuro");
    }

上記コードの9行目以降では、各マスに対してclickイベント発生時の振る舞いを定義しています。最左上のマスをクリックした場合はplacementに0が、最下右のマスをクリックした場合はplacementに63が渡されて、クリック対象のマスにクラスを追加してマスが配置されます。

上記コードの9行目では、変数定義にletを使用しています。ブラウザがES2015に対応していない場合はvarを用いて変数定義を行うよう変更する必要があります。

ただし、letとvarではスコープの違いがあるため、そのままvarに書き換えた場合、placementに渡される引数が常に64となり想定と異なる動作となります。(for文を抜ける直前にインクリメントされた変数iを参照しているため)

varへ変更する場合は、次のようにevent発生したターゲットを取得するとよいでしょう。(どういうアプローチでインデックスを取得するかについては賛否あると思いますが、今回はシンプルに..)

        for (var i = 0; i < squares.length; i++) {
            squares[i].addEventListener("click", function (event) {
                var cell = event.path[0].cellIndex;
                var row = event.path.rowIndex;
                var index = row * 8 + cell;
                placement(index);
            }, false);
        }

黒白が交互になるように状態を持つ

ここまでで、マスをクリックした時の挙動については定義できましたが、このままでは常に黒石しか置かれない動きとなってしまいます。

「黒無双の予感」

黒白交互に打つというルールを実装するため、ゲーム進行の状態を保持し、状態によって黒なのか白なのかを制御していきます。

    let squares = document.querySelectorAll("#myBoard td");
    //回数を保持する
    let turn;
    const pieces = ["kuro", "shiro"];

    window.onload = function () {
        turn = 0;
        squares[27].classList.add("shiro");
        squares[28].classList.add("kuro");
        squares[35].classList.add("kuro");
        squares[36].classList.add("shiro");

        for (let i = 0, len = squares.length; i < len; i++) {
            squares[i].addEventListener("click", function () {
                placement(i);
            }, false);
        }
    }

    function placement(index) {
        //偶数回は黒の番、奇数回は白の番とする
        squares[index].classList.add(pieces[turn % 2]);
        //打ったら次の回に進める
        turn++;
    }

交互に打てるようになったので、少しそれらしくなってきました。

「挟んでも返せないけどね」

相手色を挟んで裏返す(JavaScript)

石を打った時に、上下左右ナナメを見て相手を挟んだと判断したら色を変えましょう。

    function placement(index) {
        //偶数回は黒の番、奇数回は白の番とする
        squares[index].classList.add(pieces[turn % 2]);
        //ひっくり返す
        reverse(index);
        //打ったら次の回
        turn++;
    }

    function reverse(index) {
        //自分の色
        let oneself = pieces[turn % 2];
        //相手の色
        let opponent = pieces[(turn % 2) ^ 1];

        //左
        for (let i = index - 1, end = index - index % 8, buf = []; i >= end; i--) {
            if (squares[i].classList.length === 0) {
                break;
            }
            if (squares[i].classList.contains(oneself)) {
                buf.forEach(b => b.classList.replace(opponent, oneself));
                break;
            }
            buf.push(squares[i]);
        }
        //右
        for (let i = index + 1, end = index + (7 - index % 8), buf = []; i <= end; i++) {
            ..以下同じ..
        }
        //上
        for (let i = index - 8, buf = []; i >= 0; i -= 8) {
            ..以下同じ..
        }
        //下
        for (let i = index + 8, buf = []; i <= 63; i += 8) {
            ..以下同じ..
        }
        //左上
        for (let i = index - 9, buf = []; i >= 0; i -= 9) {
            ..以下同じ..
        }
        //左下
        for (let i = index + 7, buf = []; i <= 63; i += 7) {
            ..以下同じ..
        }
        //右上
        for (let i = index - 7, buf = []; i >= 0; i -= 7) {
            ..以下同じ..
        }
        //右下
        for (let i = index + 9, buf = []; i <= 63; i += 9) {
            ..以下同じ..
        }
    }

DRYに書けなかったせいで、もうちょっとなんとかならなかったのかと考えさせられる内容でした。やりたいことは実にシンプルで、

  • クリックされたマスから左方向に自色を探す。探している間、相手色を覚えておき、自色が見つかったら裏返す。
  • 同じことを右上下ナナメと繰り返す。

相手石を挟み込めるようになり、リバーシとしてようやく機能するようになってきました。

DRYに書きたくて少ない脳みそ回して考えましたが、8進数だとなんか余計に見辛いものになってしまい、結局以下のような書き方にもなりました。JSのプロに助言もらいたかったです。。

      const f = (coefficient) => {
            let target;
            let base;
            if (coefficient === -1 || coefficient === 1) {
                let row = ~~(index / 8);
                base = row * 8;
                target = [].slice.call(squares, base, base + 7);
            } else {
                base = 0
                target = squares;
            }

            for (let i = index - base + coefficient, len = target.length, buf = []; i >= 0 && i < len; i += coefficient) {
                if (target[i].classList.length === 0) {
                    break;
                }
                if (target[i].classList.contains(oneself)) {
                    buf.forEach(b => b.classList.replace(opponent, oneself));
                    break;
                }
                buf.push(target[i]);
            }
        };

        //左
        f(-1);
        //右
        f(1);
        //上
        f(-8);
        //下
        f(8);
        //左上
        f(-9);
        //右上
        f(-7);
        //左下
        f(7);
        //右下
        f(9);

ABOUT ME
sasakiyu
ひ弱で優しい少年だったが、デーモンと合体。 強大な力を得つつも人間の心を失わないデベルマンとなる。