🐷

tetris

2025/02/26に公開
<!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