🎮ArcadeLab

Классический Арканоид

by LuckyGlider65
413 lines15.9 KB
▶ Play
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Классический Арканоид</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background: #1a1a2e;
            font-family: 'Arial', sans-serif;
        }
        .game-container {
            background: #16213e;
            padding: 20px;
            border-radius: 12px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
        }
        canvas {
            display: block;
            margin: 0 auto;
            border-radius: 8px;
            background: #0f3460;
            cursor: none;
        }
        .score-board {
            display: flex;
            justify-content: space-between;
            align-items: center;
            color: #e0e0e0;
            font-size: 20px;
            padding: 10px 5px 15px 5px;
        }
        .restart-btn {
            background: #e94560;
            color: white;
            border: none;
            padding: 8px 20px;
            border-radius: 6px;
            font-size: 18px;
            font-weight: bold;
            cursor: pointer;
            transition: 0.2s;
        }
        .restart-btn:hover {
            background: #c73652;
            transform: scale(1.05);
        }
        .controls {
            color: #a8a8b3;
            font-size: 14px;
            text-align: center;
            padding-top: 10px;
        }
    </style>
</head>
<body>
    <div class="game-container">
        <div class="score-board">
            <span>💎 <span id="scoreDisplay">0</span></span>
            <span>❤️ <span id="livesDisplay">3</span></span>
            <button class="restart-btn" id="restartButton">🔄 Заново</button>
        </div>
        <canvas id="gameCanvas" width="600" height="400"></canvas>
        <div class="controls">← → или мышь для управления платформой</div>
    </div>

    <script>
        (function(){
            const canvas = document.getElementById('gameCanvas');
            const ctx = canvas.getContext('2d');
            const scoreSpan = document.getElementById('scoreDisplay');
            const livesSpan = document.getElementById('livesDisplay');

            // --- Параметры игры ---
            const BALL_SIZE = 8;
            const PADDLE_WIDTH = 80;
            const PADDLE_HEIGHT = 12;
            const PADDLE_SPEED = 6;
            const BRICK_ROWS = 6;
            const BRICK_COLS = 8;
            const BRICK_WIDTH = 62;
            const BRICK_HEIGHT = 20;
            const BRICK_PADDING = 5;
            const BRICK_OFFSET_TOP = 40;
            const BRICK_OFFSET_LEFT = 20;

            // --- Состояние игры ---
            let ball = {
                x: canvas.width / 2,
                y: canvas.height - 40,
                radius: BALL_SIZE / 2,
                dx: 0,
                dy: 0,
                speed: 4.5
            };
            let paddle = {
                x: (canvas.width - PADDLE_WIDTH) / 2,
                y: canvas.height - 30,
                width: PADDLE_WIDTH,
                height: PADDLE_HEIGHT,
                dx: 0
            };
            let bricks = [];
            let score = 0;
            let lives = 3;
            let gameRunning = true;
            let animationFrameId = null;
            let gameStarted = false;

            // --- Управление ---
            let keys = {
                left: false,
                right: false
            };

            // --- Инициализация кирпичей ---
            function initBricks() {
                const colors = ['#e94560', '#f5a623', '#f7d44a', '#4ecdc4', '#45b7d1', '#a29bfe'];
                bricks = [];
                for (let row = 0; row < BRICK_ROWS; row++) {
                    for (let col = 0; col < BRICK_COLS; col++) {
                        const x = BRICK_OFFSET_LEFT + col * (BRICK_WIDTH + BRICK_PADDING);
                        const y = BRICK_OFFSET_TOP + row * (BRICK_HEIGHT + BRICK_PADDING);
                        const color = colors[row % colors.length];
                        bricks.push({
                            x: x,
                            y: y,
                            width: BRICK_WIDTH,
                            height: BRICK_HEIGHT,
                            color: color,
                            alive: true
                        });
                    }
                }
            }

            // --- Сброс мяча ---
            function resetBall() {
                ball.x = canvas.width / 2;
                ball.y = canvas.height - 40;
                ball.dx = 0;
                ball.dy = 0;
                gameStarted = false;
            }

            // --- Начать движение мяча ---
            function startBall() {
                if (gameStarted) return;
                const angle = (Math.random() * 60 + 15) * Math.PI / 180;
                const direction = Math.random() > 0.5 ? 1 : -1;
                ball.dx = ball.speed * Math.cos(angle) * direction;
                ball.dy = -ball.speed * Math.sin(angle);
                gameStarted = true;
            }

            // --- Обработка столкновений ---
            function collisionDetection() {
                for (let i = 0; i < bricks.length; i++) {
                    const brick = bricks[i];
                    if (!brick.alive) continue;

                    const ballLeft = ball.x - ball.radius;
                    const ballRight = ball.x + ball.radius;
                    const ballTop = ball.y - ball.radius;
                    const ballBottom = ball.y + ball.radius;

                    if (ballRight > brick.x && ballLeft < brick.x + brick.width &&
                        ballBottom > brick.y && ballTop < brick.y + brick.height) {

                        brick.alive = false;
                        score += 10;
                        updateScore();

                        // Определение направления отскока
                        const overlapX = Math.min(ballRight - brick.x, brick.x + brick.width - ballLeft);
                        const overlapY = Math.min(ballBottom - brick.y, brick.y + brick.height - ballTop);

                        if (overlapX < overlapY) {
                            ball.dx = -ball.dx;
                        } else {
                            ball.dy = -ball.dy;
                        }

                        // Проверка победы
                        if (bricks.every(b => !b.alive)) {
                            gameRunning = false;
                            setTimeout(() => {
                                alert('🎉 Поздравляем! Вы сломали все кирпичи! 🎉');
                                resetGame();
                            }, 100);
                        }
                        return;
                    }
                }
            }

            // --- Обновление ---
            function update() {
                if (!gameRunning || !gameStarted) return;

                // Движение платформы
                if (keys.left && paddle.x > 0) {
                    paddle.x -= PADDLE_SPEED;
                }
                if (keys.right && paddle.x + paddle.width < canvas.width) {
                    paddle.x += PADDLE_SPEED;
                }

                // Движение мяча
                ball.x += ball.dx;
                ball.y += ball.dy;

                // Столкновение со стенами
                if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
                    ball.dx = -ball.dx;
                }
                if (ball.y - ball.radius < 0) {
                    ball.dy = -ball.dy;
                }

                // Столкновение с платформой
                if (ball.dy > 0 &&
                    ball.y + ball.radius > paddle.y &&
                    ball.y + ball.radius < paddle.y + paddle.height + 5 &&
                    ball.x > paddle.x - ball.radius &&
                    ball.x < paddle.x + paddle.width + ball.radius) {

                    // Отскок с изменением угла в зависимости от места попадания
                    const hitPos = (ball.x - paddle.x) / paddle.width;
                    const angle = (hitPos - 0.5) * Math.PI / 2.2;
                    const speed = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
                    ball.dx = speed * Math.sin(angle);
                    ball.dy = -speed * Math.cos(angle);

                    // Дополнительное ускорение для динамики
                    if (speed < 6.5) {
                        const currentSpeed = Math.sqrt(ball.dx * ball.dx + ball.dy * ball.dy);
                        const newSpeed = Math.min(currentSpeed * 1.02, 6.5);
                        const ratio = newSpeed / currentSpeed;
                        ball.dx *= ratio;
                        ball.dy *= ratio;
                    }
                }

                // Потеря мяча
                if (ball.y + ball.radius > canvas.height) {
                    lives--;
                    updateLives();
                    if (lives === 0) {
                        gameRunning = false;
                        setTimeout(() => {
                            alert('💔 Игра окончена! Попробуйте снова.');
                            resetGame();
                        }, 100);
                    } else {
                        resetBall();
                        paddle.x = (canvas.width - PADDLE_WIDTH) / 2;
                    }
                }

                collisionDetection();
            }

            // --- Отрисовка ---
            function draw() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                // Кирпичи
                for (const brick of bricks) {
                    if (!brick.alive) continue;
                    ctx.fillStyle = brick.color;
                    ctx.shadowColor = 'rgba(255,255,255,0.2)';
                    ctx.shadowBlur = 6;
                    ctx.fillRect(brick.x, brick.y, brick.width, brick.height);
                    ctx.shadowBlur = 0;
                    ctx.fillStyle = 'rgba(255,255,255,0.15)';
                    ctx.fillRect(brick.x + 2, brick.y + 2, brick.width - 4, 6);
                }

                // Платформа
                ctx.shadowColor = 'rgba(255,255,255,0.2)';
                ctx.shadowBlur = 10;
                ctx.fillStyle = '#4ecdc4';
                ctx.beginPath();
                ctx.roundRect(paddle.x, paddle.y, paddle.width, paddle.height, 6);
                ctx.fill();
                ctx.shadowBlur = 0;

                // Мяч
                ctx.shadowColor = 'rgba(255,215,0,0.3)';
                ctx.shadowBlur = 12;
                ctx.beginPath();
                ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
                ctx.fillStyle = '#f7d44a';
                ctx.fill();
                ctx.shadowBlur = 0;
                ctx.fillStyle = 'rgba(255,255,255,0.4)';
                ctx.beginPath();
                ctx.arc(ball.x - 2, ball.y - 3, 2.5, 0, Math.PI * 2);
                ctx.fill();
            }

            // --- Вспомогательные функции ---
            function updateScore() {
                scoreSpan.textContent = score;
            }

            function updateLives() {
                livesSpan.textContent = lives;
            }

            function resetGame() {
                score = 0;
                lives = 3;
                gameRunning = true;
                gameStarted = false;
                updateScore();
                updateLives();
                initBricks();
                paddle.x = (canvas.width - PADDLE_WIDTH) / 2;
                resetBall();
                cancelAnimationFrame(animationFrameId);
                gameLoop();
            }

            // --- Игровой цикл ---
            function gameLoop() {
                update();
                draw();
                animationFrameId = requestAnimationFrame(gameLoop);
            }

            // --- Обработчики событий ---
            document.addEventListener('keydown', (e) => {
                if (e.key === 'ArrowLeft' || e.key === 'Left' || e.key === 'a' || e.key === 'A') {
                    keys.left = true;
                    e.preventDefault();
                } else if (e.key === 'ArrowRight' || e.key === 'Right' || e.key === 'd' || e.key === 'D') {
                    keys.right = true;
                    e.preventDefault();
                } else if (e.key === ' ' || e.key === 'Space') {
                    e.preventDefault();
                    if (!gameStarted && gameRunning) {
                        startBall();
                    }
                }
            });

            document.addEventListener('keyup', (e) => {
                if (e.key === 'ArrowLeft' || e.key === 'Left' || e.key === 'a' || e.key === 'A') {
                    keys.left = false;
                    e.preventDefault();
                } else if (e.key === 'ArrowRight' || e.key === 'Right' || e.key === 'd' || e.key === 'D') {
                    keys.right = false;
                    e.preventDefault();
                }
            });

            // Управление мышью (движение платформы)
            canvas.addEventListener('mousemove', (e) => {
                const rect = canvas.getBoundingClientRect();
                const scaleX = canvas.width / rect.width;
                const mouseX = (e.clientX - rect.left) * scaleX;
                const halfPaddle = paddle.width / 2;
                paddle.x = Math.min(Math.max(mouseX - halfPaddle, 0), canvas.width - paddle.width);
            });

            // Клик для запуска мяча
            canvas.addEventListener('click', () => {
                if (!gameStarted && gameRunning) {
                    startBall();
                }
            });

            // Перезапуск
            document.getElementById('restartButton').addEventListener('click', resetGame);

            // --- Полифил для roundRect ---
            if (!CanvasRenderingContext2D.prototype.roundRect) {
                CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
                    if (r > w / 2) r = w / 2;
                    if (r > h / 2) r = h / 2;
                    this.moveTo(x + r, y);
                    this.lineTo(x + w - r, y);
                    this.quadraticCurveTo(x + w, y, x + w, y + r);
                    this.lineTo(x + w, y + h - r);
                    this.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
                    this.lineTo(x + r, y + h);
                    this.quadraticCurveTo(x, y + h, x, y + h - r);
                    this.lineTo(x, y + r);
                    this.quadraticCurveTo(x, y, x + r, y);
                    this.closePath();
                    return this;
                };
            }

            // --- Запуск ---
            initBricks();
            resetBall();
            gameLoop();
        })();
    </script>
</body>
</html>

Game Source: Классический Арканоид

Creator: LuckyGlider65

Libraries: none

Complexity: complex (413 lines, 15.9 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-luckyglider65" to link back to the original. Then publish at arcadelab.ai/publish.