/
answer.js
222 lines (196 loc) · 8.73 KB
/
answer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// 解答例 (色々な実装方法があるため、この実装はあくまでも1つの実装例として考えること。)
(() => {
// 利用するAPI情報元
// - 本サイト : https://opentdb.com/
// - 利用するAPI : https://opentdb.com/api.php?amount=10&type=multiple
const API_URL = 'https://opentdb.com/api.php?amount=10&type=multiple';
// 「gameState」オブジェクトを作る
// - クイズアプリのデータ管理用オブジェクト
// - 保持する情報
// - quizzes : fetchで取得したクイズデータの配列(resutls)を保持する
// - currentIndex : 現在何問目のクイズに取り組んでいるのかをインデックス番号で保持する
// - numberOfCorrects : 正答数を保持するう
const gameState = {
quizzes : [],
currentIndex : 0,
numberOfCorrects : 0
};
// HTMLのid値がセットされているDOMを取得する
const questionElement = document.getElementById('question');
const resultElement = document.getElementById('result');
const answersContainer = document.getElementById('answers');
const restartButton = document.getElementById('restart-button');
// ページの読み込みが完了したらクイズ情報を取得する
window.addEventListener('load', (event) => {
fetchQuizData();
});
// 「Restart」ボタンをクリックしたら再度クイズデータを取得する
restartButton.addEventListener('click', (event) => {
fetchQuizData();
});
// `fetchQuizData関数`を実装する
// - 実現したいこと
// - async/awaitを使って実装する
// - Webページ上の表示をリセットする
// - id属性値が 'question' の要素に「Now loading...」という文字列をセットする
// - id属性値が 'result' の要素に空文字列をセットする (Restartボタンでクイズ2回以上繰り返す時用)
// - id属性値が 'restart-button' の要素を非表示にする (Restartボタンでクイズ2回以上繰り返す時用)
// - クイズ取得~取得後の流れ
// 1. API_URLとFetch API(fetchメソッド)を使ってAPI経由でデータを取得する
// - https://developer.mozilla.org/ja/docs/Web/API/WindowOrWorkerGlobalScope/fetch
// 2. fetchメソッドで取得したResponseデータからJSON形式のデータをオブジェクトに変換する
// - https://developer.mozilla.org/ja/docs/Web/API/Response
// 3. 2で取得したデータの中にresultsプロパティ(配列)があり、その中には10件のクイズデータがある。
// その10件のデータを利用してクイズの出題・回答を出来るようにする。(詳しくは完成形の画像を参照)
// - 引数
// - 無し
// - 戻り値
// - 無し
const fetchQuizData = async () => {
questionElement.textContent = 'Now loading...';
resultElement.textContent = '';
restartButton.hidden = true;
try {
const response = await fetch(API_URL);
const data = await response.json();
gameState.quizzes = data.results;
gameState.currentIndex = 0;
gameState.numberOfCorrects = 0;
setNextQuiz();
} catch (error) {
alert(`読み込み失敗... (${error.message})`);
}
};
// setNextQuiz関数を実装する
// - 実現したいこと
// - 表示要素をリセットする
// - 問題文を空にする
// - 解答一覧を全て消す
// - 条件に応じて、次の問題の表示 or 結果を表示する
// - 次の問題がまだ存在すれば次の問題をセットする
// - 直前に解答したのが最後の問題であれば結果を表示する(finishQuiz関数を実行する)
// - 引数
// - 無し
// - 戻り値
// - 無し
const setNextQuiz = () => {
// 問題文と解答を空にしてから、次の問題 or 結果を表示
questionElement.textContent = '';
removeAllAnswers();
if (gameState.currentIndex < gameState.quizzes.length ) {
const quiz = gameState.quizzes[gameState.currentIndex];
makeQuiz(quiz);
} else {
finishQuiz();
}
};
// finishQuiz関数を実装する
// - 実現したいこと
// - 正答数を表示する(例: 4問正解したばあいは 「4/10 corrects」と表示する)
// - 「Restart」ボタンを表示する
// - 引数
// - 無し
// - 戻り値
// - 無し
const finishQuiz = () => {
resultElement.textContent = `${gameState.numberOfCorrects}/${gameState.quizzes.length} corrects.`;
restartButton.hidden = false;
};
// removeAllAnswers関数を実装する
// - 実現したいこと
// - 解答を全て削除する
// - 引数
// - 無し
// - 戻り値
// - 無し
const removeAllAnswers = () => {
while (answersContainer.firstChild) {
answersContainer.removeChild( answersContainer.firstChild );
}
};
// makeQuiz関数を実装する
// - 実現したいこと
// - クイズデータを元にWebページ上に問題と解答リストを表示する
// - 解答をクリックしたら、正解・不正解のチェックをする
// - 正解の場合
// - 「gameState」オブジェクトで管理している正答数プロパティをインクリメントする
// - 「Correct answer!!」とアラート表示する
// - 不正解の場合
// - 「Wrong answer... (The correct answer is "実際の答えを埋め込む")」とアラート表示する
// - 回答する度に「gameState」オブジェクトで管理している、問題数プロパティをインクリメントする
// - setNextQuiz関数を実行して次の問題をセットする(最後の問題の場合は結果を表示する。)
// - 引数
// - 無し
// - 戻り値無し
// - 無し
const makeQuiz = (quiz) => {
// シャッフル済みの解答一覧を取得する
const answers = buildAnswers(quiz);
// 問題文のセット
questionElement.textContent = unescapeHTML(quiz.question);
// 解答一覧をセット
answers.forEach((answer) => {
const liElement = document.createElement('li');
liElement.textContent = unescapeHTML(answer);
answersContainer.appendChild(liElement);
// 解答を選択したときの処理
liElement.addEventListener('click', (event) => {
unescapedCorrectAnswer = unescapeHTML(quiz.correct_answer);
if (event.target.textContent === unescapedCorrectAnswer) {
gameState.numberOfCorrects++;
alert('Correct answer!!');
} else {
alert(`Wrong answer... (The correct answer is "${unescapedCorrectAnswer}")`);
}
gameState.currentIndex++;
setNextQuiz();
});
});
};
// quizオブジェクトの中にあるcorrect_answer, incorrect_answersを結合して
// 正解・不正解の解答をシャッフルする。
const buildAnswers = (quiz) => {
const answers = [
quiz.correct_answer,
...quiz.incorrect_answers
];
const shuffledAnswers = shuffle(answers);
return shuffledAnswers;
};
// `shuffle関数` を実装する
// - 実現したいこと
// - 引数で受け取った配列内の値をシャッフルする
// - 引数で渡された配列を上書きしないように、シャッフルする配列はコピーしたものを使う
// - シャッフルの機能を実現するのに参考になるサイト
// - https://qiita.com/artistan/items/9eb9a0fb14f4ec3a8764
// - 引数
// - array : 配列
// - 戻り値
// - shffuledArray : シャッフル後の配列(引数の配列とは別の配列であることに注意する)
const shuffle = (array) => {
const copiedArray = array.slice();
for (let i = copiedArray.length - 1; i >= 0; i--){
const rand = Math.floor( Math.random() * ( i + 1 ) );
[copiedArray[i], copiedArray[rand]] = [copiedArray[rand], copiedArray[i]]
}
return copiedArray;
};
// unescapeHTML関数を実装する
// - 実現したいこと
// - &やクオーテーションマークなどが特殊文字としてセットされているので、
// 人間が読みやすい形式に変換した文字列を取得する(エスケープ文字を元に戻す)
// 参考にしたサイト : http://blog.tojiru.net/article/211339637.html
// - 引数
// - 文字列
// - 戻り値
// - 文字列
const unescapeHTML = (str) => {
var div = document.createElement("div");
div.innerHTML = str.replace(/</g,"<")
.replace(/>/g,">")
.replace(/ /g, " ")
.replace(/\r/g, " ")
.replace(/\n/g, " ");
return div.textContent || div.innerText;
};
})();