Javascriptでオセロをつくる 第3回

Javascriptでオセロをつくる 第1回
Javascriptでオセロをつくる 第2回

やり直し(待った)できるようにする

一つ前の状態に戻すには、前の状態を記録しておいて、それを盤に反映すればいいです。盤全体の石の配置を記録(save)とその記録を反映(load)する機能をつくります。
盤全体を表すクラスを作成し、それに対してsaveとloadメソッドを呼び出すようにします。
ここまで作ったプログラムでは石をひっくり返すたびにMasuオブジェクトをつくっていますが、64マスに対応するものをつくっておいて壊さないように変更しました。

Masuクラスの定義 (Masu.js)

class Masu {
    constructor (board, r, c){
        this.r = r;
        this.c = c;
        this.board = board;
    }

    appendTo(tr){
        // clickイベントで実行される関数内ではthisはクリックされたオブジェクトを参照する。
        // そのためMasuオブジェクトのプロパティにアクセスできない。
        // Masuオブジェクトのプロパティにアクセスするためにself変数をつくる。
        var self = this;

        this.td = $('<td><div class="none"></div></td>')
            .click(function (e){  // tdがクリックされたときの動作
                var masu = self.board.masu(self.r, self.c);
                if(masu.ishi() == ISHI_NONE){
                    var count = masu.set(ishi).roundReverse(false);
                    if(count > 0){
                        masu.roundReverse(true);
                        ishi *= -1;
                        self.board.update();
                    }else{
                        masu.set(ISHI_NONE);
                    }
                }
            })
        ;
        tr.append(this.id);
        return this;
    }

    ishi (){
        var div = $('div', this.td);
        return div.hasClass('black') ? ISHI_BLACK : (div.hasClass('white') ? ISHI_WHITE : ISHI_NONE);
    }

    set (ishi){
        var div = $('div', this.td);  // クラスを変更するdiv要素を取得
        div.removeClass('white').removeClass('black').addClass('none');

        div.addClass(ishi == ISHI_NONE ? 'none' : (ishi == ISHI_BLACK ? 'black' : 'white'));
        return this;
    }

    roundReverse(exec){
        var count = 0;
        for(var dr of [-1, 0, 1]){
            for(var dc of [-1, 0, 1]){
                if(dr == 0 && dc == 0) continue;
                count += this.reverse(0, this, dc, dr, exec);
            }
        }
        return count;
    }

    reverse (count, a0, dr, dc, exec){
        try{
            var neighbor = this.board.masu(this.r + dr, this.c + dc);

            if(ISHI_NONE == neighbor.ishi()) return 0;
            if(a0.ishi() != neighbor.ishi()) count = neighbor.reverse(count + 1, a0, dr, dc, exec);

            if(exec && count > 0){
                this.set(a0.ishi());
            }
            return count;

        // 盤面の外のとき0を返す
        }catch(e){
            return 0;
        }
    }
}

Boardクラスの定義 (Board.js)

class Board {
    constructor (table){  // TABLEタグをうけとって、それにマス目をいれる
        this.masus = [];

        for(var r = 0 ; r < 8 ; r ++){
            var tr = $("<tr>");
            this.masus[r] = [];
            for(var c = 0 ; c < 8 ; c ++){
                this.masus[r].push(new Masu(this, r, c).appendTo(tr));
            }
            table.append(tr);
        }

        // 初期配置
        this.set(3, 3, ISHI_BLACK)
            .set(4, 4, ISHI_BLACK)
            .set(3, 4, ISHI_WHITE)
            .set(4, 3, ISHI_WHITE)
        ;
    }

    masu (r, c){
        r = parseInt(r); c = parseInt(c);
        if(r < 0 || r >= 8 || c < 0 || c >= 8) throw 'out of board';
        return this.masus[r][c];
    }

    // 石を置く(石を返さない)
    set (r, c, ishi){
        this.masu(r, c).set(ishi);
        return this;
    }

    // 石を置く(石を返す)
    put (r, c){
        this.masu(r, c).td.click();
        return this;
    }

    // 盤の状態を保存する
    save (){
        return this.masus.map(function (row, r){
            return row.map(function (masu, c){
                return masu.ishi()
            });
        });
    }

    // 盤の状態を反映する
    load (data){
        var board = this;
        data.map(function (row, r){
            row.map(function (ishi, c){
                board.set(r, c, ishi);
            })
        });
        return this;
    }

    // 石が置かれたときに呼ばれる
    update (){
    }
}

othello1.js

const ISHI_BLACK = 1;
const ISHI_WHITE = -1;
const ISHI_NONE = 0;

var ishi = ISHI_BLACK;  // 石の白黒

jQuery(function (){
    var board = new Board($('table#board'));
    var history = [board.save()];
    var history_index = 0;

    $('input#go').click(function (e){
        if(history[history_index + 1]){
            board.load(history[++ history_index]);
            ishi = (-1) ** history_index;
            refresh();
        }
    });

    $('input#back').click(function (e){
        if(history[history_index - 1]){
            board.load(history[-- history_index]);
            ishi = (-1) ** history_index;
            refresh();
        }
    });

    board.update = function (){
        if(history_index < history.length - 1){
            history = history.slice(0, history_index + 1);
        }
        history.push(board.save());
        history_index ++;
        refresh();
    };

    function refresh(){
        $('div#status').html((history_index + 1) + '手目 ' + (ishi == ISHI_BLACK ? '黒' : '白') + 'の番');
    }
    //	テスト
    //board.put(3,5).put(4,5).put(5,5).put(2,3);
});