ハイ&ロー(カード数字当てゲーム)
このゲームは「次のカードは今より高い(High)か低い(Low
)か」を当てるシンプルなゲームです。
画像なし・外部ライブラリなし——Math.random() と if
文だけで動く、最小構成のゲームを一緒に作りましょう。
学べること
- HTML:ボタン要素・カード表示エリアの構造化
- CSS:カードのデザイン+裏返りアニメーション(
@keyframe s) - JavaScript:乱数生成(
Math.random())・条件分岐 (if / else if)・勝敗カウント
完成イメージ:1〜13の数字が書かれたカードが表示され、「▲ High」か「▼ Low」を選ぶと次のカードがめくれて正解・不正解が表示されます。
ステップ1:HTMLの骨格を作る
ゲームに必要な要素は4つです。まず全体像を把握してから書き始めましょう。
- 現在のカード番号を表示する
#current-card - 「High」「Low」を選ぶ2つのボタン
- 正解・不正解を表示する
#result - スコア(正解数・不正解数)を表示する
#score
<div id="game">
<p id="score">正解: 0 / 不正解: 0</p>
<div id="card-area">
<div id="current-card">?</div>
</div>
<p id="message">次のカードは高い?低い?</p>
<div id="btn-area">
<button onclick="guess('high')">▲ High</button>
<button onclick="guess('low')">▼ Low</button>
</div>
<p id="result"></p>
<button id="btn-next" onclick="nextRound()" style="display:none">次のカード
▶</button>
<button onclick="resetGame()">↺ リセット</button>
</div>
style="display:none" を最初からつけておくことで、ゲーム開始時に
「次のカード」ボタンを隠せます。ボタンを押したときに JavaScript
で表示・非表示を切り替えます。
ステップ2:CSSでカードをデザインする
カードらしく見えるようにスタイルを整えます。テーマのカラー変数(--primar
y・--accent
など)を活用することで、サイト全体のデザインと統一感を出せます。
#game {
text-align: center;
padding: 2rem 1rem;
font-family: 'Share Tech Mono', monospace;
color: var(--text);
}
#card-area {
margin: 1.5rem auto;
width: 120px;
height: 160px;
}
#current-card {
width: 120px;
height: 160px;
border: 3px solid var(--primary);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 3rem;
font-family: 'Press Start 2P', monospace;
color: var(--primary);
background: var(--bg-mid);
box-shadow: 0 0 16px rgba(0,255,65,0.4);
}
/* アニメーションクラス(JS から付与) */
#current-card.flip {
animation: cardFlip 0.4s ease;
}
#btn-area {
display: flex;
gap: 1rem;
justify-content: center;
margin: 1rem 0;
}
#btn-area button {
padding: 0.7rem 1.8rem;
font-family: 'Press Start 2P', monospace;
font-size: 0.75rem;
border: none;
border-radius: 6px;
cursor: pointer;
transition: transform 0.1s, opacity 0.2s;
}
#btn-area button:first-child { background: var(--primary); color: var(--bg-darkest);
}
#btn-area button:last-child { background: var(--accent); color: var(--bg-darkest);
}
#btn-area button:hover { opacity: 0.85; transform: scale(1.05); }
#result {
font-family: 'Press Start 2P', monospace;
font-size: 1.1rem;
min-height: 2rem;
margin: 0.5rem 0;
}
#btn-next,
#game > button:last-of-type {
margin: 0.4rem;
padding: 0.5rem 1.4rem;
font-family: 'Share Tech Mono', monospace;
background: var(--bg-mid);
color: var(--text);
border: 1px solid var(--primary);
border-radius: 4px;
cursor: pointer;
transition: background 0.2s, color 0.2s;
}
#btn-next:hover,
#game > button:last-of-type:hover {
background: var(--primary);
color: var(--bg-darkest);
}
カードがめくれるアニメーション(@keyframes
cardFlip)は「追加CSS」の末尾に記述します。
ステップ3:乱数でカードの数字を生成する
Math.random() は 0以上1未満のランダムな小数を返します。これを1〜1
3の整数に変換する手順を確認しましょう。
// 1〜13 のランダムな整数を返す関数
function getCard() {
return Math.floor(Math.random() * 13) + 1;
}
仕組みを1行ずつ分解すると:
Math.random() ——例:0.742…
× 13 ——→ 0 〜 12.999…
Math.floor() ——→ 0 〜 12(小数切り捨て)
+ 1 ——→ 1 〜 13
ブラウザの開発者ツール(F12 → コンソール)で何度か実行して確認してみましょう。
Math.floor(Math.random() * 13) + 1
// → 実行するたびに 1〜13 のどれかが返ってくる
ステップ4:if 文で勝敗を判定する
プレイヤーの選択('high' or 'low')と、次に生成され
たカードの数字を比較して正解・不正解を決めます。
判定条件を整理すると次のようになります:
- 「High を選んだ」かつ「次 > 現在」→ 正解
- 「Low を選んだ」かつ「次 < 現在」→ 正解
- それ以外(同じ数字を含む)→ 不正解
function guess(choice) {
const nextNum = getCard(); // 次のカードを生成
// 2つの条件を && で同時に確認する
let win = false;
if (choice === 'high' && nextNum > currentNum) {
win = true;
} else if (choice === 'low' && nextNum < currentNum) {
win = true;
}
// 結果を表示
const resultEl = document.getElementById('result');
if (win) {
resultEl.textContent = '✔ 正解!';
resultEl.style.color = 'var(--primary)';
wins++;
} else {
resultEl.textContent = '✘ ハズレ';
resultEl.style.color = '#ff4444';
losses++;
}
currentNum = nextNum; // カードを次の数字に更新
}
&&(アンド)は「AかつB」を表す論理演算子です。左右どちらかが
false なら全体が false になります。「High
を選んだだけ」「次の数字が大きいだけ」では正解になりません。
ステップ5:完成コードをまとめる
ここまでのコードをひとつのHTMLファイルにまとめた完成版です。そのままローカルに 保存してブラウザで開けば動きます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ハイ&ロー</title>
<style>
body {
background: #0a0a0a;
color: #c8c8c8;
font-family: 'Courier New', monospace;
display: flex;
justify-content: center;
padding: 2rem;
}
#game { text-align: center; }
#current-card {
width: 120px; height: 160px;
margin: 1rem auto;
border: 3px solid #00ff41;
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 3rem; color: #00ff41; background: #111;
animation: none;
}
#current-card.flip { animation: cardFlip 0.4s ease; }
@keyframes cardFlip {
0% { transform: scaleX(1); opacity: 1; }
40% { transform: scaleX(0); opacity: 0.2; }
60% { transform: scaleX(0); opacity: 0.2; }
100% { transform: scaleX(1); opacity: 1; }
}
#btn-area { display: flex; gap: 1rem; justify-content: center; margin: 1rem 0; }
button { padding: 0.7rem 1.8rem; font-size: 0.9rem; border: none; border-radius:
6px; cursor: pointer; }
#btn-area button:first-child { background: #00ff41; color: #050505; }
#btn-area button:last-child { background: #ffd700; color: #050505; }
#result { font-size: 1.2rem; min-height: 2rem; }
#btn-next, #btn-reset {
background: #111; color: #c8c8c8;
border: 1px solid #00ff41; margin: 0.3rem;
}
</style>
</head>
<body>
<div id="game">
<h1 style="font-size:1.2rem; color:#00ff41;">ハイ&ロー</h1>
<p id="score">正解: 0 / 不正解: 0</p>
<div id="current-card">?</div>
<p>次のカードは高い?低い?</p>
<div id="btn-area">
<button onclick="guess('high')">▲ High</button>
<button onclick="guess('low')">▼ Low</button>
</div>
<p id="result"></p>
<button id="btn-next" onclick="nextRound()" style="display:none">次のカード
▶</button>
<button id="btn-reset" onclick="resetGame()">↺ リセット</button>
</div>
<script>
let currentNum = getCard();
let wins = 0, losses = 0;
document.getElementById('current-card').textContent = currentNum;
function getCard() {
return Math.floor(Math.random() * 13) + 1;
}
function guess(choice) {
const nextNum = getCard();
const win = (choice === 'high' && nextNum > currentNum)
|| (choice === 'low' && nextNum < currentNum);
// カードフリップ
const cardEl = document.getElementById('current-card');
cardEl.classList.add('flip');
setTimeout(() => {
cardEl.textContent = nextNum;
cardEl.classList.remove('flip');
}, 200);
const resultEl = document.getElementById('result');
if (win) {
wins++;
resultEl.textContent = '✔ 正解!';
resultEl.style.color = '#00ff41';
} else {
losses++;
resultEl.textContent = '✘ ハズレ';
resultEl.style.color = '#ff4444';
}
currentNum = nextNum;
document.getElementById('score').textContent =
'正解: ' + wins + ' / 不正解: ' + losses;
document.getElementById('btn-area').style.display = 'none';
document.getElementById('btn-next').style.display = 'inline-block';
}
function nextRound() {
document.getElementById('result').textContent = '';
document.getElementById('btn-area').style.display = 'flex';
document.getElementById('btn-next').style.display = 'none';
}
function resetGame() {
wins = 0; losses = 0;
currentNum = getCard();
document.getElementById('current-card').textContent = currentNum;
document.getElementById('score').textContent = '正解: 0 / 不正解: 0';
document.getElementById('result').textContent = '';
document.getElementById('btn-area').style.display = 'flex';
document.getElementById('btn-next').style.display = 'none';
}
</script>
</body>
</html>
完成デモ
正解: 0 / 不正解: 0
次のカードは高い?低い?
応用・発展のヒント
- 連続正解ボーナス:正解が続くとスコアが倍になる「コンボシステ
ム」を追加してみましょう。変数
comboを用意して正解のたびにインクリメント、不正解でリセットします。 - スート(マーク)を追加:♠♥♦♣
をランダムに表示するとよりリアルなカードになります。配列から
Math.random()で要素を選ぶ練習になります。 - デッキ管理:52枚のデッキを用意して一度出たカードを取り除くと
、より本格的なゲームになります(
Array.splice()の練習に最適)。 - ハイスコア保存:
localStorageを使えばページをリロードしても記録が残ります。localStorage.setItem()/getItem()を調べてみましょう。