
// フェイスマーク
var FACE_NORMAL = 0;
var FACE_AMAZING = 1;
var FACE_CLEAR = 2;
var FACE_FAIL = 3;



// 盤面の状態 (boardState[i][j] の値)
var TILE_CLOSE = 0;    // 開いていない
var TILE_OPEN = 1;     // 開いた
var TILE_FLAG = 2;     // 旗
var TILE_QUESTION = 3; // ? マーク





// ブラウザ独自の処理を行うため、User-Agent 名を取得
var userAgent = window.navigator.userAgent.toLowerCase();


// 整数の2次元配列で盤面の状態を表示
var boardState = new Array(0);

// boolean の2次元配列で地雷の有無を記憶
var boardMine = new Array(0);

// 盤面の幅・高さ
var boardWidth = 0;
var boardHeight = 0;

// 盤面全体の地雷の個数
var mineTotal = 0;

// 地雷の場所が確定しているかどうか
var mineFixed = false;

// 決められた盤面データ
var fixedBoardData = null;

// 実際に盤面に地雷が設置されているかどうか
var mineIsSet = false;

// 盤面全体の旗の数
var flagTotal = 0;

// まだ開けられていないタイルの残り個数(地雷除く)
var restTile = 0;


// ゲームを開始したときの日付時刻
var startTime = 0;
// タイマーが動いているかどうか
var isActive = false;

// ゲームオーバー状態(クリアまたは失敗)かどうか
var isGameOver = false;


// 直前にマウスボタンを押したタイル
var pushTileX = -1;
var pushTileY = -1;





// 指定した数値に 0 を詰めて長さが length の文字列に変換
function paddingZero(num, length) {
    var numText = num.toString();
    if (numText.length > length) {
        return numText.subString(numText.length - length);
    }
    else {
        for (var i=numText.length; i<length; i++) {
            numText = "0" + numText;
        }
        return numText;
    }
}



// タイマー計測開始
function startTimer() {
    var nowTime = new Date();
    startTime = nowTime.getTime();
    isActive = true;
    tick();
}

// タイマー計測停止
function stopTimer() {
    isActive = false;
}

// タイマー刻み処理
function tick() {
    if (isActive) {
        // 現在の Date
        var nowTime = new Date();

        // startTime からの経過秒数 + 1
        var second = Math.floor((nowTime.getTime() - startTime) / 1000) + 1;

        // 秒数を表示
        showTime(second);

        // 250ミリ秒後に再び実行
        setTimeout(tick, 250);
    }
}

// 経過時間を表示
function showTime(second) {
    // 0〜999秒の範囲になるようにする
    second = Math.max(second, 0);
    second = Math.min(second, 999);
    // 3桁に満たない場合は "0" を詰める
    var secText = paddingZero(second, 3);
    // 秒数を表示
    for (var i=0; i<3; i++) {
        var idName = "timer" + i;
        var timerElement = document.getElementById(idName);
        var digit = secText.charAt(i);
        var digitX = -digit * 13;
        timerElement.style.backgroundPosition = digitX + "px 0px";
    }
}



// マークされていない地雷の個数を表示
function showCounter() {
    // 全体の地雷の数から旗の数を引く (負の場合は0と表示)
    var count = Math.max(mineTotal - flagTotal, 0);
    // 3桁に満たない場合は "0" を詰める
    var counterText = paddingZero(count, 3);
    // 地雷の個数を表示
    for (var i=0; i<3; i++) {
        var idName = "counter" + i;
        var counterElement = document.getElementById(idName);
        var digit = counterText.charAt(i);
        var digitX = -digit * 13;
        counterElement.style.backgroundPosition = digitX + "px 0px";
    }
}



// フェイスマークを設定
function setFace(face) {
    var faceElement = document.getElementById("face");
    faceElement.style.backgroundPosition = "0px " + (face * -26).toString() + "px";
}



// イベントハンドラを設定
function setEventHandler(elem, eventName, action) {
    // 要素の "onXXX" 属性を設定する
    if (userAgent.indexOf("msie") > -1) {
        // IEの場合は new Function で指定する
        elem.setAttribute(eventName, new Function(action));
    }
    else {
        // IE以外はそのまま設定
        elem.setAttribute(eventName, action);
    }
}





// HTML要素を作成
function makeHtml() {

    // jsmine を取得
    var jsmineTable = document.getElementById("jsmine");
    // 元の内容をいったん削除
    while (jsmineTable.hasChildNodes()) {
        jsmineTable.removeChild(jsmineTable.firstChild);
    }

    var jsmineTbody = document.createElement("tbody");

    // ヘッダ部分を作成
    var headerTr = document.createElement("tr");
    var headerTd = document.createElement("td");
    headerTd.style.textAlign = "center";

    // カウンターを作成
    var counterDiv = document.createElement("div");
    counterDiv.id = "counter";
    // カウンターの数字を作成
    for (var i=0; i<3; i++) {
        var counterSpan = document.createElement("span");
        counterSpan.id = "counter" + i.toString();
        counterSpan.className = "digit";
        counterDiv.appendChild(counterSpan);
    }
    headerTd.appendChild(counterDiv);

    // タイマーを作成
    var timerDiv = document.createElement("div");
    timerDiv.id = "timer";
    // タイマーの数字を作成
    for (var i=0; i<3; i++) {
        var timerSpan = document.createElement("span");
        timerSpan.id = "timer" + i.toString();
        timerSpan.className = "digit";
        timerDiv.appendChild(timerSpan);
    }
    headerTd.appendChild(timerDiv);

    // フェイスマーク
    var faceDiv = document.createElement("div");
    faceDiv.id = "face";
    setEventHandler(faceDiv, "onclick", "resetBoard();");
    headerTd.appendChild(faceDiv);

    headerTr.appendChild(headerTd);

    jsmineTbody.appendChild(headerTr);


    // タイル部分を作成
    bodyTr = document.createElement("tr");
    bodyTd = document.createElement("td");

    // タイルの境界
    var tileborderDiv = document.createElement("div");
    tileborderDiv.id = "tileborder";

    // 盤面の要素を作成
    var boardTable = document.createElement("table");
    boardTable.style.borderCollapse = "collapse";

    var boardTbody = document.createElement("tbody");

    for (var i=0; i<boardHeight; i++) {
        var tileTr = document.createElement("tr");
        for (var j=0; j<boardWidth; j++) {
            var tileTd = document.createElement("td");
            tileTd.id = "tile" + paddingZero(j, 3) + paddingZero(i, 3);
            tileTd.className = "covered";
            setEventHandler(tileTd, "onmousedown", "pushTile(event, " + j + ", " + i + ");");
            setEventHandler(tileTd, "onclick", "openTile(" + j + ", " + i + ");");
            setEventHandler(tileTd, "oncontextmenu", "return toggleFlag(" + j + ", " + i + ");");
            setEventHandler(tileTd, "ondblclick", "openAround(" + j + ", " + i + ");");

            tileTr.appendChild(tileTd);
        }
        boardTbody.appendChild(tileTr);
    }
    boardTable.appendChild(boardTbody);

    tileborderDiv.appendChild(boardTable);

    // 全体の幅を設定
    tileborderDiv.style.width = boardWidth * 16;

    bodyTd.appendChild(tileborderDiv);
    bodyTr.appendChild(bodyTd);
    jsmineTbody.appendChild(bodyTr);

    jsmineTable.appendChild(jsmineTbody);
}


// 盤面を初期化
function initBoard(width, height, count) {
    stopTimer();
    isGameOver = false;

    boardWidth = width;
    boardHeight = height;
    mineTotal = count;
    mineFixed = false;
    mineIsSet = false;
    flagTotal = 0;

    // 盤面を初期化
    boardMine = new Array(boardHeight);
    boardState = new Array(boardHeight);
    for (var i=0; i<boardHeight; i++) {
        boardMine[i] = Array(boardWidth);
        boardState[i] = Array(boardWidth);
        for (var j=0; j<boardWidth; j++) {
            boardMine[i][j] = false;
            boardState[i][j] = TILE_CLOSE;
        }
    }

    // 盤面HTMLを作成
    makeHtml();

    // タイマーの表示
    showTime(0);
    // カウンターの表示
    showCounter();
    // フェイスマークの表示
    setFace(FACE_NORMAL);
}



// 特定の盤面を読み込む
function loadBoard(data) {
    stopTimer();
    isGameOver = false;

    fixedBoardData = data;

    boardWidth = fixedBoardData.width;
    boardHeight = fixedBoardData.height;
    mineTotal = 0;
    mineFixed = true;
    mineIsSet = true;
    flagTotal = 0;

    // 盤面を初期化
    boardMine = new Array(boardHeight);
    boardState = new Array(boardHeight);
    for (var i=0; i<boardHeight; i++) {
        boardMine[i] = Array(boardWidth);
        boardState[i] = Array(boardWidth);
        for (var j=0; j<boardWidth; j++) {
            boardMine[i][j] = fixedBoardData.data[i][j];
            boardState[i][j] = TILE_CLOSE;
            if (boardMine[i][j]) {
                mineTotal += 1;
            }
        }
    }
    restTile = boardWidth * boardHeight - mineTotal;

    // 盤面HTMLを作成
    makeHtml();

    // タイマーの表示
    showTime(0);
    // カウンターの表示
    showCounter();
    // フェイスマークの表示
    setFace(FACE_NORMAL);
}




// 盤面をリセット
function resetBoard() {
    stopTimer();
    showTime(0);
    isGameOver = false;

    setFace(FACE_NORMAL);

    // mineTotal は変更しない
    mineIsSet = mineFixed;
    flagTotal = 0;
    showCounter();
    restTile = boardWidth * boardHeight - mineTotal;

    for (var i=0; i<boardHeight; i++) {
        boardMine[i] = Array(boardWidth);
        boardState[i] = Array(boardWidth);
        for (var j=0; j<boardWidth; j++) {
            if (mineFixed) {
                // 盤面データをコピー
                boardMine[i][j] = fixedBoardData.data[i][j]
            }
            else {
                // 今は地雷を配置せず、最初のタイルを開けたときに配置
                boardMine[i][j] = false;
            }
            boardState[i][j] = TILE_CLOSE;
            setTileStyle(j, i, "covered");
        }
    }
}




// 地雷を配置
function dispatchMine(count, exceptX, exceptY) {
    for (var i=0; i<count; i++) {
        var x = Math.floor(Math.random() * boardWidth);
        var y = Math.floor(Math.random() * boardHeight);
        // (exceptX, exceptY) やすでに地雷のある場所だったら (x, y) を設定しなおす
        while ((x == exceptX && y == exceptY) || (boardMine[y][x])) {
            x = Math.floor(Math.random() * boardWidth);
            y = Math.floor(Math.random() * boardHeight);
        }
        boardMine[y][x] = true;
    }
    restTile = boardWidth * boardHeight - count;
}





// タイルの表示を変更
function setTileStyle(x, y, styleName) {
    // 変更するタイルのID
    var idName = "tile" + paddingZero(x, 3) + paddingZero(y, 3);
    // DOM エレメントを取得
    var tileElement = document.getElementById(idName);
    // スタイルを設定
    tileElement.className = styleName;
}



// ゲームオーバーにする
function doGameOver() {
    // フェイスマークを変更
    setFace(FACE_FAIL);

    // 盤面上の地雷を表示
    for (var i=0; i<boardHeight; i++) {
        for (var j=0; j<boardWidth; j++) {
            if (boardState[i][j] == TILE_CLOSE && boardMine[i][j]) {
                // マークされていない地雷であれば表示
                setTileStyle(j, i, "mine");
            }
            else if (boardState[i][j] == TILE_FLAG && !boardMine[i][j]) {
                // 地雷でないのにマークされていたら×印を表示
                setTileStyle(j, i, "fake");
            }
        }
    }
}



// タイルでマウスボタンが押されたときの処理
function pushTile(e, x, y) {

    // ゲームオーバー時は何もしない
    if (isGameOver) {
        return;
    }
    // (x, y) が盤面内かどうかチェック
    if (x < 0 || x >= boardWidth) {
        return;
    }
    if (y < 0 || y >= boardHeight) {
        return;
    }

    // 何らかの原因で押しっぱなしになっているタイルがあれば表示を戻す
    if (pushTileX > -1 && pushTileY > -1) {
        // タイルがまだ開いていなければ表示を変更
        if (boardState[pushTileY][pushTileX] == TILE_CLOSE) {
            setTileStyle(pushTileX, pushTileY, "covered");
            pushTileX = -1;
            pushTileY = -1;
        }
    }

    // 左ボタンが押されたときのみ処理
    if (e.button == 1) {
        // タイルがまだ開いていなければ表示を変更
        if (boardState[y][x] == TILE_CLOSE) {
            setTileStyle(x, y, "tile0");
            // 押したタイルの位置を記憶しておく
            pushTileX = x;
            pushTileY = y;
        }
    }
}




// タイルを開く
function openTile(x, y) {
    // ゲームオーバー時は何もしない
    if (isGameOver) {
        return;
    }
    // (x, y) が盤面内かどうかチェック
    if (x < 0 || x >= boardWidth) {
        return;
    }
    if (y < 0 || y >= boardHeight) {
        return;
    }

    // タイルの状態を取得
    var originalState = boardState[y][x];

    // タイルがすでに開いていたり、旗がついていたら何もしないで戻る
    if (originalState == TILE_OPEN || originalState == TILE_FLAG) {
        return;
    }

    // ゲームが開始されていなければスタート
    if (!isActive) {
        if (mineIsSet == false) {
            dispatchMine(mineTotal, x, y);
            mineIsSet = true;
        }
        startTimer();
    }

    // タイルを開いた状態にする
    boardState[y][x] = TILE_OPEN;

    // 地雷があるかどうかチェック
    if (boardMine[y][x]) {
        // 地雷を表示
        setTileStyle(x, y, "hit");
        // ゲームオーバー
        isGameOver = true;
        stopTimer();
        doGameOver();
        return;
    }

    // 周りの地雷をカウント
    var mineCount = countMine(x, y);

    // 表示を変更
    var styleName = "tile" + mineCount;
    setTileStyle(x, y, styleName);

    // 地雷の数が 0 なら周りのマスを開く
    if (mineCount == 0) {
        for (var offsetY=-1; offsetY<=1; offsetY++) {
            var tileY = y + offsetY;
            for (var offsetX=-1; offsetX<=1; offsetX++) {
                var tileX = x + offsetX;
                if (tileX == x && tileY == y) {
                    // クリックしたタイルはカウントしない
                }
                else {
                    // 再帰的に周りのマスを開く
                    openTile(tileX, tileY);
                }
            }
        }
    }

    // 残りタイル枚数を減らす
    restTile -= 1;

    // 残りタイル枚数が 0 になったらクリア
    if (restTile <= 0) {
        stopTimer();
        isGameOver = true;
        setFace(FACE_CLEAR);
    }
}


// 指定したタイルの周りの地雷の個数をカウントして返す
function countMine(x, y) {
    var mineCount = 0;
    for (var offsetY=-1; offsetY<=1; offsetY++) {
        var tileY = y + offsetY;
        if (tileY >= 0 && tileY < boardHeight) {
            for (var offsetX=-1; offsetX<=1; offsetX++) {
                var tileX = x + offsetX;
                if (tileX >= 0 && tileX < boardWidth) {
                    if (tileX == x && tileY == y) {
                        // 指定したタイル自身はカウントしない
                    }
                    else if (boardMine[tileY][tileX]) {
                        // 周りの地雷の数をカウント
                        mineCount += 1;
                    }
                }
            }
        }
    }
    return mineCount;
}



// 指定したタイルの周りの旗の個数をカウントして返す
function countFlag(x, y) {
    var flagCount = 0;
    for (var offsetY=-1; offsetY<=1; offsetY++) {
        var tileY = y + offsetY;
        if (tileY >= 0 && tileY < boardHeight) {
            for (var offsetX=-1; offsetX<=1; offsetX++) {
                var tileX = x + offsetX;
                if (tileX >= 0 && tileX < boardWidth) {
                    if (tileX == x && tileY == y) {
                        // 指定したタイル自体はカウントしない
                    }
                    else if (boardState[tileY][tileX] == TILE_FLAG) {
                        // 旗の数をカウント
                        flagCount += 1;
                    }
                }
            }
        }
    }
    return flagCount;
}



// 周りの地雷にすべて旗がついていたら、それ以外のセルをすべて開ける
function openAround(x, y) {
    // ゲームオーバー時は何もしない
    if (isGameOver) {
        return;
    }
    // (x, y) が盤面内かどうかチェック
    if (x < 0 || x >= boardWidth) {
        return;
    }
    if (y < 0 || y >= boardHeight) {
        return;
    }

    // タイルの状態を取得
    var originalState = boardState[y][x];

    // タイルがまだ開いていなければ戻る
    if (originalState != TILE_OPEN) {
        return;
    }

    // 周りの地雷の個数をカウント
    var mineCount = countMine(x, y);

    // 周りの旗の数をカウント
    var flagCount = countFlag(x, y);

    // 地雷の数と旗の数が一致したら、それ以外のタイルを開ける
    if (mineCount == flagCount) {
        for (var offsetY=-1; offsetY<=1; offsetY++) {
            var tileY = y + offsetY;
            for (var offsetX=-1; offsetX<=1; offsetX++) {
                var tileX = x + offsetX;
                // タイルを開ける
                // すでに開いているかどうかや旗のチェックは openTile 内で行う
                openTile(tileX, tileY);
            }
        }
    }
}



// 指定したタイルの状態(旗/？マーク)を変更
function toggleFlag(x, y) {
    // ゲームオーバー時は何もしない
    if (isGameOver) {
        return false;
    }
    // (x, y) が盤面内かどうかチェック
    if (x < 0 || x >= boardWidth) {
        return false;
    }
    if (y < 0 || y >= boardHeight) {
        return false;
    }

    // タイルの状態を取得
    var originalState = boardState[y][x];

    // タイルがすでに開いていたら戻る
    if (originalState == TILE_OPEN) {
        return false;
    }

    // ゲームが開始されていなければスタート
    if (!isActive) {
        dispatchMine(mineTotal, x, y);
        startTimer();
    }

    // タイルの状態を変更
    switch(originalState) {
    case TILE_CLOSE:
        // 開いていないタイルから旗へ
        boardState[y][x] = TILE_FLAG;
        // 表示を変更
        setTileStyle(x, y, "flag");
        // 旗の数を増やす
        flagTotal++;
        showCounter();
        break;
    case TILE_FLAG:
        // 旗
        boardState[y][x] = TILE_CLOSE;
        // 表示を変更
        setTileStyle(x, y, "covered");
        // 旗の数を減らす
        flagTotal--;
        showCounter();
        break;
    }


    // 右クリック時の動作なのでコンテキストメニューを出さないように false を返す
    return false;
}

