📅 note公開日:2025-08-30 22:07
🔁 この記事はnoteで公開していた内容をQiitaへ移行・再掲したものです。(必要に応じて加筆修正しています)
じゃんけんゲーム関数化
Questで作成したじゃんけんゲームのJavaScriptを関数化して整理した記録です。
目的は 【「簡素化して見やすくする」「責務を分ける」事を学ぶ】 です📚
1,イベントハンドラー関数化
2:btn.textContentから0、1、2へ変換の関数化
3:勝敗判定を関数化
4:cpuの手とチートモードでcpuが必ず負ける手を関数化
5:配置換え!!
6:出力
修正前コード
<script>
const btnGroup = document.querySelector('.button-group');
const result = document.getElementById('result');
const cheat = document.getElementById('cheatMode');
let cheatOn = false; // 初期設定はチートOFF
cheat.addEventListener('change', (e) => {
cheatOn = e.target.checked;
if (e.target.checked) {
document.body.classList.add('cheat-active');
} else {
document.body.classList.remove('cheat-active');
}
});
btnGroup.addEventListener('click', (e) => {
const btn = e.target.closest('button');
if (!btn) return;
// グー=0, チョキ=1, パー=2
let plHand;
if (btn.textContent.includes("グー")) {
plHand = 0;
} else if (btn.textContent.includes("チョキ")) {
plHand = 1;
} else {
plHand = 2;
}
// CPU の手を決定
let cpuHand;
if (cheatOn) {
cpuHand = (plHand + 1) % 3; // チートONなら必ず負ける手
} else {
cpuHand = Math.floor(Math.random() * 3);
}
// 勝敗判定
let outcome; // 0=あいこ, 1=勝ち, 2=負け
if (plHand === 0) {
if (cpuHand === 0) {
result.textContent = "あなた:グー 相手:グー →あいこ!";
outcome = 0;
} else if (cpuHand === 1) {
result.textContent = "あなた:グー 相手:チョキ →あなたの勝ち!";
outcome = 1;
} else {
result.textContent = "あなた:グー 相手:パー →あなたの負け!";
outcome = 2;
}
} else if (plHand === 1) {
if (cpuHand === 1) {
result.textContent = "あなた:チョキ 相手:チョキ →あいこ!";
outcome = 0;
} else if (cpuHand === 2) {
result.textContent = "あなた:チョキ 相手:パー →あなたの勝ち!";
outcome = 1;
} else {
result.textContent = "あなた:チョキ 相手:グー →あなたの負け!";
outcome = 2;
}
} else {
if (cpuHand === 2) {
result.textContent = "あなた:パー 相手:パー →あいこ!";
outcome = 0;
} else if (cpuHand === 0) {
result.textContent = "あなた:パー 相手:グー →あなたの勝ち!";
outcome = 1;
} else {
result.textContent = "あなた:パー 相手:チョキ →あなたの負け!";
outcome = 2;
}
}
// 結果に応じて色を変える
let textcolor;
if (outcome === 0) {
textcolor = "draw";
} else if (outcome === 1) {
textcolor = "win";
} else {
textcolor = "lose";
}
result.classList.remove("draw", "win", "lose");
result.classList.add(textcolor);
});
</script>
copy
1:イベントハンドラー関数化
変更前
cheat.addEventListener('change', (e) => {
if (e.target.checked) {
document.body.classList.add('cheat-active');
} else {
document.body.classList.remove('cheat-active');
}
});
btnGroup.addEventListener('click', (e) => {
const btn = e.target.closest('button');
if (!btn) return;
});
copy
変更後
//--------------EVENT----------------
cheat.addEventListener('change', (e) => {
cheatOn = e.target.checked;
document.body.classList.toggle('cheat-active');
});
btnGroup.addEventListener('click', (e) => {
const btn = e.target.closest('button');
if (!btn) return;
});
copy
解説
・addとremoveの使用からtoggleに変更。
・toggle→toggle('・・・');カッコ内があれば外す/なければ付ける。
2:btn.textContentから0、1、2を返す関数化。
変更前
// グー=0, チョキ=1, パー=2
let plHand;
if (btn.textContent.includes("グー")) {
plHand = 0;
} else if (btn.textContent.includes("チョキ")) {
plHand = 1;
} else {
plHand = 2;
}
変更後
function getPlHandFromBtn(btn) {
const text = btn.textContent;
if (text.includes('グー')) return 0;
if (text.includes('チョキ')) return 1;
return 2;
}
const plHand = getPlHandFromBtn(btn);
解説
function→関数宣言!
getPlHandFromBtn(btn) {→関数名:Playerの手をBtnから取得する。引数にbtn
const text = btn.textContent;→定数textを定義し、btn.textContentを代入。
if (text.includes('グー')) return 0;→定数textにグーが含まれていれば0を返す。
if (text.includes('チョキ')) return 1;→上段がreturnで終了しており一度if文から離脱している為、ifから始まる。
return 2;→上記同様、ifから始めるがここが最終行のため本来はelseがくるが省略可能。これらを早期returnといい、returnする事で関数が終了しelseを省略できる。
const plHand = getPlHandFromBtn(btn);→関数呼び出し。
引数btn、btn.textContentとは?
イベントハンドラーで作成した定数btn。
クリックした要素を辿って一番近いbutton要素を探す動き。
btnGroup.addEventListener('click', (e) => {
const btn = e.target.closest('button');
if (!btn) return;//パー
});
button要素とは?
html内のボタン要素が該当
<div class="button-group">
<button>✊ グー</button>
<button>✌️ チョキ</button>
<button>🖐 パー</button>
</div>
3:勝敗判定を関数化
変更前
// CPU の手を決定
let cpuHand;
if (cheatOn) {
cpuHand = (plHand + 1) % 3; // チートONなら必ず負ける手
} else {
cpuHand = Math.floor(Math.random() * 3);
}
// 勝敗判定
let outcome; // 0=あいこ, 1=勝ち, 2=負け
if (plHand === 0) {
if (cpuHand === 0) {
result.textContent = "あなた:グー 相手:グー →あいこ!";
outcome = 0;
} else if (cpuHand === 1) {
result.textContent = "あなた:グー 相手:チョキ →あなたの勝ち!";
outcome = 1;
} else {
result.textContent = "あなた:グー 相手:パー →あなたの負け!";
outcome = 2;
}
} else if (plHand === 1) {
if (cpuHand === 1) {
result.textContent = "あなた:チョキ 相手:チョキ →あいこ!";
outcome = 0;
} else if (cpuHand === 2) {
result.textContent = "あなた:チョキ 相手:パー →あなたの勝ち!";
outcome = 1;
} else {
result.textContent = "あなた:チョキ 相手:グー →あなたの負け!";
outcome = 2;
}
} else {
if (cpuHand === 2) {
result.textContent = "あなた:パー 相手:パー →あいこ!";
outcome = 0;
} else if (cpuHand === 0) {
result.textContent = "あなた:パー 相手:グー →あなたの勝ち!";
outcome = 1;
} else {
result.textContent = "あなた:パー 相手:チョキ →あなたの負け!";
outcome = 2;
}
}
変更後
//------勝敗あいこ0、勝ち1、負け2を判定する関数
function judgeOutcome(plHand, cpuHand) {
if (plHand === cpuHand) return 0;//あいこ
if (plHand === 0 && cpuHand === 1) return 1;//pグー、cチョキ
if (plHand === 1 && cpuHand === 2) return 1;//pチョキ、cパー
if (plHand === 2 && cpuHand === 0) return 1;//pパー、cグー
else return 2;//それ以外は負け
}
const outcome = judgeCpuHand(plHand, cpuHand);
解説
前提
変更前は①チートモードの式と②テキストとして勝敗結果をresultに出力する式が入っている。
変更後はその2点は無視し、勝敗の判定だけに着目。UI出力は別処理へ。
③CpuHandの定義はこの処理の上に後程実装予定
function→関数宣言!
judgeOutcome(plHand, cpuHand) {→関数名:判定結果表示。引数にplHandとcpuHand。
ネーミングは結果表示の意味でOutcomeを使用
if (plHand === cpuHand) return 0;//あいこ
if (plHand === 0 && cpuHand === 1) return 1;//pグー、cチョキ
if (plHand === 1 && cpuHand === 2) return 1;//pチョキ、cパー
if (plHand === 2 && cpuHand === 0) return 1;//pパー、cグー
else return 2;//それ以外は負け
あいこのパターンは1つしかなく固定、
あとは勝ちと負けの定義だが、勝ちが決まってしまえば、
else負けと書けるため、その様な内容になっている。
const outcome→定数宣言!名前はoutcome
judgeCpuHand(plHand, cpuHand);→関数呼び出し。
4:cpuの手とチートモードでcpuが必ず負ける手を関数化
変更前
// CPU の手を決定
let cpuHand;
if (cheatOn) {
cpuHand = (plHand + 1) % 3; // チートONなら必ず負ける手
} else {
cpuHand = Math.floor(Math.random() * 3);
}
// 勝敗判定
let outcome; // 0=あいこ, 1=勝ち, 2=負け
if (plHand === 0) {
if (cpuHand === 0) {
result.textContent = "あなた:グー 相手:グー →あいこ!";
outcome = 0;
} else if (cpuHand === 1) {
result.textContent = "あなた:グー 相手:チョキ →あなたの勝ち!";
outcome = 1;
} else {
result.textContent = "あなた:グー 相手:パー →あなたの負け!";
outcome = 2;
}
} else if (plHand === 1) {
if (cpuHand === 1) {
result.textContent = "あなた:チョキ 相手:チョキ →あいこ!";
outcome = 0;
} else if (cpuHand === 2) {
result.textContent = "あなた:チョキ 相手:パー →あなたの勝ち!";
outcome = 1;
} else {
result.textContent = "あなた:チョキ 相手:グー →あなたの負け!";
outcome = 2;
}
} else {
if (cpuHand === 2) {
result.textContent = "あなた:パー 相手:パー →あいこ!";
outcome = 0;
} else if (cpuHand === 0) {
result.textContent = "あなた:パー 相手:グー →あなたの勝ち!";
outcome = 1;
} else {
result.textContent = "あなた:パー 相手:チョキ →あなたの負け!";
outcome = 2;
}
}
変更後
//------btn.textContentのグーチョキパーから0、1、2を返す関数
function getPlHandFromBtn(btn) {
const text = btn.textContent;
if (text.includes('グー')) return 0;
if (text.includes('チョキ')) return 1;//前の行でreturn→次の始まりはifでいい。
return 2; //パー
}
const plHand = getPlHandFromBtn(btn);
//------cpuの手とチートONでCpuが必ず負ける関数
function getCpuHand(plHand, cheatOn) {
//チートOFFでランダム出力
if (!cheatOn) {
return Math.floor(Math.random() * 3);
}
//チートONでCpuが必ず負ける
return (plHand + 1) % 3;
}
const cpuHand = getCpuHand(plHand, cheatOn);
//------勝敗あいこ0、勝ち1、負け2を判定する関数
function judgeOutcome(plHand, cpuHand) {
if (plHand === cpuHand) return 0;//あいこ
if (plHand === 0 && cpuHand === 1) return 1;//pグー、cチョキ
if (plHand === 1 && cpuHand === 2) return 1;//pチョキ、cパー
if (plHand === 2 && cpuHand === 0) return 1;//pパー、cグー
else return 2;//それ以外は負け
}
const outcome = judgeCpuHand(plHand, cpuHand);
解説
全体の位置関係も重要な為、2〜4の工程全体を変更後に表示させました。
今回(4)の変更点は//------cpuの手とチートONでCpuが必ず負ける関数をご覧ください。
function→関数宣言!
getCpuHand(plHand, cheatOn) {→関数名:CpuHandを取得する。引数にplHandとcheatOn。
if (!cheatOn) {→cheatOnじゃない時
return Math.floor(Math.random() * 3);
Math.floor(Math.random() * 3);
→0,1,2のどれかを等確率で作る”ためのお決まりレシピ
Math.random()→0以上 1未満 の小数を返す。(0~0.99まで)
Math.random() * 3→値を3倍して、0以上3未満 に広げる。(0.13=0.3〜0.93=2.7)
Math.floor→小数点以下を切り捨てて整数にする(0.3=0,2.7=2)
つまり、倍率を*3で留めておけば、結果は0〜2のどれかになる。
return (plHand + 1) % 3;
プレイヤーが勝つ=CPUが負ける手は?
プレイヤー:グー0 →CPU:チョキ1→(0+1)%3=1
プレイヤー:チョキ1→CPU:パー2 →(1+1)%3=2
プレイヤー:パー 2→CPU:グー0 →(2+1)%3=0
const cpuHand→定数宣言!名前はcpuHand
judgeCpuHand(plHand, cpuHand);→内容は関数呼び出し。
5:配置変え!!
ここで一旦配置換えをします。
見た目のためだけに役割ごとに関数化していきましたが、
2〜3はクリック時のイベントハンドラーで呼び出したい動き。
つまり、関数の呼び出しはクリックのイベントハンドラー内で行われるべき。
①まずは関数呼び出しをクリックのイベントハンドラー内に引越しさせます。
イベントハンドラー内
//--------------Event--------------
cheat.addEventListener('change', (e) => {
cheatOn = e.target.checked;
document.body.classList.toggle('cheat-active');
});
btnGroup.addEventListener('click', (e) => {
const btn = e.target.closest('button');
if (!btn) return;
const plHand = getPlHandFromBtn(btn);
const cpuHand = getCpuHand(plHand, cheatOn);
const outcome = judgeOutcome(plHand, cpuHand);
});
関数群
//------btn.textContentのグーチョキパーから0、1、2を返す関数
function getPlHandFromBtn(btn) {
const text = btn.textContent;
if (text.includes('グー')) return 0;
if (text.includes('チョキ')) return 1;//前の行でreturn→次の始まりはifでいい。
return 2; //パー
}
const plHand = getPlHandFromBtn(btn);
//------cpuの手とチートONでCpuが必ず負ける関数
function getCpuHand(plHand, cheatOn) {
//チートOFFでランダム出力
if (!cheatOn) {
return Math.floor(Math.random() * 3);
}
//チートONでCpuが必ず負ける
return (plHand + 1) % 3;
}
// const cpuHand = getCpuHand(plHand, cheatOn);
//------勝敗あいこ0、勝ち1、負け2を判定する関数
function judgeOutcome(plHand, cpuHand) {
if (plHand === cpuHand) return 0;//あいこ
if (plHand === 0 && cpuHand === 1) return 1;//pグー、cチョキ
if (plHand === 1 && cpuHand === 2) return 1;//pチョキ、cパー
if (plHand === 2 && cpuHand === 0) return 1;//pパー、cグー
else return 2;//それ以外は負け
}
copy
今のままでは関数定義がイベントハンドラーよりも下にあるため、
処理の順序的になんにも起こりません!!!
つまり、②イベントハンドラーを関数より下に配置します。
//------btn.textContentのグーチョキパーから0、1、2を返す関数
function getPlHandFromBtn(btn) {
const text = btn.textContent;
if (text.includes('グー')) return 0;
if (text.includes('チョキ')) return 1;//前の行でreturn→次の始まりはifでいい。
return 2; //パー
}
const plHand = getPlHandFromBtn(btn);
//------cpuの手とチートONでCpuが必ず負ける関数
function getCpuHand(plHand, cheatOn) {
//チートOFFでランダム出力
if (!cheatOn) {
return Math.floor(Math.random() * 3);
}
//チートONでCpuが必ず負ける
return (plHand + 1) % 3;
}
// const cpuHand = getCpuHand(plHand, cheatOn);
//------勝敗あいこ0、勝ち1、負け2を判定する関数
function judgeOutcome(plHand, cpuHand) {
if (plHand === cpuHand) return 0;//あいこ
if (plHand === 0 && cpuHand === 1) return 1;//pグー、cチョキ
if (plHand === 1 && cpuHand === 2) return 1;//pチョキ、cパー
if (plHand === 2 && cpuHand === 0) return 1;//pパー、cグー
else return 2;//それ以外は負け
}
//--------------Event--------------
cheat.addEventListener('change', (e) => {
cheatOn = e.target.checked;
document.body.classList.toggle('cheat-active');
});
btnGroup.addEventListener('click', (e) => {
const btn = e.target.closest('button');
if (!btn) return;
const plHand = getPlHandFromBtn(btn);
const cpuHand = getCpuHand(plHand, cheatOn);
const outcome = judgeOutcome(plHand, cpuHand);
});
6:出力
いよいよ再生成した式が動くか画面に結果表示させます。
result.textContentをクリックのイベントハンドラー最下部へ配置。
そのためにやるべき事
①result.textContent = 'あなた[phHand] 相手[cpuHand] →[outcome]'
と表示させるために、それぞれtextを代入する定数を作成。
②result.textContent = 'あなた[phHand] 相手[cpuHand] →[outcome]'に定数を当てはめる。
//----結果表示用text代入----
const textHands = ['グー','チョキ','パー'];
const textOutcome = ['→あいこ!','→あなたの勝ち!','→あなたの負け!'];
//---結果画面表示
result.textContent = 'あなた:' + textHands[plHand] + '相手:' + textHands[cpuHand] + '' + textOutcome[outcome]
解説
const textHands = ['グー','チョキ','パー'];
→人とCPUの手を配列で代入。
【2:btn.textContentから0、1、2を返す関数化。】の際に
グー0、チョキ1、パー2で定義しているので、その順番で!
const textOutcome = ['→あいこ!','→あなたの勝ち!','→あなたの負け!'];
→結果表示も同じく配列で代入。
【3:勝敗判定を関数化】の際に
あいこ0、勝ち1、負け2で定義しているので、その順番で!
result.textContent = 'あなた:' + textHands[plHand] + '相手:' + textHands[cpuHand] + '' + textOutcome[outcome]
→それぞれの欄に定数を呼び出し
textHandsは人とcpuのをそれぞれ呼び出しに注意。
7:文字色変更を追加
あとは文字色変更のコードを元に戻してあげたら完成です。
最終形態と変更前のコードを載せます。
最終形態
<script>
const btnGroup = document.querySelector('.button-group');
const result = document.getElementById('result');
const cheat = document.getElementById('cheatMode');
let cheatOn = false; // 初期設定はチートOFF
//------btn.textContentのグーチョキパーから0、1、2を返す関数
function getPlHandFromBtn(btn) {
const text = btn.textContent;
if (text.includes('グー')) return 0;
if (text.includes('チョキ')) return 1;//前の行でreturn→次の始まりはifでいい。
return 2; //パー
}
// const plHand = getPlHandFromBtn(btn);
//------cpuの手とチートONでCpuが必ず負ける関数
function getCpuHand(plHand, cheatOn) {
//チートOFFでランダム出力
if (!cheatOn) {
return Math.floor(Math.random() * 3);
}
//チートONでCpuが必ず負ける
return (plHand + 1) % 3;
}
// const cpuHand = getCpuHand(plHand, cheatOn);
//------勝敗あいこ0、勝ち1、負け2を判定する関数
function judgeOutcome(plHand, cpuHand) {
if (plHand === cpuHand) return 0;//あいこ
if (plHand === 0 && cpuHand === 1) return 1;//pグー、cチョキ
if (plHand === 1 && cpuHand === 2) return 1;//pチョキ、cパー
if (plHand === 2 && cpuHand === 0) return 1;//pパー、cグー
else return 2;//それ以外は負け
}
//--------------Event--------------
cheat.addEventListener('change', (e) => {
cheatOn = e.target.checked;
document.body.classList.toggle('cheat-active');
});
btnGroup.addEventListener('click', (e) => {
const btn = e.target.closest('button');
if (!btn) return;
const plHand = getPlHandFromBtn(btn);
const cpuHand = getCpuHand(plHand, cheatOn);
const outcome = judgeOutcome(plHand, cpuHand);
//----結果表示用text代入----
const textHands = ['グー','チョキ','パー'];
const textOutcome = ['→あいこ!','→あなたの勝ち!','→あなたの負け!'];
//---結果画面表示
result.textContent = 'あなた:' + textHands[plHand] + '相手:' + textHands[cpuHand] + '' + textOutcome[outcome]
// 結果に応じて色を変える
let textcolor;
if (outcome === 0) {
textcolor = "draw";
} else if (outcome === 1) {
textcolor = "win";
} else {
textcolor = "lose";
}
result.classList.remove("draw", "win", "lose");
result.classList.add(textcolor);
});
</script>
変更前
<script>
const btnGroup = document.querySelector('.button-group');
const result = document.getElementById('result');
const cheat = document.getElementById('cheatMode');
let cheatOn = false; // 初期設定はチートOFF
cheat.addEventListener('change', (e) => {
cheatOn = e.target.checked;
if (e.target.checked) {
document.body.classList.add('cheat-active');
} else {
document.body.classList.remove('cheat-active');
}
});
btnGroup.addEventListener('click', (e) => {
const btn = e.target.closest('button');
if (!btn) return;
// グー=0, チョキ=1, パー=2
let plHand;
if (btn.textContent.includes("グー")) {
plHand = 0;
} else if (btn.textContent.includes("チョキ")) {
plHand = 1;
} else {
plHand = 2;
}
// CPU の手を決定
let cpuHand;
if (cheatOn) {
cpuHand = (plHand + 1) % 3; // チートONなら必ず負ける手
} else {
cpuHand = Math.floor(Math.random() * 3);
}
// 勝敗判定
let outcome; // 0=あいこ, 1=勝ち, 2=負け
if (plHand === 0) {
if (cpuHand === 0) {
result.textContent = "あなた:グー 相手:グー →あいこ!";
outcome = 0;
} else if (cpuHand === 1) {
result.textContent = "あなた:グー 相手:チョキ →あなたの勝ち!";
outcome = 1;
} else {
result.textContent = "あなた:グー 相手:パー →あなたの負け!";
outcome = 2;
}
} else if (plHand === 1) {
if (cpuHand === 1) {
result.textContent = "あなた:チョキ 相手:チョキ →あいこ!";
outcome = 0;
} else if (cpuHand === 2) {
result.textContent = "あなた:チョキ 相手:パー →あなたの勝ち!";
outcome = 1;
} else {
result.textContent = "あなた:チョキ 相手:グー →あなたの負け!";
outcome = 2;
}
} else {
if (cpuHand === 2) {
result.textContent = "あなた:パー 相手:パー →あいこ!";
outcome = 0;
} else if (cpuHand === 0) {
result.textContent = "あなた:パー 相手:グー →あなたの勝ち!";
outcome = 1;
} else {
result.textContent = "あなた:パー 相手:チョキ →あなたの負け!";
outcome = 2;
}
}
// 結果に応じて色を変える
let textcolor;
if (outcome === 0) {
textcolor = "draw";
} else if (outcome === 1) {
textcolor = "win";
} else {
textcolor = "lose";
}
result.classList.remove("draw", "win", "lose");
result.classList.add(textcolor);
});
</script>
どうでしょうか?ちょっとは見やすくなったかな?
プロのエンジニアは先を見越しながら関数化させると思いますが、
出来上がっていない状態で関数化させる事が不安なので、
今回は完成してから関数化に挑戦して見ました。
ベースの形も理解した上で関数化にも挑戦できた事は知識面ではステップアップになったと思います。
では、お疲れ様でした!!
Conclusion
今回の関数化で、「クリック → 手の取得 → CPU決定 → 勝敗判定 → 表示」 の流れが追いやすくなりました。
今後は、同じ考え方で「表示(UI)」と「判定ロジック」をさらに分けると、拡張もしやすくなると思います。