無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

Reversi

JavaScriptでReversiのエンジン部分を作りました。プレーヤー(次に打つ手を返す関数)を渡すとゲームを開始し結果をコールバックします。ちゃんとテストできてないけど、適当に作ったプレーヤーを使って試した限りでは動いている様子。打つ手が固定なのでそれでうまくいっているという気もしますが。

サンプル

テスト用に適当に作ったプレーヤー同士で対戦し結果を表示します。プレーヤーのアルゴリズムは「左上からスキャンし最初に見つかったおける場所におく。」です。あと、UIは適当に作りました。

サンプル

プレーヤーはこんな感じの関数です。

// プレーヤー
// 最初に見つかったおける場所におく。
var player = function(board) {
    for ( var i=0;i<8;i++ ) {
        for ( var j=0;j<8;j++ ) {
            if ( board.enablePut( {x:i, y:j} )) {
                return {x:i, y:j};
            }
        }
    }
};

ソース

ソースは以下。

if ( typeof reversi == "undefined" ) {
    reversi = {};
}
/**
 * ゲーム
 * @param {Function} white プレーヤー1
 * @param {Function} black プレーヤー2
 */
reversi.Game = function( white, black ) {
    this.white = white;
    this.black = black;
    this.board = reversi.inner.create();
    this.color = reversi.inner.Color.WHITE;
    this.current = white;
    this.skipCount = 0;
}
reversi.Game.prototype = {
    /**
     * ゲームする
     * @param {Function} callback コールバック関数。
     */
    play: function( callback ) {
        //try {
        if ( reversi.inner.hasNext( this.board ) ) {
            if ( reversi.inner.enable( this.board, this.color )) {
                this.skipCount = 0;
                var p = this.current( new reversi.Board( reversi.inner.clone(this.board), this.color ) );
                this.board = reversi.inner.put( this.board, p, this.color );
            } else  {
                if ( this.skipCount == 0 ) {
                    this.skipCount++;
                } else {
                    // 両者共にスキップ
                    this.end(callback );
                }
            }
            // プレーヤーを変更し次のターン。
            this.color = this.color == reversi.inner.Color.WHITE ?
                reversi.inner.Color.BLACK : reversi.inner.Color.WHITE;
            this.current = this.color == reversi.inner.Color.WHITE ? this.white : this.black;

            var self = this;
            setTimeout( function(){ self.play(callback); }, 100 );
        } else {
            this.end(callback);
        }
        //    } catch ( exception ) {
        //        // 負け扱いにする。TODO
        //}
    },
    /**
     * 終了処理
     * @param {Function} callback コールバック関数。
     */
    end: function( callback ) {
        // 結果をカウントして表示。
        var result = { white:0, black:0, board:new reversi.Board(this.board, this.color) };
        reversi.inner.each( this.board, function( p, color ) {
            if ( color == reversi.inner.Color.WHITE ) {
                result.white ++;
            } else if ( color == reversi.inner.Color.BLACK ) {
                result.black++;
            }
        } ) ;
       	if ( result.white > result.black ) {
            result.winner = "white";
        } else if ( result.white < result.black ) {
            result.winner = "black";
        } else {
            result.winner = "none";
        }
        callback( result );
    }
}

// マスの状態
reversi.GridState = {
    // 自分の
    MINE: 1,
    // 敵の
    ENEMIES: 2,
    // 何もおかれていない。
    EMPTY: 0
}

/**
 * プレイヤーに渡される盤の情報
 */
reversi.Board = function( board, color ) {
    this.board = board;
    this.color = color;
}
reversi.Board.prototype = {
    /**
     * 指定した位置のマスの状態を得る。
     * @param {Hash} position 位置
     */
    getGridState : function( position ) {
        var value = this.board[position.x][position.y];
        if ( value == this.color ) {
            return reversi.GridState.MINE;
        } else if ( value == reversi.inner.Color.EMPTY ) {
            return reversi.GridState.EMPTY;
        } else {
            return reversi.GridState.ENEMIES;
        }
    },
    /**
     * 指定した位置に石を配置できるか評価する。
     * @param {Hash} position 位置
     */
    enablePut    : function( position ) {
        return reversi.inner.enablePut( this.board, this.color, position);
    }
}
// ユーティリティ関数
reversi.inner = {
    /**色*/
    Color: {
        WHITE: 1,
        BLACK: 2,
        EMPTY: 0
    },
    /**方向*/
    DIRECTIONS: [
        [-1,-1],[ 0,-1],[1,-1],
        [-1, 0],        [1, 0],
        [-1, 1],[ 0, 1],[1, 1]
    ],
    /**
     * ボードを反転し反転した石の数を返す。
     * メソッドから復帰後、ボードも反転している。
     * @param {Array} board ボード
     * @param {int} color 色
     * @param {Hash} position 位置
     * @param {Array} direction 方向
     * @return {int} 反転した数
     */
    flip: function( board, color, position, direction  ) {
        var enemyColor = ( color == reversi.inner.Color.WHITE ?
            reversi.inner.Color.BLACK : reversi.inner.Color.WHITE );
        var fliped = [];
        var count = 0;
        var current = { x: position.x + direction[0], y: position.y + direction[1] };
        while ( current.x >= 0 && current.x < board[0].length
            && current.y >= 0 && current.y < board.length ) {
            if ( board[current.x][current.y] == enemyColor ) {
                board[current.x][current.y] = color;
                fliped.push( current );
                count++;
            } else if ( board[current.x][current.y] == color ) {
                return count;
            } else {
                break;
            }
            current = { x:current.x + direction[0], y:current.y + direction[1] };
        }
        for ( var i = 0; i < fliped.length; i++ ) {
            board[fliped[i].x][fliped[i].y] = enemyColor;
        }
        return 0;
    },
    /**
     * ボードを複製する。
     * @param {Array} board ボード
     * @return {Array} 複製したボード
     */
    clone: function( board  ) {
        var newBoard = [];
        reversi.inner.each( board, function( p, color ) {
            if ( !newBoard[p.x] ) { newBoard[p.x] = [];}
            newBoard[p.x][p.y] = color;
        } );
        return newBoard;
    },
    /**
     * ボードを列挙する。
     * @param {Array} board ボード
     * @param {Function} f ハンドラ
     */
    each: function( board, f  ) {
        for ( var i=0;i<board.length;i++ ) {
            for ( var j=0;j<board[i].length;j++ ) {
                if ( f( {x:i, y:j}, board[i][j] )) {
                    return;
                }
            }
        }
    },
    /**
     * ボードを生成する。
     * @param {Array} ボード
     */
    create: function( board, f  ) {
        var board = [];
        for ( var i=0;i<8;i++ ) {
            board[i] = [];
            for ( var j=0;j<8;j++ ) {
                if ( (i==3 && j==3) || (i==4 && j==4) ) {
                    board[i][j] = reversi.inner.Color.WHITE;
                } else if ( i==3 && j==4 || i==4 && j==3 ) {
                    board[i][j] = reversi.inner.Color.BLACK;
                } else {
                    board[i][j] = reversi.inner.Color.EMPTY;
                }
            }
        }
        return board;
    },
    /**
     * 石を置く
     * @param {Array} board ボード
     * @param {Hash} position 位置
     * @param {int} color 色
     */
    put    : function( board, position, color ) {
        if ( board[position.x][position.y] != reversi.inner.Color.EMPTY ) {
            throw { "color":color, reason:"grid is not empty." }
        }
        board[position.x][position.y] = color;
        var count = 0;
        for (var i=0;i<reversi.inner.DIRECTIONS.length; i++) {
            count += reversi.inner.flip(board, color,
                position, reversi.inner.DIRECTIONS[i] );
        }
        if ( count == 0 ) {
            throw { "color":color, reason:"disable put to the position." }
        }
        return board;
    },
    /**
     * 次のターンがあるか評価する。
     * @param {Array} board ボード
     */
    hasNext: function( board ) {
        var hasNext = false;
        reversi.inner.each( board, function( p, color ) {
            if ( color == reversi.inner.Color.EMPTY) {
                hasNext = true;
                return true;
            }
        });
        return hasNext;
    },
    /**
     * 石を置く場所があるか評価する。
     * @param {Array} board ボード
     */
    enable: function( board, color ) {
        var hasNext = false;
        reversi.inner.each( board, function( p, c ) {
            if ( reversi.inner.enablePut( board, color, p ) ) {
                hasNext = true;
                return true;
            }
        });
        return hasNext;
    },
    /**
     * 石を置けるか評価する。
     * @param {Array} board ボード
     * @param {int} color 色
     * @param {Hash} position 位置
     */
    enablePut: function( board, color, position ) {
        if ( board[position.x][position.y] != reversi.inner.Color.EMPTY) {
            return false;
        }
        var count = 0;
        var tmp = reversi.inner.clone( board );
        for (var i=0;i<reversi.inner.DIRECTIONS.length; i++) {
            count += reversi.inner.flip(tmp, color,
                position, reversi.inner.DIRECTIONS[i] );
            if ( count > 0 ) {
                return true;
            }
        }
        return false;
    }
}

思ったより時間がかかった・・・。