ブロックパズルの作り方 — JavaScriptでブラウザゲームを作ろう

このゲームで遊ぶ → ソースコード全文 →

このチュートリアルで学べること

ブロックパズルは、落ちてくるブロック(テトロミノ)を回転させながら積み上げ、横一列を揃えて消していく、テトリス風の世界的に有名なパズルゲームです。テトロミノの回転データの扱い方、タイマーを使った自動落下、そして揃った行を消す処理など、ゲームプログラミングの重要なテクニックをたくさん学べます。

HTMLとCanvasの準備をしよう

ブロックパズルのボードは10列x20行のグリッドです。DOMのdiv要素で各セルを生成し、CSSグリッドで並べましょう。セルの2次元配列を作っておくと、高速に色を変更できます。

var COLS = 10;  // 横のマス数
var ROWS = 20;  // 縦のマス数

var board = [];   // ボード配列 [row][col] = '' or 色名
var cells = [];   // DOMセルの2次元配列

function createBoard() {
  boardEl.innerHTML = '';
  cells = [];
  for (var y = 0; y < ROWS; y++) {
    cells[y] = [];
    for (var x = 0; x < COLS; x++) {
      var cell = document.createElement('div');
      cell.className = 'tetris-cell';
      boardEl.appendChild(cell);
      cells[y][x] = cell;
    }
  }
}

function initBoard() {
  board = [];
  for (var y = 0; y < ROWS; y++) {
    board[y] = [];
    for (var x = 0; x < COLS; x++) {
      board[y][x] = '';
    }
  }
}

board配列がゲームのデータ、cells配列が描画用のDOM要素です。データと表示を分けることで、ロジックがすっきりします。

テトロミノの定義と回転を作ろう

7種類のブロックを配列で定義する

ブロックパズルには7種類のテトロミノ(I, O, T, S, Z, J, L)があり、それぞれ4つの回転状態を持ちます。各状態を座標の配列として定義することで、回転処理がとてもシンプルになります。

var TETROMINOS = {
  I: {
    shapes: [
      [[0,0],[1,0],[2,0],[3,0]],  // 横向き
      [[0,0],[0,1],[0,2],[0,3]],  // 縦向き
      [[0,0],[1,0],[2,0],[3,0]],
      [[0,0],[0,1],[0,2],[0,3]]
    ],
    color: 'I'
  },
  T: {
    shapes: [
      [[0,0],[1,0],[2,0],[1,1]],
      [[0,0],[0,1],[0,2],[1,1]],
      [[1,0],[0,1],[1,1],[2,1]],
      [[1,0],[1,1],[1,2],[0,1]]
    ],
    color: 'T'
  }
  // ... S, Z, J, L, O も同様に定義
};

各テトロミノの4つの回転状態を事前に定義しておくアプローチです。回転時はrotationの値を0〜3の間で循環させるだけで済みます。

衝突判定と回転処理

ピースを動かしたり回転させたりする前に、移動先が有効な位置かどうかをチェックする必要があります。ボードの範囲外や、既に他のブロックがある場所には移動できません。

function isValidPosition(piece) {
  var shape = TETROMINOS[piece.type].shapes[piece.rotation];
  for (var i = 0; i < shape.length; i++) {
    var newX = piece.x + shape[i][0];
    var newY = piece.y + shape[i][1];
    if (newX < 0 || newX >= COLS || newY < 0 || newY >= ROWS) {
      return false;
    }
    if (board[newY][newX] !== '') {
      return false;
    }
  }
  return true;
}

function rotatePiece() {
  var newRotation = (currentPiece.rotation + 1) % 4;
  var testPiece = {
    type: currentPiece.type,
    rotation: newRotation,
    x: currentPiece.x,
    y: currentPiece.y
  };

  if (isValidPosition(testPiece)) {
    currentPiece.rotation = newRotation;
    render();
    return;
  }
  // ウォールキック: 左右にずらして試す
  var kicks = [1, -1, 2, -2];
  for (var i = 0; i < kicks.length; i++) {
    testPiece.x = currentPiece.x + kicks[i];
    if (isValidPosition(testPiece)) {
      currentPiece.rotation = newRotation;
      currentPiece.x = testPiece.x;
      render();
      return;
    }
  }
}

回転が壁にぶつかる場合は、左右にずらして試す「ウォールキック」を実装しています。これにより、壁際でも快適に回転操作ができます。

落下タイマーと行消去を実装しよう

自動落下のゲームループ

テトロミノは一定間隔で自動的に1マスずつ下に落ちます。setTimeoutを使って落下タイマーを管理し、レベルが上がるにつれて速度を上げましょう。

var INITIAL_SPEED = 800;  // 初期落下速度(ms)
var MIN_SPEED = 80;       // 最速
var SPEED_DECREASE = 60;  // レベルごとの速度減少

function gameStep() {
  if (!gameRunning || !currentPiece) return;
  var testPiece = {
    type: currentPiece.type,
    rotation: currentPiece.rotation,
    x: currentPiece.x,
    y: currentPiece.y + 1
  };
  if (isValidPosition(testPiece)) {
    currentPiece.y++;
    render();
    gameLoopTimer = setTimeout(gameStep, currentSpeed);
  } else {
    // 接地 → ロック → 行消去 → 次のピース
    lockPiece();
    clearLines();
    currentPiece = spawnPiece();
    if (!currentPiece) { gameOver(); return; }
    render();
    gameLoopTimer = setTimeout(gameStep, currentSpeed);
  }
}

function calcSpeed() {
  var speed = INITIAL_SPEED - ((level - 1) * SPEED_DECREASE);
  return Math.max(speed, MIN_SPEED);
}

揃った行を消す処理

横一列がすべて埋まったら、その行を消して上の行を落とします。一度に消した行数に応じてスコアにボーナスがつきます。

var LINE_SCORES = { 1: 100, 2: 300, 3: 500, 4: 800 };

function clearLines() {
  var linesToClear = [];
  for (var y = ROWS - 1; y >= 0; y--) {
    var full = true;
    for (var x = 0; x < COLS; x++) {
      if (board[y][x] === '') { full = false; break; }
    }
    if (full) linesToClear.push(y);
  }

  if (linesToClear.length > 0) {
    // ラインを削除して上に空行を追加
    for (var i = linesToClear.length - 1; i >= 0; i--) {
      board.splice(linesToClear[i], 1);
    }
    for (var i = 0; i < linesToClear.length; i++) {
      var emptyRow = [];
      for (var x = 0; x < COLS; x++) emptyRow.push('');
      board.unshift(emptyRow);
    }
    // スコア加算
    var lineScore = LINE_SCORES[linesToClear.length] || 200;
    score += lineScore * level;
  }
}

board.spliceで揃った行を削除し、board.unshiftで上に空行を追加します。配列の操作だけで行を詰める処理が実現できるのがポイントです。4行同時消しは800点と大きなボーナスがつきます。

まとめ — 次のステップ

このチュートリアルでは、テトロミノの回転データの管理、衝突判定とウォールキック、タイマーによる自動落下、そして行消去の仕組みを学びました。さらに発展させるなら、ホールド機能(ピースの一時保管)、Tスピン判定、ネクストピースの複数表示、スコアランキングなどを追加してみましょう。

よくある質問

Q: プログラミング初心者でも作れますか?

A: はい、大丈夫です。このチュートリアルではステップごとにコードを書いていくので、初めての方でも順番に進めれば完成できます。わからないところがあれば、ひなテックの教室で質問もできますよ。

Q: どのくらい時間がかかりますか?

A: 基本部分は約30分〜1時間で作れます。見た目をこだわったり機能を追加すると、さらに楽しく発展させられます。