【ゲーム】JavaScript:71 タワースタッカー

 「🏗️🧱 タワースタッカー」は、動くブロックをタイミング良く落として、タワーを高く積み上げていくアクションパズルゲームです。積み上げたブロックの重なった面積がスコアとなり、20段積み上げるとゲームクリアとなります。操作はシンプルですが、繰り返すほどに難易度が上昇し、反射神経と集中力が試されます。

遊び方・操作方法

  • スタート:「スタート」ボタンをクリックでゲーム開始
  • 操作方法:動いているブロックのタイミングを見てゲーム画面をクリックまたはタップすると、その位置にブロックが落下し、積み上げます。
  • 繰り返し:上手く積み重ねるごとに次のブロックの幅が決まります(はみ出した部分は切り落とされ、どんどん幅が狭くなります)。
  • クリア条件:20段積み上げるとゲームクリア。積み上げたブロックの総面積(小数点以下切り捨て)がスコアです。
  • ゲームオーバー:ブロックが一切重ならなかった場合は、その時点でゲームオーバーです。

ルール

  • 最初の土台の上に、左右に動くブロックを落として重ねていく。
  • 落としたブロックは下の段と重なった部分だけが次の土台となる。
  • 重なりが無いと即ゲームオーバー
  • 20段積むとクリア!積み上げたブロックの「総面積」がスコアとなります。
  • クリア・ゲームオーバー問わず、スコアは**ランキング(TOP5)**に登録されます。

🎮ゲームプレイ

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

71 タワースタッカー

素材のダウンロード

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

tower_stacker_title.pngtower_stacker_bg.png

ゲーム画面イメージ

プログラム全体(tower_stacker.html

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>🏗️🧱 タワースタッカー 🧱🏗️</title>
  <style>
    html, body {
      width: 100%; height: 100%; margin: 0; padding: 0;
      background: url('tower_stacker_bg.png') no-repeat center center fixed;
      background-size: cover;
      font-family: 'Yu Gothic', 'Meiryo', sans-serif;
      overflow: auto;
    }
    #wrapper {
      width: 800px;
      height: calc(100% - 20px);
      margin: 20px auto 0 auto;
      position: relative;
      box-sizing: border-box;
    }
    .screen {
      display: none; width: 100%; height: 100%; box-sizing: border-box;
    }
    #titleScreen {
      display: block; text-align: center; color: #fff; padding-top: 20px;
    }
    #titleScreen img {
      display: block; margin: 0 auto 20px auto; max-width: 70%; height: auto;
    }
    .rule-panel {
      background: rgba(0,0,0,0.6); padding: 20px; border-radius: 10px;
      margin: 0 auto; max-width: 90%;
    }
    .rule-panel h2 { margin-top: 0; text-align: center; }
    .rule-panel p { text-align: left; margin: 0 0 10px 0; line-height: 1.6; }
    .btn {
      display: inline-block; padding: 12px 24px; margin-top: 20px; font-size: 1.1rem;
      font-weight: bold; color: #fff; background: #1e90ff; border: none;
      border-radius: 8px; cursor: pointer; transition: opacity .2s;
    }
    .btn:hover { opacity: 0.8; }
    #gameScreen { position: relative; color: #fff; padding-top: 20px; }
    #gameCanvas {
      display: block; margin: 0 auto; background: #111; border: 4px solid #fff;
      border-radius: 4px; width: 800px; height: 680px;
    }
    #scoreBoard {
      position: absolute; top: 30px; left: 50%; transform: translateX(-50%);
      background: rgba(0,0,0,0.6); padding: 6px 12px; border-radius: 6px;
      font-size: 1.1rem; z-index: 5;
    }
    #resultScreen { text-align: center; color: #fff; padding-top: 20px; }
    #resultScreen .message { font-size: 2rem; font-weight: bold; margin-bottom: 20px; }
    #rankingBoard {
      background: rgba(0,0,0,0.6); display: inline-block; padding: 10px 20px;
      border-radius: 10px; text-align: left;
    }
    #rankingBoard h3 { margin: 0 0 10px 0; text-align: center; }
    #rankingBoard ol { margin: 0; padding-left: 20px; }
  </style>
</head>
<body>
  <div id="wrapper">

    <!-- ==================== タイトル画面 ==================== -->
    <div id="titleScreen" class="screen">
      <img src="tower_stacker_title.png" alt="ゲームタイトル">
      <h1>🏗️🧱 <span style="background:rgba(0,0,0,0.6); padding:6px 12px; border-radius:8px;">タワースタッカー</span> 🧱🏗️</h1>
      <div class="rule-panel">
        <h2>📜 ルール説明 📜</h2>
        <p>・ブロックが左右に移動します。クリックやタップで落としてタワーを積み上げましょう。</p>
        <p>・はみ出した部分は切り落とされ、次のブロックの幅が狭くなります。</p>
        <p>・ブロックが重ならないとゲームオーバーです。</p>
        <p>・<strong>20 段</strong> 積み上げるとゲームクリア!<br>
           積み上げた<strong>ブロック総面積 (小数点以下切り捨て)</strong>がスコアになります。</p>
      </div>
      <button id="startBtn" class="btn">▶️ スタート</button>
    </div>

    <!-- ==================== ゲーム画面 ==================== -->
    <div id="gameScreen" class="screen">
      <div id="scoreBoard">
        高さ: <span id="height">0</span> 段 | 面積: <span id="area">0</span>
      </div>
      <canvas id="gameCanvas" width="800" height="680"></canvas>
    </div>

    <!-- ==================== 結果画面 ==================== -->
    <div id="resultScreen" class="screen">
      <div class="message" id="resultMessage"></div>
      <div id="rankingBoard">
        <h3>🏆 ランキング TOP 5 🏆</h3>
        <ol id="rankingList"></ol>
      </div><br>
      <button id="returnBtn" class="btn">🔙 タイトル画面に戻る</button>
    </div>
  </div>

<script>
(() => {
  /* ===== 定数 ===== */
  const BLOCK_H    = 30;      // ブロックの高さ
  const INIT_W     = 300;     // 最初のブロックの幅
  const START_SPEED= 2.5;     // 最初のスピード
  const CLEAR_LV   = 20;      // クリアとなる段数
  const LS_KEY     = 'ts_scores'; // ローカルストレージ用キー
  const RANK_MAX   = 5;       // ランキング最大数

  /* ===== DOM取得 ===== */
  const $ = id => document.getElementById(id);
  const title   = $('titleScreen');
  const game    = $('gameScreen');
  const result  = $('resultScreen');
  const startBtn= $('startBtn');
  const backBtn = $('returnBtn');
  const hSpan   = $('height');
  const aSpan   = $('area');
  const resMsg  = $('resultMessage');
  const rankList= $('rankingList');
  const cvs     = $('gameCanvas');
  const ctx     = cvs.getContext('2d');

  /* ===== ゲーム用変数 ===== */
  let blocks = [];     // 配置された全ブロック
  let moveBlk;         // 現在移動中のブロック
  let speed;           // 現在の移動スピード
  let area;            // 積み上げた総面積
  let run = false;     // ゲームループ実行フラグ
  let raf;             // requestAnimationFrame ID

  /* ===== ユーティリティ ===== */
  // ブロック生成
  const mkBlk = (x, y, w, h, d = 1) => ({ x, y, w, h, d });
  const floor = Math.floor;

  /* ===== ゲーム初期化 ===== */
  function init() {
    blocks.length = 0;

    // 一番下の土台ブロック
    const baseX = (cvs.width - INIT_W) / 2;
    const baseY = cvs.height - BLOCK_H;
    blocks.push(mkBlk(baseX, baseY, INIT_W, BLOCK_H, 0));

    // 最初の移動ブロック
    moveBlk = mkBlk(0, baseY - BLOCK_H, INIT_W, BLOCK_H, 1);

    speed = START_SPEED;
    area = floor(INIT_W * BLOCK_H);

    updHUD();
    run = true;

    cancelAnimationFrame(raf);
    raf = requestAnimationFrame(loop);
  }

  /* ===== HUD(スコア等の表示)更新 ===== */
  function updHUD() {
    hSpan.textContent = blocks.length - 1;
    aSpan.textContent = area;
  }

  /* ===== メインループ ===== */
  function loop() {
    upd();
    draw();
    if (run) raf = requestAnimationFrame(loop);
  }

  // ブロックの移動処理
  function upd() {
    moveBlk.x += moveBlk.d * speed;
    // 端で反転
    if (moveBlk.x <= 0) {
      moveBlk.x = 0;
      moveBlk.d = 1;
    } else if (moveBlk.x + moveBlk.w >= cvs.width) {
      moveBlk.x = cvs.width - moveBlk.w;
      moveBlk.d = -1;
    }
  }

  // ブロック描画
  function draw() {
    ctx.clearRect(0, 0, cvs.width, cvs.height);
    ctx.fillStyle = '#2ecc71'; // 積み上がったブロック
    blocks.forEach(b => ctx.fillRect(b.x, b.y, b.w, b.h));

    ctx.fillStyle = '#e67e22'; // 現在の移動ブロック
    ctx.fillRect(moveBlk.x, moveBlk.y, moveBlk.w, moveBlk.h);
  }

  /* ===== ブロック配置(クリック時) ===== */
  function place() {
    const top = blocks[blocks.length - 1];

    // オーバーラップ判定
    const oS = Math.max(top.x, moveBlk.x);
    const oE = Math.min(top.x + top.w, moveBlk.x + moveBlk.w);
    const oW = oE - oS;

    if (oW <= 0) { // 全く重ならなかったらゲームオーバー
      finish(false);
      return;
    }

    // 新しいブロック配置
    moveBlk.x = oS;
    moveBlk.w = oW;
    blocks.push({ ...moveBlk, d: 0 });

    area = floor(area + oW * BLOCK_H);
    updHUD();

    // クリア判定
    if (blocks.length - 1 >= CLEAR_LV) {
      finish(true);
      return;
    }

    // 次の移動ブロック準備
    moveBlk = mkBlk(0, moveBlk.y - BLOCK_H, oW, BLOCK_H, 1);
    speed += 0.1;
  }

  /* ===== ゲーム終了 ===== */
  function finish(clear) {
    run = false;
    cancelAnimationFrame(raf);

    const txt = clear ? '🎉 ゲームクリア! 🎉' : '🏁 ゲームオーバー! 🏁';
    resMsg.innerHTML = `${txt}<br>高さ: <strong>${blocks.length - 1}</strong> 段<br>面積スコア: <strong>${area}</strong>`;

    saveRank(area);
    showRank();

    game.style.display = 'none';
    result.style.display = 'block';
  }

  /* ===== ランキング処理 ===== */
  // ランキングデータ取得
  const loadRank = () => {
    try {
      const d = JSON.parse(localStorage.getItem(LS_KEY));
      return Array.isArray(d) ? d : [];
    } catch {
      return [];
    }
  };
  // ランキングデータ保存
  const saveRankArr = a => localStorage.setItem(LS_KEY, JSON.stringify(a));

  // スコア登録
  function saveRank(score) {
    const r = loadRank();
    r.push(score);
    r.sort((a, b) => b - a);
    if (r.length > RANK_MAX) r.length = RANK_MAX;
    saveRankArr(r);
  }

  // ランキング表示
  function showRank() {
    const r = loadRank();
    rankList.innerHTML = '';
    if (!r.length) {
      rankList.innerHTML = '<li>ランキングなし</li>';
      return;
    }
    r.forEach((s, i) => {
      const li = document.createElement('li');
      li.textContent = `${i + 1} 位 : ${s} 点`;
      rankList.appendChild(li);
    });
  }

  /* ===== イベントバインド ===== */
  startBtn.onclick = () => {
    title.style.display = 'none';
    result.style.display = 'none';
    game.style.display = 'block';
    init();
  };

  backBtn.onclick = () => {
    result.style.display = 'none';
    title.style.display = 'block';
  };

  cvs.onclick = () => {
    if (run) place();
  };

})();
</script>
</body>
</html>

アルゴリズムの流れ

ステップ内容
1タイトル画面・ルール説明を表示
2「スタート」ボタンでゲーム開始
3一番下に土台ブロックを生成
4その上に左右に動くブロックを生成
5画面クリック/タップでブロックを落とし、下段と重なった部分だけ残す
6面積・段数を表示しながら繰り返す
7重なりがゼロになったらゲームオーバー
820段積んだらゲームクリア
9リザルト画面+ランキング表示
10タイトル画面に戻ってリトライ可能

主要な関数・命令の解説

関数・変数名役割・内容
init()ゲーム状態初期化、土台と最初のブロック生成、面積計算リセット
loop()メインループ。ブロックの移動・描画・ゲーム進行
upd()動いているブロックの左右移動&端での反転処理
draw()キャンバスに積み上げたブロック・移動ブロックを描画
place()クリック時にブロックを落とし、重なった部分のみを残す。面積更新、クリア/ゲームオーバー判定
finish(clear)ゲーム終了処理(クリア/ゲームオーバーのメッセージ、ランキング登録・表示)
updHUD()段数と面積スコアの表示を更新
mkBlk()ブロック生成用のユーティリティ
loadRank()ランキングデータをlocalStorageから読み出し
saveRank(score)現在のスコアをランキングに追加・保存(上位5件まで)
showRank()結果画面にランキングリストを描画

関数の詳細

関数名主な役割
initゲーム状態リセット・土台と移動ブロック初期化・面積リセット・ループ開始
loopゲーム進行ループ(ブロック移動・描画・進行管理)
updブロックの位置を更新、端で移動方向を反転
drawブロックをCanvasに描画
placeブロック落下・重なり計算・面積更新・次の段用ブロック生成・クリア/終了判定
finishゲーム終了画面遷移・スコア表示・ランキング登録
loadRankランキングデータ取得
saveRankスコアのランキング保存
showRank結果画面にランキングを表示

改造のポイント

◆ 難易度調整・アレンジ

  • ブロックの速度初期幅・高さを調整して難易度を変更可能。
  • クリア段数や面積計算方法を工夫しても面白い。
  • ランキング保存数を増やしたり、プレイヤー名入力機能も追加可能。

◆ ビジュアル・演出の強化

  • ブロックや背景画像を自作素材に変更、アニメーションや効果音追加で見た目のリッチ化。
  • ブロックごとに色を変えたり、物理的な落下感・揺れなど演出追加も◎

◆ モバイル対応・操作性UP

  • スマホのタッチ操作対応や、レスポンシブデザインの強化。
  • ボタン操作・バイブレーション連動などのUI改善。

★アドバイス

積み上げた時の達成感が楽しいゲームです。
処理のシンプルさゆえに「Canvas描画、タイミング、配列操作、ローカルストレージ」など
Webゲーム制作の基礎スキル練習にも最適
自分だけの演出やギミックを追加して、世界に一つのタワースタッカーに進化させましょう!

あなたも高く積み上げて、ハイスコアとクリアを目指してください!