【Html&Cssで作る四択クイズゲーム開発記録④】HTMLのスクリプト部分(Javascript)をfunctionでまとめる
※ 当ページには【広告/PR】を含む場合があります。
2022/01/09

新年明けましておめでとうございます。
2022年も弊社タコスキングダムのPスクールブログの方も宜しくお願いいたします。
では前回に引き続き四択クイズミニゲームの修正・改良を加えていきます。
今回のお題は
前回までの課題洗い出し
まずは改良前のHTMLコードの設問部分を良く眺めてみます。
なお、改造前の全コードをはこの
//...中略
document.getElementById("stage1").addEventListener("change", (e) => {
const r_ = document.getElementById("checkmark1").checked;
if (r_) {
document.getElementById("gamestate").textContent += '⭕ ';
} else {
document.getElementById("gamestate").textContent += '❌ ';
}
});
document.getElementById("stage2").addEventListener("change", (e) => {
const r_ = document.getElementById("checkmark2").checked;
if (r_) {
document.getElementById("gamestate").textContent += '⭕ ';
} else {
document.getElementById("gamestate").textContent += '❌ ';
}
});
document.getElementById("stage3").addEventListener("change", (e) => {
const r_ = document.getElementById("checkmark3").checked;
if (r_) {
document.getElementById("gamestate").textContent += '⭕ ';
} else {
document.getElementById("gamestate").textContent += '❌ ';
}
let result = 0;
document.getElementById("checkmark1").checked && result++;
document.getElementById("checkmark2").checked && result++;
document.getElementById("checkmark3").checked && result++;
document.getElementById("score").textContent = `正解率は ${result}/3 でした。`;
});
//...中略
修正前のコードの問題点として、各設問ステージでの正誤判定を行った後の画面の描画処理で、Javascriptスクリプトから
change
このままだと力技すぎるので、今回はここからもっとスマートにコード改造に着手してみましょう。
scriptタグ内が散らかっている〜functionでまとめられるメソッドを一本化
現在はたかだか3問ですが、同じ処理をコピペしてステージの名前を変えているだけですので、これは一つの関数として圧縮するという改善の余地があります。
function shiftGamgeState({stageName, checkbox}) {
document.getElementById(stageName).addEventListener("change", (e) => {
document.getElementById("gamestate").textContent += document.getElementById(checkbox).checked ? '⭕ ' : '❌ ';
});
}
[
{stageName: "stage1", checkbox: "checkmark1"},
{stageName: "stage2", checkbox: "checkmark2"},
{stageName: "stage3", checkbox: "checkmark3"},
].forEach(g => shiftGamgeState(g));
というようにやるとスッキリして、設問を増やすのも楽になります。
集計処理を独立化する
集計処理も変更します。
コードの修正前では、最後の3番目の設問が終了時に、正誤判定の集計を行う処理もハードコーディングしていました。
document.getElementById("stage3").addEventListener("change", (e) => {
const r_ = document.getElementById("checkmark3").checked;
if (r_) {
document.getElementById("gamestate").textContent += '⭕ ';
} else {
document.getElementById("gamestate").textContent += '❌ ';
}
//👇集計処理
let result = 0;
document.getElementById("checkmark1").checked && result++;
document.getElementById("checkmark2").checked && result++;
document.getElementById("checkmark3").checked && result++;
document.getElementById("score").textContent = `正解率は ${result}/3 でした。`;
});
これだと、毎回設問を増やすたびに集計項目を手動で増やさないといけないですし、設問が多くなってくるほどミスが多くなりそうでよろしくありません。
ではどうするかというと、集計するタイミングを測る機能と、集計を行う機能を持ったfunctionを別に作成しておき、先ほどの正誤判定後の描画処理関数に追加します。
//...
function checkGameOver() {
let isGameOver = true;
document.querySelectorAll("input[id^='stage']").forEach((checkItem) => {
isGameOver = isGameOver && checkItem.checked;
});
return isGameOver;
}
function checkSum() {
let result = 0, total = 0;
document.querySelectorAll("input[id^='checkmark']").forEach((checkItem) => {
checkItem.checked && result++;
total++;
});
document.getElementById("score").textContent = `正解率は ${result}/${total} でした。`;
}
function shiftGamgeState({stageName, checkbox}) {
document.getElementById(stageName).addEventListener("change", (e) => {
document.getElementById("gamestate").textContent += document.getElementById(checkbox).checked ? '⭕ ' : '❌ ';
checkGameOver() && checkSum();//👈追加
});
}
//...
こうすることで、全ての設問に答えたときに、自動で集計処理が走るようにできます。
結果的に、散らかったソースコードを3つの関数(
checkGameOver
checkSum
shiftGamgeState
余談〜即時関数で囲う
ブラウザでゲームを作りたい場合、ブラウザの裏で走るプロセスで使われる重要な
代表格としては、
ということで、Javascriptのコードが増えていくと、ふとしたはずみでこれらの重要なグローバル変数を知らず知らず上書きしてしまったら最後、アプリケーションまともに動かなくなってしまう恐れがあります。
このことはJavascript特有の
使い方はとても簡単で、scriptタグの中で、無名関数を定義して、その中身でコードを実装していきます。
<script>
(function() {
///スクリプトの定義
})();
</script>
また別パターンで以下のような全体を
()
<script>
(function() {
///スクリプトの定義
}());
</script>
ES6以降であれば、アロー関数を利用した即時関数も最近では良く見かけます。
<script>
(() => {
///スクリプトの定義
})();
</script>
更にPromiseなどの非同期の関数をawaitで使う必要があれば、即時関数をasyncで予め記述して使うこともできます。
<script>
(async () => {
///スクリプトの定義(非同期処理を含む)
})();
</script>
ひと手間ではありますが、とりあえず即時関数を仕込んでおくと、そのあとのHTMLアプリ開発も少しは安心できるかも知れません。
まとめ
今回はfunctionを使うことで、同じような処理を行うメソッドを統合するところまで行いました。
このHTML&CSSミニゲーム作成シリーズも思い付きでやってみておりますので、四択クイズゲームとして遊べるようになるまでもう少しかかりそうな気配です...。
あと数回でHTMLの基礎的な文法の話を挟むかも知れませんが、完成まであとしばらくお付き合いください。
付録〜改良前のソースコード(バックアップ)
今回も恒例の古くなったソースコード(バージョン0.4)のバックアップを付録として掲載しておきます。
index.html
<!DOCTYPE html>
<html lang="ja">
<html>
<meta charset="utf-8"/>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<form id="game-wrapper">
<p class="game-header"><span id="gamestate">さあ4択クイズを始めましょう</span></p>
<input type="reset" id="reset"/>
<input type="checkbox" id="start"/>
<input type="checkbox" id="stage1"/>
<input type="checkbox" id="stage2"/>
<input type="checkbox" id="stage3"/>
<input type="checkbox" id="checkmark1"/>
<input type="checkbox" id="checkmark2"/>
<input type="checkbox" id="checkmark3"/>
<div id="stage---op" class="stage stage--op">
<label for="start">クリックしてスタート</label>
</div>
<div id="stage---1" class="stage stage--main stage--main--1">
<label for="stage1"><ul class="flexlist"><li><li><li><li>
</div>
<div id="stage---2" class="stage stage--main stage--main--2">
<label for="stage2"><ul class="flexlist"><li><li><li><li>
</div>
<div id="stage---3" class="stage stage--main stage--main--3">
<label for="stage3"><ul class="flexlist"><li><li><li><li>
</div>
<div id="stage---reset" class="stage stage--end">
<p class="score-board"><span id="score"></p>
<label for="reset">もう一度トライ</label>
</div>
</form>
<script>
document.getElementById("start").addEventListener("change", (e) => {
document.getElementById("gamestate").textContent = '成績: ';
});
document.getElementById("stage1").addEventListener("change", (e) => {
const r_ = document.getElementById("checkmark1").checked;
if (r_) {
document.getElementById("gamestate").textContent += '⭕ ';
} else {
document.getElementById("gamestate").textContent += '❌ ';
}
});
document.getElementById("stage2").addEventListener("change", (e) => {
const r_ = document.getElementById("checkmark2").checked;
if (r_) {
document.getElementById("gamestate").textContent += '⭕ ';
} else {
document.getElementById("gamestate").textContent += '❌ ';
}
});
document.getElementById("stage3").addEventListener("change", (e) => {
const r_ = document.getElementById("checkmark3").checked;
if (r_) {
document.getElementById("gamestate").textContent += '⭕ ';
} else {
document.getElementById("gamestate").textContent += '❌ ';
}
let result = 0;
document.getElementById("checkmark1").checked && result++;
document.getElementById("checkmark2").checked && result++;
document.getElementById("checkmark3").checked && result++;
document.getElementById("score").textContent = `正解率は ${result}/3 でした。`;
});
document.getElementById("reset").addEventListener("click", (e) => {
document.getElementById("gamestate").textContent = 'さあ4択クイズを始めましょう';
});
document.querySelectorAll("ul.flexlist > li").forEach((userItem) => {
userItem.addEventListener("click", (e) => {
const styles = window.getComputedStyle(e.target,'::after')
const content = styles['content'];
const m_ = content.match(/(\d+)\/correct/);
if (m_) {
document.getElementById("checkmark" + m_[1]).checked = true;
};
});
});
</script>
</body>
</html>
styles.scss
#game-wrapper {
width: 100%;
height: 300px;
background-color: darkgray;
box-sizing: border-box;
position:relative;
input { display: none; }
.game-header {
position: absolute;
top: 0;
left: 0;
background: #220c0c;
color: #ceeece;
z-index: 1;
margin: 0;
font-size: 22px;
padding: 6px 0 6px 0;
width: 100%;
}
.stage {
position: absolute;
display: block;
width: 100%;
height: 100%;
font-size: 24px;
&--op {
display: flex;
align-items: center;
justify-content: center;
label {
display: block;
flex: 0 0 auto;
margin: 0 auto;
animation: flash 1s linear infinite;
}
}
&--main {
display: flex;
align-items: center;
justify-content: center;
label {
flex: 0 0 auto;
margin: 0 auto;
}
@each $key, $val in (1:blue, 2:rgb(231, 185, 99), 3:rgb(211, 51, 203)) {
&--#{$key} {
color: $val;
font-size: 22px;
}
}
}
&--end {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background: darkgray;
label {
flex: 0 0 auto;
display: inline-block;
font-weight: bold;
padding: 0 0 0 0;
text-decoration: none;
color: #00BCD4;
background: #ECECEC;
margin: 0 auto;
animation: flash 1s linear infinite;
}
}
@keyframes flash {
0%,100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
}
.stage--main {
ul.flexlist {
border: 1px solid #666;
display: flex;
flex-wrap: wrap;
justify-content: center;
list-style-type: none;
margin: 10px auto 2px;
padding: 0;
width: 300px;
li {
border: 1px solid #aaa;
line-height: 110%;
margin: 5px 2px 5px 2px;
padding: 8px;
text-align: center;
width: 35%;
&:hover {
color: chartreuse;
}
&::after {
content: "wrong";
visibility: hidden;
display: block;
width: 0;
height: 0;
}
}
}
}
#stage {
&---op {display: flex}
@each $item in (1, 2, 3, reset) {
&---#{$item} { display: none; }
}
}
$stages: (
'start' : ('op', ''),
'stage1': ('1', 'タコの足は何本?', ('5本', '8本', '10本', '足はない'), 2),
'stage2': ('2', 'タコは何動物?', ('地球外生物', '緩歩動物', '実は植物', '軟体動物'), 4),
'stage3': ('3', 'タコの水揚げ量が世界一の国?', ('中国','カナダ','モーリタニア','日本'), 1)
);
@for $i from 1 through length($stages) {
$item: nth($stages, $i);
$item: nth($item,1);
##{$item}:checked ~ {
$item: nth($stages, $i);
$item: nth($item, 2);
$item: nth($item,1);
#stage---#{$item} {display: none}
@if $i == length($stages) {
#stage---reset {
display: flex;
opacity: 1;
animation: fadeIn 0.3s ease-in 0s forwards;
}
} @else {
$item: nth($stages, $i + 1);
$item: nth($item,2);
$item: nth($item,1);
#stage---#{$item} {
display: flex;
opacity: 1;
animation: fadeIn 0.3s ease-in 0s forwards;
$item: nth($stages, $i + 1);
$item: nth($item,2);
$item: nth($item,2);
label::before {
content: '問題#{$i}:#{$item}';
}
$item: nth($stages, $i + 1);
$item: nth($item,2);
$quiz_option: nth($item,3);
$answer: nth($item,4);
@for $j from 1 through length($quiz_option) {
ul.flexlist {
li:nth-child(#{$j}) {
&::before {content: '#{nth($quiz_option,$j)}';}
@if $j == $answer {
&::after { content: "#{nth($item,1)}/correct"; }
}
}
}
}
}
}
}
}
@keyframes fadeIn {
0% {
display: none;
opacity: 0;
}
1% {
display: flex;
opacity: 0;
}
100% {
display: flex;
opacity: 1;
}
}
}