【ゲーム】JavaScript:59 絵文字並び替えパズル

 「🎲 絵文字並び替えパズル 🎲」は、バラバラに並んだ絵文字をお手本どおりの順序にドラッグ&ドロップで並び替えるパズルゲームです。制限時間45秒以内に並べ替えを完了するとクリア。レベルが上がるごとに絵文字の種類が増え、難易度がアップします。

遊び方・操作方法

  1. スタートボタンをクリックすると、今のレベルの絵文字パズルが開始します。
  2. 上部に表示される「お手本」の順序をよく見て覚えてください。
  3. 下のエリアに並ぶ絵文字を、ドラッグ&ドロップで位置を入れ替えて並べ替えます。
  4. 正しい順序になったら自動的にクリア判定が入り、次のレベルへ(または終了画面へ)進みます。
  5. 制限時間45秒を過ぎるとゲームオーバーです。

ルール

  • 制限時間:45秒
  • レベル数:全7ステージ
  • 各レベル:レベル1→4種類、レベル2→5種類…最大レベル7→9種類の絵文字を使用
  • クリア条件:並び替えた絵文字が「お手本」と完全に一致
  • 失敗条件:制限時間内に並び替えが完了しない。

🎮ゲームプレイ

以下のリンク先から実際にプレイできます。

59 絵文字並び替えパズル

素材のダウンロード

以下のリンクから使用する素材をダウンロードできます。

emoji_sorting_puzzle_title.pngemoji_sorting_puzzle_bg.png

ゲーム画面イメージ

プログラム全文(emoji_sorting_puzzle.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('emoji_sorting_puzzle_bg.png') no-repeat center center fixed;
      background-size: cover;
    }
    body {
      width: 100vw;
      height: 100vh;
      overflow: auto;
    }
    .container {
      width: 1000px;
      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: 600px;
      position: relative;
    }
    .title-img {
      display: block;
      margin: 20px auto 18px auto;
      width: 400px;
      max-width: 80%;
      height: auto;
    }
    h1 {
      text-align: center;
      font-size: 2.1em;
      margin: 0.6em 0 0.15em 0;
      color: #3b5195;
      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: #3b5195;
    }
    .rule-text {
      text-align: left;
      font-size: 1.09em;
      line-height: 1.65;
      color: #233d6c;
      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, #7eafff, #3b5195);
      color: #fff;
      font-weight: bold;
      box-shadow: 0 2px 8px #3b519577;
      cursor: pointer;
      transition: background 0.2s;
    }
    .btn:hover { background: linear-gradient(90deg, #a7cfff, #233d6c); }
    .game-area {
      width: 900px;
      margin: 36px auto 0 auto;
      background: rgba(240,248,255,0.96);
      border-radius: 16px;
      box-shadow: 0 1px 10px #3b519555;
      display: flex;
      flex-direction: column;
      align-items: center;
      min-height: 220px;
      padding: 24px 0 36px 0;
      user-select: none;
    }
    .timer-bar {
      font-size: 1.18em;
      color: #c62828;
      margin-bottom: 12px;
      font-weight: bold;
      letter-spacing: 0.06em;
      text-align: center;
    }
    .level-bar {
      font-size: 1.11em;
      color: #1976d2;
      margin-bottom: 8px;
      font-weight: bold;
      letter-spacing: 0.03em;
      text-align: center;
    }
    .answer-sample {
      display: flex;
      justify-content: center;
      align-items: center;
      margin-bottom: 18px;
      gap: 15px;
      flex-wrap: wrap;
      background: rgba(255,255,255,0.93);
      border-radius: 12px;
      padding: 8px 10px 2px 10px;
      border: 1px solid #90caf9;
      font-size: 1.05em;
    }
    .sample-item {
      display: flex;
      align-items: center;
      gap: 5px;
      margin-bottom: 6px;
      min-width: 86px;
    }
    .sample-emoji {
      font-size: 1.6em;
      margin-right: 3px;
      vertical-align: middle;
    }
    .emoji-list {
      display: flex;
      justify-content: center;
      margin: 18px 0;
      gap: 18px;
      min-height: 80px;
    }
    .emoji-cell {
      font-size: 2.7em;
      background: #fff;
      border: 3px solid #3b5195;
      border-radius: 15px;
      width: 70px;
      height: 70px;
      display: flex;
      align-items: center;
      justify-content: center;
      margin: 0 4px;
      box-shadow: 0 2px 8px #3b519533;
      cursor: grab;
      transition: border-color 0.1s, background 0.1s;
    }
    .emoji-cell.drag-over {
      border-color: #fbc02d;
      background: #fffde7;
    }
    .message-box {
      text-align: center;
      background: rgba(235,245,255,0.98);
      font-size: 2em;
      color: #3b5195;
      font-weight: bold;
      border-radius: 16px;
      box-shadow: 0 4px 24px #3b519533;
      width: 80%;
      max-width: 480px;
      margin: 40px auto 0 auto;
      padding: 38px 18px;
      position: relative;
      z-index: 2;
    }
    .center-btn { margin: 24px auto 0 auto; }
    @media (max-width: 1100px) {
      .container { width: 98vw !important; min-width: 0; }
      .game-area { width: 98vw !important; min-width: 0; }
      .emoji-list { flex-wrap: wrap; }
      .emoji-cell { width: 54px; height: 54px; font-size:2em;}
      .sample-item { min-width: 60px;}
    }
  </style>
</head>
<body>
  <div class="container" id="main-container"></div>
  <script>
    // ====== 絵文字並び替えパズル ======
    // 各レベルの絵文字セットと日本語名称
    const EMOJI_SETS = [
      {emojis: ["🍎","🍌","🍇","🍉"], names: ["りんご", "バナナ", "ぶどう", "すいか"]},
      {emojis: ["🚗","🚌","🚙","🚓","🚕"], names: ["車", "バス", "SUV", "パトカー", "タクシー"]},
      {emojis: ["🌸","🌻","🌼","🌹","🌺","🌷"], names: ["さくら", "ひまわり", "たんぽぽ", "ばら", "ハイビスカス", "チューリップ"]},
      {emojis: ["🐶","🐱","🐰","🦊","🐻","🐼","🐨"], names: ["いぬ", "ねこ", "うさぎ", "きつね", "くま", "パンダ", "コアラ"]},
      {emojis: ["🥕","🍆","🥦","🌽","🥒","🍅","🍄","🧄"], names: ["にんじん", "なす", "ブロッコリー", "とうもろこし", "きゅうり", "トマト", "きのこ", "にんにく"]},
      {emojis: ["🔴","🟠","🟡","🟢","🔵","🟣","⚪","⚫","🟤"], names: ["赤", "オレンジ", "黄", "緑", "青", "紫", "白", "黒", "茶"]},
      {emojis: ["🦁","🐯","🐮","🐸","🐵","🐧","🐔","🦆","🐳"], names: ["ライオン", "トラ", "うし", "かえる", "さる", "ペンギン", "にわとり", "アヒル", "くじら"]}
    ];
    let level = 0;
    let emojiOrder = [];
    let answerOrder = [];
    let answerNames = [];
    let timer = 45;
    let intervalId = null;
    let dragFrom = null;

    // タイトル画面
    function showTitleScreen() {
      clearInterval(intervalId);
      document.getElementById('main-container').innerHTML = `
        <h1>🎲 絵文字並び替えパズル 🎲</h1>
        <img src="emoji_sorting_puzzle_title.png" class="title-img" alt="絵文字並び替えパズル タイトル">
        <div class="rule-section">
          <div class="rule-title">🎲 遊び方・ルール 🎲</div>
          <div class="rule-text">
            ・バラバラに並んだ絵文字をドラッグ&ドロップで正しい順番に並び替えましょう。<br>
            ・正しい順番にできたらクリアです。<br>
            ・レベルが上がるごとに絵文字が増えていきます。<br>
            ・制限時間(45秒)以内にクリアできなければゲームオーバーです。<br>
            ・「お手本」として正しい順番が上に表示されます。
          </div>
        </div>
        <button class="btn" id="start-btn">スタート</button>
      `;
      document.getElementById('start-btn').onclick = ()=>startGame(0);
    }

    // ゲーム開始
    function startGame(startLevel) {
      level = startLevel;
      startStage();
    }

    // 各レベルの開始
    function startStage() {
      clearInterval(intervalId);
      // 絵文字セットと日本語名称
      let emojiSet = EMOJI_SETS[level];
      answerOrder = emojiSet.emojis.slice();
      answerNames = emojiSet.names.slice();
      // シャッフル(元と同じなら再シャッフル)
      do {
        emojiOrder = shuffleArray(emojiSet.emojis);
      } while (emojiOrder.join('') === answerOrder.join(''));
      timer = 45;
      showGameScreen();
      updateTimerBar();
      intervalId = setInterval(() => {
        timer--;
        updateTimerBar();
        if (timer <= 0) {
          clearInterval(intervalId);
          setTimeout(showEndScreen, 300, false);
        }
      }, 1000);
    }

    // 配列をランダムシャッフル
    function shuffleArray(arr) {
      let tmp = arr.slice();
      for (let i = tmp.length-1; i>0; i--) {
        let j = Math.floor(Math.random() * (i+1));
        [tmp[i], tmp[j]] = [tmp[j], tmp[i]];
      }
      return tmp;
    }

    // ゲーム画面
    function showGameScreen(msg) {
      document.getElementById('main-container').innerHTML = `
        <h1>🎲 絵文字並び替えパズル 🎲</h1>
        <div class="game-area">
          <div class="timer-bar" id="timer-bar"></div>
          <div class="level-bar">レベル:${level+1} / ${EMOJI_SETS.length}</div>
          <div class="answer-sample" id="answer-sample"></div>
          <div class="emoji-list" id="emoji-list"></div>
        </div>
      `;
      drawAnswerSample();
      drawEmojiList();
      updateTimerBar();
    }

    // 正解順サンプル(絵文字+日本語名称)を描画
    function drawAnswerSample() {
      const area = document.getElementById('answer-sample');
      area.innerHTML = '';
      for(let i=0; i<answerOrder.length; i++){
        const div = document.createElement('div');
        div.className = "sample-item";
        div.innerHTML = `<span class="sample-emoji">${answerOrder[i]}</span> ${answerNames[i]}`;
        area.appendChild(div);
      }
    }

    // 並べ替え対象の絵文字リストを描画
    function drawEmojiList() {
      const list = document.getElementById('emoji-list');
      list.innerHTML = '';
      for (let i = 0; i < emojiOrder.length; i++) {
        const cell = document.createElement('div');
        cell.className = "emoji-cell";
        cell.textContent = emojiOrder[i];
        cell.draggable = true;
        cell.dataset.index = i;

        // ドラッグイベント
        cell.ondragstart = e => {
          dragFrom = i;
          cell.style.opacity = 0.5;
        };
        cell.ondragend = e => {
          dragFrom = null;
          cell.style.opacity = 1;
        };
        cell.ondragover = e => {
          e.preventDefault();
          cell.classList.add("drag-over");
        };
        cell.ondragleave = e => {
          cell.classList.remove("drag-over");
        };
        cell.ondrop = e => {
          e.preventDefault();
          cell.classList.remove("drag-over");
          if (dragFrom !== null && dragFrom !== i) {
            // スワップ
            let tmp = emojiOrder[dragFrom];
            emojiOrder[dragFrom] = emojiOrder[i];
            emojiOrder[i] = tmp;
            drawEmojiList();
            checkClear();
          }
        };
        list.appendChild(cell);
      }
    }

    // タイマーバー更新
    function updateTimerBar() {
      const bar = document.getElementById('timer-bar');
      if (bar) bar.textContent = `残り時間:${timer}秒`;
    }

    // クリア判定
    function checkClear() {
      if (emojiOrder.join('') === answerOrder.join('')) {
        clearInterval(intervalId);
        setTimeout(()=>{
          if (level === EMOJI_SETS.length-1) {
            showEndScreen(true, true); // 全クリア
          } else {
            showEndScreen(true, false);
          }
        }, 350);
      }
    }

    // 終了画面
    function showEndScreen(isClear, isAllClear) {
      document.getElementById('main-container').innerHTML = `
        <h1>🎲 絵文字並び替えパズル 🎲</h1>
        <img src="emoji_sorting_puzzle_title.png" class="title-img" alt="絵文字並び替えパズル タイトル">
        <div class="message-box">
          ${
            isClear
              ? (isAllClear
                  ? `🎉 全レベルクリア!おめでとう! 🎉`
                  : `クリア! 次のレベルに進みましょう!`)
              : `ゲームオーバー…<br>もう一度挑戦してみてね!`
          }
        </div>
        <button class="btn center-btn" id="back-title-btn">${isClear && !isAllClear ? "次のレベルへ" : "タイトル画面に戻る"}</button>
      `;
      document.getElementById('back-title-btn').onclick = () => {
        if (isClear && !isAllClear) {
          startGame(level+1);
        } else {
          showTitleScreen();
        }
      };
    }

    // 初期表示
    showTitleScreen();
  </script>
</body>
</html>

アルゴリズムの流れ

手順処理内容
1showTitleScreen() でタイトル画面を描画し、スタートボタンに startGame(0) を紐付け
2startGame(n)startStage() でレベル n の絵文字セットをロードし、並び替え順序をシャッフル
3制限時間45秒のカウントダウンを開始し、showGameScreen() でゲーム画面を描画
4drawAnswerSample() で「お手本」を表示、drawEmojiList() でドラッグ可能な絵文字セルを配置
5セルはドラッグ開始・終了・オーバー・ドロップの各イベントをハンドリング
6ドロップ時にスワップ処理を行い、checkClear() で完成判定
7正解なら次レベル(または全クリアエンディング)、時間切れならゲームオーバー画面

関数の詳細

関数名機能概要詳細説明
showTitleScreen()タイトル画面の描画ルール説明&スタートボタンを表示。既存タイマーをクリア。
startGame(startLevel)ゲーム開始/レベル設定引数で開始レベルを設定し、startStage() を呼び出す
startStage()レベル初期化該当レベルの絵文字セットを取得し、正解順 answerOrder をコピー。シャッフルして emojiOrder に設定。
shuffleArray(arr)配列シャッフルFisher–Yatesアルゴリズムで配列をランダム化。
showGameScreen()ゲーム画面描画タイマー・レベル表示・お手本エリア・絵文字エリアを生成し、それぞれ描画関数を呼び出す
drawAnswerSample()お手本エリア描画answerOrderanswerNames を組み合わせて絵文字+日本語名称を順に表示
drawEmojiList()絵文字セルエリア生成emojiOrder に応じて .emoji-cell 要素を生成し、ドラッグ&ドロップイベントを登録
updateTimerBar()タイマー表示更新毎秒呼び出され、残り時間をDOMに反映
checkClear()クリア判定emojiOrderanswerOrder の文字列比較で完成判定。クリアなら次ステージ or エンディングへ移行
showEndScreen(isClear,allClear)終了画面描画成功/失敗メッセージとボタン表示。ボタンは次レベル or タイトルへ戻る処理を紐付け

改造のポイント

  • レベル追加・カスタムセットEMOJI_SETS に好きな絵文字+ラベルを追加すれば、オリジナルテーマのパズルが作成可能。
  • 制限時間調整:ステージごとに時間を伸縮したり、残り時間を基にスコアボーナスを付与する仕組みを追加。
  • アニメーション強化:ドラッグ中のセルにスムーズな移動アニメーションや、クリア時に祝福エフェクトを出すと満足度アップ。
  • ヒント機能:一定回数失敗したら、一部セルのお手本を一瞬表示する「ヒント」を実装。
  • スマホ操作最適化:タッチでのスワップや長押し操作を追加し、モバイルUXを向上させる。

アドバイス
まずはコアのドラッグ&ドロップ動作を安定させ、次にUI演出(エフェクト・BGM・サウンド)を追加してゲーム性を高めると、ユーザー体験が格段に向上します。ステージ選択やハイスコア保存などの機能を段階的に実装すると、リプレイ性が増し飽きられにくくなります!