はじめに

テトリスは誰もが一度は遊んだことのある、落ちものパズルゲームの代名詞です。本記事では、HTML5 の Canvas と JavaScript を使って簡単なテトリスをゼロから作成する手順を解説します。プログラミングの基礎があれば、30 分~1 時間程度で動くプロトタイプを完成させられるでしょう。


前提条件

  • 言語・ライブラリ:HTML, CSS, JavaScript(ES6 以降推奨)

  • 動作環境:モダンブラウザ(Chrome, Firefox, Edge など)

  • 開発ツール:テキストエディタ(VSCode など)、ローカルでファイルを開ける環境


プロジェクト構成

tetris/
├── index.html
├── style.css
└── tetris.js

1. Canvas の準備

まず、HTML にゲーム画面用の <canvas> を用意します。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>Tetris Clone</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>
  <canvas id="game" width="300" height="600"></canvas>
  <script src="tetris.js"></script>
</body>
</html>
/* style.css */
body {
  background: #222;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  margin: 0;
}
canvas {
  background: #000;
  border: 2px solid #555;
}

2. ゲームの基盤コード(tetris.js)

Canvas コンテキスト取得

const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const COLS = 10, ROWS = 20, BLOCK_SIZE = 30;
ctx.scale(BLOCK_SIZE, BLOCK_SIZE);  // 1 ブロック=30px

盤面のデータ構造

盤面は二次元配列で管理します。空きセルは0、ブロックは色を整数で表します。

function createMatrix(w, h) {
  const matrix = [];
  while (h--) {
    matrix.push(new Array(w).fill(0));
  }
  return matrix;
}
const arena = createMatrix(COLS, ROWS);

テトリミノ定義

各ミノの形状を 2D 配列で定義し、回転させる関数を用意します。

const TETROMINOS = {
  I: [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]],
  J: [[2,0,0],[2,2,2],[0,0,0]],
  // L, O, S, T, Z も同様に定義
};

function rotate(matrix, dir) {
  // 転置
  for (let y = 0; y < matrix.length; ++y) {
    for (let x = 0; x < y; ++x) {
      [matrix[x][y], matrix[y][x]] = [matrix[y][x], matrix[x][y]];
    }
  }
  // 反転
  if (dir > 0) {
    matrix.forEach(row => row.reverse());
  } else {
    matrix.reverse();
  }
}

3. 描画&衝突判定

描画関数

function drawMatrix(matrix, offset) {
  matrix.forEach((row, y) => {
    row.forEach((value, x) => {
      if (value !== 0) {
        ctx.fillStyle = COLORS[value];
        ctx.fillRect(x + offset.x, y + offset.y, 1, 1);
      }
    });
  });
}

function draw() {
  ctx.fillStyle = '#000';
  ctx.fillRect(0, 0, COLS, ROWS);
  drawMatrix(arena, {x:0,y:0});
  drawMatrix(player.matrix, player.pos);
}

衝突判定

function collide(arena, player) {
  const [m, o] = [player.matrix, player.pos];
  for (let y = 0; y < m.length; ++y) {
    for (let x = 0; x < m[y].length; ++x) {
      if (m[y][x] !== 0 &&
         (arena[y + o.y] && arena[y + o.y][x + o.x]) !== 0) {
        return true;
      }
    }
  }
  return false;
}

4. ゲームループ&操作

タイマー更新

let dropCounter = 0;
let dropInterval = 1000; // ミリ秒

let lastTime = 0;
function update(time = 0) {
  const deltaTime = time - lastTime;
  lastTime = time;
  dropCounter += deltaTime;
  if (dropCounter > dropInterval) {
    playerDrop();
  }
  draw();
  requestAnimationFrame(update);
}
update();

キーボード操作

document.addEventListener('keydown', event => {
  if (event.keyCode === 37)  playerMove(-1); // ←
  else if (event.keyCode === 39) playerMove(1);  // →
  else if (event.keyCode === 40) playerDrop();    // ↓
  else if (event.keyCode === 81) playerRotate(-1); // Q
  else if (event.keyCode === 87) playerRotate(1);  // W
});

5. ライン消去とスコアリング

function arenaSweep() {
  outer: for (let y = arena.length - 1; y >= 0; --y) {
    for (let x = 0; x < arena[y].length; ++x) {
      if (arena[y][x] === 0) {
        continue outer;
      }
    }
    const row = arena.splice(y, 1)[0].fill(0);
    arena.unshift(row);
    ++y;
    player.score += 10;
  }
}

function playerDrop() {
  player.pos.y++;
  if (collide(arena, player)) {
    player.pos.y--;
    merge(arena, player);
    playerReset();
    arenaSweep();
    updateScore();
  }
  dropCounter = 0;
}

6. 次のミノ&ゲームオーバー

  • playerReset():新しいミノをランダム生成し、盤面の上部に配置。

  • ゲームオーバー判定:新ミノが衝突したら処理停止。

function playerReset() {
  const pieces = 'TJLOSZI';
  player.matrix = createPiece(pieces[pieces.length * Math.random() | 0]);
  player.pos.y = 0;
  player.pos.x = (arena[0].length / 2 | 0) -
                 (player.matrix[0].length / 2 | 0);
  if (collide(arena, player)) {
    arena.forEach(row => row.fill(0));
    player.score = 0;
    updateScore();
  }
}

まとめと次のステップ

以上が最小構成のテトリス実装手順です。さらに以下のような機能追加で、より本格的なゲームに発展させられます。

  • ブロックの“ホールド”機能

  • レベルアップと落下速度の段階的増加

  • サウンドエフェクトや BGM の追加

  • モバイル対応のタッチ操作

ぜひこの記事を参考に、自分だけのテトリスを作ってみてください!