🐷
tetris
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>テトリス</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
background-color: #111;
color: white;
margin: 0;
padding: 20px;
height: 100vh;
}
.game-container {
display: flex;
gap: 20px;
margin-top: 20px;
}
.tetris-board {
border: 2px solid #555;
background-color: #222;
display: grid;
grid-template-rows: repeat(20, 20px);
grid-template-columns: repeat(10, 20px);
}
.cell {
width: 20px;
height: 20px;
border: 1px solid #333;
box-sizing: border-box;
}
.filled {
border: 1px solid rgba(255, 255, 255, 0.5);
}
.info-panel {
display: flex;
flex-direction: column;
gap: 20px;
width: 150px;
}
.next-piece {
border: 2px solid #555;
background-color: #222;
padding: 10px;
display: grid;
grid-template-rows: repeat(4, 20px);
grid-template-columns: repeat(4, 20px);
}
.score-panel, .controls {
border: 2px solid #555;
background-color: #222;
padding: 15px;
}
.score-panel h3, .controls h3 {
margin-top: 0;
margin-bottom: 10px;
text-align: center;
}
.game-over {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.8);
padding: 20px;
border-radius: 10px;
text-align: center;
display: none;
}
button {
background-color: #4CAF50;
border: none;
color: white;
padding: 10px 15px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 5px;
}
button:hover {
background-color: #45a049;
}
.control-row {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
}
.control-key {
font-weight: bold;
color: #AAA;
}
/* テトロミノの色 */
.I { background-color: #00FFFF; }
.O { background-color: #FFFF00; }
.T { background-color: #800080; }
.S { background-color: #00FF00; }
.Z { background-color: #FF0000; }
.J { background-color: #0000FF; }
.L { background-color: #FFA500; }
</style>
</head>
<body>
<h1>テトリス</h1>
<div class="game-container">
<div class="tetris-board" id="board"></div>
<div class="info-panel">
<div class="score-panel">
<h3>スコア</h3>
<p>スコア: <span id="score">0</span></p>
<p>レベル: <span id="level">1</span></p>
<p>ライン: <span id="lines">0</span></p>
</div>
<div class="next-piece" id="next-piece"></div>
<div class="controls">
<h3>操作方法</h3>
<div class="control-row">
<span class="control-key">←→</span>
<span>移動</span>
</div>
<div class="control-row">
<span class="control-key">↑</span>
<span>回転</span>
</div>
<div class="control-row">
<span class="control-key">↓</span>
<span>下に移動</span>
</div>
<div class="control-row">
<span class="control-key">スペース</span>
<span>ハードドロップ</span>
</div>
<div class="control-row">
<span class="control-key">P</span>
<span>一時停止</span>
</div>
</div>
<button id="start-button">ゲーム開始</button>
</div>
</div>
<div class="game-over" id="game-over">
<h2>ゲームオーバー</h2>
<p>最終スコア: <span id="final-score">0</span></p>
<button id="restart-button">もう一度プレイ</button>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 定数
const BOARD_WIDTH = 10;
const BOARD_HEIGHT = 20;
const CELL_SIZE = 20;
// テトロミノの形状
const SHAPES = {
I: [
[0, 0, 0, 0],
[1, 1, 1, 1],
[0, 0, 0, 0],
[0, 0, 0, 0]
],
O: [
[1, 1],
[1, 1]
],
T: [
[0, 1, 0],
[1, 1, 1],
[0, 0, 0]
],
S: [
[0, 1, 1],
[1, 1, 0],
[0, 0, 0]
],
Z: [
[1, 1, 0],
[0, 1, 1],
[0, 0, 0]
],
J: [
[1, 0, 0],
[1, 1, 1],
[0, 0, 0]
],
L: [
[0, 0, 1],
[1, 1, 1],
[0, 0, 0]
]
};
// 要素の取得
const boardElement = document.getElementById('board');
const nextPieceElement = document.getElementById('next-piece');
const scoreElement = document.getElementById('score');
const levelElement = document.getElementById('level');
const linesElement = document.getElementById('lines');
const startButton = document.getElementById('start-button');
const restartButton = document.getElementById('restart-button');
const gameOverElement = document.getElementById('game-over');
const finalScoreElement = document.getElementById('final-score');
// ゲーム状態
let board = Array(BOARD_HEIGHT).fill().map(() => Array(BOARD_WIDTH).fill(0));
let currentPiece = null;
let nextPiece = null;
let score = 0;
let level = 1;
let lines = 0;
let gameInterval = null;
let isPaused = false;
let gameStarted = false;
// ボードの初期化
function initBoard() {
boardElement.innerHTML = '';
for (let y = 0; y < BOARD_HEIGHT; y++) {
for (let x = 0; x < BOARD_WIDTH; x++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.dataset.x = x;
cell.dataset.y = y;
boardElement.appendChild(cell);
}
}
}
// ネクストピースエリアの初期化
function initNextPieceArea() {
nextPieceElement.innerHTML = '';
for (let y = 0; y < 4; y++) {
for (let x = 0; x < 4; x++) {
const cell = document.createElement('div');
cell.classList.add('cell');
nextPieceElement.appendChild(cell);
}
}
}
// ボードの描画
function drawBoard() {
const cells = boardElement.querySelectorAll('.cell');
cells.forEach(cell => {
const x = parseInt(cell.dataset.x);
const y = parseInt(cell.dataset.y);
// セルのクラスをリセット
cell.className = 'cell';
if (board[y][x]) {
cell.classList.add('filled');
cell.classList.add(board[y][x]);
}
});
}
// 現在のピースを描画
function drawCurrentPiece() {
if (!currentPiece) return;
const shape = SHAPES[currentPiece.type];
const cells = boardElement.querySelectorAll('.cell');
for (let y = 0; y < shape.length; y++) {
for (let x = 0; x < shape[y].length; x++) {
if (shape[y][x]) {
const boardX = currentPiece.x + x;
const boardY = currentPiece.y + y;
if (boardX >= 0 && boardX < BOARD_WIDTH && boardY >= 0 && boardY < BOARD_HEIGHT) {
const index = boardY * BOARD_WIDTH + boardX;
cells[index].classList.add('filled');
cells[index].classList.add(currentPiece.type);
}
}
}
}
}
// ネクストピースを描画
function drawNextPiece() {
if (!nextPiece) return;
const shape = SHAPES[nextPiece.type];
const cells = nextPieceElement.querySelectorAll('.cell');
// まずすべてのセルをクリア
cells.forEach(cell => {
cell.className = 'cell';
});
// ネクストピースを中央に描画
const offsetX = nextPiece.type === 'O' ? 1 : nextPiece.type === 'I' ? 0 : 0.5;
const offsetY = nextPiece.type === 'I' ? 0 : 0.5;
for (let y = 0; y < shape.length; y++) {
for (let x = 0; x < shape[y].length; x++) {
if (shape[y][x]) {
const displayX = Math.floor(x + offsetX);
const displayY = Math.floor(y + offsetY);
const index = displayY * 4 + displayX;
if (index >= 0 && index < cells.length) {
cells[index].classList.add('filled');
cells[index].classList.add(nextPiece.type);
}
}
}
}
}
// 新しいピースを生成
function generatePiece() {
const types = Object.keys(SHAPES);
const randomType = types[Math.floor(Math.random() * types.length)];
return {
type: randomType,
x: Math.floor(BOARD_WIDTH / 2) - Math.floor(SHAPES[randomType][0].length / 2),
y: 0
};
}
// 衝突判定
function checkCollision(pieceType, x, y, rotation = 0) {
let shape = SHAPES[pieceType];
// 回転を適用
for (let i = 0; i < rotation; i++) {
shape = rotateMatrix(shape);
}
for (let row = 0; row < shape.length; row++) {
for (let col = 0; col < shape[row].length; col++) {
if (shape[row][col]) {
const boardX = x + col;
const boardY = y + row;
// ボードの範囲外または既に埋まっているセルがある場合は衝突
if (
boardX < 0 ||
boardX >= BOARD_WIDTH ||
boardY >= BOARD_HEIGHT ||
(boardY >= 0 && board[boardY][boardX])
) {
return true;
}
}
}
}
return false;
}
// 行が揃っているかチェック
function checkLines() {
let clearedLines = 0;
for (let y = BOARD_HEIGHT - 1; y >= 0; y--) {
if (board[y].every(cell => cell !== 0)) {
// 行が揃っている場合、その行を削除
board.splice(y, 1);
// 新しい空の行を先頭に追加
board.unshift(Array(BOARD_WIDTH).fill(0));
clearedLines++;
y++; // 同じ行をもう一度チェック(消えた行の上の行が下がってくるため)
}
}
if (clearedLines > 0) {
// スコア計算
const linePoints = [40, 100, 300, 1200]; // 1, 2, 3, 4行消したときのポイント
score += linePoints[clearedLines - 1] * level;
scoreElement.textContent = score;
// ライン数更新
lines += clearedLines;
linesElement.textContent = lines;
// レベル更新
const newLevel = Math.floor(lines / 10) + 1;
if (newLevel > level) {
level = newLevel;
levelElement.textContent = level;
// ゲームスピードを更新
clearInterval(gameInterval);
gameInterval = setInterval(gameLoop, Math.max(100, 1000 - (level - 1) * 100));
}
}
}
// 行列を時計回りに90度回転
function rotateMatrix(matrix) {
const N = matrix.length;
const result = Array(N).fill().map(() => Array(N).fill(0));
for (let y = 0; y < N; y++) {
for (let x = 0; x < N; x++) {
result[x][N - 1 - y] = matrix[y][x];
}
}
return result;
}
// ピースを固定
function lockPiece() {
const shape = SHAPES[currentPiece.type];
for (let y = 0; y < shape.length; y++) {
for (let x = 0; x < shape[y].length; x++) {
if (shape[y][x]) {
const boardX = currentPiece.x + x;
const boardY = currentPiece.y + y;
if (boardY >= 0 && boardY < BOARD_HEIGHT && boardX >= 0 && boardX < BOARD_WIDTH) {
board[boardY][boardX] = currentPiece.type;
}
}
}
}
// 行が揃っているかチェック
checkLines();
// 次のピースを現在のピースに
currentPiece = nextPiece;
nextPiece = generatePiece();
drawNextPiece();
// ゲームオーバーをチェック
if (checkCollision(currentPiece.type, currentPiece.x, currentPiece.y)) {
gameOver();
}
}
// ハードドロップ
function hardDrop() {
while (!checkCollision(currentPiece.type, currentPiece.x, currentPiece.y + 1)) {
currentPiece.y++;
score += 2; // ハードドロップでは1行につき2点
}
scoreElement.textContent = score;
lockPiece();
drawBoard();
drawCurrentPiece();
}
// ゲームループ
function gameLoop() {
if (isPaused) return;
if (checkCollision(currentPiece.type, currentPiece.x, currentPiece.y + 1)) {
lockPiece();
} else {
currentPiece.y++;
}
drawBoard();
drawCurrentPiece();
}
// ゲームオーバー
function gameOver() {
clearInterval(gameInterval);
gameStarted = false;
finalScoreElement.textContent = score;
gameOverElement.style.display = 'block';
}
// ゲームリセット
function resetGame() {
clearInterval(gameInterval);
board = Array(BOARD_HEIGHT).fill().map(() => Array(BOARD_WIDTH).fill(0));
score = 0;
level = 1;
lines = 0;
scoreElement.textContent = score;
levelElement.textContent = level;
linesElement.textContent = lines;
gameOverElement.style.display = 'none';
isPaused = false;
}
// ゲーム開始
function startGame() {
if (gameStarted) return;
resetGame();
gameStarted = true;
currentPiece = generatePiece();
nextPiece = generatePiece();
drawBoard();
drawCurrentPiece();
drawNextPiece();
gameInterval = setInterval(gameLoop, 1000);
}
// キーボード操作
document.addEventListener('keydown', (e) => {
if (!gameStarted || isPaused) {
if (e.key === 'p' || e.key === 'P') {
isPaused = !isPaused;
}
return;
}
switch (e.key) {
case 'ArrowLeft':
if (!checkCollision(currentPiece.type, currentPiece.x - 1, currentPiece.y)) {
currentPiece.x--;
drawBoard();
drawCurrentPiece();
}
break;
case 'ArrowRight':
if (!checkCollision(currentPiece.type, currentPiece.x + 1, currentPiece.y)) {
currentPiece.x++;
drawBoard();
drawCurrentPiece();
}
break;
case 'ArrowDown':
if (!checkCollision(currentPiece.type, currentPiece.x, currentPiece.y + 1)) {
currentPiece.y++;
score += 1; // ソフトドロップで1点
scoreElement.textContent = score;
drawBoard();
drawCurrentPiece();
}
break;
case 'ArrowUp':
// ピースを回転
const rotatedShape = rotateMatrix(SHAPES[currentPiece.type]);
let canRotate = true;
for (let y = 0; y < rotatedShape.length; y++) {
for (let x = 0; x < rotatedShape[y].length; x++) {
if (rotatedShape[y][x]) {
const boardX = currentPiece.x + x;
const boardY = currentPiece.y + y;
if (
boardX < 0 ||
boardX >= BOARD_WIDTH ||
boardY >= BOARD_HEIGHT ||
(boardY >= 0 && board[boardY][boardX])
) {
canRotate = false;
break;
}
}
}
if (!canRotate) break;
}
if (canRotate) {
SHAPES[currentPiece.type] = rotatedShape;
drawBoard();
drawCurrentPiece();
}
break;
case ' ': // スペース
hardDrop();
break;
case 'p':
case 'P':
isPaused = !isPaused;
break;
}
});
// スタートボタン
startButton.addEventListener('click', startGame);
// リスタートボタン
restartButton.addEventListener('click', () => {
gameOverElement.style.display = 'none';
startGame();
});
// 初期化
initBoard();
initNextPieceArea();
});
</script>
</body>
</html>
Discussion