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