Compétence 7 : Algorithmes¶
Identifier des solutions existantes ou originales afin de répondre à chaque problème posé en tenant compte des contraintes de performance et de scalabilité de la solution et de son environnement d'exécution.
Observable 7.1 : Algorithmes Existants Optimaux¶
Collision Detection : AABB (Axis-Aligned Bounding Box)¶
Problème¶
Détecter les collisions entre entités (missiles, ennemis, joueurs) en temps réel à 20 Hz.
Solution Standard : AABB¶
L'algorithme AABB est un standard de l'industrie pour la détection de collision 2D, offrant une complexité O(1) par paire d'entités.
Fichier : src/common/collision/AABB.hpp
namespace collision {
struct AABB {
float x, y, width, height;
constexpr AABB(float px, float py, float w, float h)
: x(px), y(py), width(w), height(h) {}
// O(1) - 4 comparaisons
constexpr bool intersects(const AABB& other) const {
return x < other.x + other.width && // Gauche < Droite autre
x + width > other.x && // Droite > Gauche autre
y < other.y + other.height && // Haut < Bas autre
y + height > other.y; // Bas > Haut autre
}
// O(1) - 4 comparaisons
constexpr bool contains(float px, float py) const {
return px >= x && px <= x + width &&
py >= y && py <= y + height;
}
constexpr float centerX() const { return x + width / 2.0f; }
constexpr float centerY() const { return y + height / 2.0f; }
};
} // namespace collision
Visualisation¶
graph LR
subgraph "Collision AABB"
A["Box A<br/>(x1, y1, w1, h1)"]
B["Box B<br/>(x2, y2, w2, h2)"]
end
subgraph "Conditions (toutes vraies = collision)"
C1["x1 < x2 + w2"]
C2["x1 + w1 > x2"]
C3["y1 < y2 + h2"]
C4["y1 + h1 > y2"]
end
A --> C1
A --> C2
B --> C3
B --> C4
Hitboxes Définies¶
| Entité | Largeur | Hauteur | Fichier |
|---|---|---|---|
| Vaisseau (Ship) | 64 | 30 | GameWorld.hpp |
| Missile | 16 | 8 | GameWorld.hpp |
| Ennemi | 40 | 40 | GameWorld.hpp |
| Missile ennemi | 16 | 8 | GameWorld.hpp |
| Boss | 150 | 120 | GameWorld.hpp |
Complexité Totale¶
Par frame (20 Hz):
- Missiles joueurs vs Ennemis: O(32 × 16) = O(512)
- Missiles ennemis vs Joueurs: O(32 × 4) = O(128)
- Total: ~640 vérifications AABB par frame
- Par seconde: 640 × 20 = 12,800 ops/s (négligeable)
Génération Aléatoire : Mersenne Twister¶
Problème¶
Générer des positions aléatoires pour les ennemis et les power-ups.
Solution Standard : std::mt19937¶
Fichier : src/server/infrastructure/game/GameWorld.hpp
class GameWorld {
private:
std::mt19937 _rng{std::random_device{}()}; // Mersenne Twister, seed aléatoire
void spawnEnemy() {
std::uniform_int_distribution<int> yDist(50, 1030); // Écran 1080p
int y = yDist(_rng);
// ...
}
};
Propriétés¶
| Propriété | Valeur |
|---|---|
| Période | 2^19937 - 1 |
| Qualité statistique | Excellente (passe Diehard tests) |
| Performance | ~10ns par nombre |
| Usage | Gameplay (non cryptographique) |
Compression : LZ4¶
Problème¶
Réduire la bande passante des snapshots (800-2000 bytes à 20 Hz).
Solution Standard : LZ4¶
Fichier : src/common/compression/Compression.hpp
inline std::vector<uint8_t> compress(const uint8_t* src, size_t srcSize) {
int maxDstSize = LZ4_compressBound(static_cast<int>(srcSize));
std::vector<uint8_t> compressed(maxDstSize);
int compressedSize = LZ4_compress_default(
reinterpret_cast<const char*>(src),
reinterpret_cast<char*>(compressed.data()),
static_cast<int>(srcSize),
maxDstSize
);
// Compression seulement si gain réel
if (static_cast<size_t>(compressedSize) >= srcSize) {
return {}; // Pas rentable
}
compressed.resize(compressedSize);
return compressed;
}
Performances Mesurées¶
| Métrique | Valeur |
|---|---|
| Ratio compression | 40-60% |
| Vitesse compression | ~400 MB/s |
| Vitesse décompression | ~1.5 GB/s |
| Latence ajoutée | ~0.1ms |
Hachage : SHA-256¶
Problème¶
Stocker les mots de passe de manière sécurisée.
Solution Standard : SHA-256 (OpenSSL)¶
Fichier : src/server/domain/value_objects/user/PasswordUtils.cpp
std::string hashPassword(std::string password) {
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256(reinterpret_cast<const unsigned char*>(password.c_str()),
password.length(), hash);
std::stringstream ss;
for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
ss << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(hash[i]);
}
return ss.str(); // 64 caractères hex
}
Observable 7.2 : Algorithmes Originaux¶
Lorsque les algorithmes standards ne suffisent pas, des solutions originales ont été développées.
Système de Combo avec Grace Period¶
Problème¶
Un système de combo classique (reset immédiat) est frustrant. Comment encourager le skill tout en restant accessible ?
Solution Originale¶
stateDiagram-v2
[*] --> Active: Kill enemy
Active --> Grace: No kill
Grace --> Decay: 3s elapsed
Decay --> Active: Kill enemy
Decay --> Reset: comboMultiplier <= 1.0
Active --> Reset: Player damaged
Grace --> Reset: Player damaged
Reset --> [*]
Fichier : src/server/infrastructure/game/GameWorld.cpp:1754-1772
static constexpr float COMBO_GRACE_TIME = 3.0f; // Période sans pénalité
static constexpr float COMBO_DECAY_RATE = 0.5f; // -0.5x par seconde après
static constexpr float COMBO_INCREMENT = 0.1f; // +0.1x par kill
static constexpr float COMBO_MAX = 3.0f; // Maximum 3.0x
void GameWorld::updateComboTimers(float deltaTime) {
for (auto& [playerId, score] : _playerScores) {
score.comboTimer += deltaTime;
// Decay progressif APRÈS grace period
if (score.comboTimer > COMBO_GRACE_TIME &&
score.comboMultiplier > 1.0f) {
float decay = COMBO_DECAY_RATE * deltaTime;
score.comboMultiplier = std::max(1.0f, score.comboMultiplier - decay);
}
}
}
void GameWorld::awardKillScore(uint8_t playerId, EnemyType enemyType, WeaponType weapon) {
auto& score = _playerScores[playerId];
// Formula: basePoints × comboMultiplier
uint16_t basePoints = getEnemyPointValue(enemyType);
uint32_t points = static_cast<uint32_t>(basePoints * score.comboMultiplier);
score.score += points;
score.kills++;
// Augmente combo (cap à 3.0x)
score.comboMultiplier = std::min(COMBO_MAX, score.comboMultiplier + COMBO_INCREMENT);
score.comboTimer = 0.0f; // Reset timer
}
void GameWorld::onPlayerDamaged(uint8_t playerId) {
auto& score = _playerScores[playerId];
score.comboMultiplier = 1.0f; // Reset immédiat sur dégâts
}
Exemple de Flux¶
| Action | Temps | Combo | Timer |
|---|---|---|---|
| Kill Basic | 0.0s | 1.1x | 0.0s |
| Kill Tracker | 0.5s | 1.2x | 0.0s |
| Attente | 3.0s | 1.2x | 3.0s |
| Attente | 4.0s | 0.7x | 4.0s (decay) |
| Kill Fast | 4.2s | 0.8x | 0.0s |
| Player hit | 5.0s | 1.0x | reset |
Mouvement des Ennemis : Patterns Algorithmiques¶
Problème¶
Les ennemis doivent avoir des comportements variés et prévisibles pour le gameplay.
Solutions Originales¶
Fichier : src/server/infrastructure/game/GameWorld.hpp:259-323
enum class EnemyType : uint8_t {
Basic, // Ligne droite
Tracker, // Poursuite joueur
Zigzag, // Oscillation verticale
Fast, // Haute vitesse + sine
Bomber, // Lent + tirs
POWArmor // Moyenne + lâche power-up
};
void GameWorld::updateEnemyPosition(Enemy& enemy, float dt) {
switch (enemy.type) {
case EnemyType::Basic:
enemy.x += SPEED_X_BASIC * dt; // -120 px/s
break;
case EnemyType::Tracker: {
// Poursuite du joueur le plus proche
auto target = findNearestPlayer(enemy.x, enemy.y);
if (target) {
float dy = target->y - enemy.y;
enemy.y += std::copysign(TRACKING_SPEED, dy) * dt;
}
enemy.x += SPEED_X_TRACKER * dt; // -100 px/s
break;
}
case EnemyType::Zigzag:
enemy.x += SPEED_X_ZIGZAG * dt; // -140 px/s
enemy.zigzagTimer += dt;
if (enemy.zigzagTimer > ZIGZAG_INTERVAL) {
enemy.zigzagTimer = 0;
enemy.zigzagDirection *= -1; // Inverse direction Y
}
enemy.y += ZIGZAG_SPEED * enemy.zigzagDirection * dt;
break;
case EnemyType::Fast:
enemy.x += SPEED_X_FAST * dt; // -220 px/s
enemy.y += std::sin(enemy.x * 0.02f) * SINE_AMPLITUDE; // Onde sinusoïdale
break;
// ... autres types
}
}
Visualisation des Patterns¶
Basic: ────────────────────────────────►
Tracker: ────────┐
└──────────────────────►
(suit le joueur en Y)
Zigzag: ────╱────╲────╱────╲────╱────╲──►
Fast: ∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿►
(sine wave rapide)
Boss Phase Transitions¶
Problème¶
Le boss doit changer de comportement selon ses points de vie.
Solution Originale¶
Fichier : src/server/infrastructure/game/GameWorld.cpp
static constexpr float BOSS_PHASE2_THRESHOLD = 0.65f; // 65% HP
static constexpr float BOSS_PHASE3_THRESHOLD = 0.30f; // 30% HP
void GameWorld::updateBossPhase() {
if (!_boss) return;
float hpRatio = static_cast<float>(_boss->health) /
static_cast<float>(_boss->maxHealth);
if (hpRatio <= BOSS_PHASE3_THRESHOLD && _boss->phase != BossPhase::Phase3) {
_boss->phase = BossPhase::Phase3; // Enrage
_boss->attackSpeed *= 1.5f;
_boss->spawnMinions = true;
}
else if (hpRatio <= BOSS_PHASE2_THRESHOLD && _boss->phase != BossPhase::Phase2) {
_boss->phase = BossPhase::Phase2; // Attaques multiples
_boss->attackPattern = AttackPattern::Spread;
}
}
Diagramme de Phases¶
graph LR
subgraph "Phase 1 (100%-65%)"
P1["Attaques simples<br/>Mouvement lent"]
end
subgraph "Phase 2 (65%-30%)"
P2["Attaques spread<br/>Mouvement moyen"]
end
subgraph "Phase 3 (<30%)"
P3["Enrage<br/>Minions<br/>Mouvement rapide"]
end
P1 -->|"HP ≤ 65%"| P2
P2 -->|"HP ≤ 30%"| P3
Homing Missiles¶
Problème¶
Les missiles guidés doivent suivre leur cible sans être parfaits (sinon impossible à éviter).
Solution Originale¶
static constexpr float HOMING_TURN_RATE = 2.0f; // rad/s max
void GameWorld::updateHomingMissile(Missile& missile, float dt) {
auto target = findNearestEnemy(missile.x, missile.y);
if (!target) {
// Continue tout droit si pas de cible
return;
}
// Calcul angle vers cible
float dx = target->x - missile.x;
float dy = target->y - missile.y;
float targetAngle = std::atan2(dy, dx);
// Rotation limitée (2 rad/s max)
float angleDiff = targetAngle - missile.angle;
// Normaliser entre -PI et PI
while (angleDiff > M_PI) angleDiff -= 2 * M_PI;
while (angleDiff < -M_PI) angleDiff += 2 * M_PI;
// Appliquer rotation limitée
float maxTurn = HOMING_TURN_RATE * dt;
missile.angle += std::clamp(angleDiff, -maxTurn, maxTurn);
// Mouvement dans la direction
missile.x += std::cos(missile.angle) * missile.speed * dt;
missile.y += std::sin(missile.angle) * missile.speed * dt;
}
Récapitulatif des Algorithmes¶
| Algorithme | Type | Complexité | Usage |
|---|---|---|---|
| AABB Intersection | Standard | O(1) | Collision detection |
| Mersenne Twister | Standard | O(1) | Spawn positions |
| LZ4 | Standard | O(n) | Network compression |
| SHA-256 | Standard | O(n) | Password hashing |
| Combo Decay | Original | O(players) | Score system |
| Enemy Patterns | Original | O(1) each | AI movement |
| Boss Phases | Original | O(1) | Boss behavior |
| Homing Turn | Original | O(1) | Guided missiles |
Conclusion¶
Le projet R-Type combine : - Algorithmes standards optimaux : AABB, LZ4, SHA-256, Mersenne Twister - Algorithmes originaux : Combo avec grace period, patterns ennemis, phases boss, homing limité
Cette approche garantit performance (algorithmes prouvés) et gameplay unique (mécaniques originales).