ぼくがこのブラウザ版テトリスをWordPressに導入した背景は、サイト訪問者の滞在時間を伸ばし、SNSで自然に拡散される体験型コンテンツを手軽に作りたかったからです。プラグインに頼らず、HTML/CSS/JavaScriptだけで実装することで、テーマ更新や他プラグインとの干渉リスクを最小化し、純粋な学習教材としても有用なコードベースが構築できました。
この記事では、ゲーム領域と背景の切り分け、UI/UX(長押しドロップ、Nextピースのセンタリング)、コアロジック(マトリクス管理・衝突判定・ライン消去)、サウンド(BGM・効果音の分離再生)、スコア・レベル設計の最適化といった各要素を、コード例+行単位の詳細解説で丁寧に解説します。実装サンプルをコピペするだけで、あなたのWordPressサイトにも“遊べるテトリス”が完成します!

PCブラウザ版での操作方法はこれさ↓
上矢印キーで、右回りでブロックが回るよ。
左右の矢印キーでブロックを左右に動かせるよ!
下矢印キーはブロックが下に落ちます~。
最初に「START」ボタンを押してね~。
スマホ版はゲーム下のボタンで操作してください!
ゲーム領域と背景の分離
WordPressテーマの背景と、テトリスを描く黒いキャンバス領域は分けて管理します。こうすることで、テーマの背景画像(宇宙っぽいメカニカルアート)を全体に表示しつつ、ゲーム画面だけを黒く塗りつぶした状態で描画できます。
/* ページ全体の背景 */
html, body {
height: 100%;
background: url('…/tetris_background.webp') center/cover no-repeat;
display: flex;
justify-content: center;
align-items: center;
font-family: sans-serif;
}
/* ゲームボード本体 */
#boardWrapper {
width: 300px;
height: 520px;
background: #000;
border: 2px solid #444;
position: relative;
}
- 背景画像:
background: url(...) center/cover no-repeat;
で縦横比を保持しながら画面いっぱいにフィット。 - ゲーム領域:固定ピクセルサイズを採用し、Canvas内部の座標系をJavaScriptで扱いやすく。
UI配置とタッチ/長押し操作のコツ
ゲームボード下中央に「◀」「⟳」「▶」「↓」の操作ボタンを並べ、サイドには「スコア」「START」「Nextピース枠」を配置。マウスやタッチ操作時にテキスト選択が入らないよう、次のCSSを追加します。
button {
user-select: none; /* 長押し時のテキスト選択を無効化 */
-webkit-user-select: none;
touch-action: none; /* タッチ動作解釈の邪魔を防止 */
}
長押しでの連続落下(ソフトドロップ)
ボタンを押しっぱなしにすると、テトリス特有の「スーッ」と落ちる挙動を実現します。
let dropHold;
const dropBtn = document.getElementById('dropButton');
dropBtn.addEventListener('mousedown', () => {
playerDrop(); // 一度だけ即時落下
dropHold = setInterval(playerDrop, 100);
});
dropBtn.addEventListener('mouseup', () => clearInterval(dropHold));
dropBtn.addEventListener('mouseleave', () => clearInterval(dropHold));
// モバイル対応
dropBtn.addEventListener('touchstart', e => {
e.preventDefault(); // テキスト選択を防ぐ
playerDrop();
dropHold = setInterval(playerDrop, 100);
});
dropBtn.addEventListener('touchend', () => clearInterval(dropHold));
playerDrop()
は一行分の落下&着地判定を行う関数。setInterval
によって 100ms 間隔で連続落下。e.preventDefault()
を必ず入れて、モバイルの長押しメニューを抑制。
コアロジック解説:マトリクス管理から衝突判定まで
テトリスの根幹は「盤面(マトリクス)」を操作することにあります。以下では、主要関数をピックアップして詳細解説します。
盤面生成とBagランダム
const ROWS = 20, COLS = 12;
const arena = Array.from({ length: ROWS }, () => Array(COLS).fill(0));
function createBag() {
const types = ['T','J','L','O','S','Z','I'];
for (let i = types.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[types[i], types[j]] = [types[j], types[i]];
}
return types;
}
- arena:20行×12列の2次元配列。初期値はすべて
0
。 - createBag():最新の“Tetris Guideline”に準じた7-bagシャッフルで偏りを解消。
衝突判定
function collide(A, P) {
for (let y = 0; y < P.matrix.length; y++) {
for (let x = 0; x < P.matrix[y].length; x++) {
if (P.matrix[y][x]) {
const newX = x + P.pos.x, newY = y + P.pos.y;
if (newX < 0 || newX >= COLS || newY >= ROWS ||
(newY >= 0 && A[newY][newX])) {
return true;
}
}
}
}
return false;
}
- 配列アクセス前に**範囲外チェック**を行い、安全にアクセス。
- 既存ブロックとの重なりを検知して真偽値を返却。
マージ(固定化)
function merge(A, P) {
P.matrix.forEach((row, y) => {
row.forEach((val, x) => {
if (val && y + P.pos.y >= 0) {
A[y + P.pos.y][x + P.pos.x] = val;
}
});
});
}
着地時にテトリミノを盤面に書き込み、次のピース生成へつなげます。
ライン消去&スコア・レベル設計
1〜4行同時消去ごとに異なる得点と音を再生し、レベルアップで落下速度を加速させます。
function arenaSweep() {
let lines = 0;
for (let y = ROWS - 1; y >= 0; y--) {
if (arena[y].every(v => v)) {
arena.splice(y, 1);
arena.unshift(new Array(COLS).fill(0));
lines++;
y++;
}
}
if (lines) {
switch (lines) {
case 1: player.score += 5; break;
case 2: player.score += 15; break;
case 3: player.score += 30; break;
case 4: player.score += 50; break;
}
player.level = Math.floor(player.score / 100) + 1;
dropInterval = Math.max(100, 1000 - (player.level - 1) * 100);
// 効果音
if (lines === 4) sounds.tetris.play();
else sounds.clear.play();
}
}
- 1行→5点、2行→15点、3行→30点、4行→50点の配点。
- レベルは
score ÷ 100
の切り捨て+1。 - 落下速度は最速100msまで加速。
描画ループとNextピースのセンタリング
CanvasをrequestAnimationFrame
で更新し、Nextピースを可変サイズでも中央表示します。
function update(time = 0) {
const delta = time - lastTime;
lastTime = time;
dropCounter += delta;
if (dropCounter > dropInterval) playerDrop();
draw();
if (!isOver) requestAnimationFrame(update);
}
function draw() {
ctx.clearRect(0,0,canvas.width,canvas.height);
drawMatrix(arena, 0, 0, ctx);
drawMatrix(player.matrix, player.pos.x * TILE, player.pos.y * TILE, ctx);
// Nextピース中央表示
nextCtx.clearRect(0,0,nextCanvas.width,nextCanvas.height);
const nw = player.next[0].length, nh = player.next.length;
const ox = Math.floor((nextCanvas.width - nw * TILE) / 2);
const oy = Math.floor((nextCanvas.height - nh * TILE) / 2);
drawMatrix(player.next, ox, oy, nextCtx);
}
まとめ:WordPressへの埋め込み手順
- テーマフォルダ内に
/tetris/
を作成し、HTML/CSS/JS/音声ファイルを配置。 - 固定ページや投稿のHTMLモードで以下を貼り付け:
<iframe src="<?php echo get_template_directory_uri(); ?>/tetris/index.html?v=2.0.8" width="700" height="600" frameborder="0"></iframe>
- キャッシュ対策にバージョンパラメータ(
?v=2.0.8
)を付与。 - 必要に応じてCSSで
max-width:100%; height:auto;
を追加。
即時実行関数(IIFE)によるスコープ管理:
コード全体を (() => { … })();
で囲むことで、グローバル変数の汚染を防ぎ、複数ページへの埋め込み時にも他スクリプトと衝突しにくくなっています。
定数定義とキャンバス初期化:const COLS=12, ROWS=20, TILE=20;
で盤面サイズを一元管理。canvas.width=COLS*TILE;
と canvas.height=ROWS*TILE;
でピクセル単位を厳密に計算し、描画がズレないようにしています。
定数 | 役割 |
---|---|
COLS | 横セル数(12) |
ROWS | 縦セル数(20) |
TILE | 1セルのピクセル幅(20px) |
MIN_INTERVAL | 落下速度の最小間隔(100ms) |
マトリクス&Bag乱数:arena
は 2 次元配列で盤面を管理。createBag()
は 7 種のテトリミノを Fisher–Yates shuffle で混ぜ、偏りなく次のピースを生成します。
衝突判定の詳細:
次のように4方向と範囲外をチェックしてブロックの重なりを検出します。
if (x+pos.x < 0 || x+pos.x >= COLS ||
y+pos.y >= ROWS ||
(y+pos.y >= 0 && arena[y+pos.y][x+pos.x])) {
return true;
}
範囲外チェックを最初に行うことで無駄な配列アクセスを抑制し、高速化しています。
ライン消去と得点計算:
消した行数に応じて得点と効果音を切り替え:
- 1行:+5点
- 2行:+15点
- 3行:+30点
- 4行(テトリス):+50点
消去後は dropInterval = Math.max(100, 1000 - (level-1)*100);
でレベルに応じて落下速度を調整します。
描画ループとNextピース:requestAnimationFrame(update)
で 1 フレームずつ時間差を計測し、dropCounter
が閾値を超えたら自動落下。
Next ピースはキャンバス幅からブロックサイズを引き、Math.floor
で中央にオフセットを計算しています。
操作ボタンのイベントバインド:
矢印キーやボタン押下で playerMove
/playerDrop
/player.rotate
を実行。
長押し時は setInterval
で 100ms ごとに連続落下させています。
サウンドの非同期再生:
各効果音は再生前に currentTime=0
を設定し、重なっても必ず最初から再生されるようにしています。BGM はループ再生し、ゲームオーバー時に pause()
で止めます。
これらの仕組みを踏まえれば、コードの各ブロックが何のためにあり、どのように動いているかが明確になります。カスタマイズや機能追加の際にも、迷わず手を入れられる基盤が整ったと言えるでしょう。
コメント