Fil d'Ariane
Mis à jour le vendredi 27 janvier 2017
  • 15 heures
  • Facile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Ce cours existe en eBook.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !

TP : Un jeu de combat (RPG)

Connectez-vous ou inscrivez-vous pour bénéficier de toutes les fonctionnalités de ce cours !

Et on terminera ce cours par un exercice pratique ! Les divers exemples que j’ai pu citer pour les différentes notions ne vous sont pas forcément pertinents. Que diriez-vous si vous saviez qu'avec ce que vous avez appris, vous pouvez réaliser un petit jeu de combat en orienté objet ? :p

On va rester très soft, le jeu se fera une fois de plus uniquement en console. Il s’agit d’un genre de sujet de TP qu’on peut souvent retrouver pour des exercices sur la POO, car il est souvent intéressant pour les développeurs.

L’énoncé

Le but de ce TP est de nous familiariser avec la POO. Vous allez créer des classes et les utiliser pour gérer un jeu de combat.

Principe du jeu

J’ai décidé de vous faire réaliser un jeu très simple :

  • Il n’y aura que deux joueurs qui pourront jouer.

  • Au début, chaque joueur pourra choisir la classe de son personnage (Archer avec pour arme un arc,Guerrier avec pour arme une épée ouMage avec pour arme un sceptre).

  • Pour chaque tour, les deux joueurs vont pouvoir réaliser une action à tour de rôle : soit attaquer son adversaire et lui faire perdre de la vie, soit améliorer son arme pour infliger plus de dégâts.

  • Le premier joueur qui a une vie à 0 a perdu !

On aura donc deux classes utiles ici :Personnage  etArme.

Classe Personnage

Un personnage devra pouvoir disposer d’un nom, d'une classe (Archer,Guerrier ouMage), d’une arme et d’une vie.

Inutile de créer des classesArcher,Guerrier ouMage. J'aimerais que pour cela vous m'utilisiez une énumération Classe qui représentera ces cas. 

La gestion de la vie pourra se faire sur une jauge de vie. Par exemple, une vie à 100 représente une jauge pleine de vie. Une jauge à 0 voudra dire que le personnage est mort. 

Un personnage devra aussi pouvoir jouer un tour de jeu, recevoir des dégâts ou en infliger à son adversaire en l’attaquant ou encore améliorer son arme en augmentant la capacité des dégâts de l’arme.

Enfin, à chaque tour, j’aimerais que vous m’affichiez le nom du personnage, sa jauge de vie et les dégâts que provoque son arme grâce à une méthode de la classePersonnage.

Classe Arme

Cette classe est toute simple, elle servira juste à indiquer le nombre de dégâts que cette arme inflige. C’est avec ces dégâts que le personnage perd de la vie. On pourrait s'en passer, mais j'aimerais bien voir des classesArc,Epee  etSceptre qui hériteront deArme. C'est dans ces trois dernières classes que j'aimerais que vous me précisiez les dégâts. 

Vous pourrez toujours définir un attribut à Personnage de typeArme. Rien ne vous empêchera de fournir à votre objet Personnage un objet de typeArc ou encoreEpee  car ces deux derniers sont desArme. Ce genre de code est donc possible :

let epee: Arme = Epee()

Ré-utilisez la fonctioninput() du premier TP !

Vous vous souvenez de la fonctioninput()  que je vous ai fourni dans le premier TP ? Servez-vous en aussi dans ce TP pour interagir avec l’utilisateur. Placez-là au même endroit, c'est-à-dire tout en haut de votre fichiermain.swift.

Trace d’exécution d’une partie

Voici un exemple de trace d’exécution d’une partie.

Classe du personnage 1 :
1. Archer
2. Guerrier
3. Mage
1

Classe du personnage 2 :
1. Archer
2. Guerrier
3. Mage
3

Nom : Personnage 1
Vie : 90
Dégâts de son arme : 12
Quel choix ?
1. Attaquer
2. Améliorer son arme
1

Nom : Personnage 2
Vie : 68
Dégâts de son arme : 15
Quel choix ?
1. Attaquer
2. Améliorer son arme
2

Nom : Personnage 1
Vie : 90
Dégâts de son arme : 12
Quel choix ?
1. Attaquer
2. Améliorer son arme
1

Nom : Personnage 2
Vie : 56
Dégâts de son arme : 20
Quel choix ?
1. Attaquer
2. Améliorer son arme
2

Nom : Personnage 1
Vie : 90
Dégâts de son arme : 12
Quel choix ?
1. Attaquer
2. Améliorer son arme
1

Nom : Personnage 2
Vie : 44
Dégâts de son arme : 25
Quel choix ?
1. Attaquer
2. Améliorer son arme
2

Nom : Personnage 1
Vie : 90
Dégâts de son arme : 12
Quel choix ?
1. Attaquer
2. Améliorer son arme
1

Nom : Personnage 2
Vie : 32
Dégâts de son arme : 30
Quel choix ?
1. Attaquer
2. Améliorer son arme
1

Nom : Personnage 1
Vie : 60
Dégâts de son arme : 12
Quel choix ?
1. Attaquer
2. Améliorer son arme
1

Nom : Personnage 2
Vie : 20
Dégâts de son arme : 30
Quel choix ?
1. Attaquer
2. Améliorer son arme
1

Nom : Personnage 1
Vie : 30
Dégâts de son arme : 12
Quel choix ?
1. Attaquer
2. Améliorer son arme
1

Nom : Personnage 2
Vie : 8
Dégâts de son arme : 30
Quel choix ?
1. Attaquer
2. Améliorer son arme
1

Le vainqueur est Personnage 2 !
Program ended with exit code: 0

Correction

Comme pour le précédent TP, je fournis ma solution. Solution qui, je le rappelle, n’est pas forcément la meilleure, mais la plus simple à mon goût. 

Ma solution

Personnage.swift

class Personnage {
    var nom: String
    let classe: Classe
    var arme: Arme
    var vie: Int
    
    init(nom: String, classe: Classe) {
        self.nom = nom
        self.classe = classe
        
        switch self.classe {
        case .Archer:
            self.arme = Arc()
            self.vie = 90
        case .Guerrier:
            self.arme = Epee()
            self.vie = 110
        case .Mage:
            self.arme = Sceptre()
            self.vie = 80
        }
    }
    
    func jouer(personnage: Personnage) {
        var choixUtilisateur: Int
        
        // On affiche d'abord les caractéristiques du personnages qui doit jouer
        self.description()
        
        // On boucle tant qu'il n'a pas choisi un de ces deux solutions
        repeat {
            print("Quel choix ?")
            print("1. Attaquer")
            print("2. Améliorer son arme")
            choixUtilisateur = input()
            print()
        } while choixUtilisateur != 1 && choixUtilisateur != 2
        
        // On réalise l'action demandée
        if choixUtilisateur == 1 {
            self.attaquer(personnage: personnage)
        } else {
            self.ameliorerArme()
        }
    }
    
    func recevoirDegats(degats: Int) {
        self.vie = self.vie - degats
        
        // Si la vie est négatif, on la met tout simplement à 0 pour dire que le personnage est mort
        if self.vie < 0 {
            self.vie = 0
        }
    }
    
    func attaquer(personnage: Personnage) {
        personnage.recevoirDegats(degats: self.arme.degats)
    }
    
    func ameliorerArme() {
        self.arme.degats += 5
    }
    
    // Affiche la description de notre personnage à l'écran
    func description() {
        print("Nom : " + self.nom)
        print("Vie : \(self.vie)")
        print("Dégâts de son arme : \(self.arme.degats)")
    }
}

Arme.swift

class Arme {
    var degats: Int

    init(degats: Int) {
        self.degats = degats
    }
}

Arc.swift

class Arc: Arme {
    init() {
        super.init(degats: 12)
    }
}

Epee.swift

class Epee: Arme {
    init() {
        super.init(degats: 10)
    }
}

Sceptre.swift

class Sceptre: Arme {
    init() {
        super.init(degats: 15)
    }
}

Classe.swift

enum Classe {
    case Archer, Guerrier, Mage
}

main.swift

import Foundation

// Fonction permettant de demander à l'utilisateur d'entrer un nombre entier
func input() -> Int {
    let strData = readLine();
    
    return Int(strData!)!
}

// Variables qui serviront à déterminer les choix des deux joueurs
var choixUtilisateur1: Int
var choixUtilisateur2: Int

// On demande quel type de personnage les joueurs veulent être
repeat {
    print("Classe du personnage 1 :")
    print("1. Archer")
    print("2. Guerrier")
    print("3. Mage")
    choixUtilisateur1 = input()
} while choixUtilisateur1 != 1 && choixUtilisateur1 != 2 && choixUtilisateur1 != 3

print()

repeat {
    print("Classe du personnage 2 :")
    print("1. Archer")
    print("2. Guerrier")
    print("3. Mage")
    choixUtilisateur2 = input()
} while choixUtilisateur2 != 1 && choixUtilisateur2 != 2 && choixUtilisateur2 != 3

// Grâce à l'énumération Classe, on va pouvoir préciser la classe des personnages des joueurs
var classe1: Classe!
switch choixUtilisateur1 {
case 1:
    classe1 = .Archer
case 2:
    classe1 = .Guerrier
case 3:
    classe1 = .Mage
default:
    break
}

var classe2: Classe!
switch choixUtilisateur2 {
case 1:
    classe2 = .Archer
case 2:
    classe2 = .Guerrier
case 3:
    classe2 = .Mage
default:
    break
}

// Création des deux personnages
// Grâce au constructeur, tout est initialisé déjà (vie et arme)
var personnage1 = Personnage(nom: "Personnage 1", classe: classe1)
var personnage2 = Personnage(nom: "Personnage 2", classe: classe2)

// On joue tant que les deux personnages sont en vie
while personnage1.vie > 0 && personnage2.vie > 0 {
    personnage1.jouer(personnage: personnage2)
    
    // Cette condition permet de ne pas faire jouer le deuxième personnage s'il est mort
    if personnage2.vie > 0 {
        personnage2.jouer(personnage: personnage1)
    }
}

// On trouve le vainqueur
var nomVainqueur: String

if personnage1.vie > 0 {
    nomVainqueur = personnage1.nom
} else {
    nomVainqueur = personnage2.nom
}

// On l'affiche !
print("Le vainqueur est " + nomVainqueur + " !")

Explications

J’ai commenté le code de façon à ce que vous puissiez voir ce que fait précisément chaque morceau de code.

Personnage.swift

Lorsque l’on instancie unPersonnage, on n'a besoin que de son nom et de sa classe. Par la suite, un personnage possède par défaut une arme qui inflige un certain nombre de dégâts selon sa classe et possède une vie qui elle aussi dépend de sa classe. Par exemple, unArc cause des dégâts de 12 et un archer possède une vie de 90.

Pour améliorer l’arme du personnage, j’accède à son attribut arme qui est un objet. Grâce à ça, je peux donc accéder à l’attributdegats de son arme et donc augmenter de 5 ses dégâts.

Arme.swift, Arc.swift, Epee.swift, Sceptre.swift

La classe mèreArc.swift possède uniquement un attribut degats qui permettra de connaitre les dégâts de l'arme et un constructeur qui permet d'initialiser les dégâts de l'arme. Ainsi, les autres classes qui héritent de Arme n'ont plus qu'à utliser le constructeur deArme  pour définir les dégâts de l'arme en question.

main.swift

Vous devriez être capable de comprendre tout ce morceau de code. Néanmoins, j'aimerais revenir sur ce morceau de code :

var classe1: Classe!
switch choixUtilisateur1 {
case 1:
    classe1 = .Archer
case 2:
    classe1 = .Guerrier
case 3:
    classe1 = .Mage
default:
    break
}

D'abord le switch,choixUtilisateur1  est unInt . De ce fait, avec ce switch on ne teste pas TOUS les cas que peut prendre comme valeur cette variable. Avec la précédente boucle, nous ne sommes pas censés avoir d'autres valeurs que 1, 2 ou 3 pourtant. Mais le switch fonctionne comme tel et Swift veut que lors d'un switch, on teste tous les cas possible, d'où la présence dudefault:. Lebreak ici est une astuce pour dire qu'on ne veut rien faire et qu'on souhaite tout simplement sortir du switch.

De ce fait, si on suit la logique de ce switch, si jamais on venait à passer dans le casdefault: alorsclasse1 ne vaudrait strictement rien du tout et Swift va savoir que par la suite cette variable peut ne rien contenir ! Or, grâce à notre boucle qui permet de définir le choix de l'utilisateur, on est absolument sûr de définir une classe à notre joueur. D'où la présence du point d'exclamation. Essayez de le retirer, et voyez les erreurs que Xcode vous fournit, vous comprendrez peut-être mieux.

Pas grand-chose de compliqué dans cet exercice. Si vous n’avez pas compris ces morceaux de code, alors prenez le temps de les relire et de relire aussi les chapitres du cours que vous n’avez pas compris (pour ce TP surtout les deux premiers chapitres). Effectuez divers tests aussi, pour vous assurer d’avoir compris.

Améliorations possibles

Vous vous doutez que ce jeu de combat n’est pas parfait et loin de là ! Je vous propose quelques pistes d’améliorations possibles, que vous devriez être capable de faire et qui demandent l’utilisation de notions autres que vues dans ce TP.

  • Créer un protocolePersonnageProtocol. À vous de trouver à quoi doit se conformer unPersonnage.

  • Dans le main, essayez de faire en sorte que plusieurs joueurs puissent jouer. Il sera plus judicieux de stocker vos personnages dans un tableau cette fois-ci. Vous devrez connaître le nombre de joueurs et grâce à une boucle, vous allez déclarer vos personnages un à un et les ajouter à votre tableau.

  • Ajoutez un nouvel attribut à votre classe Arme pour définir de combien on peut améliorer l'arme (pas toujours que 5 !).

Utilisez votre imagination pour rendre votre jeu encore plus complet !

C’est ici que s’achèvera ce cours. J’espère que vous aurez beaucoup appris et que surtout, vous avez maintenant l’envie de vous mettre au développement iOS ou encore OS X. Vous avez maintenant les capacités pour comprendre un code source en langage Swift. Vous ne serez donc pas fortement dépaysé à l’avenir.

Je remercie les divers lecteurs qui m'ont lu, qui m'ont encouragés, qui m'ont fait des critiques ou suggestions. Cela m'a permis d'améliorer la qualité de ce cours et m'a fourni une énorme motivation à finir la production de ce cours.

Je remercie aussi Laurène qui s'est occupé de la validation de mon cours. Efficacité, aide, présence et rapidité étaient au rendez-vous, c'est top !

Sur ce, je vous souhaite bon courage pour la suite. :)

Exemple de certificat de réussite
Exemple de certificat de réussite