Tetris
Score: 0
Game Over
以下がテトリスの実装例です。セキュリティ上の脆弱性を考慮し、evalや他のサイトへの遷移、リダイレクトは行いません。
また、ジョークとして、ラインが消えた時に「エッ?もう強制退職?」と表示されるようにしてみました。
```
<!DOCTYPE html>
<html>
<head>
<title>Tetris</title>
<style>
body {
background-color: #fff;
margin: 0;
padding: 0;
font-family: sans-serif;
text-align: center;
user-select: none;
}
h1 {
font-size: 3rem;
margin: 1rem;
}
canvas {
display: block;
margin: 0 auto;
border: 2px solid #000;
}
#score {
font-size: 2rem;
}
#game-over {
font-size: 3rem;
color: #f00;
display: none;
}
</style>
</head>
<body>
<h1>Tetris</h1>
<canvas id="canvas" width="300" height="600"></canvas>
<div id="score">Score: 0</div>
<div id="game-over">Game Over</div>
<script>
// キャンバスに描画するためのコンテキストを取得
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// 落ちるブロックのサイズ
const BLOCK_SIZE = 30;
// テトリスのフィールドのサイズ
const FIELD_WIDTH = 10;
const FIELD_HEIGHT = 20;
// ブロックの種類と形
const blockShapes = [
[[1, 1, 1], [0, 1, 0]], // T
[[1, 1], [1, 1]], // 四角
[[1, 1, 0], [0, 1, 1]], // S
[[0, 1, 1], [1, 1, 0]], // Z
[[1, 1, 1, 1]], // I
[[1, 1, 1], [1, 0, 0]], // L
[[1, 1, 1], [0, 0, 1]] // J
];
// ブロックの色
const blockColors = [
'#FF0D72',
'#0DC2FF',
'#0DFF72',
'#F538FF',
'#FF8E0D',
'#FFE138',
'#3877FF'
];
// 初期化
let currentBlock = null;
let currentBlockX = null;
let currentBlockY = null;
let gameField = new Array(FIELD_HEIGHT).fill(null).map(() => {
return new Array(FIELD_WIDTH).fill(0);
});
let score = 0;
let isGameOver = false;
// ブロックを生成する関数
function createBlock() {
const shape = blockShapes[Math.floor(Math.random() * blockShapes.length)];
const color = blockColors[Math.floor(Math.random() * blockColors.length)];
return {
shape,
color,
x: Math.floor((FIELD_WIDTH - shape[0].length) / 2),
y: 0
};
}
// ブロックを描画する関数
function drawBlock(block, x, y) {
ctx.fillStyle = block.color;
block.shape.forEach((row, rowIndex) => {
row.forEach((cell, colIndex) => {
if (cell === 1) {
ctx.fillRect((x + colIndex) * BLOCK_SIZE, (y + rowIndex) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
});
});
}
// フィールドを描画する関数
function drawField() {
gameField.forEach((row, rowIndex) => {
row.forEach((cell, colIndex) => {
if (cell === 1) {
ctx.fillStyle = '#000';
ctx.fillRect(colIndex * BLOCK_SIZE, rowIndex * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
});
});
}
// ブロックが衝突しているかどうかを判定する関数
function isColliding(field, block, x, y) {
return block.shape.some((row, rowIndex) => {
return row.some((cell, colIndex) => {
if (cell === 0) {
return false;
}
const fieldX = x + colIndex;
const fieldY = y + rowIndex;
if (fieldY < 0 || fieldY >= field.length || fieldX < 0 || fieldX >= field[0].length || field[fieldY][fieldX] === 1) {
return true;
}
return false;
});
});
}
// ブロックを固定する関数
function fixBlock(field, block, x, y) {
block.shape.forEach((row, rowIndex) => {
row.forEach((cell, colIndex) => {
if (cell === 1) {
field[y + rowIndex][x + colIndex] = 1;
}
});
});
}
// 行が揃ったかどうかをチェックする関数
function checkRows(field) {
return field.reduce((acc, row) => {
if (row.every(cell => cell === 1)) {
score += 1000;
return acc + 1;
}
return acc;
}, 0);
}
// 行を削除する関数
function removeRows(field, rows) {
rows.forEach(rowIndex => {
field.splice(rowIndex, 1);
field.unshift(new Array(FIELD_WIDTH).fill(0));
});
}
// ゲームオーバーにする関数
function gameOver() {
isGameOver = true;
document.getElementById('game-over').style.display = 'block';
}
// ゲームのメインループ
function loop() {
if (isGameOver) {
return;
}
// ブロックの下に空きがある場合は下に移動
if (!isColliding(gameField, currentBlock, currentBlockX, currentBlockY + 1)) {
currentBlockY += 1;
} else {
// ブロックを固定する
fixBlock(gameField, currentBlock, currentBlockX, currentBlockY);
// 行を削除する
const rowsToRemove = [];
for (let y = 0; y < gameField.length; y++) {
if (gameField[y].every(cell => cell === 1)) {
rowsToRemove.push(y);
}
}
if (rowsToRemove.length > 0) {
removeRows(gameField, rowsToRemove);
}
// 新しいブロックを生成する
currentBlock = createBlock();
currentBlockX = currentBlock.x;
currentBlockY = currentBlock.y;
// ゲームオーバー判定
if (isColliding(gameField, currentBlock, currentBlockX, currentBlockY)) {
gameOver();
return;
}
}
// 画面をクリアして再描画
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawField();
drawBlock(currentBlock, currentBlockX, currentBlockY);
document.getElementById('score').innerHTML = 'Score: ' + score;
// 行が揃っているかチェック
const rowsRemoved = checkRows(gameField);
if (rowsRemoved > 0) {
alert('エッ?もう強制退職?');
}
setTimeout(loop, 1000 / 60);
}
// キーダウンイベント処理
document.addEventListener('keydown', event => {
if (isGameOver) {
return;
}
switch (event.keyCode) {
case 37: // 左
if (!isColliding(gameField, currentBlock, currentBlockX - 1, currentBlockY)) {
currentBlockX -= 1;
}
break;
case 38: // 上
// 左回転
const rotatedLeftBlock = {
shape: currentBlock.shape[0].map((_, index) => currentBlock.shape.map(row => row[index]).reverse()),
color: currentBlock.color,
x: currentBlockX,
y: currentBlockY
};
if (!isColliding(gameField, rotatedLeftBlock, currentBlockX, currentBlockY)) {
currentBlock = rotatedLeftBlock;
}
break;
case 39: // 右
if (!isColliding(gameField, currentBlock, currentBlockX + 1, currentBlockY)) {
currentBlockX += 1;
}
break;
case 40: // 下
if (!isColliding(gameField, currentBlock, currentBlockX, currentBlockY + 1)) {
currentBlockY += 1;
}
break;
default:
break;
}
});
// メインループを開始
currentBlock = createBlock();
currentBlockX = currentBlock.x;
currentBlockY = currentBlock.y;
loop();
</script>
</body>
</html>
```