JavaScriptでオセロゲーム

JavaScriptでオセロゲームをつくりました。

プログラムの課題なんかに良く使われるオセロですが、
私は今まで作ったことがなかったので、今回が初のオセロ作成でした。


規模としては極小でかつゲームとしてもレベルが低いものなので、
せめてスマートなコードを…って思ったのですけど
結局、いつものごとく微妙なコードになりました…。


特に裏返す駒の判定の部分とか、ループでもいけそうなものを
再帰+配列で裏返した駒を保存って言う富豪プログラミングをやっています。
脳のリソースより、PCのリソースを使っちゃえっていう全くへっぽこらしい発想です。


このオセロのコードは以下に置いておきます。

[2009/11/28 追記]
斜めに置けないというミスを指摘頂きましたので修正しました。

var Board = function(x,y) {
    this.EMPTY = 0;
    this.BLACK = 1;
    this.WHITE = 2; 
    this.CELL_NAME        = "cell";
    this.ACTIVE_CELL_NAME = "activeCell";
    this.PIECE_NAME       = "piece";    
    this.IMG_PATH = ""; 
    this.IMG_PIECE = new Array(3);  
    this.IMG_PIECE[this.EMPTY]= new Image();
    this.IMG_PIECE[this.EMPTY].src = this.IMG_PATH + "image/empty.png";
    this.IMG_PIECE[this.BLACK] = new Image();
    this.IMG_PIECE[this.BLACK].src = this.IMG_PATH + "image/black.png";
    this.IMG_PIECE[this.WHITE] = new Image();
    this.IMG_PIECE[this.WHITE].src = this.IMG_PATH + "image/white.png";
    this.ROW = 8;
    this.COL = 8;
    this.CELL_SIZE = 50;
    this.WIDTH = this.COL * this.CELL_SIZE;
    this.HEIGHT = this.ROW * this.CELL_SIZE;    
    // 描画位置
    this.x = x;
    this.y = y; 
    // 現在のアクティブなセル
    this.currentActiveCell = null;  
    // 計算用の駒配列
    this.board = new Array(this.ROW);   
    // アニメーション中の駒
    this.animPiece = new Array();   
    // 画像の表示と配列の初期化
    for(var i = 0; i < this.ROW; ++i){
        this.board[i] = new Array(this.COL); 
        for(var j = 0; j < this.COL; ++j){
            document.write('<img id="'+this.CELL_NAME+(i+""+j)+'" src="'+this.IMG_PATH+'image/cell.png" STYLE="position:absolute;top:'+(y+this.CELL_SIZE*i)+';left:'+(x+this.CELL_SIZE*j)+'">');
            document.write('<img id="'+this.ACTIVE_CELL_NAME+(i+""+j)+'" src="'+this.IMG_PATH+'image/active.png" STYLE="position:absolute;top:'+(y+this.CELL_SIZE*i)+';left:'+(x+this.CELL_SIZE*j)+';visibility:hidden">');
            document.write('<img id="'+this.PIECE_NAME+(i+""+j)+'" src="'+this.IMG_PATH+'image/empty.png" STYLE="position:absolute;top:'+(y+this.CELL_SIZE*i)+';left:'+(x+this.CELL_SIZE*j)+'">');
            this.board[i][j] = this.EMPTY;
        }
    }
}

// MouseMoveイベント
// 盤の色を変える処理
Board.prototype.onMouseMove = function(x,y) {
    // 描画位置分シフト
    x -= this.x; y -= this.y;
    
    if(this.currentActiveCell){
        this.currentActiveCell.style.visibility = "hidden";
    }
    if((this.x < x && x > this.WIDTH) || (this.y < y && y > this.HEIGHT)) return false;
    
    var pos = this.pixelsToTiles(x,y);
    this.currentActiveCell = document.getElementById(this.ACTIVE_CELL_NAME+pos.ty+""+pos.tx);
    if(this.currentActiveCell){
        this.currentActiveCell.style.visibility = "visible";
    }
}

// ピクセル値からセルの位置を取得する
Board.prototype.pixelsToTiles = function(x,y) {
    return { tx:Math.floor(x/this.CELL_SIZE), ty:Math.floor(y/this.CELL_SIZE) };
}

// IDからセルの位置を取得する
Board.prototype.idToTiles = function(id) {
    return { tx:parseInt(id.charAt(id.length-1)), ty:parseInt(id.charAt(id.length-2)) };
}

// 駒を設置する
Board.prototype.putPiece = function(x,y,num) {
    // 描画位置分シフト
    x -= this.x; y -= this.y;   
    if(num > 2) return false;
    var pos = this.pixelsToTiles(x,y);
    if(this.checkPut(pos.tx,pos.ty,num,true))
        return this.put(pos.tx,pos.ty,num);
    else
        return false;
}

// 駒を設置する(内部使用)
Board.prototype.put = function(tx,ty,num) {
    //if(this.board[ty][tx] != 0) return false;
    var piece = document.getElementById(this.PIECE_NAME+ty+""+tx);
    piece.src = this.IMG_PIECE[num].src;
    this.board[ty][tx] = num;
    return true;
}
// アニメーション付き設置
Board.prototype.animationPut = function(tx,ty,num) {
    if(this.animPiece.length == 0){
        var self = this;
        this.timerID = setInterval(function(){
            self.animation();
        }, 100);
    }   
    this.animPiece.push({ num:num, tx:tx, ty:ty });
    return true;
}

Board.prototype.animation = function() {    
    var obj = this.animPiece.shift();
    this.put(obj.tx,obj.ty,obj.num) 
    if(this.animPiece.length == 0){
        clearInterval(this.timerID);
    }
}

// 盤をクリアして、初期位置に駒をセット
Board.prototype.initBoard = function() {
    for(var i = 0; i < this.ROW; ++i){
        for(var j = 0; j < this.COL; ++j){
            this.board[i][j] = this.EMPTY;
            var piece = document.getElementById(this.PIECE_NAME+i+""+j);
            piece.src = this.IMG_PIECE[this.EMPTY].src;
        }
    }
    this.put(3,3,this.WHITE);
    this.put(4,4,this.WHITE);
    this.put(4,3,this.BLACK);
    this.put(3,4,this.BLACK);
}

// 設置できるか調べる
Board.prototype.checkPut = function(tx,ty,num,auto) {
    if(!(num == this.WHITE || num == this.BLACK)) return false;
    if(this.board[ty][tx] != 0) return false;
    
    var recRet = new Array(8);
    recRet[0] = this.recSeekPos( { x:tx, y:ty, selfNum:num, array:null, mode:0, nextX: 0, nextY:-1 } );
    recRet[1] = this.recSeekPos( { x:tx, y:ty, selfNum:num, array:null, mode:0, nextX: 0, nextY: 1 } );
    recRet[2] = this.recSeekPos( { x:tx, y:ty, selfNum:num, array:null, mode:0, nextX: 1, nextY: 0 } );
    recRet[3] = this.recSeekPos( { x:tx, y:ty, selfNum:num, array:null, mode:0, nextX:-1, nextY: 0 } );
    recRet[4] = this.recSeekPos( { x:tx, y:ty, selfNum:num, array:null, mode:0, nextX: 1, nextY:-1 } );
    recRet[5] = this.recSeekPos( { x:tx, y:ty, selfNum:num, array:null, mode:0, nextX:-1, nextY: 1 } );
    recRet[6] = this.recSeekPos( { x:tx, y:ty, selfNum:num, array:null, mode:0, nextX:-1, nextY:-1 } );
    recRet[7] = this.recSeekPos( { x:tx, y:ty, selfNum:num, array:null, mode:0, nextX: 1, nextY: 1 } );
    var amount = 0;
    for(var i = 0; i < recRet.length; ++i){
        if(recRet[i].array == null ) continue;
        amount += recRet[i].array.length;
        if(auto){
            // 実際に裏返す
            for(var j = 0; j < recRet[i].array.length; ++j){
                var pos = this.idToTiles(recRet[i].array[j].id);
                this.animationPut(pos.tx,pos.ty,num);
            }
        }
    }
    // JavaScriptの数値は0,NaNならfalseになる(はず)
    return amount;
}

// へっぽこ方法
Board.prototype.recSeekPos = function(recArgs) {    
    // 調べる位置決定
    recArgs.x += recArgs.nextX;
    recArgs.y += recArgs.nextY;
            
    // 配列外に出れば終了
    if(this.board[recArgs.y] == undefined || this.board[recArgs.y][recArgs.x] == undefined){
        recArgs.array = null;
        return recArgs; 
    }
    if(recArgs.mode == 0){
        // 自分の駒か空白を見つけたので終了
        if(this.board[recArgs.y][recArgs.x] == recArgs.selfNum || this.board[recArgs.y][recArgs.x] == this.EMPTY){
            recArgs.array = null;
        // 自分以外の駒を見つけたので探索に入る
        }else {
            recArgs.array = new Array();
            recArgs.array.push(document.getElementById(this.PIECE_NAME+recArgs.y+""+recArgs.x));
            recArgs.mode = 1;
            this.recSeekPos(recArgs);
        }
    }else{
        // 返せる駒を見つけた
        if(this.board[recArgs.y][recArgs.x] == recArgs.selfNum){
            ;
        // 空白だったので返せない
        }else if(this.board[recArgs.y][recArgs.x] == this.EMPTY){
            recArgs.array = null;
        // まだ続きがある
        }else{
            recArgs.array.push(document.getElementById(this.PIECE_NAME+recArgs.y+""+recArgs.x));
            this.recSeekPos(recArgs);
        }
    }
    
    return recArgs;
}

// アニメーション中かチェック
Board.prototype.isAnimation = function() {
    return this.animPiece.length;
}

// パスできるかチェック
Board.prototype.checkPass = function(num) {
    for(var i = 0; i < this.ROW; ++i){
        for(var j = 0; j < this.COL; ++j){
            if(this.board[i][j] == this.EMPTY){
                if(this.checkPut(j,i,num)){
                    return false;
                }
            }
        }
    }
    return true;
}

// 駒の数を取り出し
Board.prototype.getPieceCount = function() {    
    var amount = new Array(0,0,0);
    for(var i = 0; i < this.ROW; ++i){
        for(var j = 0; j < this.COL; ++j){
            ++amount[this.board[i][j]];
        }
    }
    return { empty:amount[this.EMPTY], black:amount[this.BLACK], white:amount[this.WHITE] };
}

// AIというほどでもないAI
var AI = function(board) {
    // AIの名前
    this.name = "NPC";  
    // 面倒なので盤を丸ごと持つ
    this.board = board; 
    // 初期値で白
    this.turn = board.WHITE;    
    // 重み
    this.weight = new Array( 
         1, 57, 10, 18, 20, 56,  2,
        58, 60, 20, 28, 21, 59, 54,
        11, 14,  5, 30,  6, 18, 11,
        38, 41, 25, 19, 26, 42, 19,
        37, 43, 27, 33, 24, 44, 16,
         9, 13,  7, 29, 31,  8, 12,
        57, 50, 22, 24, 23, 55, 53,
         3, 51, 13, 17, 15, 52,  4
    );
}

// AIに駒を置かせる
AI.prototype.put = function() {
    
    var putList = new Array();
    for(var i = 0; i < this.board.ROW; ++i){
        for(var j = 0; j < this.board.COL; ++j){
            if(this.board.board[i][j] == this.board.EMPTY){
                var ret = this.board.checkPut(j,i,this.turn);
                if(ret){
                    putList.push({ count:ret, x:j, y:i, weight:this.weight[this.toIndex(j,i)] });
                }
            }
        }
    }
    
    if(putList.length){
        putList.sort(function(a, b) {
            return (a.weight > b.weight) ? 1 : -1;
        });
        var obj = putList.shift();
        if(this.board.checkPut(obj.x,obj.y,this.turn,true))
            this.board.put(obj.x,obj.y,this.turn);
        else
            ;//ここには入らないはず
    }else{
        alert(this.name+"は置ける場所がないのでパスします。");
    }
}

// 二次元配列添え字からインデックス取得
AI.prototype.toIndex = function(x,y) {
    return x * 8 + y;
}

var board = new Board(0,0);
board.initBoard();

var ai = new AI(board);

var MODE_WAIT   = 0;
var MODE_PLAY   = 1;
var MODE_RESULT = 2;

var PLAYER = board.BLACK;
var NPC    = board.WHITE;
        
var gameMode = MODE_WAIT;

var turn   = PLAYER;
var waitTimer = 0;

// ゲーム開始
playGame();

function getMousePos(evt) {
    var cx, cy;
    if(document.all){
        cy = document.body.scrollTop + window.event.clientY;
        cx = document.body.scrollLeft + window.event.clientX;
    }else if(document.getElementById){
        cy = evt.pageY;
        cx = evt.pageX;
    }
    return { x:cx, y:cy };
}

function onClick(evt) {
    if(gameMode != MODE_PLAY) return;
    if(turn != PLAYER) return;
    pos = getMousePos(evt);
    if(board.putPiece(pos.x,pos.y,PLAYER))
        changeTurn();
}

function mouseMove(evt) {
    pos = getMousePos(evt);
    board.onMouseMove(pos.x,pos.y);
}
    
function changeTurn() {
    gameMode = MODE_WAIT;
    turn = (turn == PLAYER ? NPC : PLAYER);
    waitTimer = setInterval(checkGameMode,100);
}

function checkGameMode() {
    if(board.isAnimation()) return;
    clearInterval(waitTimer);
    playGame();
}

function playGame() {
    if(turn == NPC){
        // NPC操作へ
        ai.put()
        changeTurn();
    }else{
        if(board.checkPass(PLAYER)){
            if(board.checkPass(NPC)){
                gameMode = MODE_RESULT;
                var result = board.getPieceCount();
                alert("黒:"+result.black+"\n"+"白:"+result.white);
            }else{
                alert("置ける場所がないのでパスします。");
                changeTurn();
            }
        }else{
            // プレイヤー操作へ
            gameMode = MODE_PLAY;
        }
    }
}