Accueil > My Life > Code d’Omega Race – Arena Komar

Code d’Omega Race – Arena Komar

06/01/2026

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Omega Race - Arena Komar (V4.4)</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            background-color: #050505;
            color: #00ffff;
            font-family: 'Courier New', Courier, monospace;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
            overflow: hidden;
            user-select: none;
        }

        #game-container {
            position: relative;
            border: 4px solid #1a1a1a;
            background-color: #000;
            box-shadow: 0 0 50px rgba(0, 255, 255, 0.1);
        }

        #game-container::after {
            content: " ";
            display: block;
            position: absolute;
            top: 0; left: 0; bottom: 0; right: 0;
            background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.1) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.02), rgba(0, 255, 0, 0.01), rgba(0, 255, 0, 0.02));
            z-index: 20;
            background-size: 100% 3px, 3px 100%;
            pointer-events: none;
        }

        canvas {
            display: block;
            filter: blur(0.3px) contrast(1.1) brightness(1.2);
        }

        .ui-overlay {
            position: absolute;
            top: 52%;
            left: 50%;
            transform: translate(-50%, -50%);
            text-align: center;
            pointer-events: none;
            z-index: 30;
            width: 85%;
        }

        #briefing-screen {
            color: #00ffff;
            text-align: justify;
            font-size: 1.1em;
            line-height: 1.6;
            height: 400px;
            text-shadow: 0 0 8px #00ffff;
            display: flex;
            flex-direction: column;
            justify-content: center;
        }

        #start-screen {
            display: none;
            background: rgba(0, 0, 0, 0.95);
            padding: 20px;
            border: 1px solid #00ffff;
            pointer-events: auto;
            box-shadow: 0 0 40px rgba(0, 255, 255, 0.2);
            max-width: 700px;
        }

        .arcade-logo-svg {
            width: 420px;
            height: auto;
            margin-bottom: 5px;
            filter: drop-shadow(0 0 12px #00ffff);
            animation: logo-flicker 4s infinite;
        }

        @keyframes logo-flicker {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.9; }
            98% { opacity: 1; }
            99% { opacity: 0.7; }
        }

        .cursor {
            display: inline-block;
            width: 10px;
            height: 20px;
            background: #00ffff;
            animation: blink 0.8s infinite;
            vertical-align: middle;
            margin-left: 5px;
        }

        @keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }

        .controls-info { 
            font-size: 0.7em; 
            color: #00aaaa; 
            margin: 10px 0; 
            line-height: 1.4;
            border-top: 1px solid #004444;
            padding-top: 10px;
            text-transform: uppercase;
        }

        #bestiary-preview {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 15px;
            margin: 15px 0;
            padding: 10px;
            border: 1px dashed #004444;
            background: rgba(0, 255, 255, 0.03);
        }

        .bestiary-item { text-align: center; }
        .enemy-icon-canvas { display: block; margin: 0 auto 5px auto; }
        .enemy-name { font-size: 0.55em; color: #008888; margin-bottom: 2px; }
        .enemy-pts { font-size: 0.65em; color: #00ffff; font-weight: bold; letter-spacing: 1px; }

        button {
            background: #000;
            border: 2px solid #00ffff;
            color: #00ffff;
            padding: 10px 25px;
            font-size: 1.1em;
            cursor: pointer;
            font-family: inherit;
            transition: all 0.2s;
            letter-spacing: 3px;
            margin-top: 5px;
        }

        button:hover {
            background: #00ffff;
            color: #000;
            box-shadow: 0 0 30px #00ffff;
        }

        #hud-top {
            position: absolute;
            top: 25px;
            width: 100%;
            display: flex;
            justify-content: space-between;
            padding: 0 50px;
            box-sizing: border-box;
            font-size: 1.4em;
            pointer-events: none;
            z-index: 25;
            color: #00ffff;
            text-shadow: 0 0 10px #00ffff;
        }

        .hud-label {
            font-size: 0.6em;
            color: #008888;
            display: block;
            margin-bottom: 4px;
            letter-spacing: 3px;
        }
    </style>
</head>
<body>

    <div id="game-container">
        <div id="hud-top">
            <div>
                <span class="hud-label">PILOTE</span>
                <span id="score-display">000000</span>
            </div>
            <div style="text-align: center;">
                <span class="hud-label">RECORD KOMAR</span>
                <span id="hi-score-display">005000</span>
            </div>
            <div style="text-align: right;">
                <span class="hud-label">SECTEUR</span>
                <span id="wave-display">1</span>
            </div>
        </div>
        <canvas id="gameCanvas"></canvas>
        
        <div id="ui" class="ui-overlay">
            <div id="briefing-screen"></div>
            <div id="start-screen">
                <svg class="arcade-logo-svg" viewBox="0 0 400 120" xmlns="http://www.w3.org/2000/svg">
                    <defs>
                        <filter id="neon-start">
                            <feGaussianBlur stdDeviation="1.5" result="blur"/>
                            <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
                        </filter>
                    </defs>
                    <g fill="none" stroke="#00ffff" stroke-width="2.5" filter="url(#neon-start)">
                        <path d="M40,70 L20,70 L20,30 L60,30 L60,70 L40,70" />
                        <path d="M75,70 L75,30 L95,50 L115,30 L115,70" />
                        <path d="M130,70 L130,30 L165,30 M130,50 L155,50 M130,70 L165,70" />
                        <path d="M205,55 L205,70 L180,70 L180,30 L210,30 L210,50 L195,50" />
                        <path d="M225,70 L240,30 L255,70 M232,55 L248,55" />
                    </g>
                    <g fill="none" stroke="#00ffff" stroke-width="1.5" filter="url(#neon-start)" opacity="0.7">
                        <path d="M130,105 L130,85 L155,85 L155,95 L130,95 L155,105" />
                        <path d="M170,105 L180,85 L190,105 M175,98 L185,98" />
                        <path d="M225,85 L205,85 L205,105 L225,105" />
                        <path d="M235,105 L235,85 L260,85 M235,95 L255,95 M235,105 L260,105" />
                    </g>
                </svg>

                <div class="controls-info">
                    Z / ↑ : PROPULSION &bull; Q, D / ←, → : ROTATION<br>
                    ESPACE : LASER &bull; CTRL : BOUCLIER TACTIQUE
                </div>

                <div id="bestiary-preview">
                    <div class="bestiary-item">
                        <div class="enemy-name">DROID</div>
                        <canvas id="icon-mine" width="40" height="40" class="enemy-icon-canvas"></canvas>
                        <div class="enemy-pts">100 PTS</div>
                    </div>
                    <div class="bestiary-item">
                        <div class="enemy-name">TRIANGLE</div>
                        <canvas id="icon-tri" width="40" height="40" class="enemy-icon-canvas"></canvas>
                        <div class="enemy-pts">250 PTS</div>
                    </div>
                    <div class="bestiary-item">
                        <div class="enemy-name">COMMANDANT</div>
                        <canvas id="icon-hunter" width="40" height="40" class="enemy-icon-canvas"></canvas>
                        <div class="enemy-pts">500 PTS</div>
                    </div>
                    <div class="bestiary-item">
                        <div class="enemy-name">INTERCEPTEUR</div>
                        <canvas id="icon-final" width="40" height="40" class="enemy-icon-canvas"></canvas>
                        <div class="enemy-pts">1000 PTS</div>
                    </div>
                </div>

                <button id="start-btn">ENTRER DANS L'ARENE</button>
            </div>
        </div>
    </div>

<script>
    const canvas = document.getElementById('gameCanvas');
    const ctx = canvas.getContext('2d');
    const startBtn = document.getElementById('start-btn');
    const startScreen = document.getElementById('start-screen');
    const briefingScreen = document.getElementById('briefing-screen');
    const scoreDisplay = document.getElementById('score-display');
    const hiScoreDisplay = document.getElementById('hi-score-display');
    const waveDisplay = document.getElementById('wave-display');

    const WIDTH = 900;
    const HEIGHT = 700;
    const INNER_W = 280;
    const INNER_H = 200;
    const BOX_X = (WIDTH - INNER_W) / 2;
    const BOX_Y = (HEIGHT - INNER_H) / 2;

    canvas.width = WIDTH;
    canvas.height = HEIGHT;

    const briefingLines = [
        "EN 2003, LE SYSTÈME OMÉGA A MIS AU POINT UNE MÉTHODE D'ENTRAÎNEMENT POUR SES PILOTES DE COMBAT.",
        " ",
        "SUR LA CITÉ DE KOMAR, DES ANDROÏDES DE COMBAT IMPITOYABLES POURCHASSENT LES PILOTES D'OMEGA DANS UNE ARÈNE SANS ÉCHAPPATOIRE.",
        " ",
        "CETTE SÉLECTION BRUTALE EST NOMMÉE :"
    ];

    let briefingLineIndex = 0;
    let charIndex = 0;
    let isBriefingActive = true;

    function typeBriefing() {
        if (briefingLineIndex < briefingLines.length) {
            let line = briefingLines[briefingLineIndex];
            if (charIndex < line.length) {
                const currentText = briefingLines.slice(0, briefingLineIndex).join("<br>") + 
                                   (briefingLineIndex > 0 ? "<br>" : "") + 
                                   line.substring(0, charIndex + 1);
                briefingScreen.innerHTML = currentText + "<span class='cursor'></span>";
                charIndex++;
                setTimeout(typeBriefing, 25);
            } else {
                briefingLineIndex++;
                charIndex = 0;
                setTimeout(typeBriefing, 600);
            }
        } else {
            setTimeout(() => {
                briefingScreen.style.opacity = '0';
                briefingScreen.style.transition = 'opacity 1s';
                setTimeout(() => {
                    briefingScreen.style.display = 'none';
                    startScreen.style.display = 'block';
                    isBriefingActive = false;
                    drawStartIcons();
                }, 1000);
            }, 1500);
        }
    }

    function drawStartIcons() {
        const drawIcon = (id, type, color) => {
            const c = document.getElementById(id);
            if (!c) return;
            const ictx = c.getContext('2d');
            ictx.clearRect(0, 0, 40, 40);
            ictx.strokeStyle = color;
            ictx.lineWidth = 2;
            ictx.save();
            ictx.translate(20, 20);
            
            if (type === 'mine' || type === 'hunter') {
                const r = 10;
                ictx.beginPath(); ictx.arc(0,0,r,0,Math.PI*2);
                for(let i=0; i<8; i++) { 
                    let a=i*Math.PI/4; ictx.moveTo(Math.cos(a)*r, Math.sin(a)*r); 
                    ictx.lineTo(Math.cos(a)*(r+5), Math.sin(a)*(r+5)); 
                }
                ictx.stroke();
                if (type === 'hunter') { ictx.beginPath(); ictx.arc(0,0,4,0,Math.PI*2); ictx.stroke(); }
            } else if (type === 'tri') {
                const r = 8;
                ictx.beginPath();
                ictx.moveTo(Math.cos(-Math.PI/2)*r, Math.sin(-Math.PI/2)*r);
                ictx.lineTo(Math.cos(Math.PI*2/3 - Math.PI/2)*r, Math.sin(Math.PI*2/3 - Math.PI/2)*r);
                ictx.lineTo(Math.cos(Math.PI*4/3 - Math.PI/2)*r, Math.sin(Math.PI*4/3 - Math.PI/2)*r);
                ictx.closePath(); ictx.stroke();
            } else if (type === 'final') {
                const r = 14;
                ictx.beginPath(); ictx.moveTo(-r, 0); ictx.lineTo(r, 0); ictx.moveTo(0, -r); ictx.lineTo(0, r); ictx.stroke();
                ictx.beginPath(); ictx.moveTo(r-2, 0); ictx.lineTo(-8, 10); ictx.lineTo(-8, -10); ictx.closePath(); ictx.stroke();
            }
            ictx.restore();
        };

        drawIcon('icon-mine', 'mine', '#ffffff');
        drawIcon('icon-tri', 'tri', '#ff3300');
        drawIcon('icon-hunter', 'hunter', '#ff3300');
        drawIcon('icon-final', 'final', '#ffff00');
    }

    const AudioEngine = {
        ctx: null, 
        initialized: false, 
        thrustNode: null, 
        shieldNode: null,
        beatTimer: 0,
        beatIndex: 0,
        beatFrequencies: [80, 70, 60, 50],
        
        init() {
            if (this.initialized) return;
            try {
                this.ctx = new (window.AudioContext || window.webkitAudioContext)();
                this.initialized = true;
            } catch(e) { console.error("Audio init failed", e); }
        },
        
        updateBeat(enemyRatio) {
            if (!this.initialized || !gameActive) return;
            const minInterval = 15;
            const maxInterval = 45;
            const beatInterval = minInterval + (enemyRatio * (maxInterval - minInterval));
            
            this.beatTimer++;
            if (this.beatTimer >= beatInterval) {
                this.playBeat(this.beatFrequencies[this.beatIndex]);
                this.beatIndex = (this.beatIndex + 1) % this.beatFrequencies.length;
                this.beatTimer = 0;
            }
        },

        playBeat(freq) {
            const osc = this.ctx.createOscillator();
            const gain = this.ctx.createGain();
            osc.type = 'square';
            osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
            gain.gain.setValueAtTime(0.04, this.ctx.currentTime);
            gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.08);
            osc.connect(gain); gain.connect(this.ctx.destination);
            osc.start(); osc.stop(this.ctx.currentTime + 0.08);
        },

        playLaser() {
            if (!this.initialized) return;
            const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain();
            osc.type = 'square'; osc.frequency.setValueAtTime(800, this.ctx.currentTime);
            osc.frequency.exponentialRampToValueAtTime(40, this.ctx.currentTime + 0.1);
            gain.gain.setValueAtTime(0.05, this.ctx.currentTime);
            gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.1);
            osc.connect(gain); gain.connect(this.ctx.destination);
            osc.start(); osc.stop(this.ctx.currentTime + 0.1);
        },

        playExplosion(large = false) {
            if (!this.initialized) return;
            const duration = large ? 0.6 : 0.2;
            const bufferSize = this.ctx.sampleRate * duration;
            const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
            const data = buffer.getChannelData(0);
            for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1;
            const noise = this.ctx.createBufferSource(); noise.buffer = buffer;
            const filter = this.ctx.createBiquadFilter(); filter.type = 'lowpass';
            filter.frequency.setValueAtTime(large ? 300 : 800, this.ctx.currentTime);
            const gain = this.ctx.createGain(); gain.gain.setValueAtTime(large ? 0.4 : 0.15, this.ctx.currentTime);
            gain.gain.linearRampToValueAtTime(0, this.ctx.currentTime + duration);
            noise.connect(filter); filter.connect(gain); gain.connect(this.ctx.destination);
            noise.start();
        },

        playBounce() {
            if (!this.initialized) return;
            const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain();
            osc.type = 'triangle'; osc.frequency.setValueAtTime(150, this.ctx.currentTime);
            gain.gain.setValueAtTime(0.05, this.ctx.currentTime);
            gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.1);
            osc.connect(gain); gain.connect(this.ctx.destination);
            osc.start(); osc.stop(this.ctx.currentTime + 0.1);
        },

        setShieldSound(active) {
            if (!this.initialized) return;
            if (active && !this.shieldNode) {
                this.shieldNode = this.ctx.createOscillator(); this.shieldGain = this.ctx.createGain();
                this.shieldNode.type = 'sawtooth'; this.shieldNode.frequency.value = 60;
                this.shieldGain.gain.value = 0.03;
                this.shieldNode.connect(this.shieldGain); this.shieldGain.connect(this.ctx.destination);
                this.shieldNode.start();
            } else if (!active && this.shieldNode) {
                if (this.shieldNode.stop) this.shieldNode.stop(); 
                this.shieldNode = null;
            }
        },

        setThrust(active) {
            if (!this.initialized) return;
            if (active && !this.thrustNode) {
                const bufferSize = this.ctx.sampleRate * 1;
                const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
                const data = buffer.getChannelData(0);
                for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1;
                this.thrustNode = this.ctx.createBufferSource(); this.thrustNode.buffer = buffer; this.thrustNode.loop = true;
                this.thrustGain = this.ctx.createGain(); this.thrustFilter = this.ctx.createBiquadFilter();
                this.thrustFilter.type = 'lowpass'; this.thrustFilter.frequency.value = 200;
                this.thrustGain.gain.value = 0.1;
                this.thrustNode.connect(this.thrustFilter); this.thrustFilter.connect(this.thrustGain);
                this.thrustGain.connect(this.ctx.destination); this.thrustNode.start();
            } else if (!active && this.thrustNode) { 
                this.thrustNode.stop(); this.thrustNode = null; 
            }
        }
    };

    let gameActive = false, score = 0;
    // Chargement initial du score
    let highScore = parseInt(localStorage.getItem('omegaRace_hiScore')) || 5000;
    
    let lives = 3, wave = 1, isRespawning = false, respawnTimer = 0, fireCooldown = 0;
    let enemiesPerWave = 0;
    let waveChanging = false;
    const keys = {};

    let bullets = [], enemyBullets = [], enemies = [], particles = [];

    window.addEventListener('keydown', e => { 
        keys[e.code] = true; 
        if(['KeyW','ArrowUp'].includes(e.code)) keys['UP']=true;
        if(['KeyA','ArrowLeft'].includes(e.code)) keys['LEFT']=true;
        if(['KeyD','ArrowRight'].includes(e.code)) keys['RIGHT']=true;
        if(['ControlLeft','ControlRight','ShiftLeft'].includes(e.code)) keys['SHIELD']=true;
    });
    window.addEventListener('keyup', e => { 
        keys[e.code] = false; 
        if(['KeyW','ArrowUp'].includes(e.code)) keys['UP']=false;
        if(['KeyA','ArrowLeft'].includes(e.code)) keys['LEFT']=false;
        if(['KeyD','ArrowRight'].includes(e.code)) keys['RIGHT']=false;
        if(['ControlLeft','ControlRight','ShiftLeft'].includes(e.code)) keys['SHIELD']=false;
    });

    const wallSegments = { top: 0, right: 0, bottom: 0, left: 0, iTop: 0, iRight: 0, iBottom: 0, iLeft: 0 };
    function pulseWall(id) { 
        wallSegments[id] = 1.0; AudioEngine.playBounce();
        if (gameActive && ship.visible && !isRespawning) { 
            ship.shieldLevel = Math.min(100, ship.shieldLevel + 10); 
        }
    }

    class Particle {
        constructor(x, y, color, vx, vy, life = 60) {
            this.x = x; this.y = y; this.vx = vx; this.vy = vy; this.maxLife = life; this.life = life; this.color = color;
        }
        update() { this.x += this.vx; this.y += this.vy; this.life--; }
        draw() { ctx.fillStyle = this.color; ctx.globalAlpha = Math.max(0, this.life / this.maxLife); ctx.fillRect(this.x, this.y, 2, 2); ctx.globalAlpha = 1; }
    }

    function createExplosion(x, y, color, count=30) {
        for(let i=0; i<count; i++) {
            const ang = Math.random() * Math.PI * 2; const speed = Math.random() * 5;
            particles.push(new Particle(x, y, color, Math.cos(ang)*speed, Math.sin(ang)*speed, 30 + Math.random()*30));
        }
    }

    class Ship {
        constructor() { this.radius = 12; this.reset(true); this.shieldAnim = 0; }
        reset(fullReset = false) {
            this.x = 120; this.y = 120; this.vx = 0; this.vy = 0; this.angle = -Math.PI / 2;
            this.invulnerable = 150; this.visible = true; this.shieldLevel = fullReset ? 0 : this.shieldLevel; this.shieldActive = false;
        }
        update() {
            if (!this.visible || isRespawning) { AudioEngine.setShieldSound(false); return; }
            if (this.invulnerable > 0) this.invulnerable--;
            if (keys['LEFT']) this.angle -= 0.1; if (keys['RIGHT']) this.angle += 0.1;
            
            if (keys['UP']) {
                const ax = Math.cos(this.angle) * 0.35; const ay = Math.sin(this.angle) * 0.35;
                this.vx += ax; this.vy += ay; AudioEngine.setThrust(true);
                if (Math.random() > 0.4) {
                   const offset = (Math.random() - 0.5) * 12;
                   particles.push(new Particle(this.x - Math.cos(this.angle)*12, this.y - Math.sin(this.angle)*12 + offset, "#ffffff", -ax*2, -ay*2, 15));
                }
            } else AudioEngine.setThrust(false);

            if (keys['SHIELD'] && this.shieldLevel > 0 && this.invulnerable <= 0) {
                this.shieldActive = true; this.shieldLevel -= 0.6; 
                this.shieldAnim += 0.2; AudioEngine.setShieldSound(true);
            } else { 
                this.shieldActive = false; AudioEngine.setShieldSound(false); 
            }
            
            this.vx *= 0.985; this.vy *= 0.985; this.x += this.vx; this.y += this.vy;
            this.handleCollisions();
        }
        handleCollisions() {
            const r = this.radius;
            if (this.x < r) { this.x = r; this.vx *= -0.7; pulseWall('left'); }
            if (this.x > WIDTH-r) { this.x = WIDTH-r; this.vx *= -0.7; pulseWall('right'); }
            if (this.y < r) { this.y = r; this.vy *= -0.7; pulseWall('top'); }
            if (this.y > HEIGHT-r) { this.y = HEIGHT-r; this.vy *= -0.7; pulseWall('bottom'); }
            if (this.x > BOX_X-r && this.x < BOX_X+INNER_W+r && this.y > BOX_Y-r && this.y < BOX_Y+INNER_H+r) {
                let dxL = Math.abs(this.x - (BOX_X-r)), dxR = Math.abs(this.x - (BOX_X+INNER_W+r)), dyT = Math.abs(this.y - (BOX_Y-r)), dyB = Math.abs(this.y - (BOX_Y+INNER_H+r));
                let min = Math.min(dxL, dxR, dyT, dyB);
                if (min === dxL) { this.x = BOX_X-r; this.vx *= -0.7; pulseWall('iLeft'); }
                else if (min === dxR) { this.x = BOX_X+INNER_W+r; this.vx *= -0.7; pulseWall('iRight'); }
                else if (min === dyT) { this.y = BOX_Y-r; this.vy *= -0.7; pulseWall('iTop'); }
                else { this.y = BOX_Y+INNER_H+r; this.vy *= -0.7; pulseWall('iBottom'); }
            }
        }
        draw() {
            if (!this.visible) return;
            ctx.save(); ctx.translate(this.x, this.y); 
            ctx.globalAlpha = (this.invulnerable > 0) ? (0.3 + Math.sin(Date.now() / 40) * 0.4) : 1.0;
            
            if (this.shieldActive) {
                const pulse = Math.sin(this.shieldAnim) * 5;
                const radius = 28 + pulse;
                const gradient = ctx.createRadialGradient(0, 0, radius - 10, 0, 0, radius + 5);
                gradient.addColorStop(0, 'rgba(0, 255, 255, 0)');
                gradient.addColorStop(0.7, 'rgba(0, 255, 255, 0.25)');
                gradient.addColorStop(1, 'rgba(0, 255, 255, 0)');
                ctx.fillStyle = gradient;
                ctx.beginPath(); ctx.arc(0, 0, radius + 5, 0, Math.PI * 2); ctx.fill();
                ctx.strokeStyle = 'rgba(0, 255, 255, 0.6)'; ctx.lineWidth = 1.5;
                ctx.beginPath(); ctx.arc(0, 0, radius, 0, Math.PI * 2); ctx.stroke();
            }

            ctx.rotate(this.angle); 
            ctx.strokeStyle = '#00aaaa'; ctx.lineWidth = 1.2;
            ctx.strokeRect(-8, 5, 4, 3); ctx.strokeRect(-8, 8, 4, 3);
            ctx.strokeRect(-8, -8, 4, 3); ctx.strokeRect(-8, -11, 4, 3);
            ctx.strokeRect(-11, -2, 5, 4);

            if (keys['UP']) {
                const fLength = 10 + Math.random() * 12;
                ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 1.5;
                ctx.beginPath();
                ctx.moveTo(-8, 6.5); ctx.lineTo(-8 - fLength*0.8, 6.5); 
                ctx.moveTo(-8, 9.5); ctx.lineTo(-8 - fLength*0.8, 9.5);
                ctx.moveTo(-8, -6.5); ctx.lineTo(-8 - fLength*0.8, -6.5);
                ctx.moveTo(-8, -9.5); ctx.lineTo(-8 - fLength*0.8, -9.5);
                ctx.moveTo(-11, 0); ctx.lineTo(-11 - fLength, 0);
                ctx.stroke();
                ctx.globalAlpha = 0.4; ctx.fillStyle = "#00ffff";
                ctx.beginPath(); ctx.arc(-11, 0, 4, 0, Math.PI*2); ctx.fill();
                ctx.globalAlpha = 1.0;
            }

            ctx.strokeStyle = '#00ffff'; ctx.lineWidth = 2.5;
            ctx.beginPath();
            ctx.moveTo(15, 0); ctx.lineTo(-4, 11); ctx.lineTo(-2, 5); ctx.lineTo(-10, 5); ctx.lineTo(-10, -5); ctx.lineTo(-2, -5); ctx.lineTo(-4, -11); ctx.closePath();
            ctx.stroke();

            ctx.fillStyle = 'rgba(0, 255, 255, 0.4)'; ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 1.0;
            ctx.beginPath(); ctx.ellipse(0, 0, 5, 3, 0, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
            ctx.fillStyle = '#ffffff'; ctx.beginPath(); ctx.arc(2, -1, 1, 0, Math.PI*2); ctx.fill();

            ctx.restore();
        }
    }

    class Enemy {
        constructor(type, x, y) {
            this.promote(type); this.angle = 0; this.rotSpeed = 0.05 + Math.random()*0.05;
            if (x !== undefined) { this.x = x; this.y = y; this.vx = 0; this.vy = 0; } else this.initPos();
        }
        promote(type) {
            this.type = type; 
            this.radius = (type === 'final') ? 18 : (type === 'tri' ? 8 : 14);
            this.color = (this.type === 'final' ? '#ffff00' : ((this.type === 'hunter' || this.type === 'tri') ? '#ff3300' : '#ffffff'));
            this.shootTimer = 60 + Math.random() * 60;
            this.dropTimer = 300 + Math.random() * 300;
            this.points = (this.type === 'final' ? 1000 : (this.type === 'hunter' ? 500 : (this.type === 'tri' ? 250 : 100)));
            if (this.type === 'final') { 
                const speed = 8.5; const ang = (Math.PI/4) + (Math.random()*Math.PI/2); 
                this.vx = Math.cos(ang) * speed; this.vy = Math.sin(ang) * speed; this.rotSpeed = 0.12; 
            }
        }
        initPos() {
            this.x = Math.random() > 0.5 ? 50 : WIDTH - 50; this.y = Math.random() * HEIGHT;
            const speed = 2.5; const ang = Math.random() * Math.PI * 2;
            this.vx = Math.cos(ang) * speed; this.vy = Math.sin(ang) * speed;
        }
        update() {
            if (this.type === 'tri') return;
            
            this.angle += this.rotSpeed;
            if (this.type === 'hunter' || this.type === 'final') {
                if (--this.dropTimer <= 0) {
                    enemies.push(new Enemy('tri', this.x, this.y));
                    this.dropTimer = 480;
                }
            }

            if (this.type === 'hunter') {
                let dx = ship.x - this.x, dy = ship.y - this.y; let dist = Math.hypot(dx, dy);
                this.vx += (dx / dist) * 0.07; this.vy += (dy / dist) * 0.07;
                if (Math.hypot(this.vx, this.vy) > 4.5) { this.vx *= 0.98; this.vy *= 0.98; }
                if (ship.visible && !isRespawning && --this.shootTimer <= 0) {
                    enemyBullets.push(new Bullet(this.x, this.y, Math.atan2(dy, dx), true));
                    this.shootTimer = 90;
                }
            } else if (this.type === 'final') {
                if (ship.visible && !isRespawning && --this.shootTimer <= 0) {
                    enemyBullets.push(new Bullet(this.x, this.y, Math.atan2(ship.y-this.y, ship.x-this.x), true, '#ffff00'));
                    this.shootTimer = 35;
                }
            }
            this.x += this.vx; this.y += this.vy; this.handleBounces();
        }
        handleBounces() {
            const r = this.radius;
            if (this.x < r || this.x > WIDTH-r) { this.vx *= -1; this.x = this.x<r?r:WIDTH-r; }
            if (this.y < r || this.y > HEIGHT-r) { this.vy *= -1; this.y = this.y<r?r:HEIGHT-r; }
            if (this.x > BOX_X-r && this.x < BOX_X+INNER_W+r && this.y > BOX_Y-r && this.y < BOX_Y+INNER_H+r) {
                let dxL=Math.abs(this.x-(BOX_X-r)), dxR=Math.abs(this.x-(BOX_X+INNER_W+r)), dyT=Math.abs(this.y-(BOX_Y-r)), dyB=Math.abs(this.y-(BOX_Y+INNER_H+r));
                let min = Math.min(dxL, dxR, dyT, dyB);
                if (min===dxL||min===dxR) this.vx*=-1; else this.vy*=-1;
            }
        }
        draw() {
            ctx.save(); ctx.translate(this.x, this.y); ctx.strokeStyle = this.color; ctx.lineWidth = 2.5;
            this.renderShape();
            ctx.restore();
        }
        
        renderShape(scale = 1.0) {
            if (this.type === 'mine' || this.type === 'hunter') {
                ctx.rotate(this.angle);
                const r = this.radius * scale;
                ctx.beginPath(); ctx.arc(0,0,r,0,Math.PI*2);
                for(let i=0; i<8; i++) { 
                    let a=i*Math.PI/4; ctx.moveTo(Math.cos(a)*r, Math.sin(a)*r); 
                    ctx.lineTo(Math.cos(a)*(r+(6*scale)), Math.sin(a)*(r+(6*scale))); 
                }
                ctx.stroke();
                if (this.type === 'hunter') { ctx.beginPath(); ctx.arc(0,0,6*scale,0,Math.PI*2); ctx.stroke(); }
            } else if (this.type === 'tri') {
                const r = this.radius * scale;
                ctx.beginPath();
                ctx.moveTo(Math.cos(-Math.PI/2)*r, Math.sin(-Math.PI/2)*r);
                ctx.lineTo(Math.cos(Math.PI*2/3 - Math.PI/2)*r, Math.sin(Math.PI*2/3 - Math.PI/2)*r);
                ctx.lineTo(Math.cos(Math.PI*4/3 - Math.PI/2)*r, Math.sin(Math.PI*4/3 - Math.PI/2)*r);
                ctx.closePath(); ctx.stroke();
            } else if (this.type === 'final') {
                const r = 22 * scale;
                ctx.save(); ctx.rotate(this.angle); ctx.beginPath(); ctx.moveTo(-r, 0); ctx.lineTo(r, 0); ctx.moveTo(0, -r); ctx.lineTo(0, r); ctx.stroke(); ctx.restore();
                ctx.save(); ctx.rotate(-this.angle * 1.5); ctx.beginPath(); ctx.moveTo(r, 0); ctx.lineTo(-14*scale, 18*scale); ctx.lineTo(-14*scale, -18*scale); ctx.closePath(); ctx.stroke(); ctx.restore();
            }
        }
    }

    class Bullet {
        constructor(x, y, angle, isEnemy = false, color = null) {
            this.x = x; this.y = y; this.vx = Math.cos(angle)*(isEnemy?6:15); this.vy = Math.sin(angle)*(isEnemy?6:15);
            this.energy = 1.0; this.isEnemy = isEnemy; this.color = color || (isEnemy?"#ff3300":"#ffffff");
            this.bounces = 0;
            if (!isEnemy) AudioEngine.playLaser();
        }
        update() { 
            this.x += this.vx; this.y += this.vy; 
            this.handleBounces();
        }
        handleBounces() {
            let bounced = false;
            if (this.x < 5 || this.x > WIDTH-5) { this.vx *= -1; bounced = true; this.x = this.x<5?5:WIDTH-5;}
            else if (this.y < 5 || this.y > HEIGHT-5) { this.vy *= -1; bounced = true; this.y = this.y<5?5:HEIGHT-5;}
            else if (this.x > BOX_X && this.x < BOX_X+INNER_W && this.y > BOX_Y && this.y < BOX_Y+INNER_H) {
                let dxL=Math.abs(this.x-BOX_X), dxR=Math.abs(this.x-(BOX_X+INNER_W)), dyT=Math.abs(this.y-BOX_Y), dyB=Math.abs(this.y-(BOX_Y+INNER_H));
                let min = Math.min(dxL, dxR, dyT, dyB);
                if (min===dxL||min===dxR) this.vx*=-1; else this.vy*=-1;
                bounced = true;
            }

            if (bounced) {
                this.bounces++;
                AudioEngine.playBounce();
                if (this.bounces >= 2) {
                    this.energy = 0; 
                } else {
                    this.color = this.isEnemy ? "#ffffaa" : "#00ffff";
                }
            }
        }
        draw() { 
            ctx.fillStyle = this.color; 
            ctx.beginPath(); ctx.arc(this.x, this.y, 2, 0, Math.PI*2); ctx.fill(); 
        }
    }

    const ship = new Ship();

    function destroyEnemy(index) {
        const e = enemies[index];
        createExplosion(e.x, e.y, e.color, 40);
        AudioEngine.playExplosion(e.type === 'final');
        score += e.points;
        const wasHunter = e.type === 'hunter';
        enemies.splice(index, 1); 
        updateHUD();
        if (wasHunter && enemies.length > 0) {
            const nextHunter = enemies.find(en => en.type === 'mine');
            if (nextHunter) nextHunter.promote('hunter');
        }
    }

    function checkWaveComplete() {
        if (waveChanging || !gameActive) return;
        if (enemies.length === 0 && bullets.length === 0 && enemyBullets.length === 0 && particles.length === 0) {
            waveChanging = true;
            setTimeout(() => {
                wave++;
                initWave();
                waveChanging = false;
            }, 1000);
        }
    }

    function update() {
        if (!gameActive) return; 

        particles.forEach((p, i) => { p.update(); if (p.life <= 0) particles.splice(i, 1); });
        for (let k in wallSegments) if (wallSegments[k] > 0) wallSegments[k] -= 0.05;

        const mobileEnemies = enemies.filter(en => en.type !== 'tri');
        if (mobileEnemies.length > 0) {
            AudioEngine.updateBeat((mobileEnemies.length - 1) / Math.max(1, enemiesPerWave - 1));
        }

        if (!isRespawning || respawnTimer < 40) {
            enemies.forEach(e => e.update());
        }
        
        bullets.forEach((b, i) => { b.update(); if (b.energy <= 0) bullets.splice(i, 1); });
        enemyBullets.forEach((eb, i) => { eb.update(); if (eb.energy <= 0) enemyBullets.splice(i, 1); });

        if (isRespawning && lives >= 0) {
            if (respawnTimer > 0) respawnTimer--;
            else { ship.reset(false); isRespawning = false; }
            return;
        }

        ship.update();
        if (keys['Space'] && fireCooldown <= 0 && !ship.shieldActive && ship.visible) { 
            bullets.push(new Bullet(ship.x, ship.y, ship.angle)); fireCooldown = 12; 
        }
        if (fireCooldown > 0) fireCooldown--;

        bullets.forEach((b, bi) => {
            enemies.forEach((e, ei) => {
                if (Math.hypot(b.x - e.x, b.y - e.y) < e.radius + 5) { destroyEnemy(ei); bullets.splice(bi, 1); }
            });
        });

        enemyBullets.forEach((eb, i) => {
            if (ship.visible && ship.invulnerable <= 0) {
                const dist = Math.hypot(eb.x-ship.x, eb.y-ship.y);
                if (ship.shieldActive && dist < 30) { enemyBullets.splice(i, 1); }
                else if (dist < 12) playerDeath();
            }
        });

        enemies.forEach((e, ei) => {
            if (ship.visible && ship.invulnerable <= 0) {
                const dist = Math.hypot(e.x-ship.x, e.y-ship.y);
                const collisionRadius = ship.shieldActive ? e.radius + 24 : e.radius + 10;
                if (dist < collisionRadius) {
                    if (ship.shieldActive) {
                        destroyEnemy(ei); const ang = Math.atan2(ship.y-e.y, ship.x-e.x); 
                        ship.vx = Math.cos(ang)*8; ship.vy = Math.sin(ang)*8;
                    } else { destroyEnemy(ei); playerDeath(); }
                }
            }
        });

        if (mobileEnemies.length === 1 && mobileEnemies[0].type !== 'final' && !isRespawning) {
            mobileEnemies[0].promote('final');
        }

        checkWaveComplete();
    }

    function drawHUDLives() {
        const livesX = WIDTH/2;
        const livesY = BOX_Y + 155;
        ctx.save();
        ctx.globalAlpha = 0.5;
        ctx.font = '10px Courier New'; ctx.fillStyle = '#00aaaa'; ctx.textAlign = 'center';
        ctx.fillText("FLOTTE ACTIVE", livesX, livesY - 15);
        const lifeSpacing = 20;
        const livesCount = lives < 0 ? 0 : lives;
        const livesTotalW = (livesCount > 0 ? livesCount - 1 : 0) * lifeSpacing;
        const livesStartX = livesX - livesTotalW / 2;
        for(let i=0; i<livesCount; i++) {
            ctx.save(); ctx.translate(livesStartX + i * lifeSpacing, livesY); ctx.scale(0.5, 0.5);
            ctx.strokeStyle = '#00ffff'; ctx.lineWidth = 2;
            ctx.beginPath(); ctx.moveTo(10, 0); ctx.lineTo(-8, 8); ctx.lineTo(-5, 0); ctx.lineTo(-8, -8); ctx.closePath();
            ctx.stroke(); ctx.restore();
        }
        if (lives >= 0) {
            ctx.font = 'bold 12px Courier New'; ctx.fillStyle = '#00ffff';
            ctx.fillText("x" + lives, livesX + (livesTotalW/2) + 20, livesY + 4);
        }
        ctx.restore();
    }

    function draw() {
        ctx.fillStyle = 'black'; ctx.fillRect(0, 0, WIDTH, HEIGHT);
        const drawWall = (x1,y1,x2,y2,i) => {
            ctx.strokeStyle = i > 0.1 ? `rgb(${Math.floor(i*180)}, 255, 255)` : '#1a1a1a'; ctx.lineWidth = 2.5;
            ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke();
        };

        drawWall(0,0,WIDTH,0,wallSegments.top); drawWall(WIDTH,0,WIDTH,HEIGHT,wallSegments.right); 
        drawWall(WIDTH,HEIGHT,0,HEIGHT,wallSegments.bottom); drawWall(0,HEIGHT,0,0,wallSegments.left);

        if (!isBriefingActive) {
            drawWall(BOX_X,BOX_Y,BOX_X+INNER_W,BOX_Y,wallSegments.iTop); 
            drawWall(BOX_X+INNER_W,BOX_Y,BOX_X+INNER_W,BOX_Y+INNER_H,wallSegments.iRight);
            drawWall(BOX_X+INNER_W,BOX_Y+INNER_H,BOX_X,BOX_Y+INNER_H,wallSegments.iBottom); 
            drawWall(BOX_X,BOX_Y+INNER_H,BOX_X,BOX_Y,wallSegments.iLeft);
        }

        if (gameActive) {
            ctx.save();
            drawHUDLives();
            ctx.font = 'bold 24px Courier New'; ctx.fillStyle = '#004444'; ctx.textAlign = 'center';
            ctx.fillText("OMEGA RACE", WIDTH/2, BOX_Y + 40);
            ctx.font = '10px Courier New'; ctx.fillText("KOMAR ARENA SYSTEM", WIDTH/2, BOX_Y + 55);
            const bW = 180, bH = 10, bX = WIDTH/2 - bW/2, bY = BOX_Y + 95;
            ctx.strokeStyle = '#003333'; ctx.strokeRect(bX, bY, bW, bH);
            ctx.fillStyle = ship.shieldLevel > 20 ? '#00aaaa' : '#aa0000';
            ctx.globalAlpha = 0.3; ctx.fillRect(bX+2, bY+2, (bW-4) * (ship.shieldLevel/100), bH-4);
            ctx.globalAlpha = 1.0;
            ctx.font = '9px Courier New'; ctx.fillStyle = '#008888'; ctx.fillText("BOUCLIER TACTIQUE", WIDTH/2, bY - 5);
            ctx.restore();

            if (ship.visible) ship.draw();
            enemies.forEach(e => e.draw()); 
            bullets.forEach(b => b.draw()); 
            enemyBullets.forEach(eb => eb.draw()); 
            particles.forEach(p => p.draw());
        }
    }

    function updateHUD() {
        scoreDisplay.innerText = score.toString().padStart(6, '0');
        
        // Mise à jour de la variable et stockage
        if (score > highScore) { 
            highScore = score; 
            localStorage.setItem('omegaRace_hiScore', highScore); 
        }
        
        // Mise à jour visuelle systématique du High Score
        hiScoreDisplay.innerText = highScore.toString().padStart(6, '0'); 
        waveDisplay.innerText = wave;
    }

    function initWave() {
        enemies = []; 
        bullets = [];
        enemyBullets = [];
        particles = [];
        enemiesPerWave = 3 + Math.min(wave, 6);
        for(let i=0; i<enemiesPerWave; i++) enemies.push(new Enemy(i === 0 ? 'hunter' : 'mine'));
        updateHUD();
    }

    function playerDeath() {
        if (isRespawning) return; isRespawning = true; ship.visible = false; respawnTimer = 120;
        createExplosion(ship.x, ship.y, '#00ffff', 60); AudioEngine.playExplosion(true); lives--;
        if (lives < 0) {
            setTimeout(() => { 
                gameActive = false; 
                startScreen.style.display = 'block'; 
                drawStartIcons(); 
                enemies = []; bullets = []; enemyBullets = [];
            }, 2000);
        }
    }

    startBtn.addEventListener('click', () => { 
        AudioEngine.init(); 
        start(); 
    });
    
    function start() {
        score = 0; 
        lives = 3; 
        wave = 1; 
        
        // S'assurer que le record est bien lu du storage avant de commencer
        highScore = parseInt(localStorage.getItem('omegaRace_hiScore')) || 5000;
        
        initWave(); 
        ship.reset(true); 
        gameActive = true; 
        isRespawning = false; 
        startScreen.style.display = 'none';
        AudioEngine.beatTimer = 0; 
        AudioEngine.beatIndex = 0;
        
        // Actualisation forcée au démarrage
        updateHUD();
    }

    window.onload = () => { 
        // Premier affichage du record stocké au chargement de la page
        hiScoreDisplay.innerText = highScore.toString().padStart(6, '0');
        typeBriefing(); 
        loop(); 
    };
    
    function loop() { 
        update(); 
        draw(); 
        requestAnimationFrame(loop); 
    }
</script>
</body>
</html>
Catégories :My Life
Concevoir un site comme celui-ci avec WordPress.com
Commencer