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();