L'objectif de cette fonctionnalité est de pouvoir choisir la vitesse du vaisseau afin que le vaisseau puisse se déplacer à une vitesse convenable dans l'application graphique.
Pour réaliser cet objectif, nous allons ajouter une vitesse au code existant de la classe Vaisseau
en prenant soin que ce changement ne vienne pas perturber le comportement actuel (c-a-d que tous les tests écrits jusqu'à présent continuent de bien de passer AU VERT)...
Pour cela, nous allons suivre les étapes suivantes :
- Quick Design Session : Comprendre ce qu'est la vitesse dans notre application et faire un choix de conception
- Ajouter la
vitesse
auVaisseau
sans régression de comportement - Régler la vitesse du vaisseau
- Faire en sorte que le déplacement se fasse correctement pour une vitesse quelconque
- Et bien, jouons maintenant !
sans oublier de ...
Quick Design Session : Comprendre ce qu'est la vitesse dans notre application et faire un choix de conception
La vitesse que nous souhaitons implémenter est une vitesse instantanée qui d'après Wikipédia peut être définie comme un vecteur obtenu en dérivant les coordonnées cartésiennes de la position par rapport au temps.
Ainsi dans un espace à deux dimensions, comme celui l'espace de jeu de Space Invaders, la vitesse d'un objet peut-être définie par un vecteur v à 2 dimensions (dx et dy) où :
- dx correspond au déplacement (c-a-d à un nombre de pixels pour le projet SpaceInvaders) effectué en 1 unité de temps sur l'axe des abscisses
- dy correspondra au déplacement (c-a-d à un nombre de pixels pour le projet SpaceInvaders) effectué en 1 unité de temps sur l'axe des ordonnées.
A partir de cette première réflexion, quel pourrait-être votre choix de conception pour donner une vitesse au Vaisseau
?
-
La solution n°1 (la plus rapide) consisterait à ajouter directement comme attributs de la classe
Vaisseau
deux types primitifs entiersint dx
etint dy
... Hmm dans ces conditions, n'est-on pas en train de faire apparaître un code smell de type Primitive Obsession ? -
La solution n°2 consisterait alors à créer une classe
Vitesse
composée des deux types primitifsdx
etdy
et à ajouter un nouvel attributvitesse
de typeVitesse
dans la classeVaisseau
...
Mais, intéressons-nous de plus près à notre application Space Invaders et à ces spécificités...
Dans un Space Invaders, les objets qui se déplacent sont le vaisseau, le(s) missile(s) et l'(es) alien(s).
Chacun de ces objets se déplace soit horizontalement, soit verticalement, mais, a priori, jamais en diagonale.
Cela signifie que le déplacement d'un objet dans le cas du Space Invaders se fera uniquement suivant une seule dimension (celle des x pour un déplacement dit horizontal, ou celle des y pour un déplacement dit vertical). Dans ces conditions, est-il vraiment nécessaire de disposer d'un vecteur vitesse à 2 dimensions ? ...
En tenant compte de cette dernière remarque et en essayant de rester le plus YAGNI possible, il semblerait que, pour l'instant, un simple attribut vitesse
de type entier soit suffisant pour représenter la vitesse du Vaisseau
: ce sera notre choix de conception pour la suite.
Peut-être serait-il d'ailleurs intéressant donner une définition de la vitesse dans votre glossaire pour en connaître ses limites et sa mesure dans le cadre de votre application... 😄
Il est maintenant grand temps de remettre les mains dans le code !
Relancez les tests pour vous assurer qu'ils passent toujours tous AU VERT !
D'après ce qui précéde, commencez à ajouter un attribut entier vitesse
dans la classe Vaisseau
.
public class Vaisseau {
private Position origine;
private Dimension dimension;
private int vitesse;
//...
}
Lorsqu'on lance l'application graphique, on constate que le vaisseau se déplace déjà à une certaine vitesse.
Deux questions devraient alors vous venir à l'esprit :
- Quelle est la valeur de cette vitesse ?
- Où se cache la vitesse dans le code de la classe
Vaisseau
déjà écrit ?
A chaque unité de temps, c'est-à-dire à chaque fois qu'un déplacement est demandé, le vaisseau se déplace actuellement de 1 pixel : la valeur actuelle de la vitesse est donc de 1.
Cette vitesse se cache dans les méthodes seDeplacerVersLaDroite
et seDeplacerVersLaGauche
où elle apparaît actuellement sous la forme +1
et -1
.
Ce constat fait, vous pouvez intervenir sur le code.
Commencez par remplacer dans les méthodes seDeplacerVersLaDroite
et seDeplacerVersLaGauche
, les +1
et -1
par +vitesse
et -vitesse
de manière à obtenir :
public void seDeplacerVersLaDroite() {
this.origine.changerAbscisse(this.origine.abscisse() + vitesse);
}
public void seDeplacerVersLaGauche() {
this.origine.changerAbscisse(this.origine.abscisse() - vitesse);
}
Vous venez de faire des modifications dans votre code...
N'oubliez pas de relancer les tests pour vérifier que le comportement de votre code n'a pas changé !
Et là bing ! les tests sont AU ROUGE !!!
Ce qui est tout à fait normal puisque nous n'avons pas encore initialisé la valeur de vitesse
à 1
et que par défaut, elle est à 0
😄
Pour faire passer les tests le plus rapidement AU VERT, il suffit donc d'ajouter une instruction (celle de l'initialisation de la vitesse this.vitesse = 1;
) dans le constructeur suivant :
public Vaisseau(Dimension dimension, Position positionOrigine) {
this.dimension = dimension;
this.origine = positionOrigine;
this.vitesse = 1;
}
Vous venez de faire des modifications dans votre code...
N'oubliez pas de relancer les tests pour vérifier que le comportement de votre code n'a pas changé !
Et là, les tests devraient normalement passer AU VERT !!!
Nous avons donc modifié notre code actuel sans en changer le comportement : pas de régression par rapport à l'existant, puisque les tests sont AU VERT !
Par contre, ce code ne fonctionne que pour une vitesse égale à 1
, il est donc maintenant temps de permettre le choix d'une autre valeur de vitesse...
Choisir la valeur de la vitesse du vaisseau devrait pouvoir se faire :
- lors de la création du vaisseau : en passant en paramètre du constructeur la valeur souhaitée
- ou à n'importe quel moment via un setteur qui permettrait alors de sélectionner une nouvelle vitesse.
Actuellement, nous n'avons pas besoin de modifier la vitesse du vaisseau une fois le jeu lancé (peut-être cela sera-t-il nécessaire plus tard lorsque vous envisagerez d'implémenter de nouvelles options dans notre jeu).
Pour l'instant, seul le réglage de la vitesse du Vaisseau via le constructeur nous est donc utile.
Pour le mettre en place, il suffit d'ajouter un nouveau constructeur à trois paramètres, dont le dernier paramètre correspond à la vitesse
:
public Vaisseau(Dimension dimension, Position positionOrigine, int vitesse) {
this.dimension = dimension;
this.origine = positionOrigine;
this.vitesse = vitesse;
}
Au passage, vous pouvez faire en sorte que le constructeur précédent (à deux paramètres) appelle désormais ce nouveau constructeur en lui passant une vitesse par défaut de 1
:
public Vaisseau(Dimension dimension, Position positionOrigine) {
this(dimension, positionOrigine, 1);
}
Vous venez de faire des modifications dans votre code...
N'oubliez pas de relancer les tests pour vérifier que le comportement de votre code n'a pas changé !
Les tests doivent continuer de passer AU VERT !
Remarque : Certes, nous n'avons pas encore écrit de code pour le constructeur à 3 paramètres, mais nous allons l'utiliser dès le prochain test 😄
Il est donc temps de reprendre une approche TDD et de tester un nouveau comportement de l'application. Pour commencer, ce sera le déplacement vers la droite...
Prenons l'exemple d'un déplacement normal vers la droite d'un vaisseau qui irait à une vitesse de 3
, et qui après son déplacement se trouverait toujours dans l'espace de jeu.
Cet exemple pourrait être illustré par le test suivant :
public void test_VaisseauAvance_DeplacerVaisseauVersLaDroite_AvecVitesse() {
spaceinvaders.positionnerUnNouveauVaisseau(new Dimension(3,2),new Position(7,9),3);
spaceinvaders.deplacerVaisseauVersLaDroite();
assertEquals("" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"..........VVV..\n" +
"..........VVV..\n" , spaceinvaders.recupererEspaceJeuDansChaineASCII());
}
Implémentez ce test dans la classe de SpaceInvadersTest
.
Une erreur de compilation apparaît puisque pour l'instant la méthode positionnerUnNouveauVaisseau
est une méthode à deux paramètres uniquement.
Nous allons transformer la signature de cette méthode automatiquement à l'aide de l'IDE pour effectuer ce changement le plus rapidement possible et le plus surement possible.
Sélectionnez positionnerUnNouveauVaisseau
puis à l'aide d'un clic droit choisissez Refactor -> Change Method Signature
.
Vérifiez que vous voyez bien l'onglet Parameters
et cliquez sur Add
:
- dans
Type
, saisissezint
- dans
Name
, saisissezvitesse
et - dans
Default Value
, saisissez1
Peut-être avez-vous remarqué que la signature de la méthode se mettait à jour automatiquement dans le rubrique Methode Signature Preview au fur et à mesure que vous complétiez les différentes cases du tableau.
Cliquez sur OK
.
Consultez le fichier SpaceInvadersTest
et constatez que tous les appels à la méthode positionnerUnNouveauVaisseau
à deux paramètres ont automatiquement été transformés en appels à la méthode positionnerUnNouveauVaisseau
à 3 paramètres avec une valeur de 1
pour le dernier paramètre.
Consultez le fichier SpaceInvaders
et constatez que la signature de la méthode positionnerUnNouveauVaisseau
est désormais constituée de 3 paramètres d'entrée.
Pour que cette méthode tienne compte la vitesse
, il faut modifier son code et faire en sorte qu'il fasse désormais appel au constructeur à 3 paramètres. Pour cela, remplacez les deux instructions suivantes en fin de méthode :
vaisseau = new Vaisseau(longueurVaisseau, hauteurVaisseau);
vaisseau.positionner(x, y);
par l'instruction suivante :
vaisseau = new Vaisseau(dimension,position,vitesse);
Enregistrez et relancez vos tests !
Ils devraient normalement passer AU VERT car le test ajouté précédemment n'apporte en réalité aucun comportement nouveau.
En effet, ce comportement était déjà testé par la méthode test_VaisseauAvance_DeplacerVaisseauVersLaDroite
que vous pouvez supprimer pour ne conserver que la méthode de test que vous venons d'écrire (test_VaisseauAvance_DeplacerVaisseauVersLaDroite_AvecVitesse
) et qui apporte le même comportement, mais de manière plus générique 😄
Renommez d'ailleurs maintenant test_VaisseauAvance_DeplacerVaisseauVersLaDroite_AvecVitesse
en test_VaisseauAvance_DeplacerVaisseauVersLaDroite
.
Vous venez de faire des modifications dans votre code...
N'oubliez pas de relancer les tests pour vérifier que le comportement de votre code n'a pas changé !
Il est maintenant temps de s'interesser aux cas limites. Deux cas limites peuvent être envisagés :
-
le premier concerne le cas où le vaisseau doit rester immobile car déjà en butée sur la droite de l'espace de jeu.
-
le second concerne le cas où le vaisseau peut se déplacer partiellement car il lui reste encore un peu de place avant d'atteindre la bordure droite de l'espace de jeu. Dans ce cas, le vaisseau peut se déplacer un peu jusqu'à la bordure droite, mais il faut bien veiller à ce que le vaisseau ne sorte pas de l'espace de jeu 😄
Ce premier cas correspond, dans le cas d'une vitesse égale à 1
, au cas de test suivant :
@Test
public void test_VaisseauImmobile_DeplacerVaisseauVersLaDroite() {
spaceinvaders.positionnerUnNouveauVaisseau(new Dimension(3,2),new Position(12,9), 1);
spaceinvaders.deplacerVaisseauVersLaDroite();
assertEquals("" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"............VVV\n" +
"............VVV\n" , spaceinvaders.recupererEspaceJeuDansChaineASCII());
}
Nous n'allons donc pas écrire de nouveau test, mais rendre le comportement de ce test plus générique en testant une vitesse de valeur 3
(à la place de 1
), ce qui revient à transformer le première instruction de la manière suivante :
spaceinvaders.positionnerUnNouveauVaisseau(new Dimension(3,2),new Position(12,9), 3);
Enregistrez et relancez les tests ! Les tests devraient passer AU VERT !
Ce cas concerne celui où le vaisseau peut se déplacer partiellement car il lui reste encore un peu de place avant d'atteindre la bordure droite de l'espace de jeu. Le déplacement est partiel car il correspond seulement à une partie des pixels utilisés dans un déplacement actuel car il faut bien sur faire en sorte que le vaisseau ne sorte pas de l'espace de jeu 😄
Un tel cas de test n'était pas pris en compte lorsque la vitesse était fixée à 1
.
Il faut donc écrire un nouveau test qui va apporter un nouveu comportement, celui du déplacement partiel et nous permettre ainsi de démarrer une nouvelle itération TDD.
Le test suivant permet, par exemple, d'illustrer un déplacement partiel vers la droite.
@Test
public void test_VaisseauAvancePartiellement_DeplacerVaisseauVersLaDroite() {
spaceinvaders.positionnerUnNouveauVaisseau(new Dimension(3,2),new Position(10,9),3);
spaceinvaders.deplacerVaisseauVersLaDroite();
assertEquals("" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"............VVV\n" +
"............VVV\n" , spaceinvaders.recupererEspaceJeuDansChaineASCII());
}
Implémentez ce test dans votre fichier de tests et lancez les tests !
La barre est ROUGE : ce test échoue donc il apporte bien un nouveau comportement 😄
Pour faire passer le test, il faut intervenir dans la méthode deplacerVaisseauVersLaDroite
de la classe SpaceInvaders
afin de rectifier éventuellement le déplacement et repositionner le vaisseau sur la bordure droite de l'espace jeu, si celui-ci est sorti de l'espace jeu lors d'un déplacement autorisé, mais qui ne pouvait pas être total par manque de place.
Au niveau du code, cela pourrait se traduire par quelque chose comme :
public void deplacerVaisseauVersLaDroite() {
if (vaisseau.abscisseLaPlusADroite() < (longueur - 1)) {
vaisseau.seDeplacerVersLaDroite();
if (!estDansEspaceJeu(vaisseau.abscisseLaPlusADroite(), vaisseau.ordonneeLaPlusHaute())) {
vaisseau.positionner(longueur - vaisseau.longueur(), vaisseau.ordonneeLaPlusHaute());
}
}
}
Relancez les tests !
Nous ne ferons pas de refactoring pour le moment...
Il ne nous reste plus qu'à traiter de même le déplacement vers la gauche afin que ce dernier tienne également compte de la vitesse
Nous allons directement modifier le test test_VaisseauAvance_DeplacerVaisseauVersLaGauche
pour qu'il illustre un cas plus générique de déplacement vers la gauche.
Remplacez l'implémentation actuelle de votre méthode test_VaisseauAvance_DeplacerVaisseauVersLaGauche
par cette implémentation :
@Test
public void test_VaisseauAvance_DeplacerVaisseauVersLaGauche() {
spaceinvaders.positionnerUnNouveauVaisseau(new Dimension(3,2),new Position(7,9), 3);
spaceinvaders.deplacerVaisseauVersLaGauche();
assertEquals("" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"....VVV........\n" +
"....VVV........\n" , spaceinvaders.recupererEspaceJeuDansChaineASCII());
}
Lancez-les tests et vérifiez qu'ils continuent à bien passer au VERT !
Comme précédemment deux cas limites vont être testés :
- le premier où le vaisseau doit rester immobile car déjà en butée sur la gauche de l'espace de jeu.
- le second où le vaisseau peut se déplacer partiellement
Placez-vous sur le test test_VaisseauImmobile_DeplacerVaisseauVersLaGauche
et rendez son comportement plus générique en testant cette fois-ci une vitesse de valeur 3
, ce qui revient à écrire le test suivant :
@Test
public void test_VaisseauImmobile_DeplacerVaisseauVersLaGauche() {
spaceinvaders.positionnerUnNouveauVaisseau(new Dimension(3,2),new Position(0,9), 3);
spaceinvaders.deplacerVaisseauVersLaGauche();
assertEquals("" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"VVV............\n" +
"VVV............\n" , spaceinvaders.recupererEspaceJeuDansChaineASCII());
}
Enregistrez votre modification et relancez les tests ! Les tests devraient passer AU VERT !
Comme précédemment, il est nécessaire d'écrire un nouveau test pour tester ce cas limite.
Le test suivant permet, par exemple, d'illustrer un déplacement partiel vers la gauche.
@Test
public void test_VaisseauAvancePartiellement_DeplacerVaisseauVersLaGauche() {
spaceinvaders.positionnerUnNouveauVaisseau(new Dimension(3,2),new Position(1,9), 3);
spaceinvaders.deplacerVaisseauVersLaGauche();
assertEquals("" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"...............\n" +
"VVV............\n" +
"VVV............\n" , spaceinvaders.recupererEspaceJeuDansChaineASCII());
}
Implémentez ce test dans votre fichier de tests et lancez les tests !
La barre est ROUGE : ce test échoue donc il apporte bien un nouveau comportement 😄
Pour faire passer le test, il faut intervenir dans la méthode deplacerVaisseauVersLaGauche
de la classe SpaceInvaders
afin de rectifier éventuellement le déplacement et repositionner le vaisseau sur la bordure gauche de l'espace jeu, si celui-ci est sorti de l'espace jeu lors d'un déplacement autorisé, mais qui ne pouvait pas être total par manque de place.
Au niveau du code, cela pourrait se traduire par quelque chose comme :
public void deplacerVaisseauVersLaGauche() {
if (0 < vaisseau.abscisseLaPlusAGauche())
vaisseau.seDeplacerVersLaGauche();
if (!estDansEspaceJeu(vaisseau.abscisseLaPlusAGauche(), vaisseau.ordonneeLaPlusHaute())) {
vaisseau.positionner(0, vaisseau.ordonneeLaPlusHaute());
}
}
Relancez les tests !
Nous ne ferons pas de refactoring pour le moment...
Par contre maintenant, nous aimerions bien profiter de cette nouvelle fonctionnalité et faire avancer plus vite notre vaisseau dans l'application graphique, pour cela quelques interventions sont encore nécessaires :
- d'une part faire en sorte que le moteur graphique fasse bien apparaître un vaisseau avec une vitesse donnée
- d'autre part trouver la bonne valeur pour la vitesse de votre vaisseau, celle qui sera le mieux adaptée à votre application.
Pour modifier la vitesse du vaisseau qui apparaît dans l'application graphique il faut modifier l'appel à la méthode initialiserJeu
de la classe SpaceInvaders
et faire en sorte que la méthode positionnerUnNouveauVaisseau
prenne en paramètre la vitesse souahitée.
Pour faciliter le réglage de ce paramètre, nous utiliserons une nouvelle constante VAISSEAU_VITESSE
dont la valeur sera arbitrairement fixée à 5
:
La méthode initialiserJeu
de la classe SpaceInvaders
devient :
public void initialiserJeu() {
Position positionVaisseau = new Position(this.longueur/2,this.hauteur-1);
Dimension dimensionVaisseau = new Dimension(Constante.VAISSEAU_LONGUEUR, Constante.VAISSEAU_HAUTEUR);
positionnerUnNouveauVaisseau(dimensionVaisseau, positionVaisseau, Constante.VAISSEAU_VITESSE);
}
Et la classe Constante
:
public class Constante {
public static final int ESPACEJEU_LONGUEUR = 150;
public static final int ESPACEJEU_HAUTEUR = 100;
public static final int VAISSEAU_LONGUEUR = 30;
public static final int VAISSEAU_HAUTEUR = 20;
public static final int VAISSEAU_VITESSE = 5;
public static final char MARQUE_FIN_LIGNE = '\n';
public static final char MARQUE_VIDE = '.';
public static final char MARQUE_VAISSEAU = 'V';
}
Il ne vous reste plus qu'à lancer le moteur graphique et à faire quelques essais de paramétrage pour trouver la bonne valeur de vitesse pour votre vaisseau !
La fonctionnalité Choisir la vitesse du vaisseau est semble-t-elle terminée et fonctionnelle : il est temps de committer les derniers changements dans votre gestionnaire de version !
Le commit pourrait refléter les changements apportés au travers du message suivant par exemple : ajout vitesse du vaisseau.