🎮ArcadeLab

Сабвей Серф | Хомяки-монеты

by SparkHawk26
491 lines18.5 KB
▶ Play
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>Сабвей Серф | Хомяки-монеты</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            user-select: none;
        }
        body {
            background: linear-gradient(145deg, #0a2f3a 0%, #05161c 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: 'Courier New', 'Segoe UI', monospace;
            touch-action: manipulation;
        }
        .game-container {
            padding: 20px;
            border-radius: 40px;
            background: rgba(0,0,0,0.4);
            box-shadow: 0 20px 35px rgba(0,0,0,0.5);
        }
        canvas {
            display: block;
            margin: 0 auto;
            border-radius: 28px;
            box-shadow: 0 0 0 4px #f5a623, 0 15px 30px black;
            cursor: pointer;
        }
        .info {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            margin-top: 16px;
            background: #1e2a2e;
            padding: 12px 25px;
            border-radius: 60px;
            color: #f7d44a;
            text-shadow: 0 2px 0 #6b2e00;
            font-weight: bold;
            font-size: 1.4rem;
            gap: 15px;
            flex-wrap: wrap;
        }
        .score-box, .best-box, .coins-box {
            background: #000000aa;
            padding: 5px 20px;
            border-radius: 40px;
            backdrop-filter: blur(4px);
        }
        .coins-box {
            background: #c97e2caa;
            color: #ffec80;
        }
        .controls {
            margin-top: 15px;
            display: flex;
            gap: 15px;
            justify-content: center;
        }
        button {
            background: #2c3e2f;
            border: none;
            color: #f1c40f;
            font-size: 1.7rem;
            font-weight: bold;
            padding: 8px 25px;
            border-radius: 50px;
            box-shadow: 0 5px 0 #1a2a1a;
            cursor: pointer;
            transition: 0.07s linear;
            font-family: monospace;
        }
        button:active {
            transform: translateY(3px);
            box-shadow: 0 2px 0 #1a2a1a;
        }
        .restart-btn {
            background: #9b2c1d;
            color: #ffd966;
        }
        @media (max-width: 700px) {
            .info { font-size: 1rem; padding: 8px 12px;}
            button { font-size: 1.2rem; padding: 6px 18px;}
        }
    </style>
</head>
<body>
<div>
    <div class="game-container">
        <canvas id="gameCanvas" width="800" height="550"></canvas>
        <div class="info">
            <span>🏃‍♂️ СКЕЙТБОРД</span>
            <span class="score-box">📊 Счёт: <span id="scoreValue">0</span></span>
            <span class="coins-box">🐹 Хомяки: <span id="coinsValue">0</span></span>
            <span class="best-box">🏆 Рекорд: <span id="bestValue">0</span></span>
        </div>
        <div class="controls">
            <button id="leftBtn">◀ ЛЕВО</button>
            <button id="rightBtn">ПРАВО ▶</button>
            <button id="restartBtn" class="restart-btn">🔄 НОВАЯ ИГРА</button>
        </div>
        <p style="text-align:center; margin-top: 12px; color:#bbd4ce; font-weight: bold;">⬅️  ➡️  стрелки | собирай крутящихся хомяков 🐹 | уклоняйся от поездов 🚆</p>
    </div>
</div>

<script>
    (function(){
        // ----- Холст -----
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');

        // ----- Игровые параметры -----
        const LANES = 3;
        const LANE_WIDTH = 110;
        const BASE_X = 180;
        const PLAYER_WIDTH = 42;
        const PLAYER_HEIGHT = 52;
        const TRAIN_WIDTH = 48;
        const TRAIN_HEIGHT = 56;
        const HAMSTER_SIZE = 32;   // размер хомяка

        // ----- Состояние -----
        let gameRunning = true;
        let score = 0;
        let coins = 0;             // количество собранных хомяков
        let bestScore = localStorage.getItem('subwayBest') ? parseInt(localStorage.getItem('subwayBest')) : 0;
        let frame = 0;
        let playerLane = 1;

        let trains = [];
        let hamsters = [];          // массив хомяков-монет { lane, y, rotationAngle }

        let baseSpeed = 4.2;
        let currentSpeed = baseSpeed;

        // Вспомогательные функции UI
        function updateBestUI() {
            document.getElementById('bestValue').innerText = bestScore;
        }
        function updateScoreUI() {
            document.getElementById('scoreValue').innerText = Math.floor(score);
        }
        function updateCoinsUI() {
            document.getElementById('coinsValue').innerText = coins;
        }

        function endGame() {
            if(!gameRunning) return;
            gameRunning = false;
            let finalScore = Math.floor(score);
            if(finalScore > bestScore) {
                bestScore = finalScore;
                localStorage.setItem('subwayBest', bestScore);
                updateBestUI();
            }
        }

        function restartGame() {
            gameRunning = true;
            score = 0;
            coins = 0;
            frame = 0;
            trains = [];
            hamsters = [];
            playerLane = 1;
            currentSpeed = baseSpeed;
            updateScoreUI();
            updateCoinsUI();
            bestScore = localStorage.getItem('subwayBest') ? parseInt(localStorage.getItem('subwayBest')) : 0;
            updateBestUI();
        }

        // Спавн поезда
        function spawnTrain() {
            let lane = Math.floor(Math.random() * LANES);
            trains.push({
                lane: lane,
                y: -TRAIN_HEIGHT - Math.random() * 40,
                width: TRAIN_WIDTH,
                height: TRAIN_HEIGHT
            });
        }

        // Спавн хомяка-монетки (крутящийся)
        function spawnHamster() {
            let lane = Math.floor(Math.random() * LANES);
            hamsters.push({
                lane: lane,
                y: -HAMSTER_SIZE - Math.random() * 70,
                rotation: Math.random() * Math.PI * 2,  // начальный угол
                size: HAMSTER_SIZE
            });
        }

        function updateGame() {
            if(!gameRunning) return;

            // сложность + скорость
            currentSpeed = baseSpeed + Math.floor(score / 450) * 0.55;
            if(currentSpeed > 12) currentSpeed = 12;

            // движение поездов
            for(let i=0; i<trains.length; i++) {
                trains[i].y += currentSpeed;
            }
            trains = trains.filter(t => t.y < canvas.height + 100);

            // движение хомяков
            for(let i=0; i<hamsters.length; i++) {
                hamsters[i].y += currentSpeed;
                // вращение: увеличиваем угол с каждым кадром
                hamsters[i].rotation = (hamsters[i].rotation + 0.12) % (Math.PI * 2);
            }
            hamsters = hamsters.filter(h => h.y < canvas.height + 80);

            // спавн (поезда + хомяки)
            let spawnRateTrains = Math.max(18, 42 - Math.floor(score / 180));
            let spawnRateHamsters = Math.max(12, 28 - Math.floor(score / 130));
            
            if(frame % spawnRateTrains === 0 && gameRunning) {
                spawnTrain();
                if(score > 700 && Math.random() < 0.25) spawnTrain();
            }
            
            if(frame % spawnRateHamsters === 0 && gameRunning) {
                spawnHamster();
                // иногда два хомяка подряд
                if(Math.random() < 0.2 && gameRunning) spawnHamster();
            }

            // ----- КОЛЛИЗИЯ ИГРОК + ПОЕЗД -----
            const playerX = BASE_X + playerLane * LANE_WIDTH + (LANE_WIDTH/2) - PLAYER_WIDTH/2;
            const playerRect = {
                x: playerX,
                y: canvas.height - PLAYER_HEIGHT - 18,
                w: PLAYER_WIDTH,
                h: PLAYER_HEIGHT
            };

            for(let i=0; i<trains.length; i++) {
                const t = trains[i];
                const trainX = BASE_X + t.lane * LANE_WIDTH + (LANE_WIDTH/2) - TRAIN_WIDTH/2;
                const trainRect = {
                    x: trainX,
                    y: t.y,
                    w: TRAIN_WIDTH,
                    h: TRAIN_HEIGHT
                };
                if(playerRect.x < trainRect.x + trainRect.w &&
                    playerRect.x + playerRect.w > trainRect.x &&
                    playerRect.y < trainRect.y + trainRect.h &&
                    playerRect.y + playerRect.h > trainRect.y) {
                    endGame();
                    break;
                }
            }

            // ----- СБОР ХОМЯКОВ (монет) -----
            for(let i=0; i<hamsters.length; i++) {
                const h = hamsters[i];
                const hamsterX = BASE_X + h.lane * LANE_WIDTH + (LANE_WIDTH/2) - HAMSTER_SIZE/2;
                const hamsterRect = {
                    x: hamsterX,
                    y: h.y,
                    w: HAMSTER_SIZE,
                    h: HAMSTER_SIZE
                };
                if(playerRect.x < hamsterRect.x + hamsterRect.w &&
                    playerRect.x + playerRect.w > hamsterRect.x &&
                    playerRect.y < hamsterRect.y + hamsterRect.h &&
                    playerRect.y + playerRect.h > hamsterRect.y) {
                    // собираем хомяка!
                    coins++;
                    updateCoinsUI();
                    hamsters.splice(i,1);
                    i--; // коррекция индекса
                }
            }

            // очки за выживание
            if(gameRunning) {
                score += 0.27;
                updateScoreUI();
            }

            frame++;
        }

        // ---- ОТРИСОВКА КРУТЯЩЕГОСЯ ХОМЯКА ----
        function drawRotatingHamster(x, y, size, angle) {
            ctx.save();
            ctx.translate(x + size/2, y + size/2);
            ctx.rotate(angle);
            // тело хомяка (круглое)
            ctx.fillStyle = "#c28a4a";
            ctx.beginPath();
            ctx.ellipse(0, 0, size/2, size/2.2, 0, 0, Math.PI*2);
            ctx.fill();
            // брюшко светлое
            ctx.fillStyle = "#e7bc8b";
            ctx.beginPath();
            ctx.ellipse(0, size*0.1, size/2.5, size/3, 0, 0, Math.PI*2);
            ctx.fill();
            // ушки
            ctx.fillStyle = "#aa6f3c";
            ctx.beginPath();
            ctx.ellipse(-size*0.25, -size*0.3, size*0.22, size*0.2, 0, 0, Math.PI*2);
            ctx.fill();
            ctx.beginPath();
            ctx.ellipse(size*0.25, -size*0.3, size*0.22, size*0.2, 0, 0, Math.PI*2);
            ctx.fill();
            // глазки
            ctx.fillStyle = "#2f1e0f";
            ctx.beginPath();
            ctx.arc(-size*0.15, -size*0.05, size*0.07, 0, Math.PI*2);
            ctx.fill();
            ctx.beginPath();
            ctx.arc(size*0.15, -size*0.05, size*0.07, 0, Math.PI*2);
            ctx.fill();
            ctx.fillStyle = "white";
            ctx.beginPath();
            ctx.arc(-size*0.18, -size*0.09, size*0.025, 0, Math.PI*2);
            ctx.fill();
            ctx.beginPath();
            ctx.arc(size*0.12, -size*0.09, size*0.025, 0, Math.PI*2);
            ctx.fill();
            // носик
            ctx.fillStyle = "#ff6f61";
            ctx.beginPath();
            ctx.ellipse(0, size*0.08, size*0.09, size*0.07, 0, 0, Math.PI*2);
            ctx.fill();
            // щёчки розовые
            ctx.fillStyle = "#ffaaaa";
            ctx.beginPath();
            ctx.ellipse(-size*0.24, size*0.12, size*0.09, size*0.06, 0, 0, Math.PI*2);
            ctx.fill();
            ctx.beginPath();
            ctx.ellipse(size*0.24, size*0.12, size*0.09, size*0.06, 0, 0, Math.PI*2);
            ctx.fill();
            // маленькая монетка сверху (эффект ценности)
            ctx.fillStyle = "#f5bc70";
            ctx.beginPath();
            ctx.ellipse(0, -size*0.28, size*0.15, size*0.1, 0, 0, Math.PI*2);
            ctx.fill();
            ctx.fillStyle = "#f7d44a";
            ctx.font = `${Math.floor(size*0.6)}px monospace`;
            ctx.shadowBlur = 2;
            ctx.fillText("🐹", -size*0.15, -size*0.22);
            ctx.restore();
        }

        // ---- ОТРИСОВКА ВСЕГО ----
        function draw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            // фон
            const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
            grad.addColorStop(0, "#183c42");
            grad.addColorStop(1, "#212f2c");
            ctx.fillStyle = grad;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            
            // рельсы
            for(let i=0; i<=LANES; i++) {
                let railX = BASE_X + i * LANE_WIDTH - 8;
                ctx.beginPath();
                ctx.lineWidth = 5;
                ctx.strokeStyle = "#cab577";
                for(let s=-10; s<canvas.height+20; s+=30) {
                    ctx.beginPath();
                    ctx.moveTo(railX, s);
                    ctx.lineTo(railX+12, s+15);
                    ctx.stroke();
                }
                ctx.strokeStyle = "#6b4c2c";
                ctx.lineWidth = 4;
                ctx.beginPath();
                ctx.moveTo(railX+2, 0);
                ctx.lineTo(railX+2, canvas.height);
                ctx.stroke();
            }
            // шпалы
            ctx.fillStyle = "#714623";
            for(let i=0; i<LANES; i++) {
                for(let y=30; y<canvas.height; y+=48) {
                    ctx.fillRect(BASE_X + i*LANE_WIDTH + 15, y, 70, 10);
                }
            }
            
            // поезда
            for(let t of trains) {
                const trainX = BASE_X + t.lane * LANE_WIDTH + (LANE_WIDTH/2) - TRAIN_WIDTH/2;
                ctx.fillStyle = "#3a2819";
                ctx.fillRect(trainX, t.y, TRAIN_WIDTH, TRAIN_HEIGHT);
                ctx.fillStyle = "#bc9a6c";
                ctx.fillRect(trainX+6, t.y+8, TRAIN_WIDTH-12, 12);
                ctx.fillStyle = "#e6c394";
                ctx.fillRect(trainX+8, t.y+24, 10, 18);
                ctx.fillRect(trainX+TRAIN_WIDTH-18, t.y+24, 10, 18);
                ctx.fillStyle = "#ffaa33";
                ctx.beginPath();
                ctx.arc(trainX+TRAIN_WIDTH/2, t.y+12, 7, 0, Math.PI*2);
                ctx.fill();
                ctx.fillStyle = "#98d9ff";
                ctx.fillRect(trainX+12, t.y+32, 24, 16);
            }
            
            // ХОМЯКИ (крутящиеся монетки)
            for(let h of hamsters) {
                const hamsterX = BASE_X + h.lane * LANE_WIDTH + (LANE_WIDTH/2) - HAMSTER_SIZE/2;
                drawRotatingHamster(hamsterX, h.y, HAMSTER_SIZE, h.rotation);
            }
            
            // ИГРОК
            const playerDrawX = BASE_X + playerLane * LANE_WIDTH + (LANE_WIDTH/2) - PLAYER_WIDTH/2;
            const playerY = canvas.height - PLAYER_HEIGHT - 18;
            ctx.fillStyle = "#1e88e5";
            ctx.fillRect(playerDrawX, playerY, PLAYER_WIDTH, PLAYER_HEIGHT);
            ctx.fillStyle = "#ffb74d";
            ctx.beginPath();
            ctx.ellipse(playerDrawX+PLAYER_WIDTH/2, playerY-8, 18, 12, 0, 0, Math.PI*2);
            ctx.fill();
            ctx.fillStyle = "#4e342e";
            ctx.fillRect(playerDrawX+8, playerY+28, 8, 18);
            ctx.fillRect(playerDrawX+PLAYER_WIDTH-16, playerY+28, 8, 18);
            ctx.fillStyle = "#f5f5dc";
            ctx.beginPath();
            ctx.arc(playerDrawX+PLAYER_WIDTH/2, playerY+12, 12, 0, Math.PI*2);
            ctx.fill();
            ctx.fillStyle = "#2c2c2c";
            ctx.fillRect(playerDrawX+12, playerY+18, 6, 6);
            ctx.fillRect(playerDrawX+PLAYER_WIDTH-18, playerY+18, 6, 6);
            ctx.fillStyle = "#d4380d";
            ctx.fillRect(playerDrawX-5, playerY+PLAYER_HEIGHT-6, PLAYER_WIDTH+10, 7);
            ctx.fillStyle = "#f0a020";
            ctx.beginPath();
            ctx.arc(playerDrawX-2, playerY+PLAYER_HEIGHT-2, 6, 0, Math.PI*2);
            ctx.arc(playerDrawX+PLAYER_WIDTH+2, playerY+PLAYER_HEIGHT-2, 6, 0, Math.PI*2);
            ctx.fill();
            
            // конец игры
            if(!gameRunning) {
                ctx.font = "bold 34monospace";
                ctx.fillStyle = "#ffd966";
                ctx.shadowBlur = 0;
                ctx.fillText("💥 GAME OVER", canvas.width/2-150, canvas.height/2-40);
                ctx.font = "22monospace";
                ctx.fillStyle = "#f7eac5";
                ctx.fillText("нажми «НОВАЯ ИГРА»", canvas.width/2-130, canvas.height/2+30);
            }
            
            // красивый счётчик хомяков
            ctx.font = "bold 20monospace";
            ctx.fillStyle = "#ffdd99";
            ctx.fillText("🐹 x"+coins, canvas.width-100, 35);
        }
        
        // управление
        function moveLeft() { if(gameRunning && playerLane > 0) playerLane--; }
        function moveRight() { if(gameRunning && playerLane < LANES-1) playerLane++; }
        
        function handleKey(e) {
            if(e.key === 'ArrowLeft') { moveLeft(); e.preventDefault();}
            else if(e.key === 'ArrowRight') { moveRight(); e.preventDefault();}
        }
        
        function gameLoop() {
            updateGame();
            draw();
            requestAnimationFrame(gameLoop);
        }
        
        window.addEventListener('load', () => {
            restartGame();
            document.getElementById('leftBtn').addEventListener('click', moveLeft);
            document.getElementById('rightBtn').addEventListener('click', moveRight);
            document.getElementById('restartBtn').addEventListener('click', () => restartGame());
            window.addEventListener('keydown', handleKey);
            gameLoop();
        });
    })();
</script>
</body>
</html>

Game Source: Сабвей Серф | Хомяки-монеты

Creator: SparkHawk26

Libraries: none

Complexity: complex (491 lines, 18.5 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: -sparkhawk26" to link back to the original. Then publish at arcadelab.ai/publish.