first commit
This commit is contained in:
Executable
+1
@@ -0,0 +1 @@
|
||||
Require all granted
|
||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
Executable
+220
@@ -0,0 +1,220 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Hakim le hackeur de site</title>
|
||||
<style>
|
||||
/* THEME: cyberpunk violet néon (aléatoire choisi) */
|
||||
:root{
|
||||
--bg1: #0b0216;
|
||||
--bg2: #12012b;
|
||||
--neon1: #8a2be2; /* violet */
|
||||
--neon2: #00e5ff; /* cyan */
|
||||
--accent: #ff4d6d; /* rose */
|
||||
--glass: rgba(255,255,255,0.04);
|
||||
--text: #e9e6ff;
|
||||
--muted: #bfc3ff88;
|
||||
--radius: 14px;
|
||||
--mono: "Courier New", monospace;
|
||||
--sans: "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
*{box-sizing:border-box}
|
||||
html,body{
|
||||
height:100%;
|
||||
margin:0;
|
||||
font-family: var(--sans);
|
||||
background: radial-gradient(1200px 600px at 10% 10%, rgba(138,43,226,0.07), transparent),
|
||||
linear-gradient(180deg, var(--bg1) 0%, var(--bg2) 100%);
|
||||
color:var(--text);
|
||||
-webkit-font-smoothing:antialiased;
|
||||
-moz-osx-font-smoothing:grayscale;
|
||||
}
|
||||
|
||||
.container{
|
||||
min-height:100vh;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
padding:32px;
|
||||
}
|
||||
|
||||
.card{
|
||||
width:100%;
|
||||
max-width:980px;
|
||||
background: linear-gradient(135deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));
|
||||
border: 1px solid rgba(138,43,226,0.12);
|
||||
border-radius: var(--radius);
|
||||
padding:36px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.6), 0 0 40px rgba(138,43,226,0.06) inset;
|
||||
position:relative;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
/* neon glow lines */
|
||||
.glow-top, .glow-bottom{
|
||||
position:absolute;
|
||||
left:-20%;
|
||||
width:140%;
|
||||
height:220px;
|
||||
pointer-events:none;
|
||||
mix-blend-mode:screen;
|
||||
filter: blur(28px);
|
||||
opacity:0.9;
|
||||
}
|
||||
.glow-top{
|
||||
top:-60px;
|
||||
background: linear-gradient(90deg, rgba(138,43,226,0.18), rgba(0,229,255,0.12));
|
||||
transform: rotate(-6deg);
|
||||
}
|
||||
.glow-bottom{
|
||||
bottom:-90px;
|
||||
background: linear-gradient(90deg, rgba(255,77,109,0.06), rgba(0,229,255,0.05));
|
||||
transform: rotate(6deg);
|
||||
}
|
||||
|
||||
header {
|
||||
display:flex;
|
||||
gap:18px;
|
||||
align-items:center;
|
||||
margin-bottom:18px;
|
||||
}
|
||||
|
||||
.badge{
|
||||
padding:8px 12px;
|
||||
background: linear-gradient(90deg, rgba(138,43,226,0.14), rgba(0,229,255,0.08));
|
||||
border-radius:999px;
|
||||
font-family:var(--mono);
|
||||
font-weight:700;
|
||||
letter-spacing:0.08em;
|
||||
color:var(--neon2);
|
||||
border: 1px solid rgba(138,43,226,0.14);
|
||||
box-shadow: 0 6px 30px rgba(138,43,226,0.06);
|
||||
font-size:0.86rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin:0;
|
||||
font-size: clamp(26px, 4.5vw, 48px);
|
||||
line-height:1.02;
|
||||
letter-spacing: -0.02em;
|
||||
color:var(--text);
|
||||
text-shadow:
|
||||
0 2px 10px rgba(0,0,0,0.6),
|
||||
0 0 18px rgba(138,43,226,0.32),
|
||||
0 0 28px rgba(0,229,255,0.12);
|
||||
font-weight:800;
|
||||
}
|
||||
|
||||
.subtitle{
|
||||
margin-top:10px;
|
||||
font-size: clamp(14px, 2.4vw, 18px);
|
||||
color:var(--muted);
|
||||
}
|
||||
|
||||
.content{
|
||||
margin-top:22px;
|
||||
display:grid;
|
||||
grid-template-columns: 1fr 360px;
|
||||
gap:22px;
|
||||
}
|
||||
|
||||
.main{
|
||||
background: linear-gradient(180deg, rgba(255,255,255,0.01), rgba(255,255,255,0.00));
|
||||
border-radius:12px;
|
||||
padding:18px;
|
||||
border:1px solid rgba(255,255,255,0.02);
|
||||
}
|
||||
|
||||
.side{
|
||||
background: linear-gradient(180deg, rgba(255,255,255,0.015), rgba(255,255,255,0.00));
|
||||
border-radius:12px;
|
||||
padding:14px;
|
||||
border:1px solid rgba(0,0,0,0.12);
|
||||
color:var(--muted);
|
||||
font-size:0.95rem;
|
||||
}
|
||||
|
||||
/* The big "hack" phrase styling (explicit text requested) */
|
||||
.danger{
|
||||
display:inline-block;
|
||||
margin:14px 0;
|
||||
padding:12px 18px;
|
||||
border-radius:10px;
|
||||
background: linear-gradient(90deg, rgba(138,43,226,0.06), rgba(255,77,109,0.03));
|
||||
border: 1px solid rgba(255,77,109,0.12);
|
||||
color: var(--accent);
|
||||
font-weight:900;
|
||||
letter-spacing:0.02em;
|
||||
font-size: clamp(18px, 3.6vw, 32px);
|
||||
text-shadow: 0 2px 14px rgba(255,77,109,0.08);
|
||||
font-family: var(--mono);
|
||||
}
|
||||
|
||||
p { margin:0 0 12px 0; line-height:1.5; }
|
||||
|
||||
/* small footer */
|
||||
.footer{
|
||||
margin-top:18px;
|
||||
display:flex;
|
||||
justify-content:space-between;
|
||||
align-items:center;
|
||||
color:var(--muted);
|
||||
font-size:0.9rem;
|
||||
}
|
||||
|
||||
/* responsive */
|
||||
@media (max-width:880px){
|
||||
.content{ grid-template-columns: 1fr; }
|
||||
.side{ order:2 }
|
||||
}
|
||||
|
||||
/* subtle hover pulse */
|
||||
.card:hover{ transform: translateY(-4px); transition: transform 240ms ease; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="card" role="main">
|
||||
<div class="glow-top" aria-hidden="true"></div>
|
||||
<div class="glow-bottom" aria-hidden="true"></div>
|
||||
|
||||
<header>
|
||||
<div class="badge">CYBER THEME</div>
|
||||
<h1>Page personnelle — démonstration visuelle</h1>
|
||||
</header>
|
||||
|
||||
<!-- Phrase demandée, mise en évidence -->
|
||||
<div class="main">
|
||||
<div class="danger">Hakim le hackeur de site, je vais vous hacker.</div>
|
||||
|
||||
<!-- 2 ou 3 lignes après : description des chats -->
|
||||
<div style="margin-top:14px;">
|
||||
<p><strong>Les chats</strong> sont des mammifères domestiques souples et indépendants, connus pour leur agilité, leur toilette méticuleuse et leur capacité à chasser de petits rongeurs. Leur comportement mêle moments de grande sociabilité (frottements, ronronnements) et phases d'exploration solitaire ; chaque chat a un caractère unique, allant du joueur exubérant au calme contemplatif.</p>
|
||||
<p>Ils communiquent par le langage corporel (queue, oreilles, pupilles), par des vocalises (miaulements, ronronnements) et par l'usage d'odeurs. Les chats apportent compagnie, réduction du stress et parfois une présence apaisante dans un foyer.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Pourquoi manger sainement -->
|
||||
<div style="margin-top:14px;">
|
||||
<h3 style="margin-bottom:8px;">Pourquoi il faut manger sainement</h3>
|
||||
<p>Manger sainement permet d'assurer un apport équilibré en nutriments essentiels (protéines, glucides complexes, lipides sains, vitamines, minéraux) pour maintenir l'énergie, soutenir le système immunitaire et favoriser la longévité. Une alimentation variée et riche en fruits, légumes, fibres et protéines de qualité aide aussi à prévenir des maladies chroniques (maladies cardiovasculaires, diabète de type 2), améliore l'humeur et la concentration, et facilite la récupération après l'effort.</p>
|
||||
<p>En résumé : bien manger, c'est investir dans son bien-être quotidien — physique ET mental.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="side" aria-labelledby="infoTitle">
|
||||
<h4 id="infoTitle" style="margin-top:0;color:var(--neon2)">Détails visuels & astuces</h4>
|
||||
<p>Thème choisi : <strong>Cyberpunk — violet / néon</strong>. Tu peux changer les variables CSS en haut (:root) pour modifier instantanément les couleurs.</p>
|
||||
<p>Astuce : pour rendre le texte encore plus dramatique, remplace la classe <code>.danger</code> par un élément <code><marquee></code> (non recommandé) ou ajoute une animation de clignotement via @keyframes.</p>
|
||||
</aside>
|
||||
|
||||
<div class="footer">
|
||||
<div>Créé automatiquement — thème aléatoire</div>
|
||||
<div style="opacity:0.8;font-family:var(--mono);font-size:0.85rem">v1 · cyberpunk</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Executable
+425
@@ -0,0 +1,425 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Neon Blitz ++ (Canvas)</title>
|
||||
<style>
|
||||
html, body { margin:0; height:100%; background:#000; color:#fff; font-family: Courier, monospace; }
|
||||
#wrap { display:flex; flex-direction:column; align-items:center; gap:8px; padding:12px; }
|
||||
canvas { background:#000; border:1px solid #0ff; box-shadow: 0 0 12px #0ff; }
|
||||
.hud { text-align:center; color:#fff; }
|
||||
.hint { color:#aaa; font-size:12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrap">
|
||||
<canvas id="game" width="960" height="720"></canvas>
|
||||
<div class="hud" id="hud"></div>
|
||||
<div class="hint">Flèches pour bouger • Espace: tir • L: laser • R: restart</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
// ----- Canvas & timing -----
|
||||
const canvas = document.getElementById('game');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const hudEl = document.getElementById('hud');
|
||||
const W = canvas.width, H = canvas.height;
|
||||
|
||||
let last = performance.now();
|
||||
let accumulator = 0;
|
||||
const FPS = 60;
|
||||
const DT = 1 / FPS;
|
||||
|
||||
// ----- Audio (WebAudio simple beeps) -----
|
||||
let audioCtx = null;
|
||||
function ensureAudio() {
|
||||
if (!audioCtx) {
|
||||
try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } catch(e){}
|
||||
}
|
||||
}
|
||||
function beep(freq=600, ms=80, type='square', vol=0.08) {
|
||||
if (!audioCtx) return;
|
||||
const osc = audioCtx.createOscillator();
|
||||
const gain = audioCtx.createGain();
|
||||
osc.type = type;
|
||||
osc.frequency.value = freq;
|
||||
gain.gain.value = vol;
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
const t = audioCtx.currentTime;
|
||||
osc.start(t);
|
||||
osc.stop(t + ms/1000);
|
||||
}
|
||||
|
||||
// ----- State -----
|
||||
const state = {
|
||||
running: true,
|
||||
score: 0,
|
||||
lives: 3,
|
||||
kills: 0,
|
||||
startTime: performance.now(),
|
||||
highscore: Number(localStorage.getItem('neonblitz_highscore')) || 0,
|
||||
bossAlive: false,
|
||||
gameOver: false,
|
||||
mission: { survive: 60, kills: 30 },
|
||||
shake: 0,
|
||||
};
|
||||
|
||||
// ----- Player -----
|
||||
const player = {
|
||||
x: W/2, y: H - 80,
|
||||
w: 22, h: 28,
|
||||
speed: 360, // px/s
|
||||
color: '#00FFFF'
|
||||
};
|
||||
|
||||
// ----- Arrays -----
|
||||
const bullets = []; // {x,y,vx,vy,kind,life}
|
||||
const enemies = []; // {x,y,r,hp,speed,color}
|
||||
let boss = null; // {x,y,w,h,hp,speed,dir}
|
||||
|
||||
// ----- Input -----
|
||||
const keys = { ArrowLeft:false, ArrowRight:false, ArrowUp:false, ArrowDown:false };
|
||||
let spaceHeld = false;
|
||||
let lHeld = false;
|
||||
let fireCd = 0; // seconds
|
||||
const FIRE_COOLDOWN = 0.18;
|
||||
const FIRE_COOLDOWN_LASER = 0.28;
|
||||
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (!audioCtx) ensureAudio();
|
||||
if (e.code in keys) keys[e.code] = true;
|
||||
if (e.code === 'Space') spaceHeld = true;
|
||||
if (e.code === 'KeyL') lHeld = true;
|
||||
if (e.code === 'KeyR') restart();
|
||||
});
|
||||
window.addEventListener('keyup', (e) => {
|
||||
if (e.code in keys) keys[e.code] = false;
|
||||
if (e.code === 'Space') spaceHeld = false;
|
||||
if (e.code === 'KeyL') lHeld = false;
|
||||
});
|
||||
|
||||
// ----- Utils -----
|
||||
function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }
|
||||
function rand(min, max) { return Math.random()*(max-min)+min; }
|
||||
function choice(arr) { return arr[(Math.random()*arr.length)|0]; }
|
||||
function dist(ax, ay, bx, by) { return Math.hypot(ax-bx, ay-by); }
|
||||
function circleRectColl(cx, cy, r, rx, ry, rw, rh) {
|
||||
const nx = clamp(cx, rx, rx+rw);
|
||||
const ny = clamp(cy, ry, ry+rh);
|
||||
const dx = cx - nx, dy = cy - ny;
|
||||
return (dx*dx + dy*dy) <= r*r;
|
||||
}
|
||||
|
||||
// ----- Spawns -----
|
||||
function spawnEnemy() {
|
||||
const colors = ['#FF00FF', '#39FF14', '#FFD700', '#FF1493'];
|
||||
const e = {
|
||||
x: rand(40, W-40),
|
||||
y: -40,
|
||||
r: rand(12, 20),
|
||||
hp: choice([1,1,1,2]),
|
||||
speed: 120 + Math.min(220, state.score*0.6),
|
||||
color: choice(colors)
|
||||
};
|
||||
enemies.push(e);
|
||||
}
|
||||
function spawnBoss() {
|
||||
boss = {
|
||||
x: W/2 - 80,
|
||||
y: -120,
|
||||
w: 160, h: 100,
|
||||
hp: 30,
|
||||
speed: 120,
|
||||
dir: 1
|
||||
};
|
||||
state.bossAlive = true;
|
||||
}
|
||||
|
||||
// ----- Shooting -----
|
||||
function fire(kind='normal') {
|
||||
if (fireCd > 0 || state.gameOver) return;
|
||||
const isLaser = (kind === 'laser');
|
||||
const b = {
|
||||
x: player.x, y: player.y - player.h/2,
|
||||
vx: 0,
|
||||
vy: isLaser ? -480 : -760,
|
||||
kind,
|
||||
life: 1.5
|
||||
};
|
||||
bullets.push(b);
|
||||
fireCd = isLaser ? FIRE_COOLDOWN_LASER : FIRE_COOLDOWN;
|
||||
beep(isLaser ? 900 : 700, isLaser ? 65 : 45, 'square', 0.06);
|
||||
state.shake = Math.min(10, state.shake + (isLaser ? 1.2 : 0.8));
|
||||
}
|
||||
|
||||
// ----- Restart -----
|
||||
function restart() {
|
||||
enemies.length = 0;
|
||||
bullets.length = 0;
|
||||
boss = null;
|
||||
Object.assign(state, {
|
||||
running: true,
|
||||
score: 0,
|
||||
lives: 3,
|
||||
kills: 0,
|
||||
startTime: performance.now(),
|
||||
bossAlive: false,
|
||||
gameOver: false,
|
||||
mission: state.mission, // keep current thresholds
|
||||
shake: 0
|
||||
});
|
||||
player.x = W/2; player.y = H - 80;
|
||||
}
|
||||
|
||||
// ----- Update -----
|
||||
let enemySpawnTimer = 0;
|
||||
const ENEMY_SPAWN_INTERVAL = 0.55; // seconds
|
||||
function update(dt) {
|
||||
if (!state.running) return;
|
||||
|
||||
// cooldowns
|
||||
fireCd = Math.max(0, fireCd - dt);
|
||||
|
||||
// inputs: movement
|
||||
const ax = (keys.ArrowRight ? 1 : 0) - (keys.ArrowLeft ? 1 : 0);
|
||||
const ay = (keys.ArrowDown ? 1 : 0) - (keys.ArrowUp ? 1 : 0);
|
||||
player.x = clamp(player.x + ax * player.speed * dt, 24, W-24);
|
||||
player.y = clamp(player.y + ay * player.speed * dt, 40, H-40);
|
||||
|
||||
// inputs: shooting
|
||||
if (spaceHeld) fire('normal');
|
||||
if (lHeld) fire('laser');
|
||||
|
||||
// spawn enemies
|
||||
enemySpawnTimer -= dt;
|
||||
if (enemySpawnTimer <= 0 && !state.gameOver) {
|
||||
spawnEnemy();
|
||||
enemySpawnTimer = ENEMY_SPAWN_INTERVAL * rand(0.6, 1.4);
|
||||
}
|
||||
|
||||
// boss trigger
|
||||
if (state.score >= 200 && !state.bossAlive) spawnBoss();
|
||||
|
||||
// bullets update
|
||||
for (let i = bullets.length-1; i >= 0; i--) {
|
||||
const b = bullets[i];
|
||||
b.x += b.vx * dt;
|
||||
b.y += b.vy * dt;
|
||||
b.life -= dt;
|
||||
if (b.y < -40 || b.life <= 0) bullets.splice(i,1);
|
||||
}
|
||||
|
||||
// enemies update & collisions
|
||||
for (let i = enemies.length-1; i >= 0; i--) {
|
||||
const e = enemies[i];
|
||||
e.y += e.speed * dt;
|
||||
|
||||
// collide with player
|
||||
if (circleRectColl(e.x, e.y, e.r, player.x - player.w/2, player.y - player.h/2, player.w, player.h)) {
|
||||
enemies.splice(i,1);
|
||||
if (!state.gameOver) {
|
||||
state.lives -= 1;
|
||||
state.shake = Math.min(18, state.shake + 4.0);
|
||||
beep(280, 180, 'sawtooth', 0.07);
|
||||
if (state.lives <= 0) {
|
||||
state.gameOver = true;
|
||||
if (state.score > state.highscore) {
|
||||
state.highscore = state.score;
|
||||
localStorage.setItem('neonblitz_highscore', String(state.highscore));
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// collide with bullets
|
||||
let hit = false;
|
||||
for (let j = bullets.length-1; j >= 0; j--) {
|
||||
const b = bullets[j];
|
||||
if (dist(e.x, e.y, b.x, b.y) <= e.r + (b.kind === 'laser' ? 14 : 10)) {
|
||||
// laser traverse: keep moving but consume life faster
|
||||
e.hp -= (b.kind === 'laser' ? 2 : 1);
|
||||
bullets.splice(j,1);
|
||||
state.score += (b.kind === 'laser' ? 6 : 4);
|
||||
state.kills += (e.hp <= 0 ? 1 : 0);
|
||||
state.shake = Math.min(14, state.shake + 2.0);
|
||||
beep(500, 80, 'triangle', 0.06);
|
||||
if (e.hp <= 0) {
|
||||
enemies.splice(i,1);
|
||||
state.score += 10;
|
||||
hit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hit) continue;
|
||||
|
||||
// off-screen enemy
|
||||
if (e.y > H + 60) {
|
||||
enemies.splice(i,1);
|
||||
if (!state.gameOver) {
|
||||
state.lives -= 1;
|
||||
if (state.lives <= 0) {
|
||||
state.gameOver = true;
|
||||
if (state.score > state.highscore) {
|
||||
state.highscore = state.score;
|
||||
localStorage.setItem('neonblitz_highscore', String(state.highscore));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// boss update & collisions
|
||||
if (boss && state.bossAlive && !state.gameOver) {
|
||||
// move boss down initially then horizontal pacing
|
||||
if (boss.y < 120) boss.y += 60 * dt;
|
||||
else {
|
||||
boss.x += boss.dir * boss.speed * dt;
|
||||
if (boss.x < 40) { boss.x = 40; boss.dir = 1; }
|
||||
if (boss.x + boss.w > W-40) { boss.x = W-40 - boss.w; boss.dir = -1; }
|
||||
}
|
||||
// bullets vs boss
|
||||
for (let j = bullets.length-1; j >= 0; j--) {
|
||||
const b = bullets[j];
|
||||
if (circleRectColl(b.x, b.y, (b.kind==='laser'?12:8), boss.x, boss.y, boss.w, boss.h)) {
|
||||
boss.hp -= (b.kind === 'laser' ? 2 : 1);
|
||||
bullets.splice(j,1);
|
||||
state.score += (b.kind === 'laser' ? 10 : 6);
|
||||
state.shake = Math.min(16, state.shake + 3.0);
|
||||
beep(420, 120, 'triangle', 0.07);
|
||||
if (boss.hp <= 0) {
|
||||
state.score += 200;
|
||||
state.bossAlive = false;
|
||||
boss = null;
|
||||
beep(240, 220, 'sawtooth', 0.08);
|
||||
}
|
||||
}
|
||||
}
|
||||
// boss collide player
|
||||
if (circleRectColl(player.x, player.y, 18, boss.x, boss.y, boss.w, boss.h)) {
|
||||
state.lives -= 1;
|
||||
state.shake = Math.min(20, state.shake + 6.0);
|
||||
beep(260, 220, 'square', 0.08);
|
||||
if (state.lives <= 0) {
|
||||
state.gameOver = true;
|
||||
if (state.score > state.highscore) {
|
||||
state.highscore = state.score;
|
||||
localStorage.setItem('neonblitz_highscore', String(state.highscore));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// missions
|
||||
const elapsed = (performance.now() - state.startTime) / 1000;
|
||||
if (!state.gameOver && elapsed >= state.mission.survive && state.kills >= state.mission.kills) {
|
||||
state.score += 500;
|
||||
state.mission.survive += 60;
|
||||
state.mission.kills += 30;
|
||||
beep(1000, 200, 'square', 0.08);
|
||||
}
|
||||
|
||||
// screen shake
|
||||
state.shake *= 0.88;
|
||||
if (state.shake < 0.15) state.shake = 0;
|
||||
}
|
||||
|
||||
// ----- Draw -----
|
||||
function draw() {
|
||||
const dx = state.shake ? rand(-state.shake*0.3, state.shake*0.3) : 0;
|
||||
const dy = state.shake ? rand(-state.shake*0.3, state.shake*0.3) : 0;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(dx, dy);
|
||||
|
||||
// background grid neon
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.fillRect(0,0,W,H);
|
||||
ctx.strokeStyle = 'rgba(0,255,255,0.12)';
|
||||
ctx.lineWidth = 1;
|
||||
for (let x=0; x<=W; x+=48) { ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x,H); ctx.stroke(); }
|
||||
for (let y=0; y<=H; y+=48) { ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(W,y); ctx.stroke(); }
|
||||
|
||||
// player
|
||||
ctx.fillStyle = player.color;
|
||||
ctx.beginPath();
|
||||
// triangle pointing up
|
||||
ctx.moveTo(player.x, player.y - player.h/2);
|
||||
ctx.lineTo(player.x - player.w/2, player.y + player.h/2);
|
||||
ctx.lineTo(player.x + player.w/2, player.y + player.h/2);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
// bullets
|
||||
for (const b of bullets) {
|
||||
ctx.fillStyle = (b.kind === 'laser') ? '#FFD700' : '#FF00FF';
|
||||
if (b.kind === 'laser') {
|
||||
ctx.fillRect(b.x - 4, b.y - 14, 8, 28);
|
||||
} else {
|
||||
ctx.beginPath();
|
||||
ctx.arc(b.x, b.y, 5, 0, Math.PI*2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
// enemies
|
||||
for (const e of enemies) {
|
||||
ctx.fillStyle = e.color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(e.x, e.y, e.r, 0, Math.PI*2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// boss
|
||||
if (boss && state.bossAlive) {
|
||||
ctx.fillStyle = '#FF4500';
|
||||
ctx.fillRect(boss.x, boss.y, boss.w, boss.h);
|
||||
// boss hp bar
|
||||
ctx.fillStyle = '#222';
|
||||
ctx.fillRect(40, 40, W-80, 16);
|
||||
const ratio = Math.max(0, boss.hp/30);
|
||||
ctx.fillStyle = '#FF4500';
|
||||
ctx.fillRect(40, 40, (W-80)*ratio, 16);
|
||||
ctx.strokeStyle = '#fff';
|
||||
ctx.strokeRect(40, 40, W-80, 16);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
||||
// HUD
|
||||
const elapsed = ((performance.now()-state.startTime)/1000)|0;
|
||||
hudEl.innerHTML =
|
||||
`Score: ${state.score} ` +
|
||||
`Vies: ${state.lives} ` +
|
||||
`Kills: ${state.kills} ` +
|
||||
`Highscore: ${state.highscore} ` +
|
||||
`Mission: survivre ${state.mission.survive}s (${elapsed}s) / détruire ${state.mission.kills} (${state.kills})` +
|
||||
(state.gameOver ? `<br/><span style="color:#FF1493;font-weight:bold">GAME OVER — R pour recommencer</span>` : '');
|
||||
}
|
||||
|
||||
// ----- Main loop -----
|
||||
function frame(t) {
|
||||
const dtMs = (t - last) / 1000;
|
||||
last = t;
|
||||
accumulator += dtMs;
|
||||
// fixe-step update
|
||||
while (accumulator >= DT) {
|
||||
update(DT);
|
||||
accumulator -= DT;
|
||||
}
|
||||
draw();
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
// ----- Start -----
|
||||
restart();
|
||||
requestAnimationFrame(frame);
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Executable
+160
@@ -0,0 +1,160 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Labyrinthe avec niveaux</title>
|
||||
<style>
|
||||
body { margin:0; background:#000; color:#fff; font-family:Courier, monospace; }
|
||||
#wrap { display:flex; flex-direction:column; align-items:center; gap:8px; padding:12px; }
|
||||
canvas { background:#000; border:2px solid #0ff; box-shadow:0 0 12px #0ff; }
|
||||
.hud { text-align:center; }
|
||||
.hint { color:#aaa; font-size:12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrap">
|
||||
<canvas id="game" width="960" height="720"></canvas>
|
||||
<div id="hud" class="hud"></div>
|
||||
<div class="hint">Flèches pour bouger • R: restart</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const canvas = document.getElementById('game');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const W = canvas.width, H = canvas.height;
|
||||
|
||||
// ----- State -----
|
||||
let COLS, ROWS, CELL, OFFSET_X, OFFSET_Y;
|
||||
const grid = [];
|
||||
const player = { c:0, r:0, steps:0 };
|
||||
const exit = { c:0, r:0 };
|
||||
const state = { level:1, win:false, startTime:0, best:0 };
|
||||
|
||||
// ----- Génération du labyrinthe -----
|
||||
function index(c,r){ return r*COLS+c; }
|
||||
function neighbors(c,r){
|
||||
const n=[];
|
||||
if(r>0) n.push([c,r-1,0]);
|
||||
if(c<COLS-1) n.push([c+1,r,1]);
|
||||
if(r<ROWS-1) n.push([c,r+1,2]);
|
||||
if(c>0) n.push([c-1,r,3]);
|
||||
return n;
|
||||
}
|
||||
function carveMaze(startC=0,startR=0){
|
||||
grid.length=0;
|
||||
for(let r=0;r<ROWS;r++){
|
||||
for(let c=0;c<COLS;c++){
|
||||
grid.push({c,r,visited:false,walls:[true,true,true,true]});
|
||||
}
|
||||
}
|
||||
const stack=[];
|
||||
let current=grid[index(startC,startR)];
|
||||
current.visited=true; stack.push(current);
|
||||
while(stack.length){
|
||||
const cell=stack[stack.length-1];
|
||||
const cand=neighbors(cell.c,cell.r)
|
||||
.map(([nc,nr,dir])=>({nc,nr,dir,cell:grid[index(nc,nr)]}))
|
||||
.filter(o=>!o.cell.visited);
|
||||
if(cand.length===0){ stack.pop(); }
|
||||
else{
|
||||
const pick=cand[(Math.random()*cand.length)|0];
|
||||
cell.walls[pick.dir]=false;
|
||||
pick.cell.walls[(pick.dir+2)%4]=false;
|
||||
pick.cell.visited=true;
|
||||
stack.push(pick.cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Reset niveau -----
|
||||
function reset(level=1){
|
||||
state.level=level;
|
||||
COLS=20+level*2; ROWS=15+level*2;
|
||||
CELL=Math.floor(Math.min(W/COLS,H/ROWS));
|
||||
OFFSET_X=Math.floor((W-COLS*CELL)/2);
|
||||
OFFSET_Y=Math.floor((H-ROWS*CELL)/2);
|
||||
carveMaze(0,0);
|
||||
player.c=0; player.r=0; player.steps=0;
|
||||
exit.c=COLS-1; exit.r=ROWS-1;
|
||||
state.startTime=performance.now();
|
||||
state.win=false;
|
||||
}
|
||||
|
||||
// ----- Input -----
|
||||
const keys={ArrowUp:false,ArrowRight:false,ArrowDown:false,ArrowLeft:false};
|
||||
let cooldown=0;
|
||||
window.addEventListener('keydown',e=>{
|
||||
if(e.code in keys) keys[e.code]=true;
|
||||
if(e.code==='KeyR') reset(1);
|
||||
});
|
||||
window.addEventListener('keyup',e=>{
|
||||
if(e.code in keys) keys[e.code]=false;
|
||||
});
|
||||
|
||||
function tryMove(dc,dr,wallIndex){
|
||||
if(state.win) return;
|
||||
const cell=grid[index(player.c,player.r)];
|
||||
if(!cell.walls[wallIndex]){
|
||||
player.c+=dc; player.r+=dr; player.steps++;
|
||||
if(player.c===exit.c && player.r===exit.r){
|
||||
state.win=true;
|
||||
const elapsed=((performance.now()-state.startTime)/1000)|0;
|
||||
if(state.level>state.best) state.best=state.level;
|
||||
setTimeout(()=>reset(state.level+1),1500); // passe au niveau suivant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Update -----
|
||||
function update(dt){
|
||||
cooldown-=dt*1000;
|
||||
if(cooldown<=0){
|
||||
if(keys.ArrowUp){ tryMove(0,-1,0); cooldown=100; }
|
||||
else if(keys.ArrowRight){ tryMove(1,0,1); cooldown=100; }
|
||||
else if(keys.ArrowDown){ tryMove(0,1,2); cooldown=100; }
|
||||
else if(keys.ArrowLeft){ tryMove(-1,0,3); cooldown=100; }
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Draw -----
|
||||
function draw(){
|
||||
ctx.fillStyle='#000'; ctx.fillRect(0,0,W,H);
|
||||
ctx.strokeStyle='#0ff'; ctx.lineWidth=2;
|
||||
for(const cell of grid){
|
||||
const x=OFFSET_X+cell.c*CELL, y=OFFSET_Y+cell.r*CELL;
|
||||
if(cell.walls[0]){ ctx.beginPath(); ctx.moveTo(x,y); ctx.lineTo(x+CELL,y); ctx.stroke(); }
|
||||
if(cell.walls[1]){ ctx.beginPath(); ctx.moveTo(x+CELL,y); ctx.lineTo(x+CELL,y+CELL); ctx.stroke(); }
|
||||
if(cell.walls[2]){ ctx.beginPath(); ctx.moveTo(x,y+CELL); ctx.lineTo(x+CELL,y+CELL); ctx.stroke(); }
|
||||
if(cell.walls[3]){ ctx.beginPath(); ctx.moveTo(x,y); ctx.lineTo(x,y+CELL); ctx.stroke(); }
|
||||
}
|
||||
// exit
|
||||
ctx.fillStyle='#39FF14';
|
||||
ctx.beginPath();
|
||||
ctx.arc(OFFSET_X+exit.c*CELL+CELL/2,OFFSET_Y+exit.r*CELL+CELL/2,CELL*0.3,0,Math.PI*2);
|
||||
ctx.fill();
|
||||
// player
|
||||
ctx.fillStyle='#00FFFF';
|
||||
ctx.beginPath();
|
||||
ctx.arc(OFFSET_X+player.c*CELL+CELL/2,OFFSET_Y+player.r*CELL+CELL/2,CELL*0.3,0,Math.PI*2);
|
||||
ctx.fill();
|
||||
// HUD
|
||||
const elapsed=((performance.now()-state.startTime)/1000)|0;
|
||||
document.getElementById('hud').innerHTML=
|
||||
`Niveau: ${state.level} Pas: ${player.steps} Temps: ${elapsed}s Meilleur: ${state.best}`;
|
||||
}
|
||||
|
||||
// ----- Loop -----
|
||||
let last=performance.now();
|
||||
function frame(t){
|
||||
const dt=(t-last)/1000; last=t;
|
||||
update(dt); draw();
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
reset(1);
|
||||
requestAnimationFrame(frame);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user