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

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

ただいまの
回答率

87.59%

Javascript ゲーム フィッシャーイェーツ乱数

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 1,304

score 46

以前似たような質問をさせていただきました。重複しない乱数処理を以下の国旗ゲームで練習しています。
おかげさまでsortを用いた乱数は使えるようになりましたが、この乱数は穴があると教えていただきました。なので、フィッシャーイェーツの乱数を練習したいのですが、以下のようにすると機能しません。おそらく以下のShuffleとsetQuestionに問題があると思うのですが、アドバイスいただけますか?

var Shuffle = function(arr) {
  var i, j, temp;
  arr = flags.slice();
  i = flags.length;
  if (i === 0) {
    return arr;
  }
  while (--i){
    j = Math.floor(Math.random() * (i + 1));
    temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
  }
  return arr;
}

function setQuestion() {
  if(flags.length <= 0) {
    alert('お疲れ様でした。問題は以上です。あなたのスコアは ' + score + '点です。')
  }
flag = Shuffle(flags); 
question.children[0].src = 'img/' + flag[0];
answer = flag[1];
}


念の為、この下に全体コードを貼らせていただきます。

(function() {
'use strict';

var question = document.getElementById('question');
var btn = document.getElementById('btn');
var input = document.getElementById('input');
var check = document.getElementById('check');
var timerLabel = document.getElementById('timer');
var scoreLabel = document.getElementById('score');
var pass = document.getElementById('pass');
var answer = '';
var score = 0;
var timerId;
var isPlaying = false;
var timer;
var flag;
var previous = [];
var flags = [
['Nepal.png', 'ネパール'],
['India.png', 'インド'],
['Netherland.png', 'オランダ'],
['Chili.png', 'チリ'],
['Morocco.png', 'モロッコ'],
['Luxembourg.png', 'ルクセンブルク'],
['Singapore.png', 'シンガポール'],
['Pakistan.png', 'パキスタン'],
['Azerbaijan.png', 'アゼルバイジャン'],
['Jamaica.png', 'ジャマイカ'],
['Belgium.png', 'ベルギー'],
['Guana.png', 'ガーナ'],
['SierraLeone.png', 'シエラレオネ'],
['Korea.png','韓国'],
['Japan.png','日本'],
['China.png','中国'],
['Mongolia.png','モンゴル'],
['Philippines.png', 'フィリピン'],
['Italy.png', 'イタリア'],
['Egypt.png', 'エジプト'],
['Australia.png','オーストラリア'],
['NewZealand.png', 'ニュージーランド'],
].sort(() => Math.random() - 0.5);

function init() {
isPlaying = false;
timer = 100;
score = 0;
scoreLabel.innerHTML = '';
scoreLabel.className = '';
timerLabel.className = '';
timerLabel.innerHTML = timer;
btn.className = '';
check.className = 'invisible';
}

init();

function updateTimer() {
timerId = setTimeout(function() {
timer--;
if(timer <=10) {
  timerLabel.classList.add('danger');
}
timerLabel.innerHTML = timer;
if(timer <= 0) {
alert('Game Overです。お疲れ様でした。あなたのスコアは ' + score + '点です。');
clearTimeout();
init();
return;
}
updateTimer();
}, 1000);
}

var Shuffle = function(arr) {
  var i, j, temp;
  arr = flags.slice();
  i = flags.length;
  if (i === 0) {
    return arr;
  }
  while (--i){
    j = Math.floor(Math.random() * (i + 1));
    temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
  }
  return arr;
}

function setQuestion() {
  if(flags.length <= 0) {
    alert('お疲れ様でした。問題は以上です。あなたのスコアは ' + score + '点です。')
  }
flag = Shuffle(flags); 
question.children[0].src = 'img/' + flag[0];
answer = flag[1];
}

btn.addEventListener('click', function() {
if (!isPlaying) {
btn.classList.add('invisible');
check.classList.remove('invisible');
isPlaying = true;
updateTimer();
setQuestion();
}
});

check.addEventListener('click', function() {
if(input.value !== '' && isPlaying) {
score += (input.value === answer)? 1: -1;
if(score < 0) {
  scoreLabel.classList.add('danger');
} else {
  scoreLabel.className = '';
}
scoreLabel.innerHTML = score;
input.value = '';
input.focus();
setQuestion();
}
});

pass.addEventListener('click', function() {
  if(!isPlaying) {
    return;
  }
  setQuestion();
});

})();
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • think49

    2019/02/16 11:01

    コメントは編集出来ます。履歴も参照されません。

    キャンセル

  • think49

    2019/02/16 11:52

    "williamsArk 2019/02/16 09:58 編集 あ" では意味が分かりません。後で読んだ人が時系列を追えるように修正して下さい。この質疑応答は質問者のみが参考にするものではなく、共有情報となります。

    キャンセル

  • williamsArk

    2019/02/16 12:08

    承知いたしました。覚えている限り訂正しました。

    キャンセル

回答 2

checkベストアンサー

+1

関数 setQuestion が呼ばれるたびにシャッフルする必要はない。関数 init で済ませる
配列 flags をシャッフルして flag に配列としてコピーしているのよね。
なので flags.length ではなくflag.length を評価するようにする
変数 anser に代入するときは、元の配列である flag から1つ減らす必要がある
[,anser] = flag.shift ();
みたいにする。

余計なことかもしれないが、
関数 Shuffle の変数 i の使い方に無駄がある。
グローバル変数が沢山あって脳内で解釈しながら理解するのが辛い。
プログラムコードにインデントしてほしい。
HTMLも見てみたいな。
img の src に代入するときは alt にも代入してほしい。
すると画像がなくても確認できるから。(本当はやらなきゃいけないこと)
音声でゲームをしている人には、聞こえちゃいますがw

さらに余計なこと、
もし旗が1万種類あったとして、旗の情報を1万個並び替えるより、インデックス番号だけの
配列をソートしたほうが負担が少ないかな。

どうでもよいこと、
ソートによるシャッフルの件だけど、偏りがあるのはしょうがない。
それを逆手にとって出題傾向をなんとなくだけど制御できるのではないか?
旗の数が増えると効率が落ちるけれど、それで十分だと、私も思います。

冒頭で'use strict'という見慣れない文字列を見たが、それらしいの使ってる?w

ちょっと興味がわいたのでソートによるシャッフルを調べてみた。
簡単な問題順に定義して、並び替えると出題傾向を弄れる?
コードがやっつけなのは勘弁!(今になってSVGが習い立てなもんで)
ちょろめと火狐では大きく違うね!こりゃ~つかえないか?

<!DOCTYPE html>
<html lang="ja">
<title>?</title>
<meta charset="utf-8">
<style>
table tbody:first-of-type { display: none; }
</style>

<body>

<table border="1">
  <caption><em>arry.sort (()=>Math.random() - X)</em> のX値による0番の移動した出現分布の累積
  <tbody><tr><th><td><svg width="800" height="120" xmlns="http://www.w3.org/2000/svg"></svg>
</table>

<script>
const
  N = 100,
  A = Array (N).fill().map ((_,i)=>i),
  S = 0.3,
  E = 0.7,
  R = 1000,//繰り返し回数
  C = 0, // 0番がどこに移動するか

  table = document.querySelector ('table'),
  tbody = table.querySelector ('tbody:first-of-type'),
  rect = document.createElementNS ('http://www.w3.org/2000/svg', 'rect'),
  setAtr = (e,o)=>[...Object.entries(o)].forEach(([a,b])=>e.setAttribute(a,b));

setAtr (rect, { width: 8, style: 'fill:rgb(0,0,255)'});

function sikakuka (n, ary) {
  let tb = table.appendChild (tbody.cloneNode (true));
  let [th, svg] = tb.querySelectorAll ('th, svg');
  th.textContent = "x = " + n.toFixed (3);
  ary.forEach ((a, i) =>setAtr (svg.appendChild (rect.cloneNode (false)), {x: i*8, y:120-a, height: a}));
}


for (let i = S; i < E; i += .025) {
  let c = Array (N).fill (0);
  for (let j = 0; j < R; j++) {
    let a = A.slice ();
    a.sort (()=>Math.random () - i);
    c[a.indexOf (C)]++;
  }
  sikakuka (i, c);
} 

</script>

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • キャンセル

  • 2019/02/16 11:45

    think49 さん、ご丁寧にありがとね。<babu_**2..... ^^;
    ステップアップしてる質問者さんを静観せずに居られなくなって、ついでしゃばった。

    HTMLではその構造にも使われている要素にも意味を持たせるべき。
    その使われている画像がいったい何を意味するのか?明示させなければならない。
    視覚障害者だって何かしらのブラウザ等で聞いているかもしれないでしょ!?

    キャンセル

  • 2019/02/16 12:10

    お二方ともかなり役立つ情報ありがとうございました。後ほど確認させていただきまして、また質問がありましたらお尋ねするかもしれません。

    キャンセル

+1

flag = Shuffle(flags);


この部分ですが、Shuffle関数の戻り値は、

  arr = flags.slice();
//...
  return arr;


とあるように、flagsをシャッフルしたものであり、flags[0]ではないです。
なので、flag[0]の中身に'Nepal.png'を期待しているかもしれませんが、そうではなくて['Nepal.png', 'ネパール']が入っています。

解決方法ですが、flag[0][0]を参照するか、Shuffle関数の戻り値をarr[0]にするといいかもしれません。

ただ、これ、たぶん、普通の乱数と同じくかぶるんじゃないですか?

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/02/16 12:48

    現在の流れで非破壊ソートを期待するなら、シャローコピーの代わりに Array#map で flags[0][0] を複製する方法も有ります。
    個人的には、データの持ち方を二次元配列から new Map に変更して、Map#values() から配列化してソートする方法を採用しますが…。

    キャンセル

  • 2019/02/16 13:45

    二次元配列からnew Mapなど難易度が高そうな言葉が多いですね。一個一個噛み砕いていく必要がありそうです。ありがとうございました。

    キャンセル

  • 2019/02/16 14:07

    > think49さん
    どっちが早いのか興味深いところですね。
    Arrayの実装が、参照のリストや配列であるなら、さほど差がつかないような気もします。

    > williamsArkさん
    ややこしい話をしてすみません。
    要は、
    arr = flags.slice();
    ↑これを削除して、
    return arr.pop();
    ↑戻り値をこうすれば動くんじゃないかな、という話です
    (動作確認してません)

    キャンセル

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

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

関連した質問

同じタグがついた質問を見る