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

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

人対人対戦をサポート

ユーザー入力を受け付けるプレーヤーを用意して、人対人で対戦できるようにしました。

  • プレーヤーの仕様が「盤の情報を受け取って次に石を置く場所を返す関数」だと処理が永遠にUIスレッドに移らず上手く動かないので、次に置く石の位置はコールバック関数に引数として渡す形にしました。
    • ただこれだと、コールバック関数をコールしないことによって、試合を凍結できるので対策が必要だな。(一定時間後に強制スキップとか。)

サンプル

青い下線があるマスが現在のプレーヤーが置ける石の位置です。クリックで「石を置く」。
サンプル

ソース

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 ) {
        var self = this;
        if ( reversi.inner.hasNext( this.board ) ) {
            if ( reversi.inner.enable( this.board, this.color )) {
                this.skipCount = 0;
                var b =  new reversi.Board( reversi.inner.clone(this.board), this.color );
                var callbacked = false;
                this.current( b, function ( position ) {
                    if ( callbacked ) {
                      // 2重Put 負け扱いにする。
                    }
                    callbacked = true;
                    try {
                        self.board = reversi.inner.put( self.board, position, self.color );
                        self.next(callback);
                    } catch ( exception ) {
                        // 負け扱いにする。TODO
                    }
                } );
            } else  {
                if ( this.skipCount == 0 ) {
                    this.skipCount++;
                    this.next(callback); // 次のターン
                } else {
                    // 両者共にスキップ
                    this.end(callback );
                }
            }
        } else {
            this.end(callback);
        }
    },
    /**
     * 次のターンに進む
     * @param {Function} callback コールバック関数。
     */
    next: function( 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 );
    },

    /**
     * 終了処理
     * @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 );
    },
        /**
     * 終了処理
     * @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;
    }
}