2004/02/18 by swat: 説明は不用だと思うので割愛。 実際にプレイするととにかくめんどくさいです。
var BOARD_WIDTH = 5; //横マス数
var BOARD_HEIGHT = 5; //縦マス数
var NUMBER_MINES = 5; //爆弾の数
//マスの状態フラグ設定
var STATUS_COVERED = 0x10; //マスが閉じている
var STATUS_MINED = 0x20; //爆弾がある
var STATUS_FLAGGED = 0x40; //旗が立っている
var STATUS_EXPLODED = 0x80; //爆弾が爆発している
var STATUS_NEIGHBORMINES_MASK = 0x0f; //周囲の爆弾の数を得るときのマスク
//状態取得用関数
function isCovered(x, y) {return ((board[y][x] & STATUS_COVERED) == STATUS_COVERED);}
function isMined(x, y) {return ((board[y][x] & STATUS_MINED) == STATUS_MINED);}
function isFlagged(x, y) {return ((board[y][x] & STATUS_FLAGGED) == STATUS_FLAGGED);}
function isExploded(x, y) {return ((board[y][x] & STATUS_EXPLODED) == STATUS_EXPLODED);}
function countNeighborMines(x, y) {return board[y][x] & STATUS_NEIGHBORMINES_MASK;}
//盤の初期化
var board = new Array(BOARD_HEIGHT);
for(var y = 0; y < BOARD_HEIGHT; y++) {
board[y] = new Array(BOARD_WIDTH);
for(var x = 0; x < BOARD_WIDTH; x++) board[y][x] = STATUS_COVERED;
}
var panelsLeft = BOARD_WIDTH * BOARD_HEIGHT; //開いていないマスの数
//爆弾を配置--指定座標(最初に開けたマス)には配置しない
function generateMines(avoidX, avoidY) {
if(NUMBER_MINES >= (BOARD_WIDTH * BOARD_HEIGHT - 1)) throw new Error("盤に対して爆弾が多すぎます");
var mine = 0;
while(mine < NUMBER_MINES) {
var x = Math.floor(Math.random() * BOARD_WIDTH);
var y = Math.floor(Math.random() * BOARD_HEIGHT);
if(!(x == avoidX && y == avoidY) && !isMined(x, y)) {
board[y][x] |= STATUS_MINED;
incrementNeighbors(x, y);
mine++;
}
}
}
//有効な座標かどうかチェック
function isValidCoord(x, y) {return ((x >= 0) && (x < BOARD_WIDTH) && (y >= 0) && (y < BOARD_HEIGHT));}
var NEIGHBORS = [[-1, -1], [0, -1], [1, -1],
[-1, 0], [1, 0],
[-1, 1], [0, 1], [1, 1]];
//周囲のマスの爆弾の数を+1する
function incrementNeighbors(checkX, checkY) {
for(var i = 0; i < NEIGHBORS.length; i++) {
var x = checkX + NEIGHBORS[i][0];
var y = checkY + NEIGHBORS[i][1];
if(isValidCoord(x, y)) board[y][x]++;
}
}
//周囲の旗の数を数える
function countNeighborFlags(checkX, checkY) {
var count = 0;
for(var i = 0; i < NEIGHBORS.length; i++) {
var x = checkX + NEIGHBORS[i][0];
var y = checkY + NEIGHBORS[i][1];
if(isValidCoord(x, y)) {if(isFlagged(x, y)) count++;}
}
return count;
}
//マスを開く
function open(x, y) {
if(isFlagged(x, y) || !isCovered(x, y)) return;
if(isMined(x, y)) {
board[y][x] |= STATUS_EXPLODED;
for(var y = 0; y < BOARD_HEIGHT; y++) {
for(var x = 0; x < BOARD_WIDTH; x++) {
if(isMined(x, y)) board[y][x] &= ~STATUS_COVERED;
}
}
var e = new Error("OOPS!");
e.name = "GameOverError";
throw e;
}
board[y][x] &= ~STATUS_COVERED;
panelsLeft--;
if(panelsLeft == NUMBER_MINES) {
var e = new Error("Congratulations!");
e.name = "GameOverError";
throw e
}
if(countNeighborMines(x, y) == 0) openNeighbors(x, y);
}
function openNeighbors(checkX, checkY) { //周囲のマスをすべて開く
for(var i = 0; i < NEIGHBORS.length; i++) {
var x = checkX + NEIGHBORS[i][0];
var y = checkY + NEIGHBORS[i][1];
if(isValidCoord(x, y)) open(x, y);
}
}
//既に開いたマスを指定し、マス目の数字と周囲の旗の数が等しければ、
//周囲のマスをすべて開く(winmineの左右ボタン同時クリックと同様)
function check(x, y) {
if(isCovered(x, y)) return;
if(countNeighborMines(x, y) != countNeighborFlags(x, y)) return;
openNeighbors(x, y);
}
//旗のON/OFF切り替え
function flag(x, y) {board[y][x] ^= STATUS_FLAGGED}
//盤を文字列として取得
function boardToString() {
var BOX_NUMBER = [" ", "1", "2", "3", "4", "5", "6", "7", "8"];
var BOX_PLAIN = "■"
var BOX_FLAGGED = "□"
var BOX_MINE = "●"
var BOX_MINE_EXPLODED = "※"
var str = "\r\n\r\n\r\n\r\n\r\n\r\n |";
for(var x = 0; x < BOARD_WIDTH; x++) str += fillSpace(x, 3);
str += "\r\n---+";
for(var x = 0; x < BOARD_WIDTH; x++) str += "---";
str += "\r\n";
for(var y = 0; y < BOARD_HEIGHT; y++) {
str += fillSpace(y, 3) + "|";
for(var x = 0; x < BOARD_WIDTH; x++) {
var chr;
if(isCovered(x, y)) {
chr = (isFlagged(x, y) ? BOX_FLAGGED : BOX_PLAIN);
} else if(isMined(x, y)) {
chr = (isExploded(x, y) ? BOX_MINE_EXPLODED : BOX_MINE);
} else {
chr = BOX_NUMBER[countNeighborMines(x, y)];
}
str += " " + chr;
}
str += "\r\n";
}
return str;
}
function fillSpace(num, digits) { //digitsに足りない桁数をスペースで補完する
var str = "";
for(i = 0; i < digits - num.toString().length; i++) str += " ";
return str + num.toString();
}
function redrawBoard() { //盤の再描画
document.selection.SelectAll();
document.selection.Text = boardToString();
}
function main() {
var isFirstOpen = true;
while(true) {
redrawBoard();
var pr = prompt("マスを開く:o(省略可) x y / 旗を立てる: f x y / 周囲のマスを一度に開く: c x y",
"コマンドを入力(キャンセルで終了)"); //
if(pr == "") break;
var cmds = /^\s*(?:(\w)\s+)?(\d+)\s+(\d+)\s*$/.exec(pr);
if(cmds == null) continue;
var cmd = cmds[1]; var x = parseInt(cmds[2]); var y = parseInt(cmds[3]);
if(!isValidCoord(x, y)) continue;
switch(cmd) {
case "":
case "o":
try {
if(isFirstOpen) {
isFirstOpen = false;
generateMines(x, y);
}
open(x, y);
} catch(e) {
if(e.name == "GameOverError") {
redrawBoard();
document.selection.Text = e.message;
return;
} else {
alert(e.message);
return;
}
}
break;
case "f":
flag(x, y);
break;
case "c":
check(x, y);
break;
}
}
}
main();
