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 • Q, D / ←, → : ROTATION<br>
ESPACE : LASER • 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