Why not login to Qiita and try out its useful features?

We'll deliver articles that match you.

You can read useful information later.

0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptでテトリス!ブラウザでテトリス!

Posted at

1000003369.png
GeminiにテトリスのPythonソースコードをJavaScriptに書き換えてもらいました。

極悪テトリミノ"X"がいきなり出現して脂肪しますんで、真面目にプレイせず、ギャグとして軽く流して下さいw

※Geminiさんがメタタグを忘れたんで、文字化けしてしまいました。
もちろん修正してあります。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>テトリス</title>
    <style>
        body {
            font-family: sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            background-color: #000;
            color: #fff;
            margin: 20px;
        }

        canvas {
            border: 2px solid #fff;
            background-color: #222;
        }

        .controls {
            display: flex;
            gap: 10px;
            margin-top: 20px;
        }

        .controls button {
            padding: 10px 20px;
            font-size: 16px;
            cursor: pointer;
            background-color: #333;
            color: #fff;
            border: 1px solid #555;
            border-radius: 5px;
        }

        .controls button:active {
            background-color: #555;
        }

        #score {
            margin-top: 10px;
            font-size: 18px;
        }

        .game-over {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: rgba(0, 0, 0, 0.8);
            padding: 20px;
            border-radius: 5px;
            text-align: center;
        }

        .game-over h1 {
            margin-top: 0;
        }

        .game-over button {
            padding: 10px 20px;
            font-size: 16px;
            cursor: pointer;
            background-color: green;
            color: #fff;
            border: none;
            border-radius: 5px;
            margin-top: 10px;
        }

        .game-over button:active {
            background-color: darkgreen;
        }
    </style>
</head>
<body>
    <canvas id="tetrisCanvas" width="300" height="700"></canvas>
    <div class="controls">
        <button id="leftButton"></button>
        <button id="rightButton"></button>
        <button id="rotateButton">回転</button>
        <button id="downButton"></button>
        <div id="score">スコア: 0</div>
    </div>

    <script>
        const canvas = document.getElementById('tetrisCanvas');
        const ctx = canvas.getContext('2d');
        const scoreElement = document.getElementById('score');
        const leftButton = document.getElementById('leftButton');
        const rightButton = document.getElementById('rightButton');
        const rotateButton = document.getElementById('rotateButton');
        const downButton = document.getElementById('downButton');

        const BLOCK_SIZE = 30;
        const GRID_WIDTH = 10;
        const GRID_HEIGHT = 20;
        const BUTTON_AREA_HEIGHT = 100;
        const SCREEN_WIDTH = BLOCK_SIZE * GRID_WIDTH;
        const SCREEN_HEIGHT = BLOCK_SIZE * GRID_HEIGHT + BUTTON_AREA_HEIGHT;
        const GAME_AREA_LEFT = 0;

        const SHAPES = [
            [[1, 1, 1, 1]],  // I
            [[1, 1], [1, 1]],  // O
            [[1, 1, 1], [0, 1, 0]],  // T
            [[1, 1, 1], [1, 0, 0]],  // L
            [[1, 1, 1], [0, 0, 1]],  // J
            [[0, 1, 1], [1, 1, 0]],  // S
            [[1, 1, 0], [0, 1, 1]],  // Z
            [[1, 0 ,1], [0, 1, 0],[1, 0, 1]]   // X
        ];

        const COLORS = ['cyan', 'yellow', 'purple', 'orange', 'blue', 'lime', 'red', 'white'];

        let grid = createGrid();
        let currentTetrimino = createTetrimino();
        let nextTetrimino = createTetrimino();
        let score = 0;
        let fallInterval = 500; // ミリ秒
        let lastFallTime = 0;
        let isGameOver = false;

        function createGrid() {
            return Array(GRID_HEIGHT).fill(null).map(() => Array(GRID_WIDTH).fill(0));
        }

        function createTetrimino() {
            const shapeIndex = Math.floor(Math.random() * SHAPES.length);
            const shape = SHAPES[shapeIndex];
            const color = COLORS[shapeIndex];
            const x = Math.floor(GRID_WIDTH / 2) - Math.floor(shape[0].length / 2);
            const y = 0;
            return { shape, color, x, y };
        }

        function rotate(tetrimino) {
            const rows = tetrimino.shape.length;
            const cols = tetrimino.shape[0].length;
            const rotatedShape = Array(cols).fill(null).map(() => Array(rows).fill(0));
            for (let i = 0; i < rows; i++) {
                for (let j = 0; j < cols; j++) {
                    rotatedShape[j][rows - 1 - i] = tetrimino.shape[i][j];
                }
            }
            return { ...tetrimino, shape: rotatedShape };
        }

        function drawBlock(x, y, color) {
            ctx.fillStyle = color;
            ctx.fillRect(GAME_AREA_LEFT + x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
            ctx.strokeStyle = '#fff';
            ctx.strokeRect(GAME_AREA_LEFT + x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
        }

        function drawGrid() {
            grid.forEach((row, y) => {
                row.forEach((value, x) => {
                    if (value !== 0) {
                        drawBlock(x, y, COLORS[value - 1]);
                    } else {
                        ctx.strokeStyle = '#fff';
                        ctx.strokeRect(GAME_AREA_LEFT + x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
                    }
                });
            });
        }

        function drawTetrimino() {
            currentTetrimino.shape.forEach((row, y) => {
                row.forEach((cell, x) => {
                    if (cell) {
                        drawBlock(currentTetrimino.x + x, currentTetrimino.y + y, currentTetrimino.color);
                    }
                });
            });
        }

        function isValidMove(tetrimino, newX, newY, newShape = tetrimino.shape) {
            for (let y = 0; y < newShape.length; y++) {
                for (let x = 0; x < newShape[y].length; x++) {
                    if (newShape[y][x]) {
                        const gridX = newX + x;
                        const gridY = newY + y;
                        if (gridX < 0 || gridX >= GRID_WIDTH || gridY >= GRID_HEIGHT || (gridY >= 0 && grid[gridY][gridX] !== 0)) {
                            return false;
                        }
                    }
                }
            }
            return true;
        }

        function moveTetrimino(dx, dy) {
            const newX = currentTetrimino.x + dx;
            const newY = currentTetrimino.y + dy;
            if (isValidMove(currentTetrimino, newX, newY)) {
                currentTetrimino.x = newX;
                currentTetrimino.y = newY;
            } else if (dy > 0) {
                freezeTetrimino();
            }
        }

        function rotateTetrimino() {
            const rotated = rotate(currentTetrimino);
            if (isValidMove(rotated, currentTetrimino.x, currentTetrimino.y, rotated.shape)) {
                currentTetrimino = rotated;
            }
        }

        function freezeTetrimino() {
            currentTetrimino.shape.forEach((row, y) => {
                row.forEach((cell, x) => {
                    if (cell) {
                        grid[currentTetrimino.y + y][currentTetrimino.x + x] = COLORS.indexOf(currentTetrimino.color) + 1;
                    }
                });
            });
            currentTetrimino = nextTetrimino;
            nextTetrimino = createTetrimino();
            checkClearRows();
            if (!isValidMove(currentTetrimino, currentTetrimino.x, currentTetrimino.y)) {
                gameOver();
            }
        }

        function checkClearRows() {
            let rowsCleared = 0;
            for (let y = GRID_HEIGHT - 1; y >= 0; y--) {
                if (grid[y].every(cell => cell !== 0)) {
                    rowsCleared++;
                    grid.splice(y, 1);
                    grid.unshift(Array(GRID_WIDTH).fill(0));
                }
            }
            updateScore(rowsCleared);
        }

        function updateScore(rows) {
            if (rows === 1) score += 100;
            else if (rows === 2) score += 300;
            else if (rows === 3) score += 500;
            else if (rows === 4) score += 800;
            scoreElement.textContent = `スコア: ${score}`;
        }

        function gameOver() {
            isGameOver = true;
            const gameOverDiv = document.createElement('div');
            gameOverDiv.classList.add('game-over');
            gameOverDiv.innerHTML = `
                <h1>ゲームオーバー!</h1>
                <p>最終スコア: ${score}</p>
                <button id="restartButton">再プレイ</button>
            `;
            document.body.appendChild(gameOverDiv);
            document.getElementById('restartButton').addEventListener('click', restartGame);
        }

        function restartGame() {
            grid = createGrid();
            currentTetrimino = createTetrimino();
            nextTetrimino = createTetrimino();
            score = 0;
            scoreElement.textContent = `スコア: 0`;
            lastFallTime = Date.now();
            isGameOver = false;
            const gameOverDiv = document.querySelector('.game-over');
            if (gameOverDiv) {
                gameOverDiv.remove();
            }
            gameLoop();
        }

        function gameLoop() {
            if (!isGameOver) {
                const currentTime = Date.now();
                if (currentTime - lastFallTime > fallInterval) {
                    moveTetrimino(0, 1);
                    lastFallTime = currentTime;
                }

                ctx.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
                drawGrid();
                drawTetrimino();
                requestAnimationFrame(gameLoop);
            }
        }

        // イベントリスナー
        document.addEventListener('keydown', (e) => {
            if (!isGameOver) {
                switch (e.key) {
                    case 'ArrowLeft':
                        moveTetrimino(-1, 0);
                        break;
                    case 'ArrowRight':
                        moveTetrimino(1, 0);
                        break;
                    case 'ArrowUp':
                        rotateTetrimino();
                        break;
                    case 'ArrowDown':
                        moveTetrimino(0, 1);
                        break;
                }
            }
        });

        leftButton.addEventListener('click', () => !isGameOver && moveTetrimino(-1, 0));
        rightButton.addEventListener('click', () => !isGameOver && moveTetrimino(1, 0));
        rotateButton.addEventListener('click', () => !isGameOver && rotateTetrimino());
        downButton.addEventListener('click', () => !isGameOver && moveTetrimino(0, 1));

        // ゲーム開始
        gameLoop();
    </script>
</body>
</html>
0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

@Cyber-Chaos's pickup articles

Comments

ffgogfb454
@ffgogfb454

clickイベントは押した要素を放した瞬間に発火する性質上、こういったゲームではタイミングが微妙にずれがちですので、要素を押した瞬間に発火するpointerdownイベントを使った方が良いかと思います。

- leftButton.addEventListener('click', () => !isGameOver && moveTetrimino(-1, 0));
- rightButton.addEventListener('click', () => !isGameOver && moveTetrimino(1, 0));
- rotateButton.addEventListener('click', () => !isGameOver && rotateTetrimino());
- downButton.addEventListener('click', () => !isGameOver && moveTetrimino(0, 1));

+ leftButton.addEventListener('pointerdown', () => !isGameOver && moveTetrimino(-1, 0));
+ rightButton.addEventListener('pointerdown', () => !isGameOver && moveTetrimino(1, 0));
+ rotateButton.addEventListener('pointerdown', () => !isGameOver && rotateTetrimino());
+ downButton.addEventListener('pointerdown', () => !isGameOver && moveTetrimino(0, 1));
0

Let's comment your feelings that are more than good

Qiita Conference 2025 will be held!: 4/23(wed) - 4/25(Fri)

Qiita Conference is the largest tech conference in Qiita!

Keynote Speaker

ymrl、Masanobu Naruse, Takeshi Kano, Junichi Ito, uhyo, Hiroshi Tokumaru, MinoDriven, Minorun, Hiroyuki Sakuraba, tenntenn, drken, konifar

View event details

Being held Article posting campaign

0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Login to continue?

Login or Sign up with social account

Login or Sign up with your email address