Projet Dominion en C++

Projet de Développement Logiciel et d'Architecture Orientée Objet

Type : Projet Académique
Technologies : C++, Pointeurs Intelligents
Concepts Clés : Polymorphisme, Gestion de la Mémoire
Focus : Architecture Logicielle (POO)

Ce projet académique est une implémentation en C++ du célèbre jeu de deck-building Dominion. L'objectif était d'appliquer les concepts de programmation orientée objet (héritage, polymorphisme) et de C++ moderne (pointeurs intelligents) pour créer un moteur de jeu complet, jouable en console.

Fonctionnalités Clés

  • Moteur de Jeu Complet : Gestion des phases de jeu (Action, Achat, Ajustement), des decks, des mains et des défausses.
  • Multijoueur (2-4 joueurs) : Le jeu est conçu pour être joué par 2, 3 ou 4 joueurs humains.
  • Intelligence Artificielle (IA) : Possibilité de remplacer un joueur par une IA avec une stratégie de base.
  • Système de Sauvegarde et Chargement : Les joueurs peuvent sauvegarder une partie et la reprendre plus tard via un fichier CSV.
  • Sélection de Cartes Personnalisée : Choix des 10 cartes "Action" qui composeront la réserve du jeu.
  • Interface Console Stylisée : Utilisation de couleurs pour différencier les types de cartes et améliorer la lisibilité de l'interface en terminal.

Galerie d'Images

Architecture Orientée Objet et C++ Moderne

Le cœur du projet repose sur une architecture robuste pour modéliser les différentes composantes du jeu, en suivant les bonnes pratiques du C++.

  • Séparation Interface/Implémentation (.h/.cpp) : Chaque classe est définie avec un fichier d'en-tête (.h) pour l'interface et un fichier source (.cpp) pour l'implémentation, garantissant la modularité.
  • Héritage et Polymorphisme (`Carte`) : Une classe de base Carte a été créée, avec des classes dérivées (CarteTresor, CarteAction, etc.), permettant de manipuler toutes les cartes de manière polymorphique.
  • Gestion de la Mémoire (`std::shared_ptr`) : Pour éviter les fuites de mémoire et simplifier la gestion de la durée de vie des objets, j'ai utilisé des pointeurs intelligents (std::shared_ptr) partout dans le projet.
  • Classes Principales :
    • Joueur : Gère l'état d'un joueur (deck, main, défausse).
    • PlateauJeu : Agit comme le contrôleur principal du jeu, gérant le déroulement des tours et la réserve de cartes.

Extraits de Code

1. Polymorphisme et Affichage en Console (`Carte.cpp`)

Cette méthode virtuelle Afficher() permet à chaque type de carte de s'afficher différemment, notamment avec une couleur spécifique.

// Extrait de Carte.cpp
void Carte::Afficher() const {
    const std::string RESET = "\033[0m";
    const std::string GREEN = "\033[92m"; // Victoire
    const std::string YELLOW = "\033[93m"; // Trésor
    // ... autres couleurs

    std::string couleur;
    if (type == "victoire") {
        couleur = GREEN;
    } else if (type == "tresor") {
        couleur = YELLOW;
    } // ...

    std::cout << couleur << " - Nom : " << getNom() 
              << " | Prix : " << getPrix() 
              << " | Type : " << getType() << RESET;
}

2. Logique d'une Carte Action (`CarteAction.cpp`)

Chaque carte "Action" possède sa propre méthode d'exécution. Voici l'exemple de la "Sorcière", qui affecte le joueur actif et ses adversaires.

// Extrait de CarteAction.cpp
void CarteAction::executerActionSorciere(std::shared_ptr<Joueur> j){
     std::cout << "\n Effet Sorciere : Piochez 2 cartes...";
     for (int i=0; i<2; i++){
        j->ajouterCarteMain();
        j->retirerCarteDeck();
     }
}

void CarteAction::executerActionSorciere2(std::shared_ptr<Joueur> adversaire, PlateauJeu* pj){
    // Logique pour ajouter une carte Malédiction à l'adversaire
    // ...
}

Défis Rencontrés et Solutions

  • Problème : Gestion du polymorphisme et du casting. Le casting entre pointeurs de base et dérivés était risqué.
    Solution : L'adoption de std::shared_ptr et std::dynamic_pointer_cast a permis un casting sécurisé et une gestion de la mémoire automatique.
  • Problème : Mise à jour de l'état des joueurs. Sans pointeurs, les modifications sur un joueur n'étaient pas répercutées partout.
    Solution : L'utilisation de std::shared_ptr<Joueur> a garanti que toutes les parties du code manipulaient une seule et même instance de chaque joueur.