Univers.php

Systeme de combat 1v1 tours par tours

1. Cœur du Système

syncState()
Chef d'Orchestre
🎯 Utilité Cette fonction gere la synchronisation entre le server et le client
⚙️ Fonctionnement Elle fait un appel AJAX. Si "Game Over", elle redirige vers index.php, ca permet de rediriger de force l'utilisateur si il a perdu, pour eviter qu'il soit coincé dans un combat terminé. Si une partie semble en cours, elle met à jour les données globales (flottes, tours) et lance le timer.
function syncState() {
    if(mode !== null || gameOver) return;
    
    $.ajax({
        url: 'api/action_game.php',
        type: 'POST',
        data: { action: 'get_state' },
        dataType: 'json',
        success: function(res) {
            if (res.gameState && res.gameState.game_over === true) {
                window.location.href = 'index.php';
                return;
            }
            if(res.gameState) {
                if (res.gameState.win === 'WIN') { showEndScreen("VICTOIRE", "L'ennemi a été éliminé.", true); return; }
                if (res.gameState.win === 'LOSE') { showEndScreen("DÉFAITE", "Votre flotte a été anéantie.", false); return; }

                ships = res.gameState.entities;
                currentTurn = res.gameState.turn;
                
                if (currentTurn !== previousTurn) {
                    visualTimer = 60; 
                    previousTurn = currentTurn;
                    $('#status-text').text("Nouveau tour tactique.");
                }
                renderGame();
                updateHud(res.msg);
            }
        }
    });
}
startVisualTimer()
Pression Temporelle
🎯 Utilité Affichage du timer
⚙️ Fonctionnement Boucle setInterval (1s). Chaque seconde la variable locale perd 1. Si elle arrive à 0, elle appelle endTurn() pour forcer le passage de tour côté client.
function startVisualTimer() {
    if (timerIntervalId) clearInterval(timerIntervalId);
    timerIntervalId = setInterval(function() {
        if (visualTimer > 0) visualTimer--;
        else if (currentTurn === 'player' && !gameOver) {
            visualTimer = 60; 
            $('#status-text').text("Temps écoulé ! Fin du tour auto.");
            endTurn();
        }
        let sec = visualTimer < 10 ? "0" + visualTimer : visualTimer;
        $('#timer-display').text("00:" + sec).toggleClass('timer-warning', visualTimer <= 10);
    }, 1000);
}

2. Requêtes AJAX (API)

commitMove(x, y)
Ordre de Mouvement
🎯 Utilité Requete ajax pour le deplacement d'une entité selectionné
⚙️ Fonctionnement Envoie l'ID du vaisseau et les coordonnées X/Y cibles à l'API. elle va ensuite réinitialise la sélection et force une synchronisation.
function commitMove(x, y) {
    $.ajax({
        url: 'api/action_game.php',
        type: 'POST',
        data: { action: 'move', id: selectedShipId, x: x, y: y },
        dataType: 'json',
        success: function(res) {
            resetMode(res.msg); 
            setTimeout(syncState, 200);
        }
    });
}
commitAttack(target)
Ordre de Combat
🎯 Utilité Requete ajax pour l'action "tirer" d'une entité selectionné
⚙️ Fonctionnement Envoie l'ID de l'attaquant et de la cible. Si les conditions sont remplis (assez d'énergie/portée), la fonction déclenche l'animation fireLaserEffect.
function commitAttack(target) {
    let attacker = ships.find(s => parseInt(s.id) === parseInt(selectedShipId));
    if (!attacker) { resetMode("Erreur: Attaquant introuvable."); return; }
    $.ajax({
        url: 'api/action_game.php',
        type: 'POST',
        data: { action: 'attack', attackerId: selectedShipId, targetId: target.id },
        dataType: 'json',
        success: function(res) {
            if(res.success) fireLaserEffect(attacker, target);
            resetMode(res.msg); syncState();
        }
    });
}
commitDrone(x, y)
Ordre de Spawn
🎯 Utilité Requete ajax pour faire apparaitre un drone
⚙️ Fonctionnement Envoie le type de drone (kamikaze/scout) et la position. Le serveur va ensuite crée une nouvelle entité dans la base de données.
function commitDrone(x, y) {
    $.ajax({
        url: 'api/action_game.php',
        type: 'POST',
        data: { action: 'spawn_drone', parentId: selectedShipId, type: droneTypeToDeploy, x: x, y: y },
        dataType: 'json',
        success: function(res) { resetMode(res.msg); syncState(); }
    });
}
commitHeal(target)
Ordre de Soutien
🎯 Utilité Requete ajax pour l'action "soigner"
⚙️ Fonctionnement Meme systeme que pour l'attaque offensive. Déclenche l'effet visuel fireHealEffect (laser vert).
function commitHeal(target) {
    let healer = ships.find(s => parseInt(s.id) === parseInt(selectedShipId));
    $.ajax({
        url: 'api/action_game.php',
        type: 'POST',
        data: { action: 'heal', healerId: selectedShipId, targetId: target.id },
        dataType: 'json',
        success: function(res) {
            if(res.success && healer) fireHealEffect(healer, target);
            resetMode(res.msg); syncState();
        }
    });
}

3. Moteur de Rendu

renderGame()
Rendu Visuel (View)
🎯 Utilité Genere la grille et l'ensemble des entités du combat
⚙️ Fonctionnement Boucle sur les 100 cases (10x10).
1. Si une entité est présente : Crée son visuel HTML, gère ses animations CSS (spawn, hit) et ses événements clics.
2. Si la case est vide : Vérifie si elle doit être interactive (verte/violette) selon le mode actif.
function renderGame() {
    const grid = $('#game-grid');
    grid.empty();
    let currentIds = ships.map(s => parseInt(s.id));

    // Gestion Morts
    for (let id in previousShipsState) {
        if (!currentIds.includes(parseInt(id))) {
            let d = previousShipsState[id];
            playDeathAnimation(d.x, d.y);
        }
    }

    // Boucle de rendu 10x10
    for (let y = 0; y < 10; y++) {
        for (let x = 0; x < 10; x++) {
            let cell = $('<div class="cell"></div>').attr('data-x', x).attr('data-y', y);
            let entity = ships.find(s => parseInt(s.x) === x && parseInt(s.y) === y);

            // LOGIQUE CASE VIDE (Interactive)
            if (!entity && currentTurn === 'player' && selectedShipId) {
                let actor = ships.find(s => parseInt(s.id) === parseInt(selectedShipId));
                if(actor) {
                    let dist = getDistance(actor.x, actor.y, x, y);
                    if (mode === 'move') {
                        let maxMove = (actor.subtype && actor.subtype.toLowerCase() === 'chasseur') ? 2 : 1;
                        if (dist <= maxMove) cell.addClass('valid-move').click(() => { playWarpOutEffect(actor.x, actor.y); commitMove(x, y); });
                    } else if (mode === 'drone' && dist === 1) {
                        cell.addClass('valid-drone').click(() => commitDrone(x, y));
                    }
                }
            }

            // LOGIQUE ENTITÉ PRÉSENTE
            if (entity) {
                let visual = createEntityVisual(entity);
                let shape = visual.find('.entity-shape');
                let actor = selectedShipId ? ships.find(s => parseInt(s.id) === parseInt(selectedShipId)) : null;
                let prevState = previousShipsState[entity.id];
                let isNew = !prevState;
                let hasMoved = prevState && (parseInt(prevState.x) !== parseInt(entity.x) || parseInt(prevState.y) !== parseInt(entity.y));

                // Animations CSS
                if (prevState && prevState.hp > entity.hp) shape.addClass('anim-hit');
                if (entity.type === 'drone' && isNew) shape.addClass('anim-spawn');
                else if (hasMoved || (isNew && Object.keys(previousShipsState).length > 0)) shape.addClass('anim-arrival');
                else shape.addClass('anim-idle');

                // Interactions (Attaque / Soin)
                if (mode === 'attack' && actor && parseInt(entity.id) !== parseInt(selectedShipId) && currentTurn === 'player') {
                    let dist = getDistance(actor.x, actor.y, entity.x, entity.y);
                    if (dist <= 4 && entity.type === 'enemy' && !entity.hidden) {
                        shape.removeClass('anim-idle').addClass('valid-target');
                        visual.off('click').click((e) => { e.stopPropagation(); commitAttack(entity); });
                    }
                }
                if (mode === 'heal' && actor && currentTurn === 'player') {
                    let dist = getDistance(actor.x, actor.y, entity.x, entity.y);
                    if (dist <= 3 && entity.type !== 'enemy') {
                        shape.removeClass('anim-idle').addClass('valid-ally');
                        visual.off('click').click((e) => { e.stopPropagation(); commitHeal(entity); });
                    }
                }
                if (!mode) visual.click((e) => { e.stopPropagation(); if (currentTurn === 'player') openPanel(entity); });
                cell.append(visual);
            }
            grid.append(cell);
        }
    }
    previousShipsState = {};
    ships.forEach(s => { previousShipsState[s.id] = { ...s }; });
}
createEntityVisual(entity)
Constructeur DOM
🎯 Utilité Cette fonction va generer les entités selon leur type, vaisseau ou drone
⚙️ Fonctionnement assigne les classes CSS (couleur équipe, forme hexagone/rond) et calcule la largeur de la barre de vie en pourcentage.
function createEntityVisual(entity) {
    let container = $('<div></div>').addClass('entity');
    if (entity.hidden) container.addClass('is-hidden');
    let shapeClass = 'entity-shape ' + (entity.type === 'ship' ? 'type-ship' : (entity.type === 'enemy' ? 'type-enemy' : 'type-drone'));
    
    if (entity.subtype === 'soigneur' || (entity.name && entity.name.toLowerCase().includes('médicale'))) shapeClass = shapeClass.replace('type-ship', 'type-soigneur');
    if (entity.type === 'drone') {
        if(entity.subtype === 'kamikaze') shapeClass += ' drone-kamikaze';
        if(entity.subtype === 'scout') shapeClass += ' drone-scout';
    }
    if (entity.id == selectedShipId) shapeClass += ' active';
    
    let pct = (entity.hp / entity.maxHp) * 100;
    let barColor = (entity.type === 'enemy') ? 'var(--neon-red)' : 'var(--neon-green)';
    container.append(`<div class="${shapeClass}"></div><div class="hp-bar"><div class="hp-fill" style="width:${pct}%; background-color:${barColor}; box-shadow: 0 0 5px ${barColor};"></div></div>`);
    return container;
}

4. Effets Visuels (FX)

createBeam(s, t, cls)
Moteur Physique (Trigonométrie)
🎯 Utilité Dessine une ligne (rayon laser ou soin) entre deux cases de la grille.
⚙️ Fonctionnement Utilise Math.atan2 pour calculer l'angle de rotation et Math.sqrt pour la longueur. Crée une DIV transformée en CSS.
function createBeam(s, t, cls) {
    let c1=$(`.cell[data-x='${s.x}'][data-y='${s.y}']`), c2=$(`.cell[data-x='${t.x}'][data-y='${t.y}']`);
    if(!c1.length||!c2.length) return;
    
    let x1=c1.offset().left+c1.width()/2, y1=c1.offset().top+c1.height()/2;
    let x2=c2.offset().left+c2.width()/2, y2=c2.offset().top+c2.height()/2;
    
    let l=Math.sqrt((x2-x1)**2+(y2-y1)**2);
    let a=Math.atan2(y2-y1, x2-x1)*180/Math.PI;
    
    let b=$('<div></div>').addClass(cls).css({top:y1,left:x1,width:l,transform:`rotate(${a}deg)`}).appendTo('body');
    setTimeout(()=>b.css('opacity',0),100); setTimeout(()=>b.remove(),600);
}
playWarpOutEffect(x, y)
Animation Mouvement
🎯 Utilité Animation de deplacement par clonage
⚙️ Fonctionnement La fonction clone le vaisseau et le place sur la nouvelle position, puis cache l'ancien vaisseau
function playWarpOutEffect(x, y) { 
    let e = $(`.cell[data-x='${x}'][data-y='${y}'] .entity`); 
    if(e.length) { 
        e.clone().removeClass('anim-idle active').addClass('fx-warp-out')
         .css({position:'absolute',top:0,left:0,zIndex:100})
         .appendTo(e.parent()); 
        e.css('opacity',0); 
    } 
}

5. Interface & Logique

openPanel(s)
Menu Contextuel
🎯 Utilité Ouvre la stats box de l'entité selectionné
⚙️ Fonctionnement Affiche les statistique du vaisseau HP, energie, etc... Affiche egalement les actions disponible en fonction du type de l'entités selectionner
function openPanel(s) {
    selectedShipId = parseInt(s.id); 
    $('#panel-title').text(s.name);
    $('#info-hp').text(s.hp + "/" + s.maxHp);
    $('#info-energy').text(s.energy);
    $('#info-owner').text(s.type === 'enemy' ? "HOSTILE" : "ALLIÉ").css('color', s.type === 'enemy' ? "var(--neon-red)" : "var(--neon-green)");
    $('#info-type').text(s.subtype ? s.subtype.toUpperCase() : (s.type === 'drone' ? 'DRONE ' + (s.subtype || '') : s.type));

    $('#controls-ship, #controls-drone-kami, #controls-healer').hide();
    if (s.type !== 'enemy') {
        $('#btn-orders').show();
        if (s.subtype === 'soigneur' || (s.name && s.name.toLowerCase().includes('médicale'))) $('#controls-healer').show();
        else if (s.type === 'ship') $('#controls-ship').show();
        else if (s.type === 'drone' && s.subtype === 'kamikaze') $('#controls-drone-kami').show();
    } else $('#btn-orders').hide();

    showInfosView();
    $('#overlay, #action-panel').fadeIn(200);
}
setMode(nm, txt)
Machine à États
🎯 Utilité Gère le mode d'interaction actuel (Déplacement, Attaque, Sélection).
⚙️ Fonctionnement Modifie la variable globale mode. Cela altère le comportement des clics dans la fonction renderGame() et affiche le bouton d'annulation.
function setMode(nm, txt) { 
    mode=nm; 
    $('#overlay, #action-panel').hide(); 
    $('#btn-cancel-action').fadeIn(200); 
    $('#status-text').text(txt).css('color','#00f3ff'); 
    renderGame(); 
}