ブラウザで動作するシンプルなテトリスゲームを作成しました。以下のコードを使用して実装できます。
HTMLコード(index.html)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テトリス</title>
<style>
canvas {
border: 1px solid #555;
background-color: #000;
}
#score {
color: #fff;
font-size: 20px;
position: absolute;
top: 10px;
left: 260px;
}
</style>
</head>
<body>
<canvas id="tetris" width="240" height="400"></canvas>
<div id="score">スコア: 0</div>
<script src="tetris.js"></script>
</body>
</html>
JavaScriptコード(tetris.js)
// キャンバスとコンテキストの取得
const canvas = document.getElementById('tetris');
const context = canvas.getContext('2d');
// スケールを設定(ブロックのサイズを調整)
context.scale(20, 20);
// ブロックの色の配列
const colors = [
null, // インデックス0は使用しない
'#FF0D72', // T
'#0DC2FF', // I
'#0DFF72', // S
'#F538FF', // Z
'#FF8E0D', // L
'#FFE138', // O
'#3877FF', // J
];
// ラインが揃ったときに消去する関数
function arenaSweep() {
let rowCount = 1;
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; // 埋まっていない場合は次の行へ
}
}
// ラインを削除し、新しい空の行を上に追加
arena.splice(y, 1);
arena.unshift(new Array(arena[0].length).fill(0));
++y;
// スコアの更新
player.score += rowCount * 10;
rowCount *= 2;
}
updateScore();
}
// 衝突を検出する関数
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;
}
// フィールド(矩形配列)を作成する関数
function createMatrix(w, h) {
const matrix = [];
while (h--) {
matrix.push(new Array(w).fill(0)); // 幅w、高さhの2次元配列を作成
}
return matrix;
}
// テトリミノ(ブロック)を作成する関数
function createPiece(type) {
switch(type) {
case 'T':
return [
[0, 0, 0],
[1, 1, 1],
[0, 1, 0],
];
case 'O':
return [
[6, 6],
[6, 6],
];
case 'L':
return [
[0, 5, 0],
[0, 5, 0],
[0, 5, 5],
];
case 'J':
return [
[0, 7, 0],
[0, 7, 0],
[7, 7, 0],
];
case 'I':
return [
[0, 2, 0, 0],
[0, 2, 0, 0],
[0, 2, 0, 0],
[0, 2, 0, 0],
];
case 'S':
return [
[0, 3, 3],
[3, 3, 0],
[0, 0, 0],
];
case 'Z':
return [
[4, 4, 0],
[0, 4, 4],
[0, 0, 0],
];
}
}
// 指定された位置にブロックを描画する関数
function drawMatrix(matrix, offset) {
matrix.forEach((row, y) => {
row.forEach((value, x) => {
if(value !== 0) {
context.fillStyle = colors[value]; // ブロックの色を設定
context.fillRect(x + offset.x,
y + offset.y,
1, 1); // ブロックを描画
}
});
});
}
// 全体を描画する関数
function draw() {
// キャンバスをクリア
context.fillStyle = '#000';
context.fillRect(0, 0, canvas.width, canvas.height);
// フィールドと現在のブロックを描画
drawMatrix(arena, {x:0, y:0});
drawMatrix(player.matrix, player.pos);
}
// ブロックをフィールドに固定する関数
function merge(arena, player) {
player.matrix.forEach((row, y) => {
row.forEach((value, x) => {
if(value !== 0) {
arena[y + player.pos.y][x + player.pos.x] = value;
}
});
});
}
// ブロックを回転する関数
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(); // 左回転
}
}
// ブロックを下に移動する関数
function playerDrop() {
player.pos.y++;
if(collide(arena, player)) {
player.pos.y--; // 衝突したら位置を戻す
merge(arena, player); // ブロックを固定
playerReset(); // 新しいブロックを生成
arenaSweep(); // ラインが揃っているか確認
}
dropCounter = 0;
}
// ブロックを左右に移動する関数
function playerMove(dir) {
player.pos.x += dir;
if(collide(arena, player)) {
player.pos.x -= dir; // 衝突したら位置を戻す
}
}
// 新しいブロックを生成する関数
function playerReset() {
const pieces = 'ILJOTSZ';
// ランダムにブロックを選択
player.matrix = createPiece(pieces[Math.floor(pieces.length * Math.random())]);
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();
}
}
// ブロックを回転させる関数
function playerRotate(dir) {
const pos = player.pos.x;
let offset = 1;
rotate(player.matrix, dir); // ブロックを回転
// 壁を突き抜けないように調整
while(collide(arena, player)) {
player.pos.x += offset;
offset = -(offset + (offset > 0 ? 1 : -1));
if(offset > player.matrix[0].length) {
rotate(player.matrix, -dir); // 回転を元に戻す
player.pos.x = pos;
return;
}
}
}
// 自動的にブロックを落下させるための変数
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); // 次のフレームを要求
}
// スコアを更新する関数
function updateScore() {
document.getElementById('score').innerText = 'スコア: ' + player.score;
}
// フィールド(12x20の配列)を作成
const arena = createMatrix(12, 20);
// プレイヤー情報の初期化
const player = {
pos: {x:0, y:0}, // ブロックの位置
matrix: null, // ブロックの形状
score: 0, // スコア
};
// キーボード入力の処理
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); // 左回転
} else if(event.keyCode === 87) {
playerRotate(1); // 右回転
}
});
// ゲームの開始
playerReset();
updateScore();
update();
説明
- HTML部分:
<canvas>
要素でゲーム画面を表示します。<div id="score">
でスコアを表示します。- スタイルシートでキャンバスの見た目を調整しています。
- JavaScript部分:
- ゲームロジック:テトリスの動作をすべてJavaScriptで実装しています。
createPiece
関数でテトリミノ(ブロック)を生成。draw
関数でブロックやスコアの描画。playerMove
、playerDrop
、playerRotate
関数でブロックの操作。arenaSweep
関数でラインが揃った際の処理。- キーボードイベントでユーザー入力を処理。
操作方法
- 移動:左矢印キー(←)、右矢印キー(→)
- 回転:
Q
キー(左回転)、W
キー(右回転) - 高速落下:下矢印キー(↓)
使用手順
- ファイルの準備:
- 上記のHTMLコードを
index.html
として保存。 - JavaScriptコードを
tetris.js
として保存。
- ファイルの配置:
- 同じフォルダ内に
index.html
とtetris.js
を配置。
- ゲームの起動:
index.html
をブラウザで開く。
- ゲームのプレイ:
- キーボードでブロックを操作してテトリスを楽しんでください。
注意:このコードは学習目的のための簡易的な実装です。実際のゲーム開発では、追加の機能やエラーチェックが必要になる場合があります。
以前のバージョンでは一発で動かす事はできなかったので、凄い進化!