質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.23%

javascript:スライドパズルゲームのリセット時にパズルがシャッフルされない

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 604

k373

score 17

■解決したいこと
16マスのスライドパズルゲームを勉強で作っています。
body onloadで、パズルがランダムにシャッフルされるようにinit()関数内に書いています。
読み込み時、問題なくシャッフルされます。

しかし、設置したresetボタンに同じinit関数をonclickで呼び出せるようにし、呼び出すとなぜかパズルがシャッフルされません。
綺麗に1から15までの数字が順番に並んでしまいます。
リセット時もシャッフル機能を問題なく動作させたいです。

<!DOCTYPE html>
<html lang="ja" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>16 puzzles</title>
    <style>
      .tile{
        width:100px;
        height:100px;
        border:1px solid #ccc;
        border-radius:10px;
        text-align:center;
        font-size:36px;
        background-color:white;
        box-shadow:rgb(128, 128, 128) 5px 5px;
      }
      .over{
        pointer-events: none;
        background-color: #ff0000;
        color:#fff;
      }
    </style>
  </head>
  <body onload="init()">
    <div id="score">0</div>
    <div id="timer"></div>
    <table id="table"></table>
    <div id="reset" onclick="init()">RESET</div>
    <script>
      "use strict"

      var tiles = [];

      var timer = document.getElementById("timer");
      var score = document.getElementById("score");
      var reset = document.getElementById("reset");
      var table = document.getElementById("table");
      var timeoutId;
      var time = 180;
      var min = 0;
      var sec = 0;
      var clickcount = 0;


      function init(){
        table.innerHTML = "";

        timer.textContent = "03:00"

        for(var i = 0; i < 4; i ++){
          var tr = document.createElement("tr");
          table.appendChild(tr);
            for(var j = 0; j < 4; j ++){
              var td = document.createElement("td");
              var index = i * 4 + j;
              td.className = "tile";
              td.value = index;
              td.index = index;
              td.textContent = index == 0 ? "" : index; //indexが0なら空でその他はその数字を代入
              tiles.push(td);
              tr.appendChild(td);
              td.onclick = click;
            }
        }
        for(var i = 0; i < 1000; i++){
          clickrandom({srcElement: {index: Math.floor(Math.random() * 16)}}) //e.srcElement.indexのように、srcElementプロパティのindexプロパティ
        }
      }


      function countdown(){
        if(time > 0){
            var min = Math.floor(time/60);
            var sec = time % 60;
            time--;
            console.log(time);
            timeoutId = setTimeout(countdown, 1000);
            timer.innerHTML = `${String(min).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
            finish();
        }else{
          timer.innerHTML = "TIME UP!";
          scoredata();
          clearTimeout(timeoutId);
          var tileElem = document.getElementsByClassName("tile")
          Array.prototype.forEach.call(tileElem, function(element){
            element.classList.add("over");
          });
        }
      }

      var first = true;//startTimerの外でtrueにしないとダメ。中だと都度呼び出されてしまう。
      function startTimer(){
        if(first){
          countdown();
        }
          first = false;
        }

      function clickrandom(e){
        var i = e.srcElement.index;

        if(i - 4 >= 0 && tiles[i - 4].value == 0){ //一番上の行ではなく、上にあるマスの値が0の時
          swap(i, i - 4);
        }else if(i + 4 < 16 && tiles[i + 4].value == 0){ //一番下の行ではなく、下にあるマスの値が0の時
          swap(i, i + 4);
        }else if(i % 4 != 0 &&  tiles[i - 1].value == 0){ //一番下の行ではなく、下にあるマスの値が0の時
          swap(i, i - 1);
        }else if(i % 4 != 3 &&  tiles[i + 1].value == 0){ //一番下の行ではなく、下にあるマスの値が0の時
          swap(i, i + 1);
        }

      }

      function click(e){
        var i = e.srcElement.index;

        if(i - 4 >= 0 && tiles[i - 4].value == 0){ //一番上の行ではなく、上にあるマスの値が0の時
          swap(i, i - 4);
          startTimer();
          clickcount++;
        }else if(i + 4 < 16 && tiles[i + 4].value == 0){ //一番下の行ではなく、下にあるマスの値が0の時
          swap(i, i + 4);
          startTimer();
          clickcount++;
        }else if(i % 4 != 0 &&  tiles[i - 1].value == 0){ //一番下の行ではなく、下にあるマスの値が0の時
          swap(i, i - 1);
          startTimer();
          clickcount++;
        }else if(i % 4 != 3 &&  tiles[i + 1].value == 0){ //一番下の行ではなく、下にあるマスの値が0の時
          swap(i, i + 1);
          startTimer();
          clickcount++;
        }
      }

      function swap(i,j){
        var tmp = tiles[i].value; //先にiの値をtmpに代入
        tiles[i].textContent = tiles[j].textContent; //iにjを代入
        tiles[i].value = tiles[j].value; //iにjを代入
        tiles[j].textContent = tmp; //jにiを代入
        tiles[j].value = tmp; //jにiを代入
      }

      function scoredata(){
        var scoreCount = time * 100 + 200 - clickcount * 10;
        score.textContent = scoreCount;
      }

      function finish(){//クリア判定
        var clear = 0;
        for(var i = 0; i < 16; i++){//16枚のタイルを判定
          if(tiles[i].value == i){
            clear++//タイルの場所とその数字が同じ場合はポイント追加
          }
        }
        console.log(clear);
        if(clear == 16){//ポイントが16(つまり全部一致)になったらクリア!
          clearTimeout(timeoutId); 
          timer.innerHTML = "FINISH!";
          scoredata();
        }
      }

    </script>
  </body>
</html>

ご教授よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

+1

不要な /がコード内にある、td.onclick = click;のclickが未定義など
プログラムとして成立していないです。

質問前にJavaScriptコードを書いたあとは
Consoleでエラーのチェックは必ず行ってください。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/07/19 22:45

    すみません。コードの一部しか載せておりませんでした。
    全体像に変更しました。

    consoleでのエラーは確認済みです。
    よろしくお願いいたします。

    キャンセル

+1

  table.appendChild(tr);

2回目のinitの実行の際には、tableって何が入ってるのか考えてみよう

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/07/19 23:09

    2回目のinit実行時、何がtableに入っているのか、consoleで色々と試してみましたが、1回目の異なる結果を得ることはできませんでした。
    tilesの中身が入ったまま追加されていることが原因だということは、上記のご回答のお陰でわかりましたが、consoleを使用してその問題にどうたどり着けるのかはまだ不明です。
    アドバイスがあればいただけますと幸いです。

    キャンセル

  • 2019/07/19 23:16

    > table.innerHTML = "";
    見落としてましたね、スンマセン
    マイナスしといてください

    キャンセル

  • 2019/07/19 23:22

    いえいえ。ご回答いただけただけで助かります。今後ともよろしくお願いいたします。
    いつもありがとうございます。

    キャンセル

+1

init() 実行時に tiles の中身をリセットしていないからですね。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/07/19 23:10

    ご回答ありがとうございます。結果は解決しました。ただ、どのようにすればその問題を発見できたのか、アドバイスをいただけますと幸いです。

    キャンセル

  • 2019/07/20 01:18

    一度目は動くけれども2度目以降でダメ、ということは、「追加していく系」の処理に不具合がある可能性が高いだろうと思いました。(一度目はゼロから追加していくから想定通りの形になるが、2度目以降は既にあるデータの後ろに追加されてしまってバグるパターンはよくあるので)
    そこまでアタリをつけられたら、疑わしいのは appendChild と push くらいなので、そのあたりを確認すればすぐにわかりました。

    この手のミスはよくやるので、私なら初期化処理と更新処理は分離し、
    onload では初期化→更新を呼ぶ、
    reset では 更新を呼ぶ
    という設計にすると思います。

    キャンセル

  • 2019/07/23 16:52

    アドバイスありがとうございます。
    問題を推測するプロセス、非常にわかりやすいです。
    また、初期化と更新の機能を分離して設計する点、勉強になります。
    今後は機能を整理して設計することを心がけます。
    ありがとうございました!

    キャンセル

checkベストアンサー

0

tilesをリセットすれば確かに直りますが、
そもそもtable内のタグ部分は1回生成すれば良いので
解決方法としてはこのようにcreateElementはonload時だけ
行った方が無駄な処理がなくなるので、より良い解決方法になります。

<body onload="init()">
<style>
.tile {
  width: 100px;
  height: 100px;
  border: 1px solid #ccc;
  border-radius: 10px;
  text-align: center;
  font-size: 36px;
  background-color: white;
  box-shadow: rgb(128, 128, 128) 5px 5px;
}
</style>
<table id="table"></table>
<div id="reset">RESET</div>
<script>
"use strict";
var tiles = [];
var reset = document.getElementById("reset");
var table = document.getElementById("table");
var isInit = false;

function init() {
  if (!isInit) {
    table.innerHTML = "";
    for (var i = 0; i < 4; i++) {
      var tr = document.createElement("tr");
      table.appendChild(tr);
      for (var j = 0; j < 4; j++) {
        var td = document.createElement("td");
        var index = i * 4 + j;
        td.className = "tile";
        td.value = index;
        td.index = index;
        td.textContent = index == 0 ? "" : index;
        tiles.push(td);
        tr.appendChild(td);
        td.addEventListener("click", clickNum);
      }
      isInit = true;
    }
  }
  for (var i = 0; i < 1000; i++) {
    clickrandom({
      srcElement: {
        index: Math.floor(Math.random() * 16)
      }
    });
  }
}
reset.addEventListener("click", init);

function clickrandom(e) {
  var i = e.srcElement.index;
  if (i - 4 >= 0 && tiles[i - 4].value == 0) {
    swap(i, i - 4);
  } else if (i + 4 < 16 && tiles[i + 4].value == 0) {
    swap(i, i + 4);
  } else if (i % 4 != 0 && tiles[i - 1].value == 0) {
    swap(i, i - 1);
  } else if (i % 4 != 3 && tiles[i + 1].value == 0) {
    swap(i, i + 1);
  }
}

function clickNum(e) {
  var i = e.srcElement.index;
  if (i - 4 >= 0 && tiles[i - 4].value == 0) {
    swap(i, i - 4);
    clickcount++;
  } else if (i + 4 < 16 && tiles[i + 4].value == 0) {
    swap(i, i + 4);
    clickcount++;
  } else if (i % 4 != 0 && tiles[i - 1].value == 0) {
    swap(i, i - 1);
    clickcount++;
  } else if (i % 4 != 3 && tiles[i + 1].value == 0) {
    swap(i, i + 1);
    clickcount++;
  }
}
function swap(i, j) {
  var tmp = tiles[i].value; //先にiの値をtmpに代入
  tiles[i].textContent = tiles[j].textContent; //iにjを代入
  tiles[i].value = tiles[j].value; //iにjを代入
  tiles[j].textContent = tmp; //jにiを代入
  tiles[j].value = tmp; //jにiを代入
}
</script>
</body>

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/07/20 00:02

    簡潔な解決方法とコードの整理ありがとうございます!!
    確かに重複処理がなくなり、綺麗です。
    自分のコードと比較して、勉強します。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.23%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる