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

Navigation globale

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

La classe Carte:

Public MustInherit Class Carte
  ''' <summary>
  ''' Constructeur protégé, seules les classes filles pourront l'appeler
  ''' </summary>
  ''' <param name="CheminDeLimage"></param>
  ''' <param name="LesPoints"></param>
  Protected Sub New(ByVal CheminDeLimage As String, ByVal LesPoints As Integer)
   CheminImage = CheminDeLimage
   Points = LesPoints
  End Sub

  ''' <summary>
  ''' Chemin vers l'image de la face
  ''' </summary>
  ''' <remarks>C'est la couche Vue qui s'occupera de l'afficher</remarks>
  Private privateCheminImage As String
        Public Property CheminImage As String
            Get
                Return privateCheminImage
            End Get
            Private Set(ByVal value As String)
                privateCheminImage = value
            End Set
        End Property

  ''' <summary>
  ''' Nombre de points marqués avec cette carte
  ''' </summary>
  Private privatePoints As Integer
        Public Property Points As Integer
            Get
                Return privatePoints
            End Get
            Protected Set(ByVal value As Integer)
                privatePoints = value
            End Set
        End Property


  Protected leJoueur As Joueur

  ''' <summary>
  ''' Prend la première carte de la pioche et la main dans la main d'un joueur
  ''' </summary>
  ''' <param name="LeJoueur"></param>
  Public Sub Piocher(ByVal LeJoueur As Joueur)
   Me.leJoueur = LeJoueur
   'ToDo implémenter la pioche
  End Sub

  ''' <summary>
  ''' Joue la carte
  ''' </summary>
  Protected Sub Jouer()
   'ToDo implémenter le jeu
  End Sub

  ''' <summary>
  ''' Jette une carte de la main du joueur vers la défausse
  ''' </summary>
  Public Sub Defausse()
   'ToDo implémenter la défausse
  End Sub
 End Class

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
End Class

Et les classes dérivées:

 Public Class Kilometre
  Inherits Carte
  Public Sub New(ByVal CheminDeLimage As String, ByVal LesKilometres As Integer) 'on appelle le constructeur de Carte
   MyBase.New(CheminDeLimage, LesKilometres)
   Distance = LesKilometres
  End Sub

  Private privateDistance As Integer
        Public Property Distance As Integer
            Get
                Return privateDistance
            End Get
            Private Set(ByVal value As Integer)
                privateDistance = value
            End Set
        End Property

        Public Shadows Sub Jouer() 'A noter le modificateur Shadows qui permet de masquer la méthode héritée
            MyBase.Jouer() 'appel du code de la classe mère
        End Sub

  ''' <summary>
  ''' Réécriture de ToString pour afficher le nombre de kilomètres
  ''' </summary>
  ''' <returns></returns>
  Public Overrides Function ToString() As String
   Return Distance.ToString & " km"
  End Function

 End Class

 Public Class Embuche
  Inherits Carte
  Public Sub New(ByVal CheminImage As String, ByVal LeType As TypeEmbuche)
   MyBase.New(CheminImage,0)
   Type = LeType
  End Sub

  Private privateType As TypeEmbuche
        Public Property Type As TypeEmbuche
            Get
                Return privateType
            End Get
            Private Set(ByVal value As TypeEmbuche)
                privateType = value
            End Set
        End Property

  ''' <summary>
  ''' Une embuche est lancé à un autre joueur
  ''' </summary>
  ''' <param name="Cible"></param>
  Public Sub Jouer(ByVal Cible As Joueur)
   'ToDo implémenter le jeu de l'embuche
  End Sub

  ''' <summary>
  ''' Réécriture de ToString pour afficher le nom de l'embuche
  ''' </summary>
  ''' <returns></returns>
  Public Overrides Function ToString() As String
   Return Type.ToString
  End Function
 End Class

 ''' <summary>
 ''' Enumération permettant de distinguer le type de chaque carte Embuche
 ''' </summary>
 Public Enum TypeEmbuche
  FeuRouge
  LimitationVitesse
  Crevaison
  PanneEssence
  Accident
 End Enum

 Public Class Parade
  Inherits Carte
  Public Sub New(ByVal CheminImage As String, ByVal LeType As TypeEmbuche)
   MyBase.New(CheminImage, 0)
   EmbucheParee = LeType
  End Sub

  Private privateEmbucheParee As TypeEmbuche
        Public Property EmbucheParee As TypeEmbuche
            Get
                Return privateEmbucheParee
            End Get
            Private Set(ByVal value As TypeEmbuche)
                privateEmbucheParee = value
            End Set
        End Property

  ''' <summary>
  ''' Réécriture de ToString pour afficher le nom de la parade
  ''' </summary>
  ''' <returns></returns>
  Public Overrides Function ToString() As String
   Select Case 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"

    Case Else 'on est obligé de mettre un Case Else ou après le Select Case 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"
   End Select
  End Function
 End Class

 Public Class Botte
  Inherits Carte
  Public Sub New(ByVal CheminImage As String, ByVal LeType As TypeEmbuche)
   MyBase.New(CheminImage, 100)
   EmbucheImmunisee = LeType
  End Sub

  Private privateEmbucheImmunisee As TypeEmbuche
        Public Property EmbucheImmunisee As TypeEmbuche
            Get
                Return privateEmbucheImmunisee
            End Get
            Private Set(ByVal value As TypeEmbuche)
                privateEmbucheImmunisee = value
            End Set
        End Property

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

  End Sub

  ''' <summary>
  ''' Réécriture de ToString pour afficher le nom de la botte
  ''' </summary>
  ''' <returns></returns>
  Public Overrides Function ToString() As String
   Select Case 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"
   End Select

   'on est obligé de mettre un Case Else ou après le Select Case 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"
  End Function

 End Class

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.
Le mot clé est

Shared

, se traduisant par partagé, j’utilise donc le terme classe partagée.

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 Sub EvenementKilometres(ByVal LeJoueur As Joueur, ByVal Kilometres As Integer) 'signature de l'évènement
  Public Event JaiGagne As EvenementKilometres 'évènement signalant que la distance est atteinte
  Public Event DistanceDepassee As EvenementKilometres 'évènement signalant que la distance est partagée

  Public Delegate Sub EvenementEmbuche(ByVal LeJoueur As Joueur) 'signature de l'évènement
  Public Event EmbucheRecue As EvenementEmbuche 'évènement signalant une embuche reçue
  Public Event CoupFourre As EvenementEmbuche 'évènement signalant un coup fourré
  Public Event FiniMonTour As EvenementEmbuche 'évènement signalant que le joueur à fini son tour.

  Public Sub New(ByVal LeNom As String)
   Nom = LeNom
   DistanceParcourue = 0
            Main = New List(Of Carte)

            Jeu = New ObservableCollection(Of Carte) 'initialisation de la collection
   AddHandler Jeu.CollectionChanged, AddressOf Jeu_CollectionChanged 'Abonnement à l'évènement signalant un changement
  End Sub

  Private privateNom As String
        Public Property Nom As String
            Get
                Return privateNom
            End Get
            Private Set(ByVal value As String)
                privateNom = value
            End Set
        End Property

        Public Property Main As List(Of Carte)

        Public Property Jeu As ObservableCollection(Of Carte)

  ''' <summary>
  ''' Propriété en lecture seule, dont la valeur est le résultat d'un calcul
  ''' </summary>
        Public ReadOnly Property Points As Integer
            Get
                Dim bonus As Integer = 0
                'si un (ou plusieurs) joueur n'a pas pu poser de kilomètre, les autres marquent 500 points de bonus
                For Each leJoueur As Joueur In Moteur.Joueurs
                    If leJoueur Is Me Then
                        Continue For 'on passe à l'itération suivante
                    End If

                    If leJoueur.DistanceParcourue = 0 Then
                        bonus += 500 'on ajoute 500 au bonus pour se joueur sans distance
                    End If
                Next leJoueur

                Dim lesPoints As Integer = 0
                For Each laCarte As Carte In Jeu
                    lesPoints += laCarte.Points 'grâce au polymorphisme, on peut additionner les points, peu importe le type de carte.
                Next laCarte

                Return lesPoints + bonus
            End Get
        End Property

  Private privateDistanceParcourue As Integer
        Public Property DistanceParcourue As Integer
            Get
                Return privateDistanceParcourue
            End Get
            Private Set(ByVal value As Integer)
                privateDistanceParcourue = value
            End Set
        End Property

  ''' <summary>
  ''' Traitements à faire après qu'une carte ait été ajoutée au jeu
  ''' </summary>
  ''' <param name="sender"></param>
  ''' <param name="e"></param>
  Private Sub Jeu_CollectionChanged(ByVal sender As Object, ByVal e As System.Collections.Specialized.NotifyCollectionChangedEventArgs)
   If e.Action <> System.Collections.Specialized.NotifyCollectionChangedAction.Add Then
    Return
   End If

   If TypeOf Jeu.Last Is Embuche Then 'si la dernière carte reçue est une embuche, car Jeu contient des Cartes
    RaiseEvent EmbucheRecue(Me) 'on émet l'évènement

    Dim lEmbuche As Embuche = CType(Jeu.Last, Embuche) '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é
    For Each laCarte As Carte In Main
     If TypeOf laCarte Is Botte AndAlso (CType(laCarte, Botte)).EmbucheImmunisee = lEmbuche.Type Then
      Dim laBotte As Botte = CType(laCarte, Botte)
      'on signale que l'on joue le coup fourré
      RaiseEvent CoupFourre(Me)

      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
      Exit For 'on sort du foreach, ça n'est pas la peine de continuer à cherche ce que l'on a déjà trouvé
     End If
    Next laCarte
   ElseIf TypeOf Jeu.Last Is Kilometre Then 'si la dernière carte est un kilomètre, on calcule la distance totale.
    Dim km As Kilometre = CType(Jeu.Last, Kilometre)
    DistanceParcourue = km + DistanceParcourue

    If DistanceParcourue = Moteur.DistanceAParcourir Then
     RaiseEvent JaiGagne(Me,DistanceParcourue) 'on émet l'évènement avec le paramètre correspondant à la signature.


    ElseIf DistanceParcourue > Moteur.DistanceAParcourir Then
     RaiseEvent DistanceDepassee(Me,DistanceParcourue)
    End If
   End If

  End Sub

  ''' <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 Sub Joue(ByVal LaCarte As Carte)
   Main.Remove(LaCarte) 'On enlève la carte de la main
   Jeu.Add(LaCarte) 'on ajoute la carte au jeu
   AnnonceFinTour()
  End Sub

  ''' <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 Sub Jouer(ByVal Km As Kilometre)
   Joue(CType(Km, Carte))
  End Sub

  ''' <summary>
  ''' Surcharge permettant de jouer une parade
  ''' </summary>
  ''' <param name="LaParade"></param>
  Public Sub Jouer(ByVal LaParade As Parade)
   Joue(CType(LaParade, Carte))
  End Sub

  ''' <summary>
  ''' Surcharge permettant de jouer une botte
  ''' </summary>
  ''' <param name="LaParade"></param>
  Public Sub Jouer(ByVal LaBotte As Botte)
   Joue(CType(LaBotte, Carte))
  End Sub

  ''' <summary>
  ''' Surcharge permettant de mettre une embuche à un adversaire
  ''' </summary>
  ''' <param name="Lembuche"></param>
  ''' <param name="Cible"></param>
  Public Sub Jouer(ByVal Lembuche As Embuche, ByVal Cible As Joueur)
   If Cible Is Me Then '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
   End If
   AnnonceFinTour()
  End Sub

  ''' <summary>
  ''' Pioche une carte
  ''' </summary>
  Public Sub Pioche()
   Main.Add(Moteur.Pioche(0)) 'pioche la première carte
   Moteur.Pioche.RemoveAt(0)
  End Sub

  ''' <summary>
  ''' Défausse une carte
  ''' </summary>
  ''' <param name="LaCarte">Carte à défausser</param>
  Public Sub Defausse(ByVal LaCarte As Carte)
   Moteur.Defausse.Add(LaCarte)
   Main.Remove(LaCarte)
   AnnonceFinTour()
  End Sub

  ''' <summary>
  ''' Annonce que le tour est terminé
  ''' </summary>
  Private Sub AnnonceFinTour()
   RaiseEvent FiniMonTour(Me)
  End Sub
 End Class

Et celle de Moteur:

 Public NotInheritable Class Moteur
  Public Delegate Sub EvenementProchainJoueur(ByVal LeJoueur As Joueur) 'signature de l'évènement
  Public Shared Event ProchainJoueur As EvenementProchainJoueur 'évènement annonçant le joueur suivant
  Public Shared Event PartieGagnee As EvenementProchainJoueur 'évènement annonçant qui a gagné

  Public Delegate Sub EvenementMoteurJeu() 'signature de l'évènement
  Public Shared Event NouvellePioche As EvenementMoteurJeu 'évènement annonçant la nouvelle pioche

        Public Shared Property DistanceAParcourir As Integer

        Public Shared Property Joueurs As List(Of Joueur)

        Public Shared Property Defausse As List(Of Carte)

        Public Shared Property Pioche As ObservableCollection(Of Carte)

  Private Sub New()
  End Sub
        Public Shared ReadOnly Property JoueurEnCours As Joueur
            Get
                Return Joueurs(indexJoueur)
            End Get
        End Property

  Private Shared indexJoueur As Integer

  ''' <summary>
  ''' Initialise les éléments de jeu avant la première partie
  ''' </summary>
  ''' <param name="NomsJoueurs"></param>
  Public Shared Sub InitPremierePartie(ByVal NomsJoueurs As List(Of String))
   Defausse = New List(Of Carte)
   Pioche = New ObservableCollection(Of Carte)
   AddHandler Pioche.CollectionChanged, AddressOf Pioche_CollectionChanged
   Joueurs = New List(Of Joueur)

   'Crée les joueurs et s'abonne aux évènements
   For Each joueur As String In NomsJoueurs
    Dim j As New Joueur(joueur)
    AddHandler j.CoupFourre, AddressOf j_CoupFourre
    AddHandler j.DistanceDepassee, AddressOf j_DistanceDepassee
    AddHandler j.JaiGagne, AddressOf j_JaiGagne
    AddHandler j.FiniMonTour, AddressOf j_FiniMonTour

    Joueurs.Add(j)
   Next joueur


   DistanceAParcourir = If(Joueurs.Count > 3, 700, 1000) 'La distance à parcourir est 700 à partir de 4 joueurs, sinon 1000

   NouvellePartie()
  End Sub

  ''' <summary>
  ''' Quand un joueur à finit, on passe au suivant
  ''' </summary>
  ''' <param name="LeJoueur"></param>
  Private Shared Sub j_FiniMonTour(ByVal LeJoueur As Joueur)
   JoueurSuivant()
  End Sub

  ''' <summary>
  ''' Signale la fin de la partie
  ''' </summary>
  ''' <param name="LeJoueur"></param>
  ''' <param name="Kilometres"></param>
  Private Shared Sub j_JaiGagne(ByVal LeJoueur As Joueur, ByVal Kilometres As Integer)
            RaiseEvent PartieGagnee(LeJoueur)
        End Sub

        ''' <summary>
        ''' Démarre une nouvelle partie
        ''' </summary>
        Public Shared Sub NouvellePartie()
            'génération des cartes
            Dim toutesLesCartes As List(Of Carte) = 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 i As Integer = 0 To 5
                For j As Integer = 0 To Moteur.Joueurs.Count - 1
                    Moteur.Joueurs(j).Pioche()
                Next j
            Next i

            indexJoueur = 0
            AnnonceProchainJoueur()
        End Sub

        Private Shared indexJoueursDisqualifies As New List(Of Integer)
        ''' <summary>
        ''' Un joueur a dépassé la distance=> il est éliminé
        ''' </summary>
        ''' <param name="LeJoueur"></param>
        ''' <param name="Kilometres"></param>
        Private Shared Sub j_DistanceDepassee(ByVal LeJoueur As Joueur, ByVal Kilometres As Integer)
            indexJoueursDisqualifies.Add(Joueurs.IndexOf(LeJoueur)) 'on ajoute ce joueur dans la liste des joueurs disqualifiés
        End Sub

        ''' <summary>
        ''' Change le joueur après un coup fourré
        ''' </summary>
        ''' <param name="LeJoueur"></param>
        Private Shared Sub j_CoupFourre(ByVal LeJoueur As Joueur)
            indexJoueur = Joueurs.IndexOf(LeJoueur)
            AnnonceProchainJoueur()
        End Sub

        ''' <summary>
        ''' Annonce le joueur suivant
        ''' </summary>
        Private Shared Sub AnnonceProchainJoueur()
            RaiseEvent ProchainJoueur(JoueurEnCours)
        End Sub

        ''' <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 Shared Sub Pioche_CollectionChanged(ByVal sender As Object, ByVal e As System.Collections.Specialized.NotifyCollectionChangedEventArgs)
            If e.Action <> System.Collections.Specialized.NotifyCollectionChangedAction.Remove Then
                Return
            End If

            If Pioche.Count = 0 Then
                Melanger(Defausse)
                RaiseEvent NouvellePioche()
            End If
        End Sub

  ''' <summary>
  ''' Mélange les cartes et (re)crée la pioche
  ''' </summary>
  ''' <param name="Cartes"></param>
  Private Shared Sub Melanger(ByVal Cartes As List(Of Carte))
   Dim rnd As New Random(Date.Now.Millisecond)
   Do
    Dim index As Integer = 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
   Loop While Cartes.Count > 0
  End Sub

  ''' <summary>
  ''' Passe au joueur suivant
  ''' </summary>
  Private Shared Sub JoueurSuivant()
   If indexJoueur = Joueurs.Count - 1 Then
    indexJoueur = 0
   Else
    indexJoueur += 1
   End If

   If indexJoueursDisqualifies.Contains(indexJoueur) Then 'si le joueur est disqualifié on passe au suivant de manière récursive
    JoueurSuivant()

   Else
    AnnonceProchainJoueur()
   End If
  End Sub
 End Class

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
  Inherits Carte
  ''' Code déjà écrit non retranscrit

  ''' <summary>
  ''' Méthode de classe qui génère la liste d'Embuches
  ''' </summary>
  ''' <returns></returns>
  Public Shared Function Generer() As List(Of Carte)
   Dim resultat As New List(Of Carte)

            For i As Integer = 0 To 4
                resultat.Add(New Embuche("Image du feu rouge", TypeEmbuche.FeuRouge))
            Next i

            For i As Integer = 0 To 3
                resultat.Add(New Embuche("Image de la limitation de vitesse", TypeEmbuche.LimitationVitesse))
            Next i

            For i As Integer = 0 To 2
                resultat.Add(New Embuche("Image de la panne d'essence", TypeEmbuche.PanneEssence))
            Next i

            For i As Integer = 0 To 2
                resultat.Add(New Embuche("Image de la crevaison", TypeEmbuche.Crevaison))
            Next i

            For i As Integer = 0 To 2
                resultat.Add(New Embuche("Image de l'accident", TypeEmbuche.Accident))
            Next i

   Return resultat
  End Function

 End Class

 Public Class Parade
  Inherits Carte
  ''' Code déjà écrit non retranscrit

  ''' <summary>
  ''' Méthode de classe qui génère la liste de Parades
  ''' </summary>
  ''' <returns></returns>
  Public Shared Function Generer() As List(Of Carte)
   Dim resultat As New List(Of Carte)

   For i As Integer = 0 To 13
    resultat.Add(New Parade("Image du feu vert", TypeEmbuche.FeuRouge))
   Next i

   For i As Integer = 0 To 5
    resultat.Add(New Parade("Image de la fin de limitation", TypeEmbuche.LimitationVitesse))
   Next i

   For i As Integer = 0 To 5
    resultat.Add(New Parade("Image de la station essence", TypeEmbuche.PanneEssence))
   Next i

   For i As Integer = 0 To 5
    resultat.Add(New Parade("Image de la roue de secours", TypeEmbuche.Crevaison))
   Next i

   For i As Integer = 0 To 5
    resultat.Add(New Parade("Image de la réparation", TypeEmbuche.Accident))
   Next i

   Return resultat
  End Function
 End Class

 Public Class Botte
  Inherits Carte
  ''' Code déjà écrit non retranscrit

  ''' <summary>
  ''' Cette méthode est appelée par le joueur en cas de coup fourré
  ''' </summary>
  Public Sub CoupFourreParLeJoueur()
   Me.Points += 300
  End Sub

  ''' <summary>
  ''' Méthode de classe qui génère la liste de Bottes
  ''' </summary>
  ''' <returns></returns>
  Public Shared Function Generer() As List(Of Carte)
   Dim resultat As New List(Of 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
  End Function

 End Class

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
  Inherits 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 Shared Function Generer() As List(Of Carte)
   Dim resultat As New List(Of Carte)

   For i As Integer = 0 To 9
    resultat.Add(New Kilometre("Image 25 km", 25))
   Next i

   For i As Integer = 0 To 9
    resultat.Add(New Kilometre("Image 50 km", 50))
   Next i

   For i As Integer = 0 To 9
    resultat.Add(New Kilometre("Image 75 km", 75))
   Next i

   For i As Integer = 0 To 11
    resultat.Add(New Kilometre("Image 100 km", 100))
   Next i

   For i As Integer = 0 To 3
    resultat.Add(New Kilometre("Image 200 km", 200))
   Next i


   Return resultat
  End Function

  ''' <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 Shared Operator +(ByVal LUn As Kilometre, ByVal LAutre As Kilometre) As Integer
   Return LUn.Distance + LAutre.Distance
  End Operator

  ''' <summary>
  ''' Opérateur qui permet d'additionner un Kilometre et un entier
  ''' </summary>
  ''' <param name="Km"></param>
  ''' <param name="Entier"></param>
  ''' <returns></returns>
  Public Shared Operator +(ByVal Km As Kilometre, ByVal Entier As Integer) As Integer
   Return Km.Distance + Entier
  End Operator

 End Class

Enumération Flagable:

Cette implémentation ne respecte pas toutes les règles, il n’est pas vérifié que:

  • 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
 EndEnum

Et comment s’en sert-on ?

On cumule deux valeurs avec l’opérateur ou (l’opérateur

OR

) : 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

XOR

) : 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 Overrides Function ToString() As String
   Select Case EmbucheImmunisee
    Case TypeEmbuche.Accident
     Return "As du volant"

    Case TypeEmbuche.Crevaison
     Return "Roue increvable"

                Case TypeEmbuche.FeuRouge Or TypeEmbuche.LimitationVitesse
                    Return "Camion de pompier"

    Case TypeEmbuche.PanneEssence
     Return "Citerne d'essence"
   End Select

   '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"
  End Function


  ''' <summary>
  ''' Méthode de classe qui génère la liste de Bottes
  ''' </summary>
  ''' <returns></returns>
  Public Shared Function Generer() As List(Of Carte)
   Dim resultat As New List(Of Carte)

            resultat.Add(New Botte("Image du camion de pompier", TypeEmbuche.FeuRouge Or 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
  End Function

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 Sub Jeu_CollectionChanged(ByVal sender As Object, ByVal e As System.Collections.Specialized.NotifyCollectionChangedEventArgs)
   If e.Action <> System.Collections.Specialized.NotifyCollectionChangedAction.Add Then
    Return
   End If

   If TypeOf Jeu.Last Is Embuche Then 'si la dernière carte reçue est une embuche, car Jeu contient des Cartes
    If Me.EmbucheRecue IsNot Nothing Then 'on vérifie que "quelqu'un" soit abonné à l'évènement
     Me.EmbucheRecue(Me) 'on émet l'évènement
    End If

    Dim lEmbuche As Embuche = CType(Jeu.Last, Embuche) '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é
    For Each laCarte As Carte In Main
     If TypeOf laCarte Is Botte AndAlso (CType(laCarte, Botte)).EmbucheImmunisee = lEmbuche.Type Then
      Dim laBotte As Botte = CType(laCarte, Botte)
      'on signale que l'on joue le coup fourré
      If Me.CoupFourre IsNot Nothing Then
       Me.CoupFourre(Me)
      End If

      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é
     End If
    Next laCarte

    EmbucheEnCours = EmbucheEnCours Or lEmbuche.Type 'on ajoute l'embûche
    If lEmbuche.Type <> TypeEmbuche.LimitationVitesse AndAlso (Not Immunite.HasFlag(TypeEmbuche.FeuRouge)) Then
     EmbucheEnCours = EmbucheEnCours Or TypeEmbuche.FeuRouge 'on ajoute un feu rouge pour que le joueur soit obligé de mettre un feu vert
    End If

   ElseIf TypeOf Jeu.Last Is Kilometre Then 'si la dernière carte est un kilomètre, on calcule la distance totale.
    Dim km As Kilometre = CType(Jeu.Last, Kilometre)
    DistanceParcourue = km + DistanceParcourue

    If DistanceParcourue = Moteur.DistanceAParcourir Then
     If Me.JaiGagne IsNot Nothing Then
      Me.JaiGagne(Me,DistanceParcourue) 'on émet l'évènement avec le paramètre correspondant à la signature.
     End If

    ElseIf DistanceParcourue > Moteur.DistanceAParcourir Then
     If Me.DistanceDepassee IsNot Nothing Then
      Me.DistanceDepassee(Me,DistanceParcourue)
     End If
    End If
   End If
  End Sub

  Private Sub Joue(ByVal LaCarte As Carte)
   If TypeOf LaCarte Is Kilometre Then
    Select Case EmbucheEnCours
     Case TypeEmbuche.Aucune 'on peut rouler

     Case TypeEmbuche.LimitationVitesse 'il n'y a aucune autre embûche
      If (CType(LaCarte, Kilometre)).Distance > 50 Then
       RaiseEvent CarteInterdite(Me, LaCarte)
       Return 'on ne peut pas dépasser 50
      End If

     Case Else 's'il y a n'importe quelle autre embuche, on ne peut pas rouler
      RaiseEvent CarteInterdite(Me, LaCarte)
      Return
    End Select

    If (CType(LaCarte, Kilometre)).Distance = 200 AndAlso Jeu.OfType(Of Kilometre).Count(Function(k) k.Distance = 200) = 2 Then
     '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"
     RaiseEvent CarteInterdite(Me, LaCarte)
     Return
    End If
   ElseIf TypeOf LaCarte Is Parade Then
    Dim maParade As Parade = CType(LaCarte, Parade)
    If Not EmbucheEnCours.HasFlag(maParade.EmbucheParee) Then
     RaiseEvent CarteInterdite(Me, LaCarte)
     Return 'on ne peut pas parer une embuche que l'on ne subit pas
    End If

    If maParade.EmbucheParee = TypeEmbuche.FeuRouge AndAlso Not(EmbucheEnCours = TypeEmbuche.FeuRouge OrElse EmbucheEnCours = (TypeEmbuche.FeuRouge Or TypeEmbuche.LimitationVitesse)) Then
     RaiseEvent CarteInterdite(Me, 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
    End If

    EmbucheEnCours = EmbucheEnCours Xor maParade.EmbucheParee
   Else
    'c'est forcément une botte, elle peut se poser n'importe quand même sans parer une embûche
    Dim laBotte As Botte = (CType(LaCarte, Botte))

    Immunite = Immunite Or laBotte.EmbucheImmunisee 'immunise

    If laBotte.EmbucheImmunisee.HasFlag(TypeEmbuche.FeuRouge) Then
     If EmbucheEnCours.HasFlag(TypeEmbuche.FeuRouge) Then
      EmbucheEnCours = EmbucheEnCours Xor TypeEmbuche.FeuRouge 'pare si besoin
     End If

     If EmbucheEnCours.HasFlag(TypeEmbuche.LimitationVitesse) Then
      EmbucheEnCours = EmbucheEnCours Xor TypeEmbuche.LimitationVitesse 'pare si besoin
     End If
    ElseIf EmbucheEnCours.HasFlag(laBotte.EmbucheImmunisee) Then
     EmbucheEnCours = EmbucheEnCours Xor laBotte.EmbucheImmunisee 'pare si besoin
    End If
   End If

   Main.Remove(LaCarte) 'On enlève la carte de la main
   Jeu.Add(LaCarte) 'on ajoute la carte au jeu
   AnnonceFinTour()
  End Sub

  Public Sub Jouer(ByVal Lembuche As Embuche, ByVal Cible As Joueur)
   If Cible Is Me Then '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")
   End If

   Dim condition1 As Boolean = Cible.Immunite.HasFlag(Lembuche.Type) 'on ne peut pas poser ni une embûche contre laquelle la cible est immunisée,
   Dim condition2 As Boolean
   If Lembuche.Type = TypeEmbuche.LimitationVitesse Then
    condition2 = Cible.EmbucheEnCours.HasFlag(TypeEmbuche.LimitationVitesse) 'ni une limitation si elle y est déjà
   Else
    condition2 = Cible.EmbucheEnCours <> TypeEmbuche.Aucune AndAlso Cible.EmbucheEnCours <> TypeEmbuche.LimitationVitesse 'ni les autres embûches s'il y a en déjà une (sauf la limitation)
   End If

   If condition1 OrElse condition2 Then
    RaiseEvent CarteInterdite(Me, 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()
   End If

  End Sub

On doit aussi ajouter un évènement quand la carte n’est pas autorisée.

    Public Delegate Sub EvenementCarte(LeJoueur As Joueur, LaCarte As Carte) '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 CarteInterdite As EvenementCarte 'évènement signalant que le joueur tente de poser une carte à l'encontre des règles.

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>
        Private Shared Sub j_CarteInterdite(LeJoueur As Joueur, LaCarte As Carte)
            RaiseEvent CarteInterdite(LeJoueur, LaCarte)
        End Sub

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 Shared Sub AnnonceProchainJoueur()
            JoueurEnCours.Pioche() 'fait piocher le joueur suivant
            RaiseEvent 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
End Enum

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 Sub New(ByVal LeNom As String)
            Nom = LeNom
            Main = New List(Of Carte)
            Jeu = New ObservableCollection(Of Carte) 'initialisation de la collection
            AddHandler Jeu.CollectionChanged, AddressOf Jeu_CollectionChanged 'Abonnement à l'évènement signalant un changement
            Niveau = Difficulte.Facile
            NouvellePartie()
        End Sub

        Public Sub New(ByVal LeNom As String, ByVal LeNiveau As Difficulte)
            Nom = LeNom
            Main = New List(Of Carte)
            Jeu = New ObservableCollection(Of Carte) 'initialisation de la collection
            AddHandler Jeu.CollectionChanged, AddressOf Jeu_CollectionChanged 'Abonnement à l'évènement signalant un changement
            Niveau = LeNiveau
            NouvellePartie()
        End Sub

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.

        Public Sub New(ByVal LeNom As String)
            Me.New(LeNom,Difficulte.Facile) 'on appelle le constructeur plus complet avec une valeur par défaut
        End Sub

        Public Sub New(ByVal LeNom As String, ByVal LeNiveau As Difficulte)
            Nom = LeNom
            Main = New List(Of Carte)
            Jeu = New ObservableCollection(Of Carte)) 'initialisation de la collection
            AddHandler Jeu.CollectionChanged, AddressOf Jeu_CollectionChanged 'Abonnement à l'évènement signalant un changement
            Niveau = LeNiveau
            NouvellePartie()
        End Sub

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 Sort, il faut implémenter IComparable

    Public MustInherit Class Carte
        Implements IComparable(Of Carte)

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.

Pour VB.Net, il faut se servir du message d’erreur, on voit que c’est une fonction retournant un entier et qui prend une Carte en paramètre.

        Public Function Compare(Other As Carte) As Integer

        End Function

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 MustOverride ReadOnly Property Ordre As Integer

        ''' <summary>
        ''' Implémentation de IComparable<Carte>
        ''' </summary>
        ''' <param name="other"></param>
        ''' <returns></returns>
        Public Function CompareTo(ByVal other As Carte) As Integer
            Return Me.Ordre.CompareTo(other.Ordre)
        End Function

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 Overrides ReadOnly Property Ordre As Integer
            Get
                Select Case EmbuheImmunisee
                    Case TypeEmbuche.Accident 'As du Volant
                        Return 0

                    Case TypeEmbuche.FeuRouge Or TypeEmbuche.LimitationVitesse 'Camion de Pompier
                        Return 1

                    Case TypeEmbuche.PanneEssence 'Citerne d'essence
                        Return 2

                    Case Else 'il ne reste que la roue de secours
                        Return 3
                End Select
            End Get
        End Property

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 Overrides ReadOnly Property Ordre As Integer
            Get
                Return CInt(Type) + 3
            End Get
        End Property

Pour les Parades

        ''' <summary>
        ''' Implémentation de ordre dans Parade
        ''' </summary>
        Public Overrides ReadOnly Property Ordre As Integer
            Get
                If EmbucheParee = TypeEmbuche.FeuRouge Then
                    Return 4
                Else
                    Return CInt(EmbucheParee) + 4
                End If
            End Get
        End Property

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

        ''' <summary>
        ''' Implémentation de ordre Kilometre
        ''' </summary>
        Public Overrides ReadOnly Property Ordre As Integer
            Get
                Return Distance + 20
            End Get
        End Property

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.

            Dim superieure As Carte = Jeu.FirstOrDefault(Function(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 IsNot Nothing Then
                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)
            End If

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 VB.Net » 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.