La programmation Objet appliquée à .Net par l’exemple Partie 2 sur 3, en C#

Navigation globale

Trop de blabla: du code, du code, du code !

La classe Carte:

    public abstract class Carte
    {
        /// <summary>
        /// Constructeur protégé, seules les classes filles pourront l'appeler
        /// </summary>
        /// <param name="CheminDeLimage"></param>
        /// <param name="LesPoints"></param>
        protected Carte(string CheminDeLimage, int LesPoints)
        {
            CheminImage = CheminDeLimage;
            Points = LesPoints;
        }

        /// <summary>
        /// Chemin vers l'image de la face
        /// </summary>
        /// <remarks>C'est la couche Vue qui s'occupera de l'afficher</remarks>
        public string CheminImage {get; private set; }//private set, car seule l'instance peut affecter/modifier son image

        /// <summary>
        /// Nombre de points marqués avec cette carte
        /// </summary>
        public int Points { get; protected set; }//private set, car seule l'instance ou une instance dérivée peut affecter/modifier les points


        protected Joueur leJoueur;

        /// <summary>
        /// Prend la première carte de la pioche et la met dans la main d'un joueur
        /// </summary>
        /// <param name="LeJoueur"></param>
        public void Piocher(Joueur LeJoueur)
        {
            leJoueur = LeJoueur;
            //ToDo implémenter la pioche
        }

        /// <summary>
        /// Joue la carte
        /// </summary>
        protected void Jouer()
        {
            //ToDo implémenter le jeu
        }

        /// <summary>
        /// Jette une carte de la main du joueur vers la défausse
        /// </summary>
        public void Defausse()
        {
            //ToDo implémenter la défausse
        }
    }

Comment ça

//ToDo implémenter la défausse

? C’est simple, nous n’avons pas encore défini ce qu’est un Joueur, la défausse et la pioche.

D’ailleurs, juste pour que ça compile

class Joueur
    {
    }

Et les classes dérivées:

    public class Kilometre:Carte
    {
        public Kilometre(string CheminDeLimage, int LesKilometres):base(CheminDeLimage, LesKilometres)//on appelle le constructeur de Carte
        {
            Distance = LesKilometres;
        }

        public int Distance { get; private set; }

        public new void Jouer()
        {
            base.Jouer();//appel du code de la classe mère
        }

        /// <summary>
        /// Réécriture de ToString pour afficher le nombre de kilomètres
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return Distance.ToString() + " km";
        }
    }

    public class Embuche:Carte
    {
        public Embuche(string CheminImage, TypeEmbuche LeType):base(CheminImage,0)
        {
            Type = LeType;
        }

        public TypeEmbuche Type { get; private set; }

        /// <summary>
        /// Une embûche est lancée à un autre joueur
        /// </summary>
        /// <param name="Cible"></param>
        public void Jouer(Joueur Cible)
        {
            //ToDo implémenter le jeu de l'embuche
        }

        /// <summary>
        /// Réécriture de ToString pour afficher le nom de l'embûche
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return Type.ToString();
        }
    }

    /// <summary>
    /// Enumération permettant de distinguer le type de chaque carte Embuche
    /// </summary>
    public enum TypeEmbuche
    {
        FeuRouge,
        LimitationVitesse,
        Crevaison,
        PanneEssence,
        Accident
    }

    public class Parade:Carte
    {
        public Parade(string CheminImage, TypeEmbuche LeType): base(CheminImage, 0)
        {
            EmbucheParee = LeType;
        }

        public TypeEmbuche EmbucheParee { get; private set; }//plutôt que de définir un lien entre un type de parade et le type d'embuche, il est aussi simple définir l'embuche parée

        /// <summary>
        /// Réécriture de ToString pour afficher le nom de la parade
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            switch(EmbucheParee)
            {
                case TypeEmbuche.Accident:
                    return "Réparation";

                case TypeEmbuche.Crevaison:
                    return "Roue de secours";

                case TypeEmbuche.FeuRouge:
                    return "Feu vert";

                case TypeEmbuche.LimitationVitesse:
                    return "Fin de limitation";

                case TypeEmbuche.PanneEssence:
                    return "Station essence";

                default: //on est obligé de mettre un default ou après le switch return quelque chose car ToString retourne un résultat, et même si on a testé toutes les options de TypeEmbuche, le compilateur considère qu'un cas non prévu pourrait se présenter et qu'il n'y aurait alors pas de résultat
                    return "Erreur";
            }
        }
    }

    public class Botte : Carte
    {
        public Botte(string CheminImage, TypeEmbuche LeType)
            : base(CheminImage, 100)
        {
            EmbucheImmunisee = LeType;
        }

        public TypeEmbuche EmbucheImmunisee { get; private set; }

        /// <summary>
        /// Cette méthode est quand la carte se joue elle-même
        /// </summary>
        public void CoupFourre()
        {
            this.Points += 300;
            Piocher(this.leJoueur);
            //ToDo implémenter le fait que ce soit au tour de ce joueur.
        }

        /// <summary>
        /// Réécriture de ToString pour afficher le nom de la botte
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            switch (EmbucheImmunisee)
            {
                case TypeEmbuche.Accident:
                    return "As du volant";

                case TypeEmbuche.Crevaison:
                    return "Roue increvable";

                case TypeEmbuche.FeuRouge:
                case TypeEmbuche.LimitationVitesse:
                    return "Camion de pompier";

                case TypeEmbuche.PanneEssence:
                    return "Citerne d'essence";
            }

            //on est obligé de mettre un default ou après le switch return quelque chose car ToString retourne un résultat, et même si on a testé toutes les options de TypeEmbuche, le compilateur considère qu'un cas non prévu pourrait se présenter et qu'il n'y aurait alors pas de résultat
            return "Erreur";
        }
    }

OK, mais il nous reste plein de code à définir, pour cela il nous faut savoir ce que sont le Joueur et le Moteur du jeu.

La classe Joueur:

Un joueur a comme propriétés :

  • Un Nom ou pseudo, en lecture seule
  • Une « Main » => cartes jouables en sa possession, la main est une collection simple, donc une List
  • Un « Jeu » => cartes jouées, le jeu est une collection qui doit permettre d’alerter le joueur en cas de changement (l’embûche en particulier). On utilisera une ObservableCollection.
  • Le nombre de points obtenus, en lecture seule
  • La distance parcourue, en lecture seule
  • Le nombre de points, en lecture seule.

Le joueur peut

  • Piocher une carte
  • Défausser une carte
  • Jouer une carte

Pour cette implémentation, je vais intentionnellement dupliquer le code de ces 3 méthodes pour montrer que les 2 options étaient possibles.

Le Joueur émet un évènement quand

  • son kilométrage atteint la distance voulue
  • son kilométrage dépasse la distance voulue
  • quand il reçoit une embuche
  • quand il joue un coup fourré
  • quand son tour est fini

Le constructeur permet d’affecter le nom.

Note : la gestion du coup fourré nécessite de modifier le nombre de points de la botte, or celui-ci est en lecture seule, on peut soit rendre public tous les points ou ceux des bottes, soit ajouter une méthode signalant un coup fourré. Je choisi la dernière option.

Le Moteur du Jeu:

La pioche et la défausse étant les mêmes pour tout le monde, on peut être tenté par des variables globales. Cette pratique est considérée comme mauvaise en .Net.
Pour suivre cette recommandation, nous avons le choix entre passer en paramètre des méthodes concernées la pioche et la défausse, ou les inclure dans une classe particulière: la classe partagée.
En C# le mot clé est

static

, je le traduis par l’expression classe partagée car statique n'est pas très parlant et qu'en VB.Net, le mot clé utilisé (shared) veut dire partagé.

Une classe partagée est une classe qui ne peut pas être instanciée, mais peut être utilisée (contrairement à la classe abstraite).
Un exemple simple, la classe Math on ne peut pas instancier cette classe, et pourtant on peut calculer un cosinus, une valeur absolue, etc…

cos = Math.Cos(10);

On constate que Cos() est une méthode, mais contrairement à celles que l’on a déjà vues, elle est « rattachée » à la classe et pas à une instance. On dit qu’il s’agit d’une méthode de classe.
De même, Math nous donne accès à π

pi = Math.PI;

PI est une propriété de classe

Une classe partagée ne dispose que de méthode(s), propriété(s) et événement(s) de classe.
Une classe « normale », peut disposer de méthode(s), propriété(s) et événement(s) de classe.
La classe Moteur est une classe partagée

Il a comme propriétés

  • Une collection de toutes les cartes
  • Une collection Pioche, qui doit permettre de prévenir le moteur quand elle est vide, on utilisera une ObservableCollection.
  • Une collection Defausse
  • Une collection de tous les joueurs
  • Le joueur en cours.
  • La distance à parcourir : 1000 par défaut, 700 à partir de 4 joueurs

Il doit

  • avant la première partie
    • Initialiser les cartes
    • Initialiser les joueurs
    • Définir le kilométrage à parcourir
  • avant chaque partie
    • Mélanger les cartes
    • Distribuer les cartes
    • Faire jouer le joueur à son tour.
  • pendant les parties
    • gérer le tour des joueurs, y compris en cas de coup fourré
    • gérer la gagne
    • gérer la disqualification d’un joueur en cas de dépassement de la distance
    • gérer la fin de la pioche

Pour initialiser les cartes, je pourrais tout à fait écrire une méthode dans le moteur dont ce serait le rôle.
Cependant, je vais vous montrer une méthode de classe avec une classe instanciable. Chaque classe fille aura sa propre méthode de génération.
Le moteur fera juste appel à cette méthode pour les 4 classe filles.

Voici l’implémentation de Joueur quand il joue les cartes:

    public class Joueur
    {
        public delegate void EvenementKilometres(Joueur LeJoueur, int Kilometres);//signature de l'évènement
        public event EvenementKilometres JaiGagne;//évènement signalant que la distance est atteinte
        public event EvenementKilometres DistanceDepassee;//évènement signalant que la distance est partagée

        public delegate void EvenementEmbuche(Joueur LeJoueur);//signature de l'évènement
        public event EvenementEmbuche EmbucheRecue;//évènement signalant une embuche reçue
        public event EvenementEmbuche CoupFourre;//évènement signalant un coup fourré
        public event EvenementEmbuche FiniMonTour;//évènement signalant que le joueur à fini son tour.

        public Joueur(string LeNom)
        {
            Nom = LeNom;
            DistanceParcourue = 0;
            Main = new List<Carte>();
            
            Jeu = new ObservableCollection<Carte>();//initialisation de la collection
            Jeu.CollectionChanged += Jeu_CollectionChanged;//Abonnement à l'évènement signalant un changement
        }

        public string Nom { get; private set; }

        public List<Carte> Main { get; set; }

        public ObservableCollection<Carte> Jeu { get; set; }

        /// <summary>
        /// Propriété en lecture seule, dont la valeur est le résultat d'un calcul
        /// </summary>
        public int Points 
        {
            get
            {
                int bonus = 0;
                //si un (ou plusieurs) joueur n'a pas pu poser de kilomètre, les autres marquent 500 points de bonus
                foreach(Joueur leJoueur in Moteur.Joueurs)
                {
                    if (leJoueur == this)
                        continue;//on passe à l'itération suivante

                    if (leJoueur.DistanceParcourue == 0)
                        bonus += 500;//on ajoute 500 au bonus pour se joueur sans distance
                }

                int lesPoints = 0;
                foreach(Carte laCarte in Jeu)
                {
                    lesPoints += laCarte.Points;//grâce au polymorphisme, on peut additionner les points, peu importe le type de carte.
                }

                return lesPoints + bonus;
            }
        }

        public int DistanceParcourue { get; private set; }

        /// <summary>
        /// Traitements à faire après qu'une carte ait été ajoutée au jeu
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Jeu_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Add)
                return;

            if (Jeu.Last() is Embuche)//si la dernière carte reçue est une embuche, car Jeu contient des Cartes
            {
                if (this.EmbucheRecue != null)//on vérifie que "quelqu'un" soit abonné à l'évènement
                    this.EmbucheRecue(this);//on émet l'évènement

                Embuche lEmbuche = (Embuche)Jeu.Last();//pour travailler sur une Embuche, à partir d'une Carte, il faut "caster" (forcer la conversion) la Carte en Embuche

                //on regarde si le joueur dispose de la botte pour faire un coup fourré
                foreach (Carte laCarte in Main)
                {
                    if (laCarte is Botte && ((Botte)laCarte).EmbucheImmunisee == lEmbuche.Type)
                    {
                        Botte laBotte = (Botte)laCarte;
                        //on signale que l'on joue le coup fourré
                        if (this.CoupFourre != null)
                            this.CoupFourre(this);

                        laBotte.CoupFourreParLeJoueur();//appelle la méthode qui va mettre le bonus de points
                        Jouer(laBotte);//On joue la botte
                        Jeu.Remove(lEmbuche);//on enlève l'embuche du jeu, ce qui va regénérer l'évènement Jeu_CollectionChanged, d'ou le premier test
                        Moteur.Defausse.Add(lEmbuche);//on jette l'embuche à la défausse
                        Pioche();//on pioche pour remplacer la botte

                        break;//on sort du foreach, ça n'est pas la peine de continuer à cherche ce que l'on a déjà trouvé
                    }
                }
            }
            else if (Jeu.Last() is Kilometre)//si la dernière carte est un kilomètre, on calcule la distance totale.
            {
                Kilometre km = (Kilometre)Jeu.Last();
                DistanceParcourue = km + DistanceParcourue;       
                
                if (DistanceParcourue == Moteur.DistanceAParcourir)
                {
                    if (this.JaiGagne != null)
                        this.JaiGagne(this,DistanceParcourue);//on émet l'évènement avec le paramètre correspondant à la signature.


                }
                else if (DistanceParcourue > Moteur.DistanceAParcourir)
                {
                    if (this.DistanceDepassee != null)
                        this.DistanceDepassee(this,DistanceParcourue);
                }
            }




        }

        /// <summary>
        /// Méthode permettant de jouer une Carte dans son jeu
        /// </summary>
        /// <param name="LaCarte"></param>
        /// <remarks>Si cette méthode était publique, on se jouerait une embuche à soi-même</remarks>
        private void Joue(Carte LaCarte)
        {
            Main.Remove(LaCarte);//On enlève la carte de la main
            Jeu.Add(LaCarte);//on ajoute la carte au jeu
            AnnonceFinTour();
        }

        /// <summary>
        /// Méthode permettant de jouer un kilomètre
        /// </summary>
        /// <param name="Km"></param>
        /// <remarks>Je crée 3 surcharges pour être sûr qu'une embuche ne sera pas transmise en paramètre</remarks>
        public void Jouer(Kilometre Km)
        {
            Joue((Carte)Km);
        }

        /// <summary>
        /// Surcharge permettant de jouer une parade
        /// </summary>
        /// <param name="LaParade"></param>
        public void Jouer(Parade LaParade)
        {
            Joue((Carte)LaParade);
        }

        /// <summary>
        /// Surcharge permettant de jouer une botte
        /// </summary>
        /// <param name="LaParade"></param>
        public void Jouer(Botte LaBotte)
        {
            Joue((Carte)LaBotte);
        }

        /// <summary>
        /// Surcharge permettant de mettre une embuche à un adversaire
        /// </summary>
        /// <param name="Lembuche"></param>
        /// <param name="Cible"></param>
        public void Jouer(Embuche Lembuche, Joueur Cible)
        {
            if (Cible == this)//si le joueur cible est le joueur en cours, on génère une erreur
                throw new Exception("Le joueur ne peut pas se mettre une embuche à lui-même");
            else
            {
                Main.Remove(Lembuche);//On enlève la carte de la main
                Cible.Jeu.Add(Lembuche);//on ajoute la carte au jeu du joueur cible
            }
            AnnonceFinTour();
        }

        /// <summary>
        /// Pioche une carte
        /// </summary>
        public void Pioche()
        {
            Main.Add(Moteur.Pioche[0]);//pioche la première carte
            Moteur.Pioche.RemoveAt(0);
        }

        /// <summary>
        /// Défausse une carte
        /// </summary>
        /// <param name="LaCarte">Carte à défausser</param>
        public void Defausse(Carte LaCarte)
        {
            Moteur.Defausse.Add(LaCarte);
            Main.Remove(LaCarte);
            AnnonceFinTour();
        }

        /// <summary>
        /// Annonce que le tour est terminé
        /// </summary>
        private void AnnonceFinTour()
        {
            if (this.FiniMonTour != null)
                this.FiniMonTour(this);
        }
    }

Et celle de Moteur:

    public static class Moteur
    {
        public delegate void EvenementProchainJoueur(Joueur LeJoueur);//signature de l'évènement
        public static event EvenementProchainJoueur ProchainJoueur;//évènement annonçant le joueur suivant
        public static event EvenementProchainJoueur PartieGagnee;//évènement annonçant qui a gagné

        public delegate void EvenementMoteurJeu();//signature de l'évènement
        public static event EvenementMoteurJeu NouvellePioche;//évènement annonçant la nouvelle pioche

        public static int DistanceAParcourir { get; set; }

        public static List<Joueur> Joueurs { get; set; }

        public static List<Carte> Defausse { get; set; }

        public static ObservableCollection<Carte> Pioche { get; set; }

        public static Joueur JoueurEnCours { get { return Joueurs[indexJoueur]; } }

        private static int indexJoueur;

        /// <summary>
        /// Initialise les éléments de jeu avant la première partie
        /// </summary>
        /// <param name="NomsJoueurs"></param>
        public static void InitPremierePartie(List<string> NomsJoueurs)
        {
            Defausse = new List<Carte>();
            Pioche = new ObservableCollection<Carte>();
            Pioche.CollectionChanged += Pioche_CollectionChanged;
            Joueurs = new List<Joueur>();

            //Crée les joueurs et s'abonne aux évènements
            foreach (string joueur in NomsJoueurs)
            {
                Joueur j = new Joueur(joueur);
                j.CoupFourre += j_CoupFourre;
                j.DistanceDepassee += j_DistanceDepassee;
                j.JaiGagne += j_JaiGagne;
                j.FiniMonTour += j_FiniMonTour;

                Joueurs.Add(j);
            }


            DistanceAParcourir = Joueurs.Count > 3 ? 700 : 1000;//La distance à parcourir est 700 à partir de 4 joueurs, sinon 1000

            NouvellePartie();
        }

        /// <summary>
        /// Quand un joueur à finit, on passe au suivant
        /// </summary>
        /// <param name="LeJoueur"></param>
        static void j_FiniMonTour(Joueur LeJoueur)
        {
            JoueurSuivant();
        }

        /// <summary>
        /// Signale la fin de la partie
        /// </summary>
        /// <param name="LeJoueur"></param>
        /// <param name="Kilometres"></param>
        private static void j_JaiGagne(Joueur LeJoueur, int Kilometres)
        {
            if (Moteur.PartieGagnee != null)
                Moteur.PartieGagnee(LeJoueur);
        }

        /// <summary>
        /// Démarre une nouvelle partie
        /// </summary>
        public static void NouvellePartie()
        {
            //génération des cartes
            List<Carte> toutesLesCartes = Kilometre.Generer();
            toutesLesCartes.AddRange(Embuche.Generer());
            toutesLesCartes.AddRange(Parade.Generer());
            toutesLesCartes.AddRange(Botte.Generer());

            //mélange des cartes
            Melanger(toutesLesCartes);

            //distribution des cartes 6 par joueur, une par une chacun son tour
            for (int i = 0; i < 6; i++)
            {
                for (int j = 0; j < Moteur.Joueurs.Count; j++)
                {
                    Moteur.Joueurs[j].Pioche();
                }
            }

            indexJoueur = 0;
            AnnonceProchainJoueur();
        }

        private static List<int> indexJoueursDisqualifies = new List<int>();
        /// <summary>
        /// Un joueur a dépassé la distance=> il est éliminé
        /// </summary>
        /// <param name="LeJoueur"></param>
        /// <param name="Kilometres"></param>
        static void j_DistanceDepassee(Joueur LeJoueur, int Kilometres)
        {
            indexJoueursDisqualifies.Add(Joueurs.IndexOf(LeJoueur));//on ajoute ce joueur dans la liste des joueurs disqualifiés
        }

        /// <summary>
        /// Change le joueur après un coup fourré
        /// </summary>
        /// <param name="LeJoueur"></param>
        private static void j_CoupFourre(Joueur LeJoueur)
        {
            indexJoueur = Joueurs.IndexOf(LeJoueur);
            AnnonceProchainJoueur();
        }

        /// <summary>
        /// Annonce le joueur suivant
        /// </summary>
        private static void AnnonceProchainJoueur()
        {
            if (ProchainJoueur != null)
                ProchainJoueur(JoueurEnCours);
        }

        /// <summary>
        /// Surveille la pioche, et la refait à partir de la défausse quand elle est vide
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void Pioche_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
                return;

            if (Pioche.Count == 0)
            {
                Melanger(Defausse);
                if (Moteur.NouvellePioche != null)
                    Moteur.NouvellePioche();
            }
        }

        /// <summary>
        /// Mélange les cartes et (re)crée la pioche
        /// </summary>
        /// <param name="Cartes"></param>
        private static void Melanger(List<Carte> Cartes)
        {
            Random rnd = new Random();
            do
            {
                int index = rnd.Next(Cartes.Count);//génère un entier aléatoire compris entre 0 et Cartes.Count -1
                Pioche.Add(Cartes[index]);//ajoute la carte à cet index dans la pioche
                Cartes.RemoveAt(index);//enlève la carte de la collection d'origine
            } while (Cartes.Count > 0);
        }

        /// <summary>
        /// Passe au joueur suivant
        /// </summary>
        private static void JoueurSuivant()
        {
            if (indexJoueur == Joueurs.Count - 1)
                indexJoueur = 0;
            else
                indexJoueur++;

            if (indexJoueursDisqualifies.Contains(indexJoueur))//si le joueur est disqualifié on passe au suivant de manière récursive
                JoueurSuivant();

            else AnnonceProchainJoueur();
        }
    }

On constate que les classes interagissent constamment. Cela implique d’avoir bien réfléchi au fonctionnement du logiciel avant de commencer à coder (d’où tout ce blabla avant de vous montrer un peu de code) et de coder un peu tout en même temps. Cette gymnastique fait partie de la difficulté à bien écrire une application tout objet.

D’ailleurs ça ne compile pas.
Evidement je ne vous ai pas fourni les modifications faites aux classes filles de Carte.
Voici Embuche, Parade et Botte

    public class Embuche:Carte
    {
        /// Code déjà écrit non retranscrit

        /// <summary>
        /// Méthode de classe qui génère la liste d'Embuches
        /// </summary>
        /// <returns></returns>
        public static List<Carte> Generer()
        {
            List<Carte> resultat = new List<Carte>();

            for (int i = 0; i < 5; i++)
            {
                resultat.Add(new Embuche("Image du feu rouge", TypeEmbuche.FeuRouge));
            }

            for (int i = 0; i < 4; i++)
            {
                resultat.Add(new Embuche("Image de la limitation de vitesse", TypeEmbuche.LimitationVitesse));
            }

            for (int i = 0; i < 3; i++)
            {
                resultat.Add(new Embuche("Image de la panne d'essence", TypeEmbuche.PanneEssence));
            }

            for (int i = 0; i < 3; i++)
            {
                resultat.Add(new Embuche("Image de la crevaison", TypeEmbuche.Crevaison));
            }

            for (int i = 0; i < 3; i++)
            {
                resultat.Add(new Embuche("Image de l'accident", TypeEmbuche.Accident));
            }

            return resultat;
        }

    }

    public class Parade:Carte
    {
        /// Code déjà écrit non retranscrit

        /// <summary>
        /// Méthode de classe qui génère la liste de Parades
        /// </summary>
        /// <returns></returns>
        public static List<Carte> Generer()
        {
            List<Carte> resultat = new List<Carte>();

            for (int i = 0; i < 14; i++)
            {
                resultat.Add(new Parade("Image du feu vert", TypeEmbuche.FeuRouge));
            }

            for (int i = 0; i < 6; i++)
            {
                resultat.Add(new Parade("Image de la fin de limitation", TypeEmbuche.LimitationVitesse));
            }

            for (int i = 0; i < 6; i++)
            {
                resultat.Add(new Parade("Image de la station essence", TypeEmbuche.PanneEssence));
            }

            for (int i = 0; i < 6; i++)
            {
                resultat.Add(new Parade("Image de la roue de secours", TypeEmbuche.Crevaison));
            }

            for (int i = 0; i < 6; i++)
            {
                resultat.Add(new Parade("Image de la réparation", TypeEmbuche.Accident));
            }

            return resultat;
        }
    }

    public class Botte : Carte
    {
        /// Code déjà écrit non retranscrit

        /// <summary>
        /// Cette méthode est appelée par le joueur en cas de coup fourré
        /// </summary>
        public void CoupFourreParLeJoueur()
        {
            this.Points += 300;
        }

        /// <summary>
        /// Méthode de classe qui génère la liste de Bottes
        /// </summary>
        /// <returns></returns>
        public static List<Carte> Generer()
        {
            List<Carte> resultat = new List<Carte>();

            resultat.Add(new Botte("Image du camion de pompier", TypeEmbuche.FeuRouge));
            resultat.Add(new Botte("Image de la citerne d'essence", TypeEmbuche.PanneEssence));
            resultat.Add(new Botte("Image de la roue increvable", TypeEmbuche.Crevaison));
            resultat.Add(new Botte("Image de l'as du volant", TypeEmbuche.Accident));

            return resultat;
        }

    }

Et pourquoi pas Kilometre? C’est parce qu’avant, on va aborder un autre type de méthode

Les opérateurs:

Un opérateur est une méthode particulière permettant de faire une opération entre deux objets, en général de même type.
Dans la classe Joueur, pour calculer la distance parcourue, j’ajoute la Distance de la dernière carte posée.
Je pourrais décider qu’un entier plus un Kilometre est possible, et que ça ajoute la Distance de ce Kilometre. Il me faudrait alors définir l’opérateur +.

On va commencer par l’opérateur + entre deux Kilometre, et ensuite par l’opérateur permettant d’additionner un entier et un Kilometre.

    public class Kilometre:Carte
    {
        /// Code déjà écrit non retranscrit

        /// <summary>
        /// Méthode de classe qui génère la liste de Kilomètres
        /// </summary>
        /// <returns></returns>
        public static List<Carte> Generer()
        {
            List<Carte> resultat = new List<Carte>();

            for (int i = 0; i < 10; i++)
            {
                resultat.Add(new Kilometre("Image 25 km", 25));
            }

            for (int i = 0; i < 10; i++)
            {
                resultat.Add(new Kilometre("Image 50 km", 50));
            }

            for (int i = 0; i < 10; i++)
            {
                resultat.Add(new Kilometre("Image 75 km", 75));
            }

            for (int i = 0; i < 12; i++)
            {
                resultat.Add(new Kilometre("Image 100 km", 100));
            }

            for (int i = 0; i < 4; i++)
            {
                resultat.Add(new Kilometre("Image 200 km", 200));
            }


            return resultat;
        }

        /// <summary>
        /// Opérateur qui permet d'additionner deux Kilometre et de retourner un entier égal à la somme des distances.
        /// </summary>
        /// <param name="LUn"></param>
        /// <param name="LAutre"></param>
        /// <returns></returns>
        public static int operator +(Kilometre LUn, Kilometre LAutre)
        {
            return LUn.Distance + LAutre.Distance;
        }

        /// <summary>
        /// Opérateur qui permet d'additionner un Kilometre et un entier
        /// </summary>
        /// <param name="Km"></param>
        /// <param name="Entier"></param>
        /// <returns></returns>
        public static int operator +(Kilometre Km, int Entier)
        {
            return Km.Distance + Entier;
        }

    }

Enumération Flagable:

le joueur dispose d’un feu vert pour poser un kilomètre.

  • la limitation de vitesse est respectée.
  • le joueur a paré l’embuche et posé un feu vert avant de repartir.
  • un joueur ne peut pas subir 2 embûches en même temps, sauf si l’une des 2 est la limitation de vitesse.
  • le joueur ne tente pas de mettre plus de deux cartes « 200 km ».

On pourrait parfaitement traiter ces règles rien qu’en regardant les cartes dans le jeu du joueur.
Cependant c’est l’occasion de traiter une option, bien pratique, des énumérations.
On peut faire en sorte que les valeurs d’énumération se cumulent.
Pour cela on ne va pas servir de la valeur à proprement dite, mais de sa représentation binaire.
Exemple avec 1 et 2 :

  • Valeur1= 01
  • Valeur2 = 10

Si l’énumération vaut 01, alors c’est Valeur1 qui est sélectionné, si elle vaut 10, c’est Valeur2, et quand elle vaut 11 alors Valeur1 et Valeur2 sont cumulées.
Mais alors on ne peut pas définir Valeur3 = 3 ? Et bien non, il faut définir des valeurs dont la représentation binaire ne comporte qu’un seul 1 et ces valeurs doivent être toutes différentes.
Mais faut-il convertir toutes nos valeurs pour vérifier ces 2 conditions ? Non, il suffit de choisir la constante en puissance de 2 : 1, 2, 4, 8, 16, 32, 64, 256, 512, 1024, 2048, 4096 etc.

Cas particulier de la valeur 0, cette valeur existe, même si elle n’est pas définie, elle correspond à « Pas de Valeur » Si on ne définit pas 0, il peut arriver que cette valeur non prévue soit à l’origine de bugs. C’est pourquoi, il est fortement conseillé de la définir.
Dans notre cas, cette valeur sera utile donc la question ne se pose pas.

Choisir les valeurs adéquates ne suffit pas à rendre l’énumération cumulable, il faut ajouter l’attribut Flags à la déclaration de l’énumération.

    [Flags()]
    public enum TypeEmbuche
    {
        Aucune = 0,
        FeuRouge = 1,
        LimitationVitesse = 2,
        Crevaison = 4,
        PanneEssence = 8,
        Accident = 16
    }

Et comment s’en sert-on ?

On cumule deux valeurs avec l’opérateur ou (l’opérateur |) : FeuRouge ou LimitationVitesse vaut 01 ou 10 => 11, le cumul se fait bit à bit.
On enlève une valeur avec l’opérateur ou exclusif (l’opérateur ^) : 11 ouex FeuRouge vaut 11 ouex 01 => 10 LimitationVitesse.
Attention, si on essaye d’enlever une valeur qui n’est pas présente, en fait ça l’ajoute (effet de bord de l’ouex 01 ouex 10 => 11). Par contre, en ajoutant (avec un ou) une valeur déjà présente, cela ne change rien.

On peut savoir si l’énumération est égale à une valeur (cumulée ou non) par un test d’égalité classique, on peut aussi savoir si une valeur est présente dans l’accumulation avec la méthode HasFlag().

Ce point permet d’affiner la classe Botte, en effet, le camion de pompier immunise à la fois du feu rouge et de la limitation de vitesse.

        /// <summary>
        /// Réécriture de ToString pour afficher le nom de la botte
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            switch (EmbucheImmunisee)
            {
                case TypeEmbuche.Accident:
                    return "As du volant";

                case TypeEmbuche.Crevaison:
                    return "Roue increvable";

                case TypeEmbuche.FeuRouge | TypeEmbuche.LimitationVitesse:
                    return "Camion de pompier";

                case TypeEmbuche.PanneEssence:
                    return "Citerne d'essence";
            }

            //on est obligé de mettre un default ou après le swicth return quelque chose car ToString retourne un résultat, et même si on a testé toutes les options de TypeEmbuche, le compilateur considère qu'un cas non prévu pourrait se présenter et qu'il n'y aurait alors pas de résultat
            return "Erreur";
        }


        /// <summary>
        /// Méthode de classe qui génère la liste de Bottes
        /// </summary>
        /// <returns></returns>
        public static List<Carte> Generer()
        {
            List<Carte> resultat = new List<Carte>();

            resultat.Add(new Botte("Image du camion de pompier", TypeEmbuche.FeuRouge | TypeEmbuche.LimitationVitesse));
            resultat.Add(new Botte("Image de la citerne d'essence", TypeEmbuche.PanneEssence));
            resultat.Add(new Botte("Image de la roue increvable", TypeEmbuche.Crevaison));
            resultat.Add(new Botte("Image de l'as du volant", TypeEmbuche.Accident));

            return resultat;
        }

Ajoutons deux propriétés à Joueur du type TypeEmbuche, EmbucheEnCours qui va cumuler les embûches et Immunite qui va cumuler les immunités.

Au moment de joueur une carte, il conviendra de vérifier chaque règle.
A noter, pour commencer et après avoir paré un accident, une panne d’essence ou une crevaison, il faut un feu vert (sauf si on possède le camion de pompier). C’est exactement comme si on subissait un feu rouge. Donc on cumule l’embûche et le feu rouge.

Il faut donc modifier les méthodes Joue(Carte), Jouer (Embuche, Cible) et Jeu_CollectionChanged

        private void Jeu_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Add)
                return;

            if (Jeu.Last() is Embuche)//si la dernière carte reçue est une embuche, car Jeu contient des Cartes
            {
                if (this.EmbucheRecue != null)//on vérifie que "quelqu'un" soit abonné à l'évènement
                    this.EmbucheRecue(this);//on émet l'évènement

                Embuche lEmbuche = (Embuche)Jeu.Last();//pour travailler sur une Embuche, à partir d'une Carte, il faut "caster" (forcer la conversion) la Carte en Embuche

                //on regarde si le joueur dispose de la botte pour faire un coup fourré
                foreach (Carte laCarte in Main)
                {
                    if (laCarte is Botte && ((Botte)laCarte).EmbucheImmunisee == lEmbuche.Type)
                    {
                        Botte laBotte = (Botte)laCarte;
                        //on signale que l'on joue le coup fourré
                        if (this.CoupFourre != null)
                            this.CoupFourre(this);

                        laBotte.CoupFourreParLeJoueur();//appelle la méthode qui va mettre le bonus de points
                        Jouer(laBotte);//On joue la botte
                        Jeu.Remove(lEmbuche);//on enlève l'embuche du jeu, ce qui va regénérer l'évènement Jeu_CollectionChanged, d'ou le premier test
                        Moteur.Defausse.Add(lEmbuche);//on jette l'embuche à la défausse
                        Pioche();//on pioche pour remplacer la botte

                        return;//on sort de la méthode, ça n'est pas la peine de continuer à cherche ce que l'on a déjà trouvé
                    }
                }

                EmbucheEnCours |= lEmbuche.Type;//on ajoute l'embûche
                if (lEmbuche.Type != TypeEmbuche.LimitationVitesse && !Immunite.HasFlag(TypeEmbuche.FeuRouge))
                    EmbucheEnCours |= TypeEmbuche.FeuRouge;//on ajoute un feu rouge pour que le joueur soit obligé de mettre un feu vert

            }
            else if (Jeu.Last() is Kilometre)//si la dernière carte est un kilomètre, on calcule la distance totale.
            {
                Kilometre km = (Kilometre)Jeu.Last();
                DistanceParcourue = km + DistanceParcourue;       
                
                if (DistanceParcourue == Moteur.DistanceAParcourir)
                {
                    if (this.JaiGagne != null)
                        this.JaiGagne(this,DistanceParcourue);//on émet l'évènement avec le paramètre correspondant à la signature.

                }
                else if (DistanceParcourue > Moteur.DistanceAParcourir)
                {
                    if (this.DistanceDepassee != null)
                        this.DistanceDepassee(this,DistanceParcourue);
                }
            }
        }

        private void Joue(Carte LaCarte)
        {
            if (LaCarte is Kilometre)
            {
                switch (EmbucheEnCours)
                {
                    case TypeEmbuche.Aucune://on peut rouler
                        break;

                    case TypeEmbuche.LimitationVitesse://il n'y a aucune autre embûche
                        if (((Kilometre)LaCarte).Distance > 50)
                        {
                            AnnonceCarteInterdite(LaCarte);
                            return;//on ne peut pas dépasser 50
                        }
                        break;

                    default://s'il y a n'importe quelle autre embuche, on ne peut pas rouler
                        AnnonceCarteInterdite(LaCarte);
                        return;
                }

                if (((Kilometre)LaCarte).Distance == 200 && Jeu.OfType<Kilometre>().Count(k => k.Distance == 200) == 2)
                {
                    //On ne peut pas joueur plus de 2 "200 km"
                    //à noter pour cette recherche, au lieu d'imbriquer des boucles (comme plus haut), j'ai utilisé une requête Linq, qui filtre sur le type puis compte le nombre de "200 km"
                    AnnonceCarteInterdite(LaCarte);
                    return;
                }
            }
            else if(LaCarte is Parade)
            {
                Parade maParade = (Parade)LaCarte;
                if (!EmbucheEnCours.HasFlag(maParade.EmbucheParee))
                {
                    AnnonceCarteInterdite(LaCarte);
                    return;//on ne peut pas parer une embuche que l'on ne subit pas
                }

                if (maParade.EmbucheParee == TypeEmbuche.FeuRouge && !(EmbucheEnCours == TypeEmbuche.FeuRouge || EmbucheEnCours == (TypeEmbuche.FeuRouge | TypeEmbuche.LimitationVitesse)))
                {
                    AnnonceCarteInterdite(LaCarte);
                    return;//on ne peut poser un feu vert qui si on est en "état" de feu rouge, ou feu rouge et limitation, toutes les autres embuches doivent avoir été parées avant
                }

                EmbucheEnCours ^= maParade.EmbucheParee;
            }
            else
            {
                //c'est forcément une botte, elle peut se poser n'importe quand même sans parer une embûche
                Botte laBotte = ((Botte)LaCarte);

                Immunite |= laBotte.EmbucheImmunisee;//immunise

                if (laBotte.EmbucheImmunisee.HasFlag(TypeEmbuche.FeuRouge))
                {
                    if (EmbucheEnCours.HasFlag(TypeEmbuche.FeuRouge))
                        EmbucheEnCours ^= TypeEmbuche.FeuRouge;//pare si besoin
                    
                    if (EmbucheEnCours.HasFlag(TypeEmbuche.LimitationVitesse))
                        EmbucheEnCours ^= TypeEmbuche.LimitationVitesse;//pare si besoin
                }
                else if (EmbucheEnCours.HasFlag(laBotte.EmbucheImmunisee))
                    EmbucheEnCours ^= laBotte.EmbucheImmunisee;//pare si besoin
            }

            Main.Remove(LaCarte);//On enlève la carte de la main
            Jeu.Add(LaCarte);//on ajoute la carte au jeu
            AnnonceFinTour();
        }

        public void Jouer(Embuche Lembuche, Joueur Cible)
        {
            if (Cible == this)//si le joueur cible est le joueur en cours, on génère une erreur
                throw new Exception("Le joueur ne peut pas se mettre une embuche à lui-même");

            bool condition1 = Cible.Immunite.HasFlag(Lembuche.Type);//on ne peut pas poser ni une embûche  contre laquelle la cible est immunisée,
            bool condition2;
            if (Lembuche.Type == TypeEmbuche.LimitationVitesse)
                condition2 = Cible.EmbucheEnCours.HasFlag(TypeEmbuche.LimitationVitesse);//ni une limitation si elle y est déjà
            else
                condition2 = Cible.EmbucheEnCours != TypeEmbuche.Aucune && Cible.EmbucheEnCours != TypeEmbuche.LimitationVitesse;//ni les autres embûches s'il y a en déjà une (sauf la limitation)

            if (condition1 || condition2)
            {
                AnnonceCarteInterdite(Lembuche);
            }
            else
            {                
                Main.Remove(Lembuche);//On enlève la carte de la main
                Cible.Jeu.Add(Lembuche);//on ajoute la carte au jeu du joueur cible
                
                AnnonceFinTour();
            }

        }

On doit aussi ajouter un évènement quand la carte n’est pas autorisée et une méthode qui génère cet évènement, cette méthode va tester si un objet est abonné avant de générer l’évènement.

    public delegate void EvenementCarte(Joueur LeJoueur,Carte LaCarte); //signature de l'évènement, je déclare ce délégué en dehors de la classe, ainsi, il est visible dans tout l'espace de nom

    public class Joueur
    {
        public event EvenementCarte CarteInterdite; //évènement signalant que le joueur tente de poser une carte à l'encontre des règles.

        //Code déjà écrit non retranscrit

        /// <summary>
        /// Génère l'évènement
        /// </summary>
        private void AnnonceCarteInterdite(Carte LaCarte)
        {
            if (this.CarteInterdite != null)
                this.CarteInterdite(this, LaCarte);
        }

Notez, que le délégué est déclaré en dehors de la classe, ainsi il est public pour tout l’espace de nom. Le moteur peut ainsi générer un évènement avec une signature identique:

        /// <summary>
        /// Transmet l'alerte qu'un joueur tente de joueur une carte "illégale"
        /// </summary>
        /// <param name="LeJoueur"></param>
        /// <param name="LaCarte"></param>
        static void j_CarteInterdite(Joueur LeJoueur, Carte LaCarte)
        {
            if (Moteur.CarteInterdite != null)
                Moteur.CarteInterdite(LeJoueur, LaCarte);
        }

Et puis tant qu’on y est, on va faire en sorte que le moteur fasse piocher le joueur quand c’est son tour.

        /// <summary>
        /// Annonce le joueur suivant
        /// </summary>
        private static void AnnonceProchainJoueur()
        {
            JoueurEnCours.Pioche();//fait piocher le joueur suivant
            if (ProchainJoueur != null)
                ProchainJoueur(JoueurEnCours);
        }

Surcharge de constructeur:

Admettons, que nous voulions choisir le niveau d’un joueur, on créerait une énumération qui reflète la difficulté du jeu

    public enum Difficulte
    {
        Facile,
        Normale,
        Difficile,
        Expert
    }

Et partant du principe que le niveau d’un joueur doit être initialisé dès l’instanciation de celui-ci, on va mettre ce paramètre dans la signature du constructeur.
Pour ne pas changer tout notre projet, on crée une surcharge avec laquelle on définit ce niveau.
Dans le constructeur initial, on met le niveau à une valeur par défaut.

        public Joueur(string LeNom)
        {
            Nom = LeNom;
            Main = new List<Carte>();
            Jeu = new ObservableCollection<Carte>();//initialisation de la collection
            Jeu.CollectionChanged += Jeu_CollectionChanged;//Abonnement à l'évènement signalant un changement
            Niveau = Difficulte.Facile;
            NouvellePartie();
        }

        public Joueur(string LeNom, Difficulte LeNiveau)
        {
            Nom = LeNom;
            Main = new List<Carte>();
            Jeu = new ObservableCollection<Carte>();//initialisation de la collection
            Jeu.CollectionChanged += Jeu_CollectionChanged;//Abonnement à l'évènement signalant un changement
            Niveau = LeNiveau;
            NouvellePartie();
        }

Mais là, les 2 méthodes sont quasiment identiques, on devrait pouvoir factoriser ?
Et bien, il est possible qu’un constructeur en appelle un autre.
Le constructeur qui n’a que le nom en paramètre, peut appeler le 2eme et lui passer le niveau par défaut.
La syntaxe C# est un peu particulière :

        public Joueur(string LeNom):this(LeNom,Difficulte.Facile)//on appelle le constructeur plus complet avec une valeur par défaut
        {
        }

        public Joueur(string LeNom, Difficulte LeNiveau)
        {
            Nom = LeNom;
            Main = new List<Carte>();
            Jeu = new ObservableCollection<Carte>();//initialisation de la collection
            Jeu.CollectionChanged += Jeu_CollectionChanged;//Abonnement à l'évènement signalant un changement
            Niveau = LeNiveau;
            NouvellePartie();
        }

Les interfaces:

Plus haut, je vous ai expliqué qu’en .Net, il n’est pas possible de dériver de plusieurs classes mères. Cela pourrait pourtant parfois s’avérer pratique.
Par exemple, si on a une List d’entiers (12, 31, 5, 19, 3, 26, 2, 16, 8) que l’on veut trier ; il y a la méthode Sort().

Mais si dans la classe Joueur, j’essaie de trier la Main (Main.Sort()), ça ne fonctionne pas, ça génère même une exception.

Celle-ci est due au fait que pour être triable la classe doit « dériver » de quelque chose qui permet de comparer deux Cartes.
Cependant, ça n’est pas possible, mais en plus, on ne peut pas comparer des Kilometres de la même façon que des cafetières.
Il nous faut donc un mécanisme qui permette au développeur de savoir ce qu’il doit faire, une sorte d’héritage qui décrive quelle méthode doit être écrite.
Ce sont les interfaces.

Pour utiliser Sort, il faut implémenter IComparable

public abstract class Carte : IComparable<Carte>
{
            //etc.

Et ça ne compile pas ?
C’est parce qu’en implémentant une interface, on n’hérite pas d’une (ou plusieurs) méthode(s), mais de l’obligation de l’(es) écrire.

En C#, le compilateur nous donne un bon coup de main

Vient nous écrire tout seul

        public int CompareTo(Carte other)
        {
            throw new NotImplementedException();
        }

Cependant, on ne sait pas trop quoi en faire, pas le choix un petit tour sur MSDN (d'abord) ou dans les forums, pour découvrir que Compare retourne

  • 0 pour deux instances « égales »,
  • -1 si l’instance en cours et « plus petite » que l’autre
  • +1 dans le cas contraire.

Reste à définir comment on compare des Kilometre, des Embuches, des Parades et des Bottes.
Je choisis qu’en premier viennent les bottes par ordre alphabétique, puis les Feux Verts, puis les Embuches, par ordre alphabétique, intercalés avec la Parade associée et enfin les Kilometre par ordre de distance.
On pourrait écrire une méthode compliquée qui fait ce tri, mais il est plus simple de définir, dans Carte, une propriété abstraite, en lecture seule qui retourne une valeur facilement comparable (un entier), qui respecte l’ordre défini.
Notre méthode Compare, comparera cette propriété entre les 2 instances.

        /// <summary>
        /// Propriété pour le tri
        /// </summary>
        public abstract int Ordre
        { 
            get; 
        }

        /// <summary>
        /// Implémentation de IComparable<Carte>
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public int CompareTo(Carte other)
        {
            return this.Ordre.CompareTo(other.Ordre);
        }

Ensuite, on implémente la propriété dans chaque classe fille, en commençant par les Bottes

        /// <summary>
        /// Implémentation de ordre dans Botte
        /// </summary>
        public override int Ordre
        {
            get 
            { 
                switch(EmbucheImmunisee)
                {
                    case TypeEmbuche.Accident://As du Volant
                        return 0;

                    case TypeEmbuche.FeuRouge | TypeEmbuche.LimitationVitesse://Camion de Pompier
                        return 1;

                    case TypeEmbuche.PanneEssence://Citerne d'essence
                        return 2;

                    default: //il ne reste que la roue de secours
                        return 3;
                }
            }
        }

Ensuite les Embuche et les Parades en intercalage, avec le Feu Vert devant (il prendra la valeur 4, à la suite des Bottes).
Pour se simplifier la tache nous allons changer les valeurs de TypeEmbuche, de façon à se quelle correspondent à l’ordre alphabétique, qu’il soit possible d’ajouter 1 pour intercaler la parade sans conflit tout en respectant les « valeurs flagables ».

        Aucune = 0,
        FeuRouge = 8,
        LimitationVitesse = 16,
        Crevaison = 4,
        PanneEssence = 32,
        Accident = 2

L’accident devra donc prendre la valeur 5, la réparation 6 et ainsi de suite.
Pour les Embuches

        /// <summary>
        /// Implémentation de ordre dans Embuche
        /// </summary>
        public override int Ordre
        {
            get { return (int)Type + 3; }
        }

Pour les Parades

        /// <summary>
        /// Implémentation de ordre dans Parade
        /// </summary>
        public override int Ordre
        {
            get
            {
                if (EmbucheParee == TypeEmbuche.FeuRouge)
                    return 4;
                else
                    return (int)EmbucheParee + 4;
            }
        }

Enfin, le plus petit Kilometre devra avoir une valeur supérieure à 37.

        /// <summary>
        /// Implémentation de ordre Kilometre
        /// </summary>
        public override int Ordre
        {
            get { return Distance + 20; }
        }

Malheureusement, ObservableCollection ne dispose pas de la méthode Sort(), cependant grâce à la propriété Ordre, on peut savoir « où » déposer une carte dans le jeu.

            Carte superieure = Jeu.FirstOrDefault(c => c.Ordre > LaCarte.Ordre);//on cherche la première carte dont l'ordre est supérieur à la carte jouée, si elle existe.
            if (superieure != null)
                Jeu.Insert(Jeu.IndexOf(superieure), LaCarte);//si la carte supérieure existe, on insère la carte jouée à l'index de la carte supérieure
            else
                Jeu.Add(LaCarte);//sinon on ajoute la carte jouée au jeu (donc à la fin)

Cela implique une petite modification de la méthode Jeu_CollectionChanged, en effet, nous testions la dernière carte de la collection, il faut maintenant chercher la carte ajoutée là ou elle est grâce à son index fournit par

e.NewStartingIndex

Une interface nous oblige à écrire une ou plusieurs méthodes, il n’y a donc pas de factorisation de code, par contre on peut profiter de tous les avantages du polymorphisme. Et contrairement à l’héritage, on peut implémenter plusieurs interfaces tout en héritant d’une classe.

La suite du tutoriel

Ce document intitulé « La programmation Objet appliquée à .Net par l’exemple Partie 2 sur 3, en C# » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.