LoginSignup
1
1

More than 1 year has passed since last update.

LightsOutゲームのプログラミング

Last updated at Posted at 2022-04-30

LightsOutという数学パズルをHTML + JavaScriptでコーディングしました。
遊べるし、解法も表示されます。

ゲームのルールの説明です。

Lights Outというのは、いくつかのボタンがあり、
その中のいくつかのボタンがオンになっている状態からボタンを適当に押すことによって、
全てのボタンをオフになるようにするパズルです。
【ルール】

  1. ボタンを押すと、そのボタン、およびそのボタンの上下左右にあるボタンのオン、オフが切り替わります。
  2. 隅の方のボタンで、上下左右にボタンが無い場合は、その部分は無視されます。

Ex1.5-1.gif

なお、今回は作るプログラムでは、
盤面の大きさは任意のn × nの大きさとします。
また、初期状態は全てのボタンがオンになっているものとします。

以下、コードです。

index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-language" content="ja">
<meta charset="UTF-8">
</head>

<body>
<p>
【Lights Out】<br>
いくつかのボタンがあり、その中のいくつかのボタンがオン(オレンジ色)になっている状態から、<br>
ボタンを適当に押すことによって、全てのボタンをオフ(黒色)になるようにするパズルです。<br>
【ルール】<br>
   1. ボタンを押すと、そのボタン,およびそのボタンの上下左右にあるボタンのオン、オフが切り替わります。<br>
   2. 隅の方のボタンで、上下左右にボタンが無い場合は、その部分は無視してください。<br>
</p>
<form name="myform" action="javascript:void(0)">
	<p>盤面のサイズを決めてください。(5x5だったら5と入力)<br>
	   黄色のマス目の盤面が表示されます。<br>
	   マス目をクリックすればオンオフが切り替わります。
	<input name="mytext" type="text">
	<button id="btn">送信</button>
	</p>
</form>

<form name="answer" action="javascript:void(0)">
	<p>【解法】<br>(盤面のサイズを指定した後に「解答表示」ボタンを押してください)<br>
	   (緑色のマス目の場所を順不同で押していけば全部のマス目が黒くなります。解答は複数パターンある場合もあります。)<br><br>
	<button id="answer_btn">解答表示</button>
	</p>
</form>

<div id="lightsout"></div>
<script type="text/javascript" src="lightsout.js"></script>
</body>
</html>
lightsout.js
var board_size = 0;
var board_flag = false;
// 盤面の初期化
function initBoard(size) {
  var element = document.getElementById("lightsout");
  while (element.firstChild) {
    element.removeChild(element.firstChild);
  }
  var df = document.createDocumentFragment();
  var base_style = "border: 1px solid black; float: left; height: 60px; width: 60px; background: orange; style: pointer";
  for (var y = 1; y <= size; y++) {
    for (var x = 1; x <= size; x++) {
      var cell = document.createElement("div");
      if (10 <= y && 10 <= x) {
        cell.id = "p" + y + x;
      } else if (10 > y && 10 > x) {
        cell.id = "p" + "0" + y + "0" + x;
      } else if (10 <= y) {
        cell.id = "p" + y + "0" + x;
      } else if (10 <= x) {
        cell.id = "p" + "0" + y + x;
      } else {
        console.log("initBoard_error!!!");
      }
      cell.setAttribute("style", base_style);
      cell.addEventListener("click", clickEvent, false);
      df.appendChild(cell);
      // sizeピース毎に改行する
      if ((x % size) === 1) {
        cell.style.clear = "both";
      }
    }
  }
  element.appendChild(df);
}
// [y, x] に相当する DOM の element を得る。
function getElement(y, x) {
  var ret_val;
  if (10 <= y && 10 <= x) {
    ret_val = "p" + y + x;
  } else if (10 > y && 10 > x) {
    ret_val = "p" + "0" + y + "0" + x;
  } else if (10 <= y) {
    ret_val = "p" + y + "0" + x;
  } else if (10 <= x) {
    ret_val = "p" + "0" + y + x;
  } else {
    console.log("error!!!");
  }
  return document.getElementById(ret_val);
}
// マウスクリック時の処理。
function clickEvent() {
  var element = document.getElementById(this.id);
  var y = parseInt(element.id.substr(-4, 2));
  var x = parseInt(element.id.substr(-2, 2));
  changeBackground(y, x);
  if (y - 1 >= 1) changeBackground(y - 1, x);
  if (y + 1 <= board_size) changeBackground(y + 1, x);
  if (x - 1 >= 1) changeBackground(y, x - 1);
  if (x + 1 <= board_size) changeBackground(y, x + 1);
}
// 指定のマス目の色を変える。
function changeBackground(y, x) {
  var f = getElement(y, x);
  var col = f.style.backgroundColor;
  if (col == "black") {
    f.style.backgroundColor = "orange";
  } else if (col == "orange") {
    f.style.backgroundColor = "black";
  } else {
    console.log("color_error!!!");
  }
}
onload = function () {
  var btn = document.getElementById('btn');
  var answer_btn = document.getElementById('answer_btn');
  btn.addEventListener('click', function () {
    var suuti_data = document.myform.mytext.value;
    if (suuti_data.match(/^[0-9]+$/) && 1 <= suuti_data && suuti_data <= 15) {
      board_size = document.myform.mytext.value;
      board_flag = true;
    } else {
      alert("正の整数(1~15)を入力してください");
    }
  })
  answer_btn.addEventListener('click', function () {
    correctAnswer();
  })
};
////////
(function () {
  // targetを監視してtrueになったら関数を実行
  (function observe() {
    if (board_flag == true) {
      // 関数実行
      execute();
      board_flag = false;
    } else {}
    setTimeout(observe, 1);
  })();
  // trueになったら実行する処理
  function execute() {
    initBoard(board_size);
  }
})();

function correctAnswer() {
  var element = document.getElementById("lightsout");
  while (element.firstChild) {
    element.removeChild(element.firstChild);
  }
  var top_df = document.createDocumentFragment();
  var i;
  var j;
  var pattern = 1;
  var board = [];
  var play_board = [];
  var tmp;
  var x;
  var y;
  for (i = 0; i < board_size; i++) {
    board[i] = [];
    play_board[i] = [];
    for (j = 0; j < board_size; j++) {
      board[i][j] = 0;
      play_board[i][j] = 0;
    }
  }
  /* 最初の1段目の全パターンを求める */
  for (i = 0; i < board_size; i++) {
    pattern *= 2;
  }
  /* 最初の1段目の全パターン数だけ繰り返し */
  for (i = 0; i < pattern; i++) {
    /* 0で初期化 */
    for (j = 0; j < board_size; j++) {
      board[0][j] = 0;
    }
    /* 2進数の処理 */
    tmp = i; /* 今何回目かを保存 */
    j = board_size - 1; /* カウンタの初期化 */
    while (tmp != 0) { /* 0になるまで繰り返し */
      board[0][j] = tmp % 2; /* 2で割った余りを代入 */
      tmp = Math.floor(tmp / 2) /* 次の桁へ */
      j--; /* デクリメント */
    }
    /* パターンを求める */
    for (y = 0; y < board_size - 1; y++) {
      for (x = 0; x < board_size; x++) {
        tmp = board[y][x]; /* 初期化 */
        if (x != 0) tmp += board[y][x - 1];
        if (x != board_size - 1) tmp += board[y][x + 1];
        if (y != 0) tmp += board[y - 1][x];
        if (tmp % 2 == 0) {
          board[y + 1][x] = 1;
        } else {
          board[y + 1][x] = 0;
        }
      }
    }
    /* 確認用の盤面の初期化 */
    for (var ii = 0; ii < board_size; ii++) {
      for (var jj = 0; jj < board_size; jj++) {
        play_board[ii][jj] = 0;
      }
    }
    /* 解を求める  */
    for (y = 0; y < board_size; y++) {
      for (x = 0; x < board_size; x++) {
        /* 押してたら */
        if (board[y][x] == 1) {
          change_board(play_board, board_size, x, y); /* ボタンを押す */
        }
      }
    }
    /* 盤面が全て1だったら */
    if (check_board(play_board, board_size) == 1) {
      /* 盤面を子ノードに挿入 */
      top_df.appendChild(nodeCreate_func(board));
    }
  }
  element.appendChild(top_df);
}
/***
 *** (i,j)を押した時の盤面を変更する
 ***/
function change_board(board, size, xx, yy) {
  /* 盤面の変更 */
  if (xx != 0) {
    if (board[yy][xx - 1] == 0) {
      board[yy][xx - 1] = 1;
    } else if (board[yy][xx - 1] == 1) {
      board[yy][xx - 1] = 0;
    } else {
      console.log("cahnge_board_error!!!");
    }
  }
  if (xx != size - 1) {
    if (board[yy][xx + 1] == 0) {
      board[yy][xx + 1] = 1;
    } else if (board[yy][xx + 1] == 1) {
      board[yy][xx + 1] = 0;
    } else {
      console.log("cahnge_board_error!!!");
    }
  }
  if (yy != 0) {
    if (board[yy - 1][xx] == 0) {
      board[yy - 1][xx] = 1;
    } else if (board[yy - 1][xx] == 1) {
      board[yy - 1][xx] = 0;
    } else {
      console.log("cahnge_board_error!!!");
    }
  }
  if (yy != size - 1) {
    if (board[yy + 1][xx] == 0) {
      board[yy + 1][xx] = 1;
    } else if (board[yy + 1][xx] == 1) {
      board[yy + 1][xx] = 0;
    } else {
      console.log("cahnge_board_error!!!");
    }
  }
  if (board[yy][xx] == 0) {
    board[yy][xx] = 1;
  } else if (board[yy][xx] == 1) {
    board[yy][xx] = 0;
  } else {
    console.log("cahnge_board_error!!!");
  }
}
/***
 *** 盤面が全て1かをチェックする
 ***/
function check_board(board, size) {
  var count = 0; /* 全部1か確認する用のカウンタ */
  var flag = 0; /* 全部1か確認する用のフラグ */
  var x, y; /* 座標用変数 */
  /* 全部1か確認 */
  for (y = 0; y < size; y++) {
    for (x = 0; x < size; x++) {
      if (board[y][x] == 1) count++; /* 全部1だったらカウント */
    }
  }
  if (count == size * size) flag = 1; /* 全部1だったら終了 */
  return flag;
}
// 盤面の初期化
function nodeCreate_func(board) {
  var df = document.createDocumentFragment();
  var base_style_orange = "border: 1px solid black; float: left; height: 60px; width: 60px; background: orange; style: pointer";
  var base_style_green = "border: 1px solid black; float: left; height: 60px; width: 60px; background: green; style: pointer";
  var base_style_white = "float: left; height: 60px; width: 60px; background: white; style: pointer";
  for (var y = 1; y <= board_size; y++) {
    for (var x = 1; x <= board_size; x++) {
      var cell = document.createElement("div");
      if (10 <= y && 10 <= x) {
        cell.id = "p" + y + x;
      } else if (10 > y && 10 > x) {
        cell.id = "p" + "0" + y + "0" + x;
      } else if (10 <= y) {
        cell.id = "p" + y + "0" + x;
      } else if (10 <= x) {
        cell.id = "p" + "0" + y + x;
      } else {
        console.log("nodeCreate_func_error!!!");
      }
      if (board[y - 1][x - 1] == 0) {
        cell.setAttribute("style", base_style_orange);
      } else if (board[y - 1][x - 1] == 1) {
        cell.setAttribute("style", base_style_green);
      } else {
        console.log("nodeCreate_func_orange_green_error!!!");
      }
      df.appendChild(cell);
      // board_sizeピース毎に改行する
      if ((x % board_size) === 1) {
        cell.style.clear = "both";
      }
    }
    if (y == board_size) {
      for (var x = 1; x <= board_size; x++) {
        var cell = document.createElement("div");
        if (10 <= y) {
          if (10 <= x) {
            cell.id = "p" + (board_size + 1) + x;
          } else if (10 > x) {
            cell.id = "p" + (board_size + 1) + "0" + x;
          } else {
            console.log("board_size_x_error!!!");
          }
        } else if (10 > y) {
          if (10 <= x) {
            cell.id = "p" + "0" + (board_size + 1) + x;
          } else if (10 > x) {
            cell.id = "p" + "0" + (board_size + 1) + "0" + x;
          } else {
            console.log("board_size_x_error!!!");
          }
        } else {
          console.log("board_size_y_error!!!");
        }
        cell.setAttribute("style", base_style_white);
        df.appendChild(cell);
        // board_sizeピース毎に改行する
        if ((x % board_size) === 1) {
          cell.style.clear = "both";
        }
      }
    }
  }
  return df;
}

以下、コードの解説をしていきます。

まずはHTMLファイルのindex.htmlの一部の説明

<form name="myform" action="javascript:void(0)">
	<p>盤面のサイズを決めてください。(5x5だったら5と入力)<br>
	   黄色のマス目の盤面が表示されます。<br>
	   マス目をクリックすればオンオフが切り替わります。
	<input name="mytext" type="text">
	<button id="btn">送信</button>
	</p>
</form>

formタグからaction="javascript:void(0)"で画面遷移を起こさずに、javascriptを実行
inputタグはname="mytext"
buttonタグはid="btn"を指定

<form name="answer" action="javascript:void(0)">
	<p>【解法】<br>(盤面のサイズを指定した後に「解答表示」ボタンを押してください)<br>
	   (緑色のマス目の場所を順不同で押していけば全部のマス目が黒くなります。解答は複数パターンある場合もあります。)<br><br>
	<button id="answer_btn">解答表示</button>
	</p>
</form>

formタグからaction="javascript:void(0)"で画面遷移を起こさずに、javascriptを実行
buttonタグはid="answer_btn"を指定

<div id="lightsout"></div>

divタグでid='lightsout'を設定します。
このタグに、DOMツリーを作っていって、LightsOutのマス目を表示していきます。

次はjavascriptコード、lightsout.jsのコードの説明

onload = function () {
  var btn = document.getElementById('btn');
  var answer_btn = document.getElementById('answer_btn');
  btn.addEventListener('click', function () {
    var suuti_data = document.myform.mytext.value;
    if (suuti_data.match(/^[0-9]+$/) && 1 <= suuti_data && suuti_data <= 15) {
      board_size = document.myform.mytext.value;
      board_flag = true;
    } else {
      alert("正の整数(1~15)を入力してください");
    }
  })
  answer_btn.addEventListener('click', function () {
    correctAnswer();
  })
};

onloadでページロード時の処理の関数。
DOMのid 'btn'要素を取得してbtn変数に保持します。
DOMのid 'answer_btn'要素を取得してanswer_btn変数に保持します。
btn変数のクリック時の動作の記述をします。
index.htmlのformからmytextの値を取得して、suuti_data変数に保持します。
suuti_data変数の中身が数字であり、かつ、1以上15以下の場合、if文を通ります。
if文を通った場合、mytextの値をborad_size変数に保持して、このサイズをLightsOutパズルの縦横のサイズにします。
3なら3 x 3 のマス目の正方形のLightsOutです。
board_flagフラグ変数をtrueにして、ボード表示用の関数を呼び出す為の、フラグを立てます。
if文を通らない場合は、alertで1~15を入力するように促します。

answer_btn変数のクリック時には、correctAnswer()関数を呼び出します。

(function () {
  // targetを監視してtrueになったら関数を実行
  (function observe() {
    if (board_flag == true) {
      // 関数実行
      execute();
      board_flag = false;
    } else {}
    setTimeout(observe, 1);
  })();
  // trueになったら実行する処理
  function execute() {
    initBoard(board_size);
  }
})();

クロージャを使います。
setTimeout()関数を使う事で、observe関数を常に監視します。
先ほどの、board_flag変数がtrueの時、execute()関数を呼び出して、またフラグをfalseに戻します。
それでまた、監視に戻ります。
execute()関数は、initBoard()関数を引数にboard_size変数を指定して呼び出します。
盤面のサイズを指定する事で盤面の初期化表示処理に移ります。

var board_size = 0;
var board_flag = false;

グローバル変数でboard_size変数は0、board_flag変数はfalseに初期化しておきます。

function initBoard(size) {
  var element = document.getElementById("lightsout");
  while (element.firstChild) {
    element.removeChild(element.firstChild);
  }
  var df = document.createDocumentFragment();
  var base_style = "border: 1px solid black; float: left; height: 60px; width: 60px; background: orange; style: pointer";
  for (var y = 1; y <= size; y++) {
    for (var x = 1; x <= size; x++) {
      var cell = document.createElement("div");
      if (10 <= y && 10 <= x) {
        cell.id = "p" + y + x;
      } else if (10 > y && 10 > x) {
        cell.id = "p" + "0" + y + "0" + x;
      } else if (10 <= y) {
        cell.id = "p" + y + "0" + x;
      } else if (10 <= x) {
        cell.id = "p" + "0" + y + x;
      } else {
        console.log("initBoard_error!!!");
      }
      cell.setAttribute("style", base_style);
      cell.addEventListener("click", clickEvent, false);
      df.appendChild(cell);
      // sizeピース毎に改行する
      if ((x % size) === 1) {
        cell.style.clear = "both";
      }
    }
  }
  element.appendChild(df);
}

initBoard()関数の処理の記述です。
まず、DOMのid 'lightsout'の要素を取得してelement変数に保持します。
while文を使ってelement以下のDOMを全て削除します。
df変数にDocumentFragmentを用意します。
base_style変数にcssのスタイルを記述します。
1マスのスタイルの指定です。
外枠は1pxの黒線で、幅と縦60px、背景色オレンジを指定します。
これがマス目一つのスタイルです。
二重のfor文で二次元を網羅探索します。
div要素を生成してcell変数に保持します。
if文でyかxが1桁の時は0埋めするように、cell変数のidを指定します。
例えば、x = 9, y = 10の時は'p1009'がidになります。
それぞれの要素にbase_style変数のスタイルを指定して、クリック時にclickEvent()関数を呼び出す処理を指定して、その要素をdfの子ノードとして格納していきます。
sizeピース毎に改行のスタイルを代入していきます。
全ての処理が終わったら、df変数をelement変数の子ノードとして保持します。
これで盤面の初期化は完了です。

// マウスクリック時の処理。
function clickEvent() {
  var element = document.getElementById(this.id);
  var y = parseInt(element.id.substr(-4, 2));
  var x = parseInt(element.id.substr(-2, 2));
  changeBackground(y, x);
  if (y - 1 >= 1) changeBackground(y - 1, x);
  if (y + 1 <= board_size) changeBackground(y + 1, x);
  if (x - 1 >= 1) changeBackground(y, x - 1);
  if (x + 1 <= board_size) changeBackground(y, x + 1);
}

clickEvent()関数の処理の記述をします。
先ほど、一つのマス目にクリック時に呼び出す関数として、指定した関数です。
まずthis.idで、この要素のidのDOMを取得してelement変数に保持します。
idはあらかじめ決められているので、
substr変数でidの文字列を区切って、整数にparseIntで戻して、それぞれyとxの変数にx座標とy座標の数値を保持します。
changeBackground変数はマス目の色を反転させます。
指定したマス目と縦横の十字のマス目の色を反転させます。
範囲外の時は、処理をしません。

// 指定のマス目の色を変える。
function changeBackground(y, x) {
  var f = getElement(y, x);
  var col = f.style.backgroundColor;
  if (col == "black") {
    f.style.backgroundColor = "orange";
  } else if (col == "orange") {
    f.style.backgroundColor = "black";
  } else {
    console.log("color_error!!!");
  }
}

changeBackground()関数です。
yとxの引数のxy座標で指定されたマス目の色を反転させます。
getElement()関数にyとxの座標を指定して、DOMのノードを取得します。
getElement()関数は後述します。
f.style.backgroundColorでマス目の色を取得して、col変数に保持します。
if文でcol変数がblackだった時と、orangeだった時に色を反転させます。
これで、黒とオレンジの色を反転出来ます。

// [y, x] に相当する DOM の element を得る。
function getElement(y, x) {
  var ret_val;
  if (10 <= y && 10 <= x) {
    ret_val = "p" + y + x;
  } else if (10 > y && 10 > x) {
    ret_val = "p" + "0" + y + "0" + x;
  } else if (10 <= y) {
    ret_val = "p" + y + "0" + x;
  } else if (10 <= x) {
    ret_val = "p" + "0" + y + x;
  } else {
    console.log("error!!!");
  }
  return document.getElementById(ret_val);
}

getElement()関数です。
yとxの引数でxy座標を指定して、0埋めのidの文字列をret_val変数に保持します。
そのidはあらかじめ、マス目を作ってDOMにする時に、決められているので、document.getElementById()で、idのDOMを取得して返します。

function correctAnswer() {
  var element = document.getElementById("lightsout");
  while (element.firstChild) {
    element.removeChild(element.firstChild);
  }
  var top_df = document.createDocumentFragment();
  var i;
  var j;
  var pattern = 1;
  var board = [];
  var play_board = [];
  var tmp;
  var x;
  var y;
  for (i = 0; i < board_size; i++) {
    board[i] = [];
    play_board[i] = [];
    for (j = 0; j < board_size; j++) {
      board[i][j] = 0;
      play_board[i][j] = 0;
    }
  }
  /* 最初の1段目の全パターンを求める */
  for (i = 0; i < board_size; i++) {
    pattern *= 2;
  }
  /* 最初の1段目の全パターン数だけ繰り返し */
  for (i = 0; i < pattern; i++) {
    /* 0で初期化 */
    for (j = 0; j < board_size; j++) {
      board[0][j] = 0;
    }
    /* 2進数の処理 */
    tmp = i; /* 今何回目かを保存 */
    j = board_size - 1; /* カウンタの初期化 */
    while (tmp != 0) { /* 0になるまで繰り返し */
      board[0][j] = tmp % 2; /* 2で割った余りを代入 */
      tmp = Math.floor(tmp / 2) /* 次の桁へ */
      j--; /* デクリメント */
    }
    /* パターンを求める */
    for (y = 0; y < board_size - 1; y++) {
      for (x = 0; x < board_size; x++) {
        tmp = board[y][x]; /* 初期化 */
        if (x != 0) tmp += board[y][x - 1];
        if (x != board_size - 1) tmp += board[y][x + 1];
        if (y != 0) tmp += board[y - 1][x];
        if (tmp % 2 == 0) {
          board[y + 1][x] = 1;
        } else {
          board[y + 1][x] = 0;
        }
      }
    }
    /* 確認用の盤面の初期化 */
    for (var ii = 0; ii < board_size; ii++) {
      for (var jj = 0; jj < board_size; jj++) {
        play_board[ii][jj] = 0;
      }
    }
    /* 解を求める  */
    for (y = 0; y < board_size; y++) {
      for (x = 0; x < board_size; x++) {
        /* 押してたら */
        if (board[y][x] == 1) {
          change_board(play_board, board_size, x, y); /* ボタンを押す */
        }
      }
    }
    /* 盤面が全て1だったら */
    if (check_board(play_board, board_size) == 1) {
      /* 盤面を子ノードに挿入 */
      top_df.appendChild(nodeCreate_func(board));
    }
  }
  element.appendChild(top_df);
}

次はcorrectAnswer()関数の処理の記述です。
盤面に対して、解答ボタンを押して、解答を表示する時の関数です。
まず、id 'lightsout'の子以下の要素のノードを全て削除します。
変数宣言をします。
top_df変数にDocumentFragmentを作成しておきます。

盤面のサイズの2次元配列をboard変数とplay_board変数に両方0で初期化して用意しておきます。

LightOutというゲームは自分のマス目と周囲の十字のマス目を、押してあるマス目を数えて、それが奇数なら、そのマス目は押された事になり、偶数なら、押されて無い事になります。
なので、一番上の段のパターンが決まれば、自動的に、段々と一段ずつ、下に下がっていくように、自分と周囲の十字のマス目が押された数が奇数ならば、下の段のマス目はどうなるかが必然的に決定されていきます。

なので、一番上の段の全パターンを求めればいいのです。
黒とオレンジの2色しか無いので、マス目を桁と捉えた2進数で表せます。

最初のfor文でpattern変数に2進数の表示出来る数を、盤面の幅のサイズだけ、指定して保持します。

探索を行います。
一番上の段の2進数の全パターンだけfor文で繰り返します。
まずboard変数の2次元配列の一番上の段を全部0で初期化します。

カウンタiは2進数で表示可能な数値を10進数でカウントしてるので、tmp変数を使い、2進数に戻して、今の数値をboard変数の一番上の段に、0と1の値を入れて、設定します。

次に2重のfor文で、最後の1段の1つ手前まで、探索します。
今、探索してるy,x座標のboard変数の2次元配列の要素をtmp変数に保持します。
押されてるなら1、押されてないなら0です。
周囲の十字のマス目も範囲外の場合は想定しない事を鑑みて、マス目の要素(1か0)を足し算していきます。
その値、tmp変数が、2で割り切れるなら(偶数)、一つ下の段は押されている事になっていないといけないので、1に設定します。
2で割り切れないなら(奇数)、押されてはいけないので、0に設定します。

すると、一段ずつ必然的に数値が決まっていくので、最後の段の一つ手前まで、探索してboard変数の全要素を0か1で埋めます。

次のfor文で確認用の二次元配列play_board変数を0で初期化します。

二重のfor文で二次元配列の網羅探索を行い、board変数の二次元配列の要素が押されているなら、change_board()関数を呼び出して、board変数で指定してあるように、ボタンを押す作業をしていきます。
board変数に書いてあるようにplay_board変数のボタンを押す作業を全て終えたら、check_board()関数で、play_board変数の全要素が全部1になってるか確認してOKなら、それが答えなので、そのboard変数の要素の組み合わせが答えの一例になります。

なので、nodeCreate_func()関数の引数にboard変数を渡して、答えの盤面のノードを生成して、top_df変数の子ノードに加えていきます。

最後にelementにtop_dfを代入する事で、解答一覧の盤面リストのDOMが生成されます。

/***
 *** (i,j)を押した時の盤面を変更する
 ***/
function change_board(board, size, xx, yy) {
  /* 盤面の変更 */
  if (xx != 0) {
    if (board[yy][xx - 1] == 0) {
      board[yy][xx - 1] = 1;
    } else if (board[yy][xx - 1] == 1) {
      board[yy][xx - 1] = 0;
    } else {
      console.log("cahnge_board_error!!!");
    }
  }
  if (xx != size - 1) {
    if (board[yy][xx + 1] == 0) {
      board[yy][xx + 1] = 1;
    } else if (board[yy][xx + 1] == 1) {
      board[yy][xx + 1] = 0;
    } else {
      console.log("cahnge_board_error!!!");
    }
  }
  if (yy != 0) {
    if (board[yy - 1][xx] == 0) {
      board[yy - 1][xx] = 1;
    } else if (board[yy - 1][xx] == 1) {
      board[yy - 1][xx] = 0;
    } else {
      console.log("cahnge_board_error!!!");
    }
  }
  if (yy != size - 1) {
    if (board[yy + 1][xx] == 0) {
      board[yy + 1][xx] = 1;
    } else if (board[yy + 1][xx] == 1) {
      board[yy + 1][xx] = 0;
    } else {
      console.log("cahnge_board_error!!!");
    }
  }
  if (board[yy][xx] == 0) {
    board[yy][xx] = 1;
  } else if (board[yy][xx] == 1) {
    board[yy][xx] = 0;
  } else {
    console.log("cahnge_board_error!!!");
  }
}

change_board()関数の処理の記述を行います。

範囲外に気を付けながら、自分のマス目と周囲の十字のマス目の要素0と1の、反転作業を行います。
board変数に記録していきます。

/***
 *** 盤面が全て1かをチェックする
 ***/
function check_board(board, size) {
  var count = 0; /* 全部1か確認する用のカウンタ */
  var flag = 0; /* 全部1か確認する用のフラグ */
  var x, y; /* 座標用変数 */
  /* 全部1か確認 */
  for (y = 0; y < size; y++) {
    for (x = 0; x < size; x++) {
      if (board[y][x] == 1) count++; /* 全部1だったらカウント */
    }
  }
  if (count == size * size) flag = 1; /* 全部1だったら終了 */
  return flag;
}

check_board()関数の処理を記述します。
board変数の全要素の探索を行い、押されてたら(1だったら)、count変数を数えていきます。
最後に、count変数が要素数であれば、flagを1にして、満たない場合は0を返します。
満たない場合は、全ての要素が1で無いという事です。
押されていないという事です。

// 盤面の初期化
function nodeCreate_func(board) {
  var df = document.createDocumentFragment();
  var base_style_orange = "border: 1px solid black; float: left; height: 60px; width: 60px; background: orange; style: pointer";
  var base_style_green = "border: 1px solid black; float: left; height: 60px; width: 60px; background: green; style: pointer";
  var base_style_white = "float: left; height: 60px; width: 60px; background: white; style: pointer";
  for (var y = 1; y <= board_size; y++) {
    for (var x = 1; x <= board_size; x++) {
      var cell = document.createElement("div");
      if (10 <= y && 10 <= x) {
        cell.id = "p" + y + x;
      } else if (10 > y && 10 > x) {
        cell.id = "p" + "0" + y + "0" + x;
      } else if (10 <= y) {
        cell.id = "p" + y + "0" + x;
      } else if (10 <= x) {
        cell.id = "p" + "0" + y + x;
      } else {
        console.log("nodeCreate_func_error!!!");
      }
      if (board[y - 1][x - 1] == 0) {
        cell.setAttribute("style", base_style_orange);
      } else if (board[y - 1][x - 1] == 1) {
        cell.setAttribute("style", base_style_green);
      } else {
        console.log("nodeCreate_func_orange_green_error!!!");
      }
      df.appendChild(cell);
      // board_sizeピース毎に改行する
      if ((x % board_size) === 1) {
        cell.style.clear = "both";
      }
    }
    if (y == board_size) {
      for (var x = 1; x <= board_size; x++) {
        var cell = document.createElement("div");
        if (10 <= y) {
          if (10 <= x) {
            cell.id = "p" + (board_size + 1) + x;
          } else if (10 > x) {
            cell.id = "p" + (board_size + 1) + "0" + x;
          } else {
            console.log("board_size_x_error!!!");
          }
        } else if (10 > y) {
          if (10 <= x) {
            cell.id = "p" + "0" + (board_size + 1) + x;
          } else if (10 > x) {
            cell.id = "p" + "0" + (board_size + 1) + "0" + x;
          } else {
            console.log("board_size_x_error!!!");
          }
        } else {
          console.log("board_size_y_error!!!");
        }
        cell.setAttribute("style", base_style_white);
        df.appendChild(cell);
        // board_sizeピース毎に改行する
        if ((x % board_size) === 1) {
          cell.style.clear = "both";
        }
      }
    }
  }
  return df;
}

nodeCreate_func()関数の処理の記述をします。
引数board変数の二次元配列の0と1の要素の組み合わせの盤面のノードを生成して返します。
df変数にDocumentFragmentを生成します。

スタイルで、マス目がオレンジと緑のスタイルと、空白用のスタイルの変数を、それぞれ、base_style_orange、base_style_green、base_style_whileに格納します。
二重のfor文で二次元配列の全要素を探索します。
div要素を生成してcell変数に代入して、y = 9, x = 10の場合だったら、'p0910'となる文字列のidを生成してcell変数のidに設定します。

board変数の二次元配列でy,x座標の要素を確認して1だったら、greenのスタイル、0だったら、orangeのスタイルを設定して、押す所はgreen、押さない所はorangeのスタイルにする事で、解答の盤面を表します。

一つ出来たら、dfの子ノードとして格納します。
board_sizeピース毎に改行します。

最後の段まで終わったら、空白の段用のセルを生成して、子ノードに追加します。
これで次の解答の盤面との間に空行のスペースを入れられます。

以上で、LightsOutゲームのプログラミングのコードの説明を終えます。

1
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
1
1