anycoder-881162a7 / index.html
Mousco's picture
Upload folder using huggingface_hub
2cce7d7 verified
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Vice City Web - PSP Edition</title>
<style>
/* IMPORTATION DE POLICE */
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Press+Start+2P&display=swap');
/* RESET & BASE */
* { box-sizing: border-box; touch-action: none; user-select: none; -webkit-user-select: none; }
body, html { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background-color: #000; font-family: 'Orbitron', sans-serif; color: white; }
/* LIEN ANYCODER */
.anycoder-link {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
color: #0ff;
text-decoration: none;
font-size: 12px;
text-shadow: 0 0 5px #0ff;
font-family: sans-serif;
opacity: 0.8;
}
/* CANEVAS DE JEU */
#gameCanvas { display: block; width: 100%; height: 100%; image-rendering: pixelated; }
/* INTERFACE UTILISATEUR (HUD) */
#ui-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; display: flex; flex-direction: column; justify-content: space-between; padding: 10px; }
.hud-top { display: flex; justify-content: space-between; align-items: flex-start; }
.player-stats { text-shadow: 2px 2px 0 #000; }
.health-bar-container { width: 150px; height: 15px; background: #333; border: 2px solid white; margin-bottom: 5px; }
.health-bar { width: 100%; height: 100%; background: #f00; transition: width 0.2s; }
.money { color: #0f0; font-size: 20px; font-weight: bold; text-shadow: 2px 2px 0 #000; }
.wanted-level { color: #FFD700; font-size: 20px; letter-spacing: 5px; text-shadow: 2px 2px 0 #000; }
.radio-display { background: rgba(0,0,0,0.7); padding: 5px; border: 1px solid #0ff; border-radius: 5px; color: #0ff; font-size: 12px; text-align: right; display: none; }
.radio-display.active { display: block; animation: radioPulse 2s infinite; }
@keyframes radioPulse { 0% { opacity: 0.7; } 50% { opacity: 1; text-shadow: 0 0 10px #0ff; } 100% { opacity: 0.7; } }
/* NOTIFICATIONS DE MISSION */
#mission-text {
position: absolute;
bottom: 20%;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.8);
border-left: 5px solid #f0f;
padding: 15px;
max-width: 80%;
display: none;
color: white;
font-size: 14px;
}
/* MENUS (PAUSE, MAP) */
#pause-menu, #map-screen {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.85);
display: none; flex-direction: column; align-items: center; justify-content: center;
z-index: 500; pointer-events: auto;
}
#map-screen { background: #001133; }
.menu-title { font-family: 'Press Start 2P', cursive; font-size: 30px; color: #f0f; margin-bottom: 20px; text-shadow: 4px 4px 0 #0ff; text-align: center; }
.menu-item { color: white; font-size: 18px; margin: 10px 0; cursor: pointer; border: 1px solid transparent; padding: 5px; }
.menu-item:hover { border: 1px solid #f0f; color: #f0f; }
/* CONTRÔLES MOBILES (STYLE PSP) */
#mobile-controls {
display: none; /* Caché sur PC par défaut, activé via JS si tactile */
position: absolute; bottom: 0; left: 0; width: 100%; height: 100%;
pointer-events: none; z-index: 200;
}
/* D-PAD */
.dpad-container { position: absolute; bottom: 40px; left: 30px; width: 140px; height: 140px; pointer-events: auto; }
.dpad-btn { position: absolute; background: rgba(50,50,50,0.8); border: 2px solid #888; border-radius: 5px; }
.dpad-btn:active { background: #f0f; border-color: white; }
.dpad-up { top: 0; left: 45px; width: 50px; height: 45px; border-radius: 5px 5px 0 0; }
.dpad-down { bottom: 0; left: 45px; width: 50px; height: 45px; border-radius: 0 0 5px 5px; }
.dpad-left { top: 45px; left: 0; width: 45px; height: 50px; border-radius: 5px 0 0 5px; }
.dpad-right { top: 45px; right: 0; width: 45px; height: 50px; border-radius: 0 5px 5px 0; }
.dpad-center { top: 45px; left: 45px; width: 50px; height: 50px; background: #333; }
/* BOUTONS ACTION */
.actions-container { position: absolute; bottom: 30px; right: 30px; width: 180px; height: 140px; pointer-events: auto; }
.action-btn {
position: absolute; width: 50px; height: 50px;
border-radius: 50%; border: 2px solid white;
display: flex; align-items: center; justify-content: center;
font-weight: bold; font-size: 18px; color: white; text-shadow: 1px 1px 0 #000;
box-shadow: 0 4px 0 rgba(0,0,0,0.5);
}
.action-btn:active { transform: translateY(4px); box-shadow: none; background: #ccc; }
.btn-x { bottom: 0; right: 40px; background: rgba(100,100,255,0.8); } /* Croix - Bleu */
.btn-o { bottom: 40px; right: 0; background: rgba(255,100,100,0.8); } /* Rond - Rouge */
.btn-tri { bottom: 80px; right: 40px; background: rgba(100,255,100,0.8); } /* Triangle - Vert */
.btn-sq { bottom: 40px; right: 80px; background: rgba(255,100,255,0.8); } /* Carré - Rose */
/* SHOULDER BUTTONS */
.btn-l { top: 20px; left: 20px; width: 60px; height: 30px; border-radius: 10px; background: #555; font-size: 12px; pointer-events: auto; }
.btn-r { top: 20px; right: 20px; width: 60px; height: 30px; border-radius: 10px; background: #555; font-size: 12px; pointer-events: auto; }
/* START / SELECT */
.sys-btns { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); display: flex; gap: 20px; pointer-events: auto; }
.btn-start, .btn-select { font-size: 10px; color: #aaa; text-transform: uppercase; text-align: center; }
.rect-btn { width: 40px; height: 10px; background: #666; border-radius: 5px; margin: 0 auto 5px auto; }
/* MEDIA QUERIES */
@media (hover: none) and (pointer: coarse) {
#mobile-controls { display: block; }
}
</style>
</head>
<body>
<!-- LIEN OBLIGATOIRE -->
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">Built with anycoder</a>
<!-- CANEVAS -->
<canvas id="gameCanvas"></canvas>
<!-- INTERFACE HUD -->
<div id="ui-layer">
<div class="hud-top">
<div class="player-stats">
<div class="health-bar-container"><div class="health-bar" id="healthBar"></div></div>
<div class="money">$<span id="moneyDisplay">0</span></div>
</div>
<div class="wanted-level" id="wantedStars">★★★★★</div>
</div>
<div class="radio-display" id="radioDisplay">
<div id="radioStation">RADIO: OFF</div>
<div id="radioSong" style="font-size: 10px; color: #ccc;"></div>
</div>
<div id="mission-text"></div>
</div>
<!-- MENU PAUSE -->
<div id="pause-menu">
<div class="menu-title">PAUSE</div>
<div class="menu-item" onclick="game.togglePause()">Reprendre</div>
<div class="menu-item" onclick="game.resetGame()">Nouvelle Partie</div>
</div>
<!-- ECRAN CARTE -->
<div id="map-screen">
<div class="menu-title">CARTE RADAR</div>
<div style="color: #aaa; font-size: 12px; text-align: center; max-width: 300px;">
Position: <span id="mapCoords">0, 0</span><br>
Zone: <span id="mapZone">Centre-Ville</span>
</div>
<div class="menu-item" style="margin-top: 50px;" onclick="game.toggleMap()">Fermer (Select)</div>
</div>
<!-- CONTRÔLES MOBILES -->
<div id="mobile-controls">
<div class="btn-l" id="btnL">L</div>
<div class="btn-r" id="btnR">R</div>
<div class="dpad-container">
<div class="dpad-btn dpad-up" data-key="ArrowUp"></div>
<div class="dpad-btn dpad-down" data-key="ArrowDown"></div>
<div class="dpad-btn dpad-left" data-key="ArrowLeft"></div>
<div class="dpad-btn dpad-right" data-key="ArrowRight"></div>
<div class="dpad-btn dpad-center"></div>
</div>
<div class="actions-container">
<div class="action-btn btn-tri" data-key="Triangle"></div>
<div class="action-btn btn-sq" data-key="Square"></div>
<div class="action-btn btn-x" data-key="Cross"></div>
<div class="action-btn btn-o" data-key="Circle"></div>
</div>
<div class="sys-btns">
<div class="btn-select" id="btnSelect"><div class="rect-btn"></div>Select</div>
<div class="btn-start" id="btnStart"><div class="rect-btn" style="width: 50px;"></div>Start</div>
</div>
</div>
<script>
/**
* MOTEUR DE JEU - VICE CITY WEB
* Architecture: Boucle de jeu unique, Gestion d'état, Rendu Canvas 2D
*/
// --- CONSTANTES & CONFIGURATION ---
const CONFIG = {
TILE_SIZE: 100,
WORLD_WIDTH: 4000,
WORLD_HEIGHT: 4000,
PLAYER_SPEED: 4,
PLAYER_RUN_SPEED: 7,
CAR_SPEED: 12,
CAR_ROTATION_SPEED: 0.06,
COLORS: {
ROAD: '#333',
ROAD_MARKING: '#FFD700',
GRASS: '#2d5a27',
SAND: '#e6c288',
WATER: '#006994',
BUILDING_SIDE: '#556',
BUILDING_ROOF: '#ff00ff', // Style Vice City
BUILDING_ROOF_2: '#00ffff'
}
};
const RADIO_STATIONS = [
{ name: "Vice Wave FM", song: "Electro Tropical - 1986" },
{ name: "Rock Classics", song: "Turn Up The Radio" },
{ name: "Fever 105", song: "Boogie Wonderland" },
{ name: "Espantoso", song: "Mambo No. 5" }
];
// --- GESTION DES ENTRÉES (CLAVIER & TACTILE) ---
class InputHandler {
constructor() {
this.keys = {};
this.pressed = {}; // Pour les impulsions (une seule fois par appui)
// Mapping clavier PC
this.keyMap = {
'w': 'ArrowUp', 'W': 'ArrowUp', 'ArrowUp': 'ArrowUp',
's': 'ArrowDown', 'S': 'ArrowDown', 'ArrowDown': 'ArrowDown',
'a': 'ArrowLeft', 'A': 'ArrowLeft', 'ArrowLeft': 'ArrowLeft',
'd': 'ArrowRight', 'D': 'ArrowRight', 'ArrowRight': 'ArrowRight',
' ': 'Triangle', 'Triangle': 'Triangle',
'f': 'Square', 'F': 'Square', 'Square': 'Square',
'e': 'Circle', 'E': 'Circle', 'Circle': 'Circle',
'Shift': 'Cross', 'Cross': 'Cross',
'p': 'Start', 'P': 'Start', 'Start': 'Start',
'm': 'Select', 'M': 'Select', 'Select': 'Select',
'q': 'L', 'Q': 'L', 'L': 'L',
'r': 'R', 'R': 'R', 'R': 'R'
};
window.addEventListener('keydown', (e) => this.onKey(e, true));
window.addEventListener('keyup', (e) => this.onKey(e, false));
this.setupTouchControls();
}
onKey(e, isDown) {
const mapped = this.keyMap[e.key];
if (mapped) {
this.keys[mapped] = isDown;
if (isDown) this.pressed[mapped] = true;
}
}
setupTouchControls() {
const bindTouch = (selector, key) => {
const el = document.querySelector(selector);
if (!el) return;
el.addEventListener('touchstart', (e) => { e.preventDefault(); this.keys[key] = true; this.pressed[key] = true; });
el.addEventListener('touchend', (e) => { e.preventDefault(); this.keys[key] = false; });
};
bindTouch('.dpad-up', 'ArrowUp');
bindTouch('.dpad-down', 'ArrowDown');
bindTouch('.dpad-left', 'ArrowLeft');
bindTouch('.dpad-right', 'ArrowRight');
bindTouch('.btn-x', 'Cross');
bindTouch('.btn-o', 'Circle');
bindTouch('.btn-tri', 'Triangle');
bindTouch('.btn-sq', 'Square');
bindTouch('.btn-l', 'L');
bindTouch('.btn-r', 'R');
bindTouch('#btnStart', 'Start');
bindTouch('#btnSelect', 'Select');
}
isDown(key) { return !!this.keys[key]; }
isPressed(key) {
if (this.pressed[key]) {
this.pressed[key] = false;
return true;
}
return false;
}
resetPressed() { this.pressed = {}; }
}
// --- CLASSES DU JEU ---
class Camera {
constructor() {
this.x = 0;
this.y = 0;
this.zoom = 1;
}
follow(target, width, height) {
// Interpolation fluide (Lerp)
const targetX = target.x - width / 2;
const targetY = target.y - height / 2;
this.x += (targetX - this.x) * 0.1;
this.y += (targetY - this.y) * 0.1;
// Limites du monde
this.x = Math.max(0, Math.min(this.x, CONFIG.WORLD_WIDTH - width));
this.y = Math.max(0, Math.min(this.y, CONFIG.WORLD_HEIGHT - height));
}
}
class Entity {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.width = 20;
this.height = 20;
this.color = color;
this.angle = 0;
this.speed = 0;
this.vx = 0;
this.vy = 0;
this.markedForDeletion = false;
}
update() {
this.x += this.vx;
this.y += this.vy;
}
draw(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.fillStyle = this.color;
ctx.fillRect(-this.width/2, -this.height/2, this.width, this.height);
ctx.restore();
}
getBounds() {
return { x: this.x - this.width/2, y: this.y - this.height/2, w: this.width, h: this.height };
}
collidesWith(other) {
const a = this.getBounds();
const b = other.getBounds();
return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
}
}
class Vehicle extends Entity {
constructor(x, y, type = 'car') {
super(x, y, type === 'sport' ? '#ff0055' : '#00aa00');
this.type = type;
this.width = 40;
this.height = 20;
this.maxSpeed = type === 'sport' ? CONFIG.CAR_SPEED * 1.5 : CONFIG.CAR_SPEED;
this.acceleration = 0.2;
this.friction = 0.96;
this.turnSpeed = CONFIG.CAR_ROTATION_SPEED;
this.driver = null;
this.radioIndex = 0;
}
update(input) {
if (this.driver) {
// Contrôles du véhicule
if (input.isDown('Cross')) { // X
this.speed += this.acceleration;
} else if (input.isDown('Square')) { // Frein/Reculer
this.speed -= this.acceleration;
} else {
this.speed *= this.friction;
}
// Limite de vitesse
this.speed = Math.max(-this.maxSpeed / 2, Math.min(this.speed, this.maxSpeed));
// Direction (seulement si en mouvement)
if (Math.abs(this.speed) > 0.1) {
const dir = this.speed > 0 ? 1 : -1;
if (input.isDown('ArrowLeft')) this.angle -= this.turnSpeed * dir;
if (input.isDown('ArrowRight')) this.angle += this.turnSpeed * dir;
}
this.vx = Math.cos(this.angle) * this.speed;
this.vy = Math.sin(this.angle) * this.speed;
// Sync driver position
this.driver.x = this.x;
this.driver.y = this.y;
this.driver.angle = this.angle;
} else {
this.speed *= 0.9; // Arrêt progressif si vide
this.vx = Math.cos(this.angle) * this.speed;
this.vy = Math.sin(this.angle) * this.speed;
}
// Collisions bords du monde
this.x = Math.max(0, Math.min(this.x, CONFIG.WORLD_WIDTH));
this.y = Math.max(0, Math.min(this.y, CONFIG.WORLD_HEIGHT));
super.update();
}
draw(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
// Corps de la voiture
ctx.fillStyle = this.color;
ctx.shadowBlur = 10;
ctx.shadowColor = this.color;
ctx.fillRect(-20, -10, 40, 20);
// Pare-brise
ctx.fillStyle = '#000';
ctx.fillRect(0, -8, 10, 16);
// Phares
ctx.fillStyle = '#ffeda0';
ctx.fillRect(18, -9, 4, 4);
ctx.fillRect(18, 5, 4, 4);
ctx.restore();
}
}
class NPC extends Entity {
constructor(x, y) {
super(x, y, `hsl(${Math.random()*360}, 70%, 50%)`);
this.state = 'idle'; // idle, walk, flee
this.timer = 0;
this.walkSpeed = 1 + Math.random();
}
update(player) {
const dist = Math.hypot(player.x - this.x, player.y - this.y);
if (this.state === 'idle') {
if (Math.random() < 0.01) this.state = 'walk';
this.vx = 0; this.vy = 0;
} else if (this.state === 'walk') {
this.timer++;
if (this.timer > 100) { this.state = 'idle'; this.timer = 0; }
this.vx = Math.cos(this.angle) * this.walkSpeed;
this.vy = Math.sin(this.angle) * this.walkSpeed;
} else if (this.state === 'flee') {
this.angle = Math.atan2(this.y - player.y, this.x - player.x);
this.vx = Math.cos(this.angle) * (this.walkSpeed * 2);
this.vy = Math.sin(this.angle) * (this.walkSpeed * 2);
if (dist > 300) this.state = 'idle';
}
// Collision Player (Fuir si attaqué)
if (player.isAttacking && dist < 50) {
this.state = 'flee';
// Blesser PNJ ?
}
super.update();
// Limites simples
this.x = Math.max(0, Math.min(this.x, CONFIG.WORLD_WIDTH));
this.y = Math.max(0, Math.min(this.y, CONFIG.WORLD_HEIGHT));
}
draw(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(0, 0, 8, 0, Math.PI * 2);
ctx.fill();
// Direction
ctx.fillStyle = '#000';
ctx.fillRect(0, -2, 10, 4);
ctx.restore();
}
}
class Cop extends Entity {
constructor(x, y) {
super(x, y, '#000088');
this.width = 24;
this.height = 24;
this.sirenTimer = 0;
}
update(target) {
const angle = Math.atan2(target.y - this.y, target.x - this.x);
this.angle = angle;
const speed = 3.5; // Un peu plus rapide que le joueur
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
super.update();
}
draw(ctx) {
this.sirenTimer++;
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.fillStyle = this.color;
ctx.fillRect(-12, -12, 24, 24);
// Gyro
ctx.fillStyle = this.sirenTimer % 20 < 10 ? 'red' : 'blue';
ctx.fillRect(-5, -15, 10, 5);
ctx.restore();
}
}
class Player extends Entity {
constructor(x, y) {
super(x, y, '#fff');
this.width = 16;
this.height = 16;
this.health = 100;
this.money = 0;
this.isDriving = false;
this.currentVehicle = null;
this.isJumping = false;
this.z = 0; // Hauteur de saut
this.isAttacking = false;
this.attackTimer = 0;
}
update(input, vehicles) {
this.isAttacking = false;
// Interaction véhicule
if (input.isPressed('Circle')) { // O
if (this.isDriving) {
// Sortir
this.isDriving = false;
this.x = this.currentVehicle.x + 40;
this.y = this.currentVehicle.y;
this.currentVehicle.driver = null;
this.currentVehicle = null;
game.updateRadio(false);
} else {
// Entrer
vehicles.forEach(v => {
if (!v.driver && Math.hypot(v.x - this.x, v.y - this.y) < 50) {
this.isDriving = true;
this.currentVehicle = v;
v.driver = this;
game.updateRadio(true);
}
});
}
}
if (this.isDriving && this.currentVehicle) {
// Logique véhicule gérée par le véhicule, mais on met à jour la position joueur
this.currentVehicle.update(input);
this.x = this.currentVehicle.x;
this.y = this.currentVehicle.y;
this.angle = this.currentVehicle.angle;
} else {
// Logique piéton
let speed = input.isDown('Cross') ? CONFIG.PLAYER_RUN_SPEED : CONFIG.PLAYER_SPEED;
let dx = 0;
let dy = 0;
if (input.isDown('ArrowUp')) dy = -1;
if (input.isDown('ArrowDown')) dy = 1;
if (input.isDown('ArrowLeft')) dx = -1;
if (input.isDown('ArrowRight')) dx = 1;
if (dx !== 0 || dy !== 0) {
this.angle = Math.atan2(dy, dx);
this.x += dx * speed;
this.y += dy * speed;
}
// Saut
if (input.isPressed('Triangle') && !this.isJumping) {
this.isJumping = true;
this.vz = 5;
}
// Gravité saut
if (this.isJumping) {
this.z += this.vz;
this.vz -= 0.5;
if (this.z <= 0) {
this.z = 0;
this.isJumping = false;
}
}
// Attaque
if (input.isPressed('Square')) {
this.isAttacking = true;
this.attackTimer = 10;
}
if (this.attackTimer > 0) this.attackTimer--;
}
// Limites
this.x = Math.max(0, Math.min(this.x, CONFIG.WORLD_WIDTH));
this.y = Math.max(0, Math.min(this.y, CONFIG.WORLD_HEIGHT));
}
draw(ctx) {
if (this.isDriving) return; // Dessiné par le véhicule
ctx.save();
ctx.translate(this.x, this.y);
// Ombre
ctx.fillStyle = 'rgba(0,0,0,0.3)';
ctx.beginPath(); ctx.ellipse(0, 0, 8, 4, 0, 0, Math.PI*2); ctx.fill();
// Saut (Offset Y)
ctx.translate(0, -this.z);
ctx.rotate(this.angle);
// Corps
ctx.fillStyle = '#eee'; // Chemise hawaïenne stylée
ctx.fillRect(-8, -8, 16, 16);
// Tête
ctx.fillStyle = '#dcb';
ctx.beginPath(); ctx.arc(0, 0, 5, 0, Math.PI*2); ctx.fill();
// Arme / Attaque
if (this.attackTimer > 0) {
ctx.fillStyle = '#555';
ctx.fillRect(8, -2, 15, 4); // Poing américain
}
ctx.restore();
}
}
// --- MOTEUR PRINCIPAL ---
class Game {
constructor() {
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.getContext('2d');
this.input = new InputHandler();
this.camera = new Camera();
this.resize();
window.addEventListener('resize', () => this.resize());
this.state = 'PLAYING'; // PLAYING, PAUSED, MAP
this.dayTime = 0; // 0 à 1
this.daySpeed = 0.0002;
this.initWorld();
this.loop = this.loop.bind(this);
requestAnimationFrame(this.loop);
}
resize() {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
}
initWorld() {
this.player = new Player(2000, 2000);
this.vehicles = [];
this.npcs = [];
this.cops = [];
this.buildings = [];
this.missions = [];
this.wantedLevel = 0;
this.wantedTimer = 0;
// Génération procédurale simple
// Routes
this.roads = [];
for(let i=0; i<CONFIG.WORLD_WIDTH; i+=400) {
this.roads.push({x: i, y: 0, w: 80, h: CONFIG.WORLD_HEIGHT, vertical: true});
}
for(let i=0; i<CONFIG.WORLD_HEIGHT; i+=400) {
this.roads.push({x: 0, y: i, w: CONFIG.WORLD_WIDTH, h: 80, vertical: false});
}
// Bâtiments (Entre les routes)
for(let x=100; x<CONFIG.WORLD_WIDTH; x+=400) {
for(let y=100; y<CONFIG.WORLD_HEIGHT; y+=400) {
if (Math.random() > 0.2) {
const w = 200 + Math.random() * 100;
const h = 200 + Math.random() * 100;
const color = Math.random() > 0.5 ? CONFIG.COLORS.BUILDING_ROOF : CONFIG.COLORS.BUILDING_ROOF_2;
this.buildings.push({x: x, y: y, w: w, h: h, color: color});
}
}
}
// Véhicules
for(let i=0; i<20; i++) {
this.vehicles.push(new Vehicle(Math.random() * CONFIG.WORLD_WIDTH, Math.random() * CONFIG.WORLD_HEIGHT, Math.random() > 0.8 ? 'sport' : 'car'));
}
// PNJ
for(let i=0; i<50; i++) {
this.npcs.push(new NPC(Math.random() * CONFIG.WORLD_WIDTH, Math.random() * CONFIG.WORLD_HEIGHT));
}
// Mission initiale
this.startMission(0);
}
startMission(index) {
this.missionIndex = index;
this.missionStep = 0;
this.showMissionText("MISSION: Trouvez une voiture (Bouton O)");
}
showMissionText(text) {
const el = document.getElementById('mission-text');
el.innerText = text;
el.style.display = 'block';
setTimeout(() => el.style.display = 'none', 4000);
}
updateMissions() {
if (this.missionIndex === 0) {
if (this.player.isDriving) {
this.showMissionText("OBJECTIF: Allez à la plage (Sud-Est)");
this.missionIndex = 1;
}
} else if (this.missionIndex === 1) {
// Plage approx zone sud-est
if (this.player.x > CONFIG.WORLD_WIDTH - 1000 && this.player.y > CONFIG.WORLD_HEIGHT - 1000) {
this.player.money += 100;
this.updateHUD();
this.showMissionText("SUCCÈS! +$100. Retournez en ville.");
this.missionIndex = 2;
}
}
}
updatePolice() {
// Gestion étoiles
if (this.wantedLevel > 0) {
this.wantedTimer++;
if (this.wantedTimer > 600) { // 10 sec sans crime
this.wantedLevel--;
this.wantedTimer = 0;
}
}
// Spawn police
if (this.wantedLevel > this.cops.length) {
const angle = Math.random() * Math.PI * 2;
const dist = 600;
this.cops.push(new Cop(this.player.x + Math.cos(angle)*dist, this.player.y + Math.sin(angle)*dist));
} else if (this.wantedLevel < this.cops.length) {
this.cops.pop();
}
// Update cops
this.cops.forEach(cop => cop.update(this.player));
// Collision flic -> joueur
this.cops.forEach(cop => {
if (cop.collidesWith(this.player)) {
this.player.health -= 0.5;
this.updateHUD();
if (this.player.health <= 0) this.gameOver();
}
});
}
addWantedLevel() {
if (this.wantedLevel < 5) {
this.wantedLevel++;
this.wantedTimer = 0;
this.updateHUD();
}
}
updateRadio(active) {
const display = document.getElementById('radioDisplay');
if (active) {
display.classList.add('active');
const station = RADIO_STATIONS[Math.floor(Math.random() * RADIO_STATIONS.length)];
document.getElementById('radioStation').innerText = "RADIO: " + station.name;
document.getElementById('radioSong').innerText = station.song;
} else {
display.classList.remove('active');
}
}
togglePause() {
this.state = this.state === 'PAUSED' ? 'PLAYING' : 'PAUSED';
document.getElementById('pause-menu').style.display = this.state === 'PAUSED' ? 'flex' : 'none';
}
toggleMap() {
this.state = this.state === 'MAP' ? 'PLAYING' : 'MAP';
document.getElementById('map-screen').style.display = this.state === 'MAP' ? 'flex' : 'none';
if (this.state === 'MAP') this.updateMap();
}
updateMap() {
document.getElementById('mapCoords').innerText = `${Math.floor(this.player.x)}, ${Math.floor(this.player.y)}`;
let zone = "Centre-Ville";
if (this.player.y > CONFIG.WORLD_HEIGHT - 800) zone = "Plage Vice";
if (this.player.x > CONFIG.WORLD_WIDTH - 800) zone = "Port";
document.getElementById('mapZone').innerText = zone;
}
updateHUD() {
document.getElementById('healthBar').style.width = this.player.health + '%';
document.getElementById('moneyDisplay').innerText =