sapbot

City Lights

Feb 14th, 2026
59
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 39.58 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html lang="ru">
  3. <head>
  4.    <meta charset="UTF-8">
  5.    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.    <title>City Lights - Arcade Action</title>
  7.    <style>
  8.        @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&display=swap');
  9.        
  10.        * {
  11.            margin: 0;
  12.            padding: 0;
  13.            box-sizing: border-box;
  14.        }
  15.        
  16.        body {
  17.            background: #0a0a0a;
  18.            color: #fff;
  19.            font-family: 'Orbitron', monospace;
  20.            overflow: hidden;
  21.            display: flex;
  22.            justify-content: center;
  23.            align-items: center;
  24.            min-height: 100vh;
  25.        }
  26.        
  27.        #gameContainer {
  28.            position: relative;
  29.            width: 100vw;
  30.            height: 100vh;
  31.            display: flex;
  32.            justify-content: center;
  33.            align-items: center;
  34.        }
  35.        
  36.        canvas {
  37.            box-shadow: 0 0 50px rgba(0, 150, 255, 0.5);
  38.            border: 2px solid #333;
  39.            image-rendering: pixelated;
  40.        }
  41.        
  42.        #ui {
  43.            position: absolute;
  44.            top: 20px;
  45.            left: 20px;
  46.            font-size: 18px;
  47.            text-shadow: 0 0 10px #00ffff;
  48.            pointer-events: none;
  49.            z-index: 10;
  50.        }
  51.        
  52.        #gameOver {
  53.            position: absolute;
  54.            top: 50%;
  55.            left: 50%;
  56.            transform: translate(-50%, -50%);
  57.            text-align: center;
  58.            display: none;
  59.            background: rgba(0,0,0,0.9);
  60.            padding: 40px;
  61.            border: 3px solid #ff0066;
  62.            box-shadow: 0 0 30px #ff0066;
  63.            z-index: 20;
  64.        }
  65.        
  66.        #gameOver h2 {
  67.            font-size: 48px;
  68.            color: #ff0066;
  69.            margin-bottom: 20px;
  70.            text-shadow: 0 0 20px #ff0066;
  71.        }
  72.        
  73.        #gameOver button {
  74.            font-family: 'Orbitron', monospace;
  75.            font-size: 24px;
  76.            padding: 15px 30px;
  77.            background: #00ffff;
  78.            color: #000;
  79.            border: none;
  80.            cursor: pointer;
  81.            margin-top: 20px;
  82.            transition: all 0.3s;
  83.        }
  84.        
  85.        #gameOver button:hover {
  86.            background: #fff;
  87.            box-shadow: 0 0 20px #00ffff;
  88.        }
  89.        
  90.        #startScreen {
  91.            position: absolute;
  92.            top: 50%;
  93.            left: 50%;
  94.            transform: translate(-50%, -50%);
  95.            text-align: center;
  96.            background: rgba(0,0,0,0.95);
  97.            padding: 60px;
  98.            border: 3px solid #00ffff;
  99.            box-shadow: 0 0 50px rgba(0, 255, 255, 0.5);
  100.            z-index: 20;
  101.        }
  102.        
  103.        #startScreen h1 {
  104.            font-size: 64px;
  105.            margin-bottom: 20px;
  106.            background: linear-gradient(45deg, #00ffff, #ff00ff, #ffff00);
  107.            -webkit-background-clip: text;
  108.            -webkit-text-fill-color: transparent;
  109.            background-clip: text;
  110.            text-shadow: none;
  111.            animation: glow 2s ease-in-out infinite alternate;
  112.        }
  113.        
  114.        @keyframes glow {
  115.            from { filter: drop-shadow(0 0 20px #00ffff); }
  116.            to { filter: drop-shadow(0 0 40px #ff00ff); }
  117.        }
  118.        
  119.        #startScreen p {
  120.            font-size: 18px;
  121.            margin: 10px 0;
  122.            color: #aaa;
  123.        }
  124.        
  125.        #startScreen .controls {
  126.            margin: 30px 0;
  127.            padding: 20px;
  128.            background: rgba(255,255,255,0.05);
  129.            border-left: 4px solid #00ffff;
  130.            text-align: left;
  131.        }
  132.        
  133.        #startScreen button {
  134.            font-family: 'Orbitron', monospace;
  135.            font-size: 28px;
  136.            padding: 20px 40px;
  137.            background: linear-gradient(45deg, #00ffff, #0099ff);
  138.            color: #000;
  139.            border: none;
  140.            cursor: pointer;
  141.            margin-top: 30px;
  142.            font-weight: bold;
  143.            transition: all 0.3s;
  144.            text-transform: uppercase;
  145.        }
  146.        
  147.        #startScreen button:hover {
  148.            transform: scale(1.1);
  149.            box-shadow: 0 0 40px #00ffff;
  150.        }
  151.        
  152.        .hud-item {
  153.            margin: 5px 0;
  154.        }
  155.        
  156.        .warning {
  157.            color: #ff0066;
  158.            animation: blink 0.5s infinite;
  159.        }
  160.        
  161.        @keyframes blink {
  162.            0%, 50% { opacity: 1; }
  163.            51%, 100% { opacity: 0.3; }
  164.        }
  165.        
  166.        #autopilotIndicator {
  167.            position: absolute;
  168.            top: 20px;
  169.            right: 20px;
  170.            color: #00ff00;
  171.            font-size: 24px;
  172.            text-shadow: 0 0 10px #00ff00;
  173.            display: none;
  174.            z-index: 10;
  175.        }
  176.        
  177.        /* Debug input styles */
  178.        #debugSection {
  179.            margin-top: 20px;
  180.            padding: 15px;
  181.            background: rgba(255,170,0,0.1);
  182.            border: 2px solid #ffaa00;
  183.            border-radius: 5px;
  184.        }
  185.        
  186.        #debugSection label {
  187.            display: block;
  188.            color: #ffaa00;
  189.            font-size: 14px;
  190.            margin-bottom: 8px;
  191.        }
  192.        
  193.        #debugInput {
  194.            font-family: 'Orbitron', monospace;
  195.            font-size: 18px;
  196.            width: 80px;
  197.            padding: 8px;
  198.            background: #1a1a1a;
  199.            border: 2px solid #ffaa00;
  200.            color: #fff;
  201.            text-align: center;
  202.            border-radius: 3px;
  203.        }
  204.        
  205.        #debugInput:focus {
  206.            outline: none;
  207.            box-shadow: 0 0 10px #ffaa00;
  208.        }
  209.        
  210.        #debugButton {
  211.            font-family: 'Orbitron', monospace;
  212.            font-size: 16px;
  213.            padding: 10px 20px;
  214.            background: #ffaa00;
  215.            color: #000;
  216.            border: none;
  217.            cursor: pointer;
  218.            margin-left: 10px;
  219.            border-radius: 3px;
  220.            font-weight: bold;
  221.            transition: all 0.3s;
  222.        }
  223.        
  224.        #debugButton:hover {
  225.            background: #ffcc00;
  226.            box-shadow: 0 0 15px #ffaa00;
  227.        }
  228.    </style>
  229. </head>
  230. <body>
  231.    <div id="gameContainer">
  232.        <canvas id="gameCanvas"></canvas>
  233.        
  234.        <div id="ui">
  235.            <div class="hud-item">РАУНД: <span id="roundDisplay">0</span></div>
  236.            <div class="hud-item">ОЧКИ: <span id="scoreDisplay">0</span></div>
  237.            <div class="hud-item">ЛАМП: <span id="lampsDisplay">0/0</span></div>
  238.            <div class="hud-item" id="timeDisplay"></div>
  239.            <div class="hud-item" id="hooliganWarning" style="display:none;" class="warning">⚠ ХУЛИГАН!</div>
  240.        </div>
  241.        
  242.        <div id="autopilotIndicator">🤖 АВТОПИЛОТ (F2)</div>
  243.        
  244.        <div id="startScreen">
  245.            <h1>CITY LIGHTS</h1>
  246.            <p>Аркадный экшен о мастере по ремонту фонарей</p>
  247.            <div class="controls">
  248.                <p><strong>Управление:</strong></p>
  249.                <p>🎮 Геймпад: Левый стик / D-Pad для движения</p>
  250.                <p>⌨️ Клавиатура: WASD или Стрелки для движения</p>
  251.                <p>🤖 F2 - Включить/выключить автопилот</p>
  252.                <p>🎯 Цель: Чините лампы до истечения времени!</p>
  253.                <p>⚠️ С 10 раунда: Останавливайте хулиганов!</p>
  254.                
  255.                <!-- Debug input field -->
  256.                <div id="debugSection">
  257.                    <label for="debugInput">🐛 Debug: Начать с раунда</label>
  258.                    <input type="number" id="debugInput" min="1" max="50" value="10" placeholder="1-50">
  259.                    <button id="debugButton" onclick="debugStart()">СТАРТ</button>
  260.                </div>
  261.            </div>
  262.            <button onclick="startGame()">НАЧАТЬ ИГРУ (Раунд 1)</button>
  263.        </div>
  264.        
  265.        <div id="gameOver">
  266.            <h2>ИГРА ОКОНЧЕНА</h2>
  267.            <p style="font-size: 24px; margin: 20px 0;">Раунд: <span id="finalRound">0</span></p>
  268.            <p style="font-size: 24px; margin: 20px 0;">Очки: <span id="finalScore">0</span></p>
  269.            <button onclick="restartGame()">ИГРАТЬ СНОВА</button>
  270.        </div>
  271.    </div>
  272.  
  273.    <script>
  274.        const canvas = document.getElementById('gameCanvas');
  275.        const ctx = canvas.getContext('2d');
  276.        
  277.        // Game state
  278.        let gameState = 'start';
  279.        let round = 0;
  280.        let score = 0;
  281.        let lamps = [];
  282.        let particles = [];
  283.        let hooligans = [];
  284.        let lastTime = 0;
  285.        let gamepadIndex = null;
  286.        let roundTransitionTimer = 0;
  287.        let isRoundTransition = false;
  288.        let autopilotEnabled = false;
  289.        let debugStartRound = 0;
  290.        
  291.        // Player
  292.        const player = {
  293.            x: 0,
  294.            y: 0,
  295.            radius: 15,
  296.            speed: 300,
  297.            color: '#00ffff',
  298.            vx: 0,
  299.            vy: 0,
  300.            targetLamp: null
  301.        };
  302.        
  303.        // Constants
  304.        const CANVAS_WIDTH = 1280;
  305.        const CANVAS_HEIGHT = 720;
  306.        const LAMP_RADIUS = 25;
  307.        const PLAYER_RADIUS = 15;
  308.        const FIX_DISTANCE = 40;
  309.        const STOP_HOOLIGAN_DISTANCE = 60;
  310.        
  311.        // Resize canvas
  312.        function resizeCanvas() {
  313.            const aspect = CANVAS_WIDTH / CANVAS_HEIGHT;
  314.            const windowAspect = window.innerWidth / window.innerHeight;
  315.            
  316.            let displayWidth, displayHeight;
  317.            
  318.            if (windowAspect > aspect) {
  319.                displayHeight = window.innerHeight;
  320.                displayWidth = displayHeight * aspect;
  321.            } else {
  322.                displayWidth = window.innerWidth;
  323.                displayHeight = displayWidth / aspect;
  324.            }
  325.            
  326.            canvas.style.width = displayWidth + 'px';
  327.            canvas.style.height = displayHeight + 'px';
  328.            canvas.width = CANVAS_WIDTH;
  329.            canvas.height = CANVAS_HEIGHT;
  330.        }
  331.        
  332.        window.addEventListener('resize', resizeCanvas);
  333.        resizeCanvas();
  334.        
  335.        // Input handling
  336.        const keys = {};
  337.        window.addEventListener('keydown', (e) => {
  338.            keys[e.key.toLowerCase()] = true;
  339.            
  340.            // F2 для автопилота
  341.            if (e.key === 'F2') {
  342.                toggleAutopilot();
  343.            }
  344.        });
  345.        window.addEventListener('keyup', (e) => {
  346.            keys[e.key.toLowerCase()] = false;
  347.        });
  348.        
  349.        // Debug start function
  350.        function debugStart() {
  351.            const input = document.getElementById('debugInput');
  352.            const r = parseInt(input.value);
  353.            
  354.            if (r > 0 && r <= 50) {
  355.               debugStartRound = r;
  356.                document.getElementById('startScreen').style.display = 'none';
  357.                startGame();
  358.            } else {
  359.                alert("Введите число от 1 до 50");
  360.                input.value = 10;
  361.            }
  362.        }
  363.        
  364.        // Allow Enter key in input field
  365.        document.getElementById('debugInput').addEventListener('keypress', function(e) {
  366.            if (e.key === 'Enter') {
  367.                debugStart();
  368.            }
  369.        });
  370.        
  371.        function toggleAutopilot() {
  372.            if (gameState !== 'playing') return;
  373.            autopilotEnabled = !autopilotEnabled;
  374.            document.getElementById('autopilotIndicator').style.display = autopilotEnabled ? 'block' : 'none';
  375.            if (autopilotEnabled) {
  376.                player.targetLamp = null;
  377.            }
  378.        }
  379.        
  380.        // Gamepad handling
  381.        window.addEventListener('gamepadconnected', (e) => {
  382.            console.log('Gamepad connected:', e.gamepad.id);
  383.            gamepadIndex = e.gamepad.index;
  384.        });
  385.        
  386.        window.addEventListener('gamepaddisconnected', (e) => {
  387.            if (gamepadIndex === e.gamepad.index) {
  388.                gamepadIndex = null;
  389.            }
  390.        });
  391.        
  392.        function getGamepadInput() {
  393.            if (gamepadIndex === null) return { x: 0, y: 0 };
  394.            const gamepad = navigator.getGamepads()[gamepadIndex];
  395.            if (!gamepad) return { x: 0, y: 0 };
  396.            
  397.            let x = gamepad.axes[0];
  398.            let y = gamepad.axes[1];
  399.            
  400.            if (gamepad.buttons[12]?.pressed) y = -1;
  401.            if (gamepad.buttons[13]?.pressed) y = 1;
  402.            if (gamepad.buttons[14]?.pressed) x = -1;
  403.            if (gamepad.buttons[15]?.pressed) x = 1;
  404.            
  405.            const deadzone = 0.15;
  406.            if (Math.abs(x) < deadzone) x = 0;
  407.           if (Math.abs(y) < deadzone) y = 0;
  408.          
  409.           return { x, y };
  410.       }
  411.      
  412.       function getKeyboardInput() {
  413.           let x = 0, y = 0;
  414.           if (keys['w'] || keys['arrowup']) y -= 1;
  415.           if (keys['s'] || keys['arrowdown']) y += 1;
  416.           if (keys['a'] || keys['arrowleft']) x -= 1;
  417.           if (keys['d'] || keys['arrowright']) x += 1;
  418.          
  419.           if (x !== 0 || y !== 0) {
  420.               const len = Math.sqrt(x*x + y*y);
  421.               x /= len;
  422.               y /= len;
  423.           }
  424.          
  425.           return { x, y };
  426.       }
  427.      
  428.       // Автопилот
  429.       function getAutopilotInput() {
  430.           if (!autopilotEnabled || lamps.length === 0) {
  431.               return { x: 0, y: 0 };
  432.           }
  433.          
  434.           let target = null;
  435.           let minDist = Infinity;
  436.          
  437.           for (let lamp of lamps) {
  438.               if (lamp.state === 'hooligan' && lamp.hooligan) {
  439.                   const dist = Math.hypot(player.x - lamp.hooligan.x, player.y - lamp.hooligan.y);
  440.                   if (dist < minDist) {
  441.                       minDist = dist;
  442.                       target = { x: lamp.hooligan.x, y: lamp.hooligan.y, type: 'hooligan', lamp: lamp };
  443.                   }
  444.               }
  445.           }
  446.          
  447.           if (!target) {
  448.               for (let lamp of lamps) {
  449.                   if (lamp.state === 'broken') {
  450.                       const dist = Math.hypot(player.x - lamp.x, player.y - lamp.y);
  451.                       if (dist < minDist) {
  452.                           minDist = dist;
  453.                           target = { x: lamp.x, y: lamp.y, type: 'broken', lamp: lamp };
  454.                       }
  455.                   }
  456.               }
  457.           }
  458.          
  459.           if (!target) {
  460.               for (let lamp of lamps) {
  461.                   if (lamp.state === 'working') {
  462.                       const dist = Math.hypot(player.x - lamp.x, player.y - lamp.y);
  463.                       if (dist < minDist) {
  464.                           minDist = dist;
  465.                           target = { x: lamp.x, y: lamp.y, type: 'working', lamp: lamp };
  466.                       }
  467.                   }
  468.               }
  469.           }
  470.          
  471.           if (!target) {
  472.               return { x: 0, y: 0 };
  473.           }
  474.          
  475.           const dx = target.x - player.x;
  476.           const dy = target.y - player.y;
  477.           const dist = Math.hypot(dx, dy);
  478.          
  479.           if (target.type === 'broken' && dist < FIX_DISTANCE - 5) {
  480.               return { x: 0, y: 0 };
  481.           }
  482.          
  483.           if (target.type === 'hooligan' && dist < STOP_HOOLIGAN_DISTANCE - 5) {
  484.               return { x: 0, y: 0 };
  485.           }
  486.          
  487.           if (target.type === 'working') {
  488.               const optimalDist = FIX_DISTANCE + 10;
  489.               if (dist < optimalDist) {
  490.                   return { x: 0, y: 0 };
  491.               }
  492.           }
  493.          
  494.           if (dist > 0) {
  495.                return { x: dx / dist, y: dy / dist };
  496.            }
  497.            
  498.            return { x: 0, y: 0 };
  499.        }
  500.        
  501.        // Lamp class
  502.        class Lamp {
  503.            constructor(x, y) {
  504.                this.x = x;
  505.                this.y = y;
  506.                this.state = 'working';
  507.                this.timer = 0;
  508.                this.maxTime = 5 + (round * 0.5);
  509.                this.blinkPhase = Math.random() * Math.PI * 2;
  510.                this.hooligan = null;
  511.                this.fixProgress = 0;
  512.            }
  513.            
  514.            update(dt) {
  515.                this.blinkPhase += dt * 3;
  516.                
  517.                if (this.state === 'broken') {
  518.                    this.timer -= dt;
  519.                    if (this.timer <= 0) {
  520.                       gameOver();
  521.                   }
  522.               } else if (this.state === 'hooligan' && this.hooligan) {
  523.                   this.hooligan.update(dt);
  524.                  
  525.                   const dist = Math.hypot(player.x - this.hooligan.x, player.y - this.hooligan.y);
  526.                   if (dist < STOP_HOOLIGAN_DISTANCE) {
  527.                       this.state = 'working';
  528.                       this.hooligan = null;
  529.                       score += 50;
  530.                       createParticles(this.x, this.y, '#00ff00', 10);
  531.                      
  532.                       const workingLamps = lamps.filter(l => l.state === 'working');
  533.                        if (workingLamps.length > 0 && nextBreakTimer <= 0) {
  534.                           nextBreakTimer = Math.random() * 2 + 1;
  535.                        }
  536.                    } else {
  537.                        const lampDist = Math.hypot(this.x - this.hooligan.x, this.y - this.hooligan.y);
  538.                        if (lampDist < 10) {
  539.                           this.breakLamp();
  540.                       }
  541.                   }
  542.               }
  543.           }
  544.          
  545.           breakLamp() {
  546.               this.state = 'broken';
  547.               this.timer = this.maxTime;
  548.               this.hooligan = null;
  549.               createParticles(this.x, this.y, '#ff0066', 15);
  550.           }
  551.          
  552.           startHooligan() {
  553.               if (round < 10) return;
  554.              
  555.               const side = Math.floor(Math.random() * 4);
  556.               let hx, hy;
  557.               switch(side) {
  558.                   case 0: hx = this.x; hy = -50; break;
  559.                   case 1: hx = CANVAS_WIDTH + 50; hy = this.y; break;
  560.                   case 2: hx = this.x; hy = CANVAS_HEIGHT + 50; break;
  561.                   case 3: hx = -50; hy = this.y; break;
  562.               }
  563.              
  564.               this.hooligan = {
  565.                   x: hx,
  566.                   y: hy,
  567.                   speed: 80 + round * 5,
  568.                   targetX: this.x,
  569.                   targetY: this.y,
  570.                   update: function(dt) {
  571.                       const dx = this.targetX - this.x;
  572.                       const dy = this.targetY - this.y;
  573.                       const dist = Math.hypot(dx, dy);
  574.                       if (dist > 0) {
  575.                            this.x += (dx / dist) * this.speed * dt;
  576.                            this.y += (dy / dist) * this.speed * dt;
  577.                        }
  578.                    }
  579.                };
  580.                this.state = 'hooligan';
  581.            }
  582.            
  583.            draw(ctx) {
  584.                const glowIntensity = this.state === 'working' ? 0.8 + Math.sin(this.blinkPhase) * 0.2 : 0.2;
  585.                
  586.                ctx.fillStyle = '#444';
  587.                ctx.fillRect(this.x - 3, this.y, 6, 100);
  588.                
  589.                ctx.fillStyle = '#222';
  590.                ctx.beginPath();
  591.                ctx.arc(this.x, this.y, LAMP_RADIUS, 0, Math.PI * 2);
  592.                ctx.fill();
  593.                
  594.                if (this.state !== 'broken') {
  595.                    const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, LAMP_RADIUS * 3);
  596.                    const color = this.state === 'hooligan' ? '#ff0066' : '#ffff00';
  597.                    gradient.addColorStop(0, color);
  598.                    gradient.addColorStop(0.3, color + '80');
  599.                    gradient.addColorStop(1, 'transparent');
  600.                    
  601.                    ctx.fillStyle = gradient;
  602.                    ctx.globalAlpha = glowIntensity;
  603.                    ctx.beginPath();
  604.                    ctx.arc(this.x, this.y, LAMP_RADIUS * 3, 0, Math.PI * 2);
  605.                    ctx.fill();
  606.                    ctx.globalAlpha = 1;
  607.                    
  608.                    ctx.fillStyle = this.state === 'hooligan' ? '#ff0066' : '#ffffaa';
  609.                    ctx.beginPath();
  610.                    ctx.arc(this.x, this.y, LAMP_RADIUS * 0.6, 0, Math.PI * 2);
  611.                    ctx.fill();
  612.                } else {
  613.                    ctx.fillStyle = '#333';
  614.                    ctx.beginPath();
  615.                    ctx.arc(this.x, this.y, LAMP_RADIUS * 0.6, 0, Math.PI * 2);
  616.                    ctx.fill();
  617.                    
  618.                    ctx.strokeStyle = '#666';
  619.                    ctx.lineWidth = 2;
  620.                    ctx.beginPath();
  621.                    ctx.moveTo(this.x - 10, this.y - 5);
  622.                    ctx.lineTo(this.x + 5, this.y + 10);
  623.                    ctx.moveTo(this.x + 8, this.y - 8);
  624.                    ctx.lineTo(this.x - 5, this.y + 5);
  625.                    ctx.stroke();
  626.                    
  627.                    const progress = this.timer / this.maxTime;
  628.                    ctx.strokeStyle = progress > 0.3 ? '#00ff00' : '#ff0000';
  629.                    ctx.lineWidth = 4;
  630.                    ctx.beginPath();
  631.                    ctx.arc(this.x, this.y - 40, 15, -Math.PI/2, -Math.PI/2 + progress * Math.PI * 2);
  632.                    ctx.stroke();
  633.                }
  634.                
  635.                if (this.hooligan) {
  636.                    this.drawHooligan(ctx);
  637.                }
  638.            }
  639.            
  640.            drawHooligan(ctx) {
  641.                const h = this.hooligan;
  642.                
  643.                ctx.fillStyle = '#ff0066';
  644.                ctx.beginPath();
  645.                ctx.arc(h.x, h.y, 12, 0, Math.PI * 2);
  646.                ctx.fill();
  647.                
  648.                ctx.fillStyle = '#fff';
  649.                ctx.beginPath();
  650.                ctx.arc(h.x - 4, h.y - 2, 3, 0, Math.PI * 2);
  651.                ctx.arc(h.x + 4, h.y - 2, 3, 0, Math.PI * 2);
  652.                ctx.fill();
  653.                
  654.                ctx.strokeStyle = '#8B4513';
  655.                ctx.lineWidth = 4;
  656.                ctx.beginPath();
  657.                ctx.moveTo(h.x + 8, h.y);
  658.                ctx.lineTo(h.x + 20, h.y - 15);
  659.                ctx.stroke();
  660.                
  661.                ctx.strokeStyle = '#ff0066';
  662.                ctx.lineWidth = 2;
  663.                ctx.setLineDash([5, 5]);
  664.                ctx.beginPath();
  665.                ctx.moveTo(h.x, h.y);
  666.                ctx.lineTo(this.x, this.y);
  667.                ctx.stroke();
  668.                ctx.setLineDash([]);
  669.            }
  670.        }
  671.        
  672.        class Particle {
  673.            constructor(x, y, color) {
  674.                this.x = x;
  675.                this.y = y;
  676.                this.vx = (Math.random() - 0.5) * 200;
  677.                this.vy = (Math.random() - 0.5) * 200;
  678.                this.life = 1;
  679.                this.color = color;
  680.                this.size = Math.random() * 4 + 2;
  681.            }
  682.            
  683.            update(dt) {
  684.                this.x += this.vx * dt;
  685.                this.y += this.vy * dt;
  686.                this.life -= dt * 2;
  687.                this.vy += 300 * dt;
  688.            }
  689.            
  690.            draw(ctx) {
  691.                ctx.globalAlpha = Math.max(0, this.life);
  692.                ctx.fillStyle = this.color;
  693.                ctx.beginPath();
  694.                ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
  695.                ctx.fill();
  696.                ctx.globalAlpha = 1;
  697.            }
  698.        }
  699.        
  700.        function createParticles(x, y, color, count) {
  701.            for (let i = 0; i < count; i++) {
  702.               particles.push(new Particle(x, y, color));
  703.           }
  704.       }
  705.      
  706.       function initRound() {
  707.           lamps = [];
  708.           const lampCount = round === 0 ? 0 : round * 2;
  709.          
  710.           if (round === 0) return;
  711.          
  712.           const cols = Math.ceil(Math.sqrt(lampCount));
  713.           const rows = Math.ceil(lampCount / cols);
  714.           const spacingX = (CANVAS_WIDTH - 200) / cols;
  715.           const spacingY = (CANVAS_HEIGHT - 200) / rows;
  716.           const startX = 100 + spacingX / 2;
  717.           const startY = 100 + spacingY / 2;
  718.          
  719.           for (let i = 0; i < lampCount; i++) {
  720.               const col = i % cols;
  721.               const row = Math.floor(i / cols);
  722.               lamps.push(new Lamp(
  723.                   startX + col * spacingX + (Math.random() - 0.5) * 50,
  724.                   startY + row * spacingY + (Math.random() - 0.5) * 50
  725.               ));
  726.           }
  727.          
  728.           nextBreakTimer = Math.random() * 2 + 1;
  729.       }
  730.      
  731.       let nextBreakTimer = 0;
  732.      
  733.       function breakRandomLamp() {
  734.           const workingLamps = lamps.filter(l => l.state === 'working');
  735.            if (workingLamps.length === 0) return;
  736.            
  737.            const lamp = workingLamps[Math.floor(Math.random() * workingLamps.length)];
  738.            
  739.            if (round >= 10 && Math.random() < 0.4) {
  740.               lamp.startHooligan();
  741.            } else {
  742.                lamp.breakLamp();
  743.            }
  744.            
  745.            const remainingWorking = lamps.filter(l => l.state === 'working');
  746.            if (remainingWorking.length > 0) {
  747.                nextBreakTimer = Math.random() * 3 + 2;
  748.            } else {
  749.                nextBreakTimer = 0;
  750.            }
  751.        }
  752.        
  753.        function update(dt) {
  754.            if (gameState !== 'playing') return;
  755.            
  756.            if (isRoundTransition) {
  757.                roundTransitionTimer -= dt;
  758.                if (roundTransitionTimer <= 0) {
  759.                   isRoundTransition = false;
  760.                   startRound();
  761.               }
  762.               return;
  763.           }
  764.          
  765.           let inputX = 0;
  766.           let inputY = 0;
  767.          
  768.           if (autopilotEnabled) {
  769.               const autoInput = getAutopilotInput();
  770.               inputX = autoInput.x;
  771.               inputY = autoInput.y;
  772.           } else {
  773.               const gamepadInput = getGamepadInput();
  774.               const keyboardInput = getKeyboardInput();
  775.              
  776.               if (Math.abs(gamepadInput.x) > Math.abs(keyboardInput.x)) {
  777.                    inputX = gamepadInput.x;
  778.                } else {
  779.                    inputX = keyboardInput.x;
  780.                }
  781.                
  782.                if (Math.abs(gamepadInput.y) > Math.abs(keyboardInput.y)) {
  783.                    inputY = gamepadInput.y;
  784.                } else {
  785.                    inputY = keyboardInput.y;
  786.                }
  787.            }
  788.            
  789.            player.vx = inputX * player.speed;
  790.            player.vy = inputY * player.speed;
  791.            
  792.            player.x += player.vx * dt;
  793.            player.y += player.vy * dt;
  794.            
  795.            player.x = Math.max(PLAYER_RADIUS, Math.min(CANVAS_WIDTH - PLAYER_RADIUS, player.x));
  796.            player.y = Math.max(PLAYER_RADIUS, Math.min(CANVAS_HEIGHT - PLAYER_RADIUS, player.y));
  797.            
  798.            let allFixed = true;
  799.            let hasHooligan = false;
  800.            let hasWorking = false;
  801.            
  802.            for (let i = 0; i < lamps.length; i++) {
  803.               const lamp = lamps[i];
  804.               lamp.update(dt);
  805.              
  806.               const dist = Math.hypot(player.x - lamp.x, player.y - lamp.y);
  807.              
  808.               if (lamp.state === 'broken' && dist < FIX_DISTANCE) {
  809.                   lamp.fixProgress += dt;
  810.                   if (lamp.fixProgress >= 0.5) {
  811.                        lamp.state = 'fixed';
  812.                        lamp.fixProgress = 0;
  813.                        score += 100;
  814.                        createParticles(lamp.x, lamp.y, '#00ff00', 8);
  815.                        
  816.                        const workingLamps = lamps.filter(l => l.state === 'working');
  817.                        if (workingLamps.length > 0 && nextBreakTimer <= 0) {
  818.                           nextBreakTimer = Math.random() * 2 + 1;
  819.                        }
  820.                    }
  821.                } else {
  822.                    lamp.fixProgress = Math.max(0, lamp.fixProgress - dt);
  823.                }
  824.                
  825.                if (lamp.state === 'working' || lamp.state === 'broken' || lamp.state === 'hooligan') {
  826.                    allFixed = false;
  827.                }
  828.                
  829.                if (lamp.state === 'hooligan') hasHooligan = true;
  830.                if (lamp.state === 'working') hasWorking = true;
  831.            }
  832.            
  833.            document.getElementById('hooliganWarning').style.display = hasHooligan ? 'block' : 'none';
  834.            
  835.            if (lamps.length > 0 && allFixed && !isRoundTransition) {
  836.               const fixedCount = lamps.filter(l => l.state === 'fixed').length;
  837.                if (fixedCount > 0) {
  838.                    nextRound();
  839.                }
  840.            }
  841.            
  842.            if (nextBreakTimer > 0 && hasWorking) {
  843.               nextBreakTimer -= dt;
  844.                if (nextBreakTimer <= 0) {
  845.                   breakRandomLamp();
  846.               }
  847.           }
  848.          
  849.           particles = particles.filter(p => {
  850.                p.update(dt);
  851.                return p.life > 0;
  852.            });
  853.            
  854.            updateUI();
  855.        }
  856.        
  857.        function draw() {
  858.            ctx.fillStyle = 'rgba(10, 10, 20, 0.3)';
  859.            ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  860.            
  861.            drawCityBackground();
  862.            
  863.            lamps.forEach(lamp => lamp.draw(ctx));
  864.            
  865.            drawPlayer();
  866.            
  867.            particles.forEach(p => p.draw(ctx));
  868.            
  869.            if (round === 0) {
  870.                drawTitleScreen();
  871.            }
  872.            
  873.            if (isRoundTransition) {
  874.                ctx.fillStyle = 'rgba(0,0,0,0.8)';
  875.                ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  876.                
  877.                ctx.font = 'bold 60px Orbitron';
  878.                ctx.textAlign = 'center';
  879.                ctx.fillStyle = '#00ff00';
  880.                ctx.shadowColor = '#00ff00';
  881.                ctx.shadowBlur = 20;
  882.                ctx.fillText(`РАУНД ${round} ПРОЙДЕН!`, CANVAS_WIDTH/2, CANVAS_HEIGHT/2);
  883.                
  884.                ctx.font = '30px Orbitron';
  885.                ctx.fillStyle = '#00ffff';
  886.                ctx.shadowBlur = 10;
  887.                ctx.fillText(`Следующий раунд: ${round + 1}`, CANVAS_WIDTH/2, CANVAS_HEIGHT/2 + 60);
  888.                ctx.shadowBlur = 0;
  889.            }
  890.            
  891.            if (autopilotEnabled && gameState === 'playing') {
  892.               drawAutopilotIndicator();
  893.            }
  894.        }
  895.        
  896.        function drawAutopilotIndicator() {
  897.            let target = null;
  898.            let minDist = Infinity;
  899.            
  900.            for (let lamp of lamps) {
  901.                if (lamp.state === 'hooligan' && lamp.hooligan) {
  902.                   const dist = Math.hypot(player.x - lamp.hooligan.x, player.y - lamp.hooligan.y);
  903.                    if (dist < minDist) {
  904.                       minDist = dist;
  905.                       target = { x: lamp.hooligan.x, y: lamp.hooligan.y, type: 'hooligan' };
  906.                   }
  907.               }
  908.           }
  909.          
  910.           if (!target) {
  911.               for (let lamp of lamps) {
  912.                   if (lamp.state === 'broken') {
  913.                       const dist = Math.hypot(player.x - lamp.x, player.y - lamp.y);
  914.                       if (dist < minDist) {
  915.                           minDist = dist;
  916.                           target = { x: lamp.x, y: lamp.y, type: 'broken' };
  917.                       }
  918.                   }
  919.               }
  920.           }
  921.          
  922.           if (target) {
  923.               ctx.strokeStyle = 'rgba(0, 255, 0, 0.5)';
  924.               ctx.lineWidth = 2;
  925.               ctx.setLineDash([10, 5]);
  926.               ctx.beginPath();
  927.               ctx.moveTo(player.x, player.y);
  928.               ctx.lineTo(target.x, target.y);
  929.               ctx.stroke();
  930.               ctx.setLineDash([]);
  931.              
  932.               ctx.strokeStyle = target.type === 'hooligan' ? '#ff0066' : '#00ff00';
  933.               ctx.lineWidth = 3;
  934.               ctx.beginPath();
  935.               ctx.arc(target.x, target.y, 20, 0, Math.PI * 2);
  936.               ctx.stroke();
  937.              
  938.               ctx.fillStyle = '#00ff00';
  939.               ctx.font = '16px Orbitron';
  940.               ctx.textAlign = 'center';
  941.               ctx.fillText('🎯', target.x, target.y - 25);
  942.           }
  943.       }
  944.      
  945.       function drawCityBackground() {
  946.           ctx.fillStyle = '#1a1a2e';
  947.           for (let i = 0; i < 12; i++) {
  948.               const h = 80 + Math.sin(i * 1.3) * 40;
  949.               const w = 110;
  950.               const x = i * 110 - 30;
  951.               ctx.fillRect(x, CANVAS_HEIGHT - h, w, h);
  952.              
  953.               ctx.fillStyle = '#2a2a3e';
  954.               for (let wy = CANVAS_HEIGHT - h + 20; wy < CANVAS_HEIGHT - 20; wy += 25) {
  955.                   for (let wx = x + 10; wx < x + w - 10; wx += 20) {
  956.                       if (Math.random() > 0.6) {
  957.                            ctx.fillRect(wx, wy, 12, 15);
  958.                        }
  959.                    }
  960.                }
  961.                ctx.fillStyle = '#1a1a2e';
  962.            }
  963.        }
  964.        
  965.        function drawPlayer() {
  966.            const gradient = ctx.createRadialGradient(player.x, player.y, 0, player.x, player.y, 30);
  967.            gradient.addColorStop(0, autopilotEnabled ? '#00ff00' : player.color);
  968.            gradient.addColorStop(0.5, (autopilotEnabled ? '#00ff00' : player.color) + '40');
  969.            gradient.addColorStop(1, 'transparent');
  970.            ctx.fillStyle = gradient;
  971.            ctx.beginPath();
  972.            ctx.arc(player.x, player.y, 30, 0, Math.PI * 2);
  973.            ctx.fill();
  974.            
  975.            ctx.fillStyle = autopilotEnabled ? '#00ff00' : player.color;
  976.            ctx.beginPath();
  977.            ctx.arc(player.x, player.y, PLAYER_RADIUS, 0, Math.PI * 2);
  978.            ctx.fill();
  979.            
  980.            ctx.fillStyle = '#ffaa00';
  981.            ctx.beginPath();
  982.            ctx.arc(player.x, player.y - 5, PLAYER_RADIUS * 0.8, Math.PI, 0);
  983.            ctx.fill();
  984.            
  985.            ctx.save();
  986.            ctx.translate(player.x + 10, player.y + 5);
  987.            ctx.rotate(Math.sin(Date.now() / 200) * 0.3);
  988.            ctx.fillStyle = '#aaa';
  989.            ctx.fillRect(-3, -15, 6, 20);
  990.            ctx.fillRect(-8, -15, 16, 6);
  991.            ctx.restore();
  992.            
  993.            if (autopilotEnabled) {
  994.                ctx.fillStyle = '#00ff00';
  995.                ctx.font = '20px Arial';
  996.                ctx.textAlign = 'center';
  997.                ctx.fillText('🤖', player.x, player.y - 25);
  998.            }
  999.            
  1000.            const nearBroken = lamps.find(l => {
  1001.                const dist = Math.hypot(player.x - l.x, player.y - l.y);
  1002.                return l.state === 'broken' && dist < FIX_DISTANCE;
  1003.            });
  1004.            
  1005.            if (nearBroken) {
  1006.                ctx.strokeStyle = '#00ff00';
  1007.                ctx.lineWidth = 3;
  1008.                ctx.setLineDash([5, 5]);
  1009.                ctx.beginPath();
  1010.                ctx.arc(player.x, player.y, FIX_DISTANCE, 0, Math.PI * 2);
  1011.                ctx.stroke();
  1012.                ctx.setLineDash([]);
  1013.                
  1014.                const progress = nearBroken.fixProgress / 0.5;
  1015.                ctx.fillStyle = '#00ff00';
  1016.                ctx.fillRect(player.x - 20, player.y - 30, 40 * progress, 5);
  1017.                ctx.strokeStyle = '#fff';
  1018.                ctx.lineWidth = 1;
  1019.                ctx.strokeRect(player.x - 20, player.y - 30, 40, 5);
  1020.            }
  1021.        }
  1022.        
  1023.        function drawTitleScreen() {
  1024.            ctx.fillStyle = 'rgba(0,0,0,0.7)';
  1025.            ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
  1026.            
  1027.            ctx.font = 'bold 80px Orbitron';
  1028.            ctx.textAlign = 'center';
  1029.            ctx.fillStyle = '#00ffff';
  1030.            ctx.shadowColor = '#00ffff';
  1031.            ctx.shadowBlur = 20;
  1032.            ctx.fillText('CITY LIGHTS', CANVAS_WIDTH/2, CANVAS_HEIGHT/2 - 50);
  1033.            ctx.shadowBlur = 0;
  1034.            
  1035.            ctx.font = '30px Orbitron';
  1036.            ctx.fillStyle = '#aaa';
  1037.            ctx.fillText('Нажмите ПРОБЕЛ или A на геймпаде', CANVAS_WIDTH/2, CANVAS_HEIGHT/2 + 50);
  1038.            
  1039.            const gamepad = gamepadIndex !== null ? navigator.getGamepads()[gamepadIndex] : null;
  1040.            if (keys[' '] || (gamepad && gamepad.buttons[0]?.pressed)) {
  1041.               startRound();
  1042.            }
  1043.        }
  1044.        
  1045.        function updateUI() {
  1046.            document.getElementById('roundDisplay').textContent = round;
  1047.            document.getElementById('scoreDisplay').textContent = score;
  1048.            const fixedCount = lamps.filter(l => l.state === 'fixed').length;
  1049.            const totalLamps = lamps.length;
  1050.            document.getElementById('lampsDisplay').textContent = `${fixedCount}/${totalLamps}`;
  1051.            
  1052.            const brokenLamps = lamps.filter(l => l.state === 'broken');
  1053.            if (brokenLamps.length > 0) {
  1054.                const minTime = Math.min(...brokenLamps.map(l => l.timer));
  1055.                document.getElementById('timeDisplay').textContent = `ВРЕМЯ: ${minTime.toFixed(1)}с`;
  1056.                document.getElementById('timeDisplay').className = minTime < 2 ? 'warning' : '';
  1057.           } else {
  1058.               document.getElementById('timeDisplay').textContent = '';
  1059.           }
  1060.       }
  1061.      
  1062.       function startGame() {
  1063.           document.getElementById('startScreen').style.display = 'none';
  1064.          
  1065.           if (debugStartRound > 0) {
  1066.                round = debugStartRound - 1;
  1067.            } else {
  1068.                round = 0;
  1069.            }
  1070.            
  1071.            score = 0;
  1072.            player.x = CANVAS_WIDTH / 2;
  1073.            player.y = CANVAS_HEIGHT / 2;
  1074.            initRound();
  1075.            gameState = 'playing';
  1076.            lastTime = performance.now();
  1077.            requestAnimationFrame(gameLoop);
  1078.        }
  1079.        
  1080.        function startRound() {
  1081.            round++;
  1082.            initRound();
  1083.            player.x = CANVAS_WIDTH / 2;
  1084.            player.y = CANVAS_HEIGHT / 2;
  1085.            
  1086.            if (round > 1) {
  1087.                score += round * 50;
  1088.            }
  1089.        }
  1090.        
  1091.        function nextRound() {
  1092.            isRoundTransition = true;
  1093.            roundTransitionTimer = 2;
  1094.            nextBreakTimer = 0;
  1095.        }
  1096.        
  1097.        function gameOver() {
  1098.            gameState = 'gameover';
  1099.            document.getElementById('finalRound').textContent = round;
  1100.            document.getElementById('finalScore').textContent = score;
  1101.            document.getElementById('gameOver').style.display = 'block';
  1102.            autopilotEnabled = false;
  1103.            document.getElementById('autopilotIndicator').style.display = 'none';
  1104.            debugStartRound = 0;
  1105.        }
  1106.        
  1107.        function restartGame() {
  1108.            document.getElementById('gameOver').style.display = 'none';
  1109.            round = 0;
  1110.            score = 0;
  1111.            lamps = [];
  1112.            particles = [];
  1113.            isRoundTransition = false;
  1114.            autopilotEnabled = false;
  1115.            debugStartRound = 0;
  1116.            document.getElementById('autopilotIndicator').style.display = 'none';
  1117.            startGame();
  1118.        }
  1119.        
  1120.        function gameLoop(currentTime) {
  1121.            const dt = Math.min((currentTime - lastTime) / 1000, 0.1);
  1122.            lastTime = currentTime;
  1123.            
  1124.            update(dt);
  1125.            draw();
  1126.            
  1127.            requestAnimationFrame(gameLoop);
  1128.        }
  1129.        
  1130.        // Initial draw
  1131.        resizeCanvas();
  1132.        draw();
  1133.    </script>
  1134. </body>
  1135. </html>
Advertisement
Add Comment
Please, Sign In to add comment