
【ゲーム】JavaScript:61 カップシャッフルゲーム
「🥤 カップシャッフルゲーム 🥤」は、ボールを隠したカップを高速でシャッフルし、どのカップにボールが入っているかを当てる集中力テスト・マインドゲームです。カップの動きをしっかり目で追い、タイミングよくクリックして正解を狙います。
遊び方・操作方法
- タイトル画面で「スタート」ボタンをクリック。
- ゲーム画面では、まずカップの下にボール(🔴)を一瞬だけ表示。
- ボールを隠すと同時にカップが自動でシャッフルを開始します(12回ランダムスワップ)。
- シャッフルが完了したら、「どのカップだ?」というメッセージに切り替わり、カップをクリックして選択。
- 正解なら「正解!」、不正解なら「残念!」が表示され、タイトル画面に戻ります。
ルール
- カップ数:3個
- シャッフル回数:12手順
- シャッフル速度:1ステップあたり約0.54秒のアニメーション
- 正解判定:プレイヤーがクリックしたカップIDと実際にボールを持つカップIDが一致
- 終了:一発勝負。正解/不正解後に結果画面へ。
🎮ゲームプレイ
以下のリンク先から実際にプレイできます。
61 カップシャッフルゲーム
素材のダウンロード
以下のリンクから使用する素材をダウンロードできます。
cup_shuffle_title.png | cup_shuffle_bg.png |
---|---|
![]() | ![]() |
ゲーム画面イメージ

プログラム全文(cup_shuffle.html)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>🥤 カップシャッフルゲーム 🥤</title>
<style>
html, body {
margin: 0; padding: 0;
width: 100vw; height: 100vh;
font-family: 'Yu Gothic', 'Meiryo', sans-serif;
background: url('cup_shuffle_bg.png') no-repeat center center fixed;
background-size: cover;
}
body {
width: 100vw;
height: 100vh;
overflow: auto;
}
.container {
width: 800px;
margin: 40px auto;
background: rgba(255,255,255,0.94);
border-radius: 22px;
box-shadow: 0 4px 24px rgba(0,0,0,0.13);
padding-bottom: 32px;
min-height: 500px;
position: relative;
}
.title-img {
display: block;
margin: 20px auto 18px auto;
width: 340px;
max-width: 80%;
height: auto;
}
h1 {
text-align: center;
font-size: 2.1em;
margin: 0.6em 0 0.15em 0;
color: #1976d2;
text-shadow: 1px 1px 7px #fff;
letter-spacing: 0.04em;
}
.rule-section {
background: rgba(235,245,255,0.98);
border-radius: 14px;
margin: 28px 32px 14px 32px;
padding: 16px 24px;
box-shadow: 0 2px 8px #4d90fe22;
}
.rule-title {
text-align: center;
font-weight: bold;
font-size: 1.3em;
margin-bottom: 10px;
color: #1976d2;
}
.rule-text {
text-align: left;
font-size: 1.09em;
line-height: 1.65;
color: #174078;
letter-spacing: 0.01em;
}
.btn {
display: block;
margin: 28px auto 0 auto;
padding: 14px 50px;
font-size: 1.2em;
border: none;
border-radius: 28px;
background: linear-gradient(90deg, #87c9fa, #1565c0);
color: #fff;
font-weight: bold;
box-shadow: 0 2px 8px #1976d266;
cursor: pointer;
transition: background 0.2s;
}
.btn:hover { background: linear-gradient(90deg, #b7e4fe, #1976d2); }
.game-area {
width: 740px;
margin: 36px auto 0 auto;
background: rgba(240,248,255,0.96);
border-radius: 16px;
box-shadow: 0 1px 10px #1565c055;
display: flex;
flex-direction: column;
align-items: center;
min-height: 250px;
padding: 16px 0 20px 0;
user-select: none;
}
.cups-area {
width: 600px;
height: 160px;
margin: 32px auto 16px auto;
position: relative;
}
.cup {
width: 120px;
height: 130px;
background: #fffbe6;
border: 3px solid #1976d2;
border-radius: 0 0 90px 90px / 0 0 110px 110px;
position: absolute;
left: 0; top: 0;
box-shadow: 0 6px 14px #1565c044;
cursor: pointer;
display: flex;
align-items: flex-end;
justify-content: center;
transition: left 0.55s cubic-bezier(.53,.1,.5,1.2), border-color 0.16s, box-shadow 0.16s;
z-index: 1;
}
.cup.selected {
border-color: #e53935;
box-shadow: 0 0 25px #e5393544;
z-index: 2;
}
.ball {
position: absolute;
left: 50%;
bottom: 10px;
transform: translateX(-50%);
font-size: 2.6em;
z-index: 10;
pointer-events: none;
opacity: 1;
transition: opacity 0.2s;
}
.hide-ball {
opacity: 0;
}
.message-box {
text-align: center;
background: rgba(235,245,255,0.98);
font-size: 1.7em;
color: #1976d2;
font-weight: bold;
border-radius: 16px;
box-shadow: 0 4px 24px #1976d233;
width: 80%;
max-width: 480px;
margin: 34px auto 0 auto;
padding: 32px 18px;
position: relative;
z-index: 2;
}
.center-btn { margin: 22px auto 0 auto; }
.info {
text-align: center;
color: #1976d2;
font-size: 1.13em;
margin: 4px 0 0 0;
}
@media (max-width: 900px) {
.container, .game-area { width: 98vw !important; min-width: 0; }
.cups-area { width: 97vw !important;}
.cup { width: 22vw; height: 23vw; min-width:80px; min-height:90px; }
}
</style>
</head>
<body>
<div class="container" id="main-container"></div>
<script>
// =============================
// カップシャッフルゲーム
// =============================
const NUM_CUPS = 3;
const SWAP_COUNT = 12;
let ballPos = 1; // ボールの位置(0,1,2)
let cups = [0,1,2]; // カップのインデックス(実際の位置)
let swapIndex = 0;
let shuffling = false;
let canSelect = false;
let resultMsg = "";
// カップのX座標(ピクセル単位)
const cupX = [40, 240, 440];
// タイトル画面を表示
function showTitleScreen() {
document.getElementById('main-container').innerHTML = `
<h1>🥤 カップシャッフルゲーム 🥤</h1>
<img src="cup_shuffle_title.png" class="title-img" alt="カップシャッフルゲーム タイトル">
<div class="rule-section">
<div class="rule-title">🥤 遊び方・ルール 🥤</div>
<div class="rule-text">
・ボールが入ったカップがシャッフルされます。<br>
・よく見て、ボールがどのカップに入っているか当てましょう!<br>
・カップをクリックして答えを選択します。<br>
・正解なら「正解!」、外れたら「残念!」と表示されます。
</div>
</div>
<button class="btn" id="start-btn">スタート</button>
`;
document.getElementById('start-btn').onclick = showGameScreen;
}
// ゲーム画面
function showGameScreen() {
ballPos = Math.floor(Math.random() * NUM_CUPS); // ボール初期位置をランダム
cups = [0,1,2];
swapIndex = 0;
shuffling = false;
canSelect = false;
resultMsg = "";
renderGame();
setTimeout(()=>{
revealBall(false); // 一瞬見せる
setTimeout(()=>{
revealBall(true); // ボール隠す
setTimeout(startShuffle, 600);
}, 900);
}, 400);
}
// ボールを隠す/見せる
function revealBall(hide) {
// すべてのcupを確認し、ballPos(ボールの位置)のみボールを見せる
const cupsNodes = document.querySelectorAll('.cup');
cupsNodes.forEach((cupNode, idx) => {
const ball = cupNode.querySelector('.ball');
// ballPosは「どのカップIDにボールがあるか」、cups[idx]は「この位置にいるカップID」
// 位置idxのカップIDがballPosと一致するものだけボールを見せる
if (cups[idx] === ballPos) {
if(hide) ball.classList.add('hide-ball');
else ball.classList.remove('hide-ball');
} else {
ball.classList.add('hide-ball');
}
});
}
// ゲーム画面描画(カップをアニメ配置)
function renderGame(selectedIdx = null) {
let cupHtml = '';
for(let i=0; i<NUM_CUPS; i++) {
// cups配列は現在「座標に何番カップがあるか」= cups[物理位置]=カップID
let cupId = cups[i];
// cupIdがballPosと一致するカップだけボールを描画
let showBall = (cupId === ballPos && !shuffling && !canSelect);
cupHtml += `
<div class="cup${selectedIdx===i?' selected':''}" data-cup="${i}"
style="left:${cupX[i]}px;" onclick="onSelectCup(${i})">
${showBall ? `<div class="ball">🔴</div>` : `<div class="ball hide-ball">🔴</div>`}
</div>
`;
}
document.getElementById('main-container').innerHTML = `
<h1>🥤 カップシャッフルゲーム 🥤</h1>
<div class="game-area">
<div class="info" id="info">${shuffling ? "シャッフル中..." : canSelect ? "どのカップ?クリックで選ぼう!" : "カップの位置を覚えてください。"}</div>
<div class="cups-area">${cupHtml}</div>
${resultMsg ? `<div class="message-box">${resultMsg}</div>` : ""}
</div>
`;
}
// シャッフル開始
function startShuffle() {
shuffling = true;
canSelect = false;
swapIndex = 0;
shuffleStep();
}
// シャッフル手順(アニメで交換)
function shuffleStep() {
if (swapIndex >= SWAP_COUNT) {
shuffling = false;
canSelect = true;
setTimeout(()=>{ renderGame(); }, 250);
return;
}
// 2つのカップ物理位置をランダムで選ぶ
let a = Math.floor(Math.random()*NUM_CUPS);
let b;
do { b = Math.floor(Math.random()*NUM_CUPS); } while (b === a);
// 見た目の位置をアニメでスワップ
let cupNodes = document.querySelectorAll('.cup');
let [leftA, leftB] = [cupX[a], cupX[b]];
cupNodes[a].style.left = leftB + "px";
cupNodes[b].style.left = leftA + "px";
// 実際の配列順もスワップ(見た目の動きが終わるまで配列は保留)
setTimeout(()=>{
[cups[a], cups[b]] = [cups[b], cups[a]];
renderGame();
swapIndex++;
setTimeout(shuffleStep, 380);
}, 540); // アニメduration+α
}
// カップ選択時
function onSelectCup(idx) {
if (!canSelect) return;
canSelect = false;
let cupId = cups[idx];
let isCorrect = (cupId === ballPos);
resultMsg = isCorrect ? "🎉 正解!お見事!" : "😢 残念!またチャレンジしてね。";
renderGame(idx);
setTimeout(showEndScreen, 1500, isCorrect);
}
// 終了画面
function showEndScreen(isCorrect) {
document.getElementById('main-container').innerHTML = `
<h1>🥤 カップシャッフルゲーム 🥤</h1>
<img src="cup_shuffle_title.png" class="title-img" alt="カップシャッフルゲーム タイトル">
<div class="message-box">
${isCorrect ? "🎉 おめでとう!正解です!🎉" : "😢 残念!また挑戦してね。"}
</div>
<button class="btn center-btn" id="back-title-btn">タイトル画面に戻る</button>
`;
document.getElementById('back-title-btn').onclick = showTitleScreen;
}
// windowに選択関数を生やす
window.onSelectCup = onSelectCup;
// 初期表示
showTitleScreen();
</script>
</body>
</html>
アルゴリズムの流れ
手順 | 処理内容 |
---|---|
1 | showTitleScreen() でタイトル画面の HTML を描画 |
2 | 「スタート」クリック → showGameScreen() でボール位置をランダム設定→一瞬見せ→隠す→シャッフル |
3 | startShuffle() → shuffleStep() で12回ランダムペアを選び、見た目と配列の両方をスワップ |
4 | シャッフル完了後に canSelect=true 、画面内のカップをクリック可能 |
5 | クリックされた位置 idx のカップID と ballPos を比較 → 正誤メッセージをセット |
6 | 1.5秒後に showEndScreen() を呼び出し、正解/不正解画面を描画 |
7 | 「タイトルに戻る」ボタンで再びタイトル画面へ |
関数の解説
関数名 | 機能概要 | 詳細説明 |
---|---|---|
showTitleScreen() | タイトル画面描画 | スタートボタン・ルール説明を含む初期画面を描画。クリックで showGameScreen() を呼び出し。 |
showGameScreen() | ゲーム画面初期化 | ballPos をランダム設定し、カップを初期配置。ボールの一瞬表示→隠蔽を経て startShuffle() を呼び出す。 |
revealBall(hide) | ボールの見せ/隠し切り替え | cups 配列と ballPos の一致判定で対象カップのみボール要素 (.ball ) のクラスを制御。 |
renderGame(sel) | 画面再描画 | 現在の cups 配列・状態フラグ (shuffling / canSelect ) を反映し、カップ要素とメッセージを動的に再構築。 |
startShuffle() | シャッフル開始 | フラグ初期化後、shuffleStep() によって非同期スワップを連続実行。 |
shuffleStep() | シャッフルステップ(アニメ+配列) | 物理位置インデックス a ,b のランダム選出 → CSS アニメーションで左右入れ替え → 配列内もスワップ → 次ステップへ再帰呼び出し。 |
onSelectCup(i) | プレイヤー選択処理 | canSelect フラグチェック後、選択位置 i のカップID と ballPos を比較。結果メッセージ設定→ showEndScreen() 呼び出し。 |
showEndScreen(c) | 結果画面描画 | 正誤に応じたメッセージと「タイトルに戻る」ボタンを表示。ボタンで showTitleScreen() を再呼び出し。 |
改造のポイント
- シャッフル難易度:
SWAP_COUNT
を増減させるほか、ステップごとのアニメ速度(setTimeoutの値)を調整し、初心者~上級者向けモードを実装。 - カップ数変更:
NUM_CUPS
を増やして難易度を上げる。多いほど視認が困難になります。 - マルチラウンド制:3ラウンド制など複数回チャレンジ制を導入し、得点合計を競わせるランキング要素を追加。
- 視覚・サウンド演出:シャッフル中のBGM、クリック時のSE、正解時のエフェクトを加えると没入感アップ。
- モバイル最適化:タッチ操作時のアニメ速度やクリック判定領域を微調整し、指でタップしやすくする。
アドバイス
まずはシャッフル/選択の安定動作を最優先で実装し、次にUI演出・サウンドを足して「見て楽しい・聞いて楽しい」ゲーム体験を作りましょう。難易度やモードを段階的に増やすことで、長期的に遊んでもらえるコンテンツになります!