LoginSignup
2
1

More than 5 years have passed since last update.

マインスイーパーで言語学習 javascript編

Posted at

前回のあらすじ

私が実践しているマインスイーパーを作ることで言語を学習する機雷除去言語学習法についてまとめました。
pythonコードをサンプルで上げましたが、コメントで指摘を頂き、pythonの重要な部分を理解していないことがよくわかりました。

今回の内容

古いPCの中身を整理していたらjavascriptで学習したソースコードがあったので、手直ししたコードをまたアップします。そして恥の上塗りをする。

あと、前回は中身の処理について前回触れていなかったので、どのメソッドでどのような機能を学習するべきかまとめておきます。

仕様

demo.gif

クリックするとセルを開きます。開いたセルがマインだと終了です
CTRLキーを押しながらクリックするとセルに旗を設置します。旗があるとマインであることを意味します。
マイン以外のすべてのセルを開くとクリアです
Resetボタンをクリックすると状態を初期化します。

操作 仕様
左クリック クリックしたセルを開く
Ctrl+左クリック クリックしたセルに旗を設定する
Resetボタン 状態をリセットして初期化する
  • 表示
    • プレイ時間
    • 残表示
    • クリア回数
    • プレイ回数

ソース

minesw.html
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>ms</title>
    <script language="javascript" src="./minesw.js" type="text/javascript"></script>
  </head>
  <body onload="onLoad()" bgcolor="silver" >
    <input type="button" value="Reset" onclick="onButtonReset();">
    <div id="time_label"></div>
    <div id="rest_label"></div>
    <div id="main_div"></div>
    <br/>
    <br/>
  </body>
</html>
minesw.js
// easy(9,9,10) nomal(16,16,40) hard(30,16,99)
var FIELD_W = 30;
var FIELD_H = 16;
var FIELD_M = 99;

var CELL_CLOSE = "close";
var CELL_OPEN = "open";
var CELL_FLAG = "flag";
var CELL_MINE = "mine";

var ICON = [];  // アイコン画像格納用
ICON[1] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAXUlEQVR4nOWSoRHAMAwDk1JTj5AdY5MQE2/hCTODAkpSVPd6RX0q/YmoAihpVPXIt09eCwDmnGbWWksJqsrMY4zsgrsDiIiscMsvhYIrvfc9JaI9FZH6+fmqiDwSFrUsOXOXOlP/AAAAAElFTkSuQmCC"
ICON[2] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAA2ElEQVR4nGP8//8/A9GgsrKSiXjVEECZhh8/fhQXF2toaHBxcUlKSsbFxT1//hyfho8fP16/fn3WrFkvX748fPjwo0ePQkJC0K34jxvs2bMHYgpcpKKiAp8fnj17xsPDw8vLi9NJyOD79+8dHR0xMTGMjIyENfz58yc2NpaXl7enpwefpyHgx48foaGhT5482b59Ozc3NwENb968cXZ2ZmZm3r9/v6CgIKZxKBru379vYWHh5OS0evVqTk5OrK5FCdbly5djKiguLkYOVkaaJz7GiooKkjQAAH4zimgKZm2tAAAAAElFTkSuQmCC"
ICON[3] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAA7UlEQVR4nLWSMa5FQBSGeShkCsVETSQUorEDDbagmnUMsQa2YQcWQDWtQiU09IKEYjK3uMnNfcnjeXm5f3lyvuT/cg7PGONuJ47jr/vbz/wPYIwlSeI4DgAAQuj7PiHkCqCUVlWVZdk4jm3bWpYVBMG+798Idp6maTiOG4bhNcEYiz8WpZT2fZ+mKUJI07TfpRVFMU1TEIQ8z68cXlmWZZomwzBc153n+a4DpVSW5aIo3h2u7rBt23EckiSdVirLEiFECFnXtes6hJCu62EYngK2bYuiGEWRqqqe50EI67oGALzv8B9/Ph5j/CfgAfefpFbPfI13AAAAAElFTkSuQmCC"
ICON[4] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAApklEQVR4nK2OsQ2EIBhGES9hAGdgBRyCjoQZmMAEeyZgBgt7ShiAihFYAAagw+KSi97pBRO/9n8v7+9qraB58zzDdvq954RlWSCEzrkmwVqrlOr7vqkQQhBCGGOahBgj53xdV4zxafwgpJQopVprQsgp/S0wxqZpopRe0QAAUHcbhuEXGMfxA0gpD4Wc895HCFlrvfeXL7XstvD6cyulPFDopJS3hA3TtlA7f698oAAAAABJRU5ErkJggg=="
ICON[5] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAw0lEQVR4nK2PKw6EMBBAtxsaQBE+AmwTjoEllfUExyFAYfFYuAcCPZygtlyCpKC6Yg0ky5b9PDfJvMwbpJS6Xaaqqvv17Sc/C5RSdKTrOs2Fuq7VjqIo/pr0jdA0DcY4DENKKQBohGEY1nWVUgJAHMdpmgoh9EmGYRBC2ra1LGscx6s/bNsmpTRN81TgnDPGAGBZlnme8zx3XZcxdipEUeT7fpZlQRAkSWLb9jRNjuMcaveD53l937+J1PzwElSW5UfCA1THSYYeYhrVAAAAAElFTkSuQmCC"
ICON[6] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABDUlEQVR4nK2SMatGcBSH/W/KIIUYKKXe3UQMvoHRZFAWX4ASn8CHMCgyWmS2MNhMRpmMSlksuMOt2/vecq+37jOeznNO59cB53lCtwmC4ON+9xf/IfR9bxgGwzAIgpim+YfQNI2iKBzH1XW9bVuapj/nnU/s+/54PFzXPS/wff9lQ13XwzB4nnf3hq7rSJIMw5DneRRFBUGI4/g3YVkWAICqql3XTdPkOI5t23meXwoYhtE0res6QRA4jluWpWlaURSXgiRJ4zjO8/xd2bYNhuHLlI7jkGVZ07RhGNZ1jaIIQZC2bS9TAgCUZUmSpCiKLMtmWVZVlSzLzz2v6yCIoqgkSaBr3v4l4Pv+W8InxtyXYS+9ibIAAAAASUVORK5CYII="
ICON[7] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAvElEQVR4nGP8//8/A9GgsrKSiXjVEECZhjlz5jBigJycHJwaUlJS/qMCKysrRUVFYp20du3ae/fuZWZmEqXhz58/VVVVdXV1XFxcRGmYOXPm379/U1JS0MRZsKr+8uVLU1PThAkTWFlZ0aSw29DZ2SklJRUREYEphcWG58+f9/f3r1mzhpGREVMWiw11dXUmJiYeHh5YLUe34fr16wsWLDh8+DBW1Vg0aGpq/v79G5dq7E7CDxgrKipI0gAAPkJIlmv70nAAAAAASUVORK5CYII="
ICON[8] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGUlEQVR4nLWSsYqDQBRFVVBiERnt0vkF6UIgiIjCkNZfSC+k1MZKC7t8zNSWigTSCFailcVAQI0gBoTZYkESwSXLsre7773L4cKjCSHUx3Ich/n8+lt/Dtxut+PxKEkSAEDX9SiKfgpgjA3DUBSlKIqyLFVVhRBWVfWWIC9CCDEMMwzDNFmtVgihydq2/UbY7XYAgPP53LYtRVFRFK3X68PhsEgghKRpKkmSIAimaW632yzLXrdzQtM0p9MpCII8zw3DeD6flmV1XbdI8H1/v99Ptu97WZY9z1skPB6PcRwny/P8ZrPBGC8Srtcry7Ku697v97quL5cLx3FJkrwS5qXDMNQ0DQAgiiKEMI7jWWn635+Ptm37V4Eve5n2aDulmdYAAAAASUVORK5CYII="
ICON[CELL_CLOSE] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAJElEQVR4nGOsqKhgIBq0t7czEa8aAkY1jGoYMA2M////J0kDAJp+BhnlNCSXAAAAAElFTkSuQmCC"
ICON[CELL_OPEN] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAJElEQVR4nGP8//8/A9GgsrKSiXjVEDCqYVTDgGlgrKioIEkDAKybBe8o/6vDAAAAAElFTkSuQmCC"
ICON[CELL_FLAG] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAJElEQVR4nGNk2P6BgWjw34OfiXjVEDCqYVTDgGlg/P//P0kaAKWRBhlLmfA4AAAAAElFTkSuQmCC"
ICON[CELL_MINE] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAJElEQVR4nGN8qyLNQDQQuv2EiXjVEDCqYVTDgGlg/P//P0kaAI/yBhngMLANAAAAAElFTkSuQmCC"

// 座標定義用
var Position = function(x,y){
    this.x = x;
    this.y = y;
}

// 隣接座標用
var ROUNDS = [  new Position(-1,-1),    new Position(0,-1), new Position(1,-1),
                new Position(-1,0),                         new Position(1,0),
                new Position(-1,1),     new Position(0,1),  new Position(1,1)];

var CTRL = false;
var FIELD_INTERFACE;
var field;
var view;

var Cell = function(){
    this.open = false;
    this.flag = false;
    this.mine = false;
};
var Field = function(w,h,mine){
    this.x = w;
    this.y = h;
    this.mine = mine;
    this._gameOver = false;
    this._clear = false;
    this._cells = [];
    for(var i = 0;i < this.x *this.y;i++){
        this._cells.push(new Cell());
    }
    // ランダムでmineの位置を設定する
    for(var m = 0;m < mine;m++){
        while(true){
            var randomPosition = Math.floor(Math.random() * this.y * this.x);
            if(this._cells[randomPosition].mine == false){  // 設定済の場合は再度random()実行
                this._cells[randomPosition].mine = true;
                break;
            }
        }
    }
}
Field.prototype.cell = function(x,y){
    var cell = null;
    if(0 <= x && x < this.x && 0 <= y && y < this.y)
        cell = this._cells[y*this.x + x];
    return cell;
}
Field.prototype.isMine = function(x,y){
    var mine = false;
    var cell = this.cell(x,y);
    if(cell != null)
        mine = cell.mine;
    return mine;
}
Field.prototype.isOpen = function(x,y){  // 開いている=true 閉じている=false,範囲外=false
    var open = false;
    var cell = this.cell(x,y);
    if(cell != null)
        open = cell.open;
    return open;
}
Field.prototype.isFlag = function(x,y){  // 旗あり=true 旗なし=false,範囲外=false
    var flag = false;
    var cell = this.cell(x,y);
    if(cell != null)
        flag = cell.flag;
    return flag;
},
Field.prototype.isClose = function(x,y){  // 閉じている=true 開いている=false,範囲外=false
    var close = false;
    var cell = this.cell(x,y);
    if(cell != null)
        if(cell.open == false)
            close = true;
    return close;
}
Field.prototype.isUnsafe = function(x,y){  // 閉じている、旗なし=true それ以外=false,範囲外の場合=false
    var closeAndNoFlag = false;
    var cell = this.cell(x,y);
    if(cell != null)
        if(cell.open == false && cell.flag == false)
            closeAndNoFlag = true;
    return closeAndNoFlag;
}
Field.prototype.openCell = function(x,y){
    if(this.isUnsafe(x,y)){
        this.cell(x,y).open = true;
        // 開いた場所の隣接にmineが無い場合は隣接をすべて開く
        if(this.roundCount(x,y)== 0 && this.isMine(x,y) == false){
            for(var i = 0;i < ROUNDS.length;i++){
                this.openCell(x + ROUNDS[i].x,y + ROUNDS[i].y);
            }
        }
        this._validOver();
    }
}
Field.prototype.setFlag = function(x,y){
    if(this.isClose(x,y)){
        if(this.isFlag(x,y) == false)
            this.cell(x,y).flag = true;
        else
            this.cell(x,y).flag = false;
    }
}
Field.prototype._validOver = function(){
    var closeCellCount = 0;
    for(var y = 0;y < this.y;y++){
        for(var x = 0;x < this.x;x++){
            if(this.isClose(x,y))
                closeCellCount++;
            if(this.isOpen(x,y) && this.isMine(x,y)){
                this._gameOver = true;
                break;
            }
        }
        if(this._gameOver == true)
            break;
    }
    if(this._gameOver == false && closeCellCount == this.mine){
        this._gameOver = true;
        this._clear = true;
    }
}
Field.prototype.roundCount = function(x,y){ // セルに表示する数値を返す
    var count = 0;
    for( var i = 0 ; i < ROUNDS.length ; i++){
        if(this.isMine(x + ROUNDS[i].x,y + ROUNDS[i].y))
            count++;
    }
    return count;
}
Field.prototype.isClear = function(){
    return this._clear;
}
Field.prototype.isGameOver = function(){
    return this._gameOver;
}
Field.prototype.restCount = function(){
    var restCount = this.mine;
    for(var y = 0;y < this.y;y++){
        for(var x = 0;x < this.x;x++){
            if(this.isFlag(x,y))
                restCount--;
        }
    }
    return restCount;
}

var View = function(){
    this.autoPlayId = null;
    this.timerId = null;
    this.timeCount = 0;
    this.playCount = 0;
    this.clearCount = 0;
}
View.prototype.reset = function(){
    if(this.autoPlayId != null)
        clearTimeout(this.autoPlayId);
        this.autoPlayId = null;
    if(this.timerId != null){
        clearTimeout(this.timerId);
        this.timerId = null;
        this.playCount++;
    }
    this.timeCount = 0;
}
View.prototype.draw = function(drawField){
    this._drawField(drawField);
    this._drawRestCount(drawField);
    this.drawTimeCount();
}
View.prototype._drawField = function(drawField){
    var table = "";
    for(var y = 0;y < drawField.y;y++){
        for(var x = 0;x < drawField.x;x++){
            if(drawField.isMine(x,y) && (drawField.isOpen(x,y) || drawField.isGameOver())){
                table += this._drawCell(CELL_MINE,-1,-1);
            }
            else if(drawField.isFlag(x,y) && drawField.isOpen(x,y) == false){
                table += this._drawCell(CELL_FLAG,x,y);
            }
            else if(drawField.isOpen(x,y) == false){
                table += this._drawCell(CELL_CLOSE,x,y);
            }
            else{
                var roundCount = drawField.roundCount(x,y);
                if(roundCount != 0)
                    table +=  this._drawCell(roundCount,-1,-1);
                else
                    table +=  this._drawCell(CELL_OPEN,-1,-1);
            }
        }
        table += "<br/>";
    }
    document.getElementById('main_div').innerHTML = table;
}
View.prototype._drawCell = function(icon_name,x,y){
    if(x==-1 && y==-1)
        return "<img src=\"" + ICON[icon_name] + "\" ></img>";
    else
        return "<img id=\"image_num_" + x + "_" + y + "\" src=\"" + ICON[icon_name] + "\" onclick=\"return onClickCell(" + x + "," + y + ");\" ></img>";
}
View.prototype._drawRestCount = function(drawField){
    var clearPerPlay = 0;
    if(this.playCount > 0)
    clearPerPlay = (this.clearCount / this.playCount) * 100;
    document.getElementById("rest_label").innerHTML = "[Rest:" + drawField.restCount() + "]" + " (Clear:" + this.clearCount + "/Play:" + this.playCount + ") " + clearPerPlay.toFixed([2]) + "%";
}
View.prototype.drawTimeCount = function(){
    document.getElementById("time_label").innerHTML = "[Time:" + this.timeCount + "]";
}
View.prototype.updateTimer = function(drawField){
    if(this.timerId == null)
        this.timerId = setTimeout(onTimeCounter,1000);
    if(drawField.isGameOver())
        clearTimeout(this.timerId);
    this.drawTimeCount();
}
View.prototype.updateClear = function(drawField){
    if(drawField.isClear())
        this.clearCount++;
}

//////////////////////////////////////////////////////////////////////////////
// イベント関数
//////////////////////////////////////////////////////////////////////////////

function onLoad(){
    document.onkeydown = onKeyDown;
    document.onkeyup = onKeyUp;
    FIELD_INTERFACE = Field;
    view = new View();
    onButtonReset();
}

function onButtonReset(){
    view.reset();
    field = new FIELD_INTERFACE(FIELD_W,FIELD_H,FIELD_M);
    view.draw(field);
}

function onClickCell(x,y){
    if(field.isGameOver() == false){
        if(CTRL == true)
            field.setFlag(x,y); // CTRLクリックした場合は旗を設置
        else
            field.openCell(x,y); // 普通にクリックした場合は開く
        view.updateTimer(field);
        view.updateClear(field);
    }
    view.draw(field);
}

function onTimeCounter(){
    view.timeCount++;
    view.drawTimeCount();
    view.timerId = setTimeout(onTimeCounter,1000);
}

function onKeyDown(e){
    if(getKeyCode(e) == '17')   // CTRLキーのDown状態を更新
        CTRL = true;
}
function onKeyUp(e){
    if(getKeyCode(e) == '17')   // CTRLキーのUp状態を更新
        CTRL = false;
}
function getKeyCode(e){
    if(document.all)
        return window.event.keyCode;
    else
        return e.keyCode;
}

クラス構成

クラス 機能 学習ポイント
Position x,yの位置を保持します。 クラス宣言、メンバ変数、引数付きのコンストラクタ
Cell open状態、flag状態、mine有無を保持します。 クラス宣言、メンバ変数、コンストラクタ
Field 複数のCellをテーブルとして管理します。セルを開く処理では、開いた場所の数値が0の場合にすべての隣接セルを開く仕様のため、再帰的な呼び出しを行います。 配列、ループ、分岐、パブリック、プライベート、ランダム値を取得するためのimport
View 渡されたFieldクラスを描画します 文字操作、図形/画像描画、入力操作(クリック、標準入力など)

自動操作

自動で操作する処理を追加で実装します。
ページロード時に自動操作が開始されます。Resetボタンを押すと停止します。

ソース

minesw.html
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>ms</title>
    <script language="javascript" src="./minesw.js" type="text/javascript"></script>
    <script language="javascript" src="./autoplay.js" type="text/javascript"></script>
  </head>
  <body onload="onLoadAutoPlay()" bgcolor="silver" >
    <input type="button" value="Reset" onclick="onButtonReset();">
    <div id="time_label"></div>
    <div id="rest_label"></div>
    <div id="main_div"></div>
    <br/>
    <br/>
  </body>
</html>
autoplay.js
var INTERVAL = 50; // ms
var ACTION_OPEN = 0;
var ACTION_FLAG = 1;

// 自動操作で利用する機能をFieldクラスに拡張する
EnhancedField = function(w,h,mine) {
    Field.call(this,w,h,mine);
}
EnhancedField.prototype = Object.create(Field.prototype);
EnhancedField.prototype.constructor = EnhancedField;

EnhancedField.prototype.unsafeCount = function(){
    var unsafeCount = 0;
    for(var y = 0;y < this.y;y++){
        for(var x = 0;x < this.x;x++){
            if(this.isUnsafe(x,y))
                unsafeCount++;
        }
    }
    return unsafeCount;
}
EnhancedField.prototype.roundFlagCount = function(x,y){
    var flagCount = 0;
    for(var i = 0 ; i < ROUNDS.length ; i++){
        if(this.isFlag(x + ROUNDS[i].x,y + ROUNDS[i].y))
            flagCount++;
    }
    return flagCount;
}
EnhancedField.prototype.roundUnsafeCount = function(x,y){
    var unsafeCount = 0;
    for(var i = 0 ; i < ROUNDS.length ; i++){
        if(this.isUnsafe(x + ROUNDS[i].x,y + ROUNDS[i].y))
            unsafeCount++;
    }
    return unsafeCount;
}
EnhancedField.prototype.roundCloseCount = function(x,y){
    var closeCount = 0;
    for(var i = 0 ; i < ROUNDS.length ; i++){
        if(this.isClose(x + ROUNDS[i].x,y + ROUNDS[i].y))
            closeCount++;
    }
    return closeCount;
}
EnhancedField.prototype.getRoundUnsafeCells = function(x,y){
    var cells = [];
    for(var i = 0;i < ROUNDS.length;i++){
        if(this.isUnsafe(x + ROUNDS[i].x,y + ROUNDS[i].y))
            cells.push(new Position(x + ROUNDS[i].x,y + ROUNDS[i].y));
    }
    return cells;
}

// 自動操作のアクションと位置を管理する
AutoplayPosition = function(position,action) {
    this.action = -1;
    if(position != null){
        Position.call(this,position.x,position.y);
        this.action = action;
    }
}
AutoplayPosition.prototype = Object.create(Position.prototype);
AutoplayPosition.prototype.constructor = AutoplayPosition;

// mine情報をセルの固まりとして保持するクラス
var CellUnit = function(paramPositions,paramCount){
    this.positions = paramPositions; // このリストの座標を全部もっていたらmineCount個のmineが確定している
    this.mineCount = paramCount;
}

// 確率情報を保持するクラス
var Probabilities = function(targetField){
    this._field = targetField;
    this._probabilityList = [];
    for(var i = 0;i < this._field.x * this._field.y;i++){
        this._probabilityList.push(0);
    }
}
Probabilities.prototype.get = function(x,y){
    return this._probabilityList[y*this._field.x + x ]
}
Probabilities.prototype.set = function(x,y,value){
    this._probabilityList[y*this._field.x + x ] = value;
}
Probabilities.prototype.getMin = function(){
    var min = 1;
    for(var i = 0 ; i < this._probabilityList.length;i++ ){
        if( this._probabilityList[i] != 0 ){
            if( min > this._probabilityList[i])
                min = this._probabilityList[i];
        }
    }
    return min;
}
Probabilities.prototype.getMinPositions = function(){
    var positions = [];
    var min = this.getMin();
    for(var y = 0;y < this._field.y;y++){
        for(var x = 0;x < this._field.x;x++){
            if(min == this.get(x,y)){   // 確率が最低のセルのみを対象にする
                positions.push(new Position(x,y));
            }
        }
    }
    return positions;
}
Probabilities.prototype.getAvg = function(){
    return this._field.restCount() / this._field.unsafeCount(); // 全体をランダムで開く場合の確立 = 残りmine / 残りflagなしclose
}

// 隣接情報から次のアクションと座標を返す
function getAutoPlayPositionFromRound(){
    var flagPositions = [];
    var openPositions = [];
    for(var y = 0;y < field.y;y++){
        for(var x = 0;x < field.x;x++){
            if(field.isOpen(x,y) == true){
                if(field.roundCloseCount(x,y) == field.roundCount(x,y)) // 閉じている数 = mine数 → 残りはmine確定
                    flagPositions = flagPositions.concat(field.getRoundUnsafeCells(x,y));
                else if(field.roundFlagCount(x,y) == field.roundCount(x,y)) // flag数 = mine数 → 残りは安全
                    openPositions = openPositions.concat(field.getRoundUnsafeCells(x,y));
            }
        }
    }
    if(flagPositions.length > 0)
        return new AutoplayPosition(_randomArrayItem(flagPositions),ACTION_FLAG);
    if(openPositions.length > 0)
        return new AutoplayPosition(_randomArrayItem(openPositions),ACTION_OPEN);
    return new AutoplayPosition(null,-1);
}

// 全体の情報から次のアクションと座標を返す
function getAutoPlayPositionFromField(){
    var flagPositions = [];
    var openPositions = [];
    var probabilities = new Probabilities(field);

    var cellUnitList = _createCellUniList();
    if(cellUnitList.length > 0){
        for(var y = 0;y < field.y;y++){
            for(var x = 0;x < field.x;x++){
                if(field.isOpen(x,y)){
                    var unsafeCells = field.getRoundUnsafeCells(x,y);
                    if(unsafeCells.length >= 1){
                        var roundCount = field.roundCount(x,y);
                        var flagCount = field.roundFlagCount(x,y);
                        var unsafeList = unsafeCells;
                        var removeCount = 0;
                        var debugFlg = false;
                        for(var m = 0;m < cellUnitList.length;m++){  // TODO _containAndRemoveList()の組み合わせを考慮すると確率上がるかも
                            var tmpList = _containAndRemoveList(unsafeList,cellUnitList[m].positions);
                            if(tmpList.length > 0){
                                unsafeList = tmpList;
                                removeCount += cellUnitList[m].mineCount;
                            }
                        }
                        if(unsafeList.length > 0){
                            // mine数 - flag数 - CellUnitでmineが確定している数 = 閉じている数 → 残りはmine確定
                            if(roundCount - flagCount - removeCount == unsafeList.length)
                                flagPositions = flagPositions.concat(unsafeList);
                                // mine数 - flag数 - CellUnitでmineが確定している数 = 0 → 残りはmine確定
                            if(roundCount - flagCount - removeCount == 0)
                                openPositions = openPositions.concat(unsafeList);
                        }

                        // 確率を保持しておく
                        var probability = (roundCount - flagCount) / unsafeCells.length;
                        for(var i = 0;i < unsafeCells.length;i++){
                            if(probabilities.get(unsafeCells[i].x,unsafeCells[i].y) < probability){
                                probabilities.set(unsafeCells[i].x,unsafeCells[i].y,probability); // 現在値より危険な場合に対象外になるように値を更新
                            }
                        }
                    }
                }
            }
        }
    }

    if(flagPositions.length > 0)    // 旗が確定している座標
        return new AutoplayPosition(_randomArrayItem(flagPositions),ACTION_FLAG);
    if(openPositions.length > 0)    // 安全が確定している座標
        return new AutoplayPosition(_randomArrayItem(openPositions),ACTION_OPEN);

    // 確定している場所がないので、確率の低い場所を返す
    if( probabilities.getMin() < probabilities.getAvg() ){  // 平均値より高い場合は抽出したリストは使用しない
        var positions = probabilities.getMinPositions();
        if(positions.length > 0)
            return new AutoplayPosition(_randomArrayItem(positions),ACTION_OPEN);
    }
    return new AutoplayPosition(null,-1);
}
// 開いていないセルをランダムで選択
function getAutoPlayRandomPosition(){
    var positions = [];
    for(var y = 0;y < field.y;y++){
        for(var x = 0;x < field.x;x++){
            if(field.isUnsafe(x,y))
                positions.push(new Position(x,y));
        }
    }
    return new AutoplayPosition(_randomArrayItem(positions),ACTION_OPEN);
}
// 引数の配列からランダムでアイテムを返す
function _randomArrayItem(arrayItems){
    if(arrayItems.length >= 1)
        return arrayItems[Math.floor(Math.random() * arrayItems.length)];
    return null;
}
// listBaseと衝突するアイテムがlistCheckにある場合削除して返す。衝突しない場合は空の配列
function _containAndRemoveList(listBase,listCheck){
    var result = [];
    result = result.concat(listBase); // コピー配列の用意

    for(var m = 0;m < listCheck.length;m++){
        var hitIndex = -1;
        for(var n = 0;n < result.length;n++){
            if(result[n].x == listCheck[m].x &&
                result[n].y == listCheck[m].y){
                    hitIndex = n;
                    break;
            }
        }
        if(hitIndex != -1){
            result.splice(hitIndex,1);
        }
        else{
            return [];
        }
    }
    return result;
}
// field全体の情報からCellUnitの配列を生成する
function _createCellUniList(){
    var cellUnitList = [];
    for(var y = 0;y < field.y;y++){
        for(var x = 0;x < field.x;x++){
            if(field.isOpen(x,y)){
                var unsafePositions = field.getRoundUnsafeCells(x,y);
                if(unsafePositions.length > 0){
                    var removeCount = field.roundCount(x,y) - field.roundFlagCount(x,y);
                    if(removeCount > 0)
                        cellUnitList.push(new CellUnit(unsafePositions,removeCount))
                }
            }
        }
    }
    return cellUnitList;
}

//////////////////////////////////////////////////////////////////////////////
// イベント関数
//////////////////////////////////////////////////////////////////////////////

var AUTO_PALY_FUNCTIONS = [];   // 自動操作関数を配列に格納。上から順番に処理する。
AUTO_PALY_FUNCTIONS.push(getAutoPlayPositionFromRound);
AUTO_PALY_FUNCTIONS.push(getAutoPlayPositionFromField);
AUTO_PALY_FUNCTIONS.push(getAutoPlayRandomPosition);

function autoPlay(){
    if(view.playCount >= 1000){ // 無限に実行しないように制限を入れておく
        return
    }

    if(field.isGameOver() == false){
        for(var i = 0;i < AUTO_PALY_FUNCTIONS.length;i++){
            var autoPosition = AUTO_PALY_FUNCTIONS[i]();
            if(autoPosition.action == ACTION_OPEN){
                onClickCell(autoPosition.x,autoPosition.y);
                break;
            }else if(autoPosition.action == ACTION_FLAG){
                field.setFlag(autoPosition.x,autoPosition.y);
                break;
            }
        }
    }
    else{
        onButtonReset();
    }
    view.autoPlayId = setTimeout(autoPlay,INTERVAL);
}

function onLoadAutoPlay(){
    document.onkeydown = onKeyDown;
    document.onkeyup = onKeyUp;
    FIELD_INTERFACE = EnhancedField;
    view = new View();
    onButtonReset();
    autoPlay();
}

クラス構成

クラス 機能 学習ポイント
EnhancedField Fieldを継承し自動操作の機能拡張したクラス 継承
AutoplayPosition Positionを継承し座標と自動で行う操作を管理します。 継承
CellUnit 複数のセルを1つの固まりとして管理します。
Probabilities 確率情報を管理します。

処理の説明

モデルはある程度ちゃんと設計したけど、コントローラー系は適当だったので自動操作はグローバル定義した変数にアクセスする関数で実装しています。

  1. 隣接したセルの情報から確定しているセル、安全なセルを探し出す
    1-1. 表示されている数字と残りのセルが一致するから残りはmine確定という判定する処理
    1-2. 表示されている数字と旗の数が一致するから残りは安全という判定する処理

  2. 与えられている情報から確定しているセル、安全なセル、確率の低いセルを探す
    2-1. 未確定2つの中に1つmineがあるので、他のセルは安全mine確定という判定する処理
    2-2. 未確定の2つの中に1つmineがあるので、他のセルは安全という判定する処理
    2-3. セルごとの確率を計算して、確率の低いセルの中からランダムで選択する処理

  3. ランダムで開く

の3つの機能を別な関数として実装しています。1~3の順で実行して取得できた座標に対して操作を行います。この実装だとクリア率10%くらいです。マインスイーパーが得意なQiita民はさらに高いクリア率を目指すのもいいと思います。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1