Aller au contenu

Exercices en POO

Consignes

  • Ne pas se faire aider par des IA ou genAI

Série 1

  1. Définir une classe Personne avec les attributs nom, prenom et age. Ajouter une méthode afficher qui affiche les informations de la personne. Implémenter les méthodes __eq__ et __str__.
    • Créer quelques personnes et les afficher..
    • Afficher le résultat du ==
  2. On souhaite gérer un collection de consoles et jeux rétro avec la POO. Définir les classes VideoGame et VideoGameConsole. Chaque classe propose les propriétés: name, releaseYear. La classe VideoGame a en plus la propriété developer qui est la compagnie ou le développeur qui a développé le jeu. La classe VideoGameConsole a en plus la propriété manufacturer qui est la compagnie qui a fabriqué la console. Implémenter la méthode __str__.
    • Créer une liste consoles qui contient les consoles suivantes avec les name, manufacturer et releaseYear suivants:
      • DegaDrive, Dega, 1992.
      • Satourne, Dega, 1995.
      • Super Nontendo, Nontendo, 1991.
      • Nontendo, Nontendo, 1983.
      • Ponystation, Pony, 1996.
    • Créer une liste games qui contient les jeux suivants avec les name, developer et releaseYear suivants:
      • Sanic, Dega, 1991.
      • Spodermin, Morvel, 1992.
      • Y-Men, Morvel, 1993.
      • Nario, Nontendo, 1985.
      • Zebda, Nontendo, 1986.
      • First Fantasy, Rectangle, 1987.
      • Paper Gear, Bonami, 1987.
    • A partir de la liste consoles, afficher:
      • La liste des consoles fabriquées par Nontendo.
      • La liste des consoles sorties à partir du 1er janvier 1990.
    • A partir de la liste games, afficher:
      • La liste des jeux développés par Morvel.
      • La liste des jeux sortis à partir du premier janvier 1985.
  3. Définir une classe Point qui possède deux propriétés x et y de type correspondant aux coordonnées du point.
    • Implémenter le constructeur __init__(self, x, y) qui permet d'initialiser les coordonnées.
    • Implémenter la méthode __str__ qui représente le point de cette façon (ici, x = 2 et y = 3): Point | x : 2 | y : 3
    • Implémenter la méthode translate(self, tx, ty) qui ajoute tx à x et ty à y.
    • Implémenter la méthode distance(self, point) qui retourne la distance entre le point courant et le point passé en paramètre et retourne cela sous forme d'un point.
    • Instancier deux points et :
      • Afficher la distance entre les deux points.
      • Translater le premier point de 2 en x et 3 en y.
      • Afficher les deux points.
      • Afficher la nouvelle distance entre les deux points.
  4. Définir une classe BankAccount modélisant un compte en banque. La classe possède deux propriétés initialisées dans le constructeur. balance correspond au solde du compte. managementCost qui correspond au frais de gestion du compte.
    • Implémenter le constructeur __init__(...).
    • Implémenter la méthode __str__(self) qui affiche les informations du compte de cette façon: Compte ( solde: 1000€ | frais de gestion: 13€ )
    • Implémenter la méthode debit(self, amount) qui enlève amount au solde du compte uniquement si le solde est suffisant. Elle retourne un booléen qui renvoie true si le débit a réussi, sinon false.
    • Implémenter la méthode send(self, bankAccount, amount) qui transfère de l'argent vers un autre compte uniquement si le solde est suffisant. La méthode retourne un booléen calculé de la même façon que la méthode debit.
    • Instancier deux comptes et effectuer des opérations de débit et de transfert. Afficher l'état de réussite de chaque opération et l'état des comptes après chaque opération.
  5. On souhaite générer des prédictions météorologiques sur une durée de 30 jours et faire des traitements dessus. Définir la classe WeatherForecast avec la propriété day (entier entre 1 et 30) et temperature (entier qui représente la température en Celsius).
    • Implémenter le constructeur
    • Implémenter la méthode to_farhenheit(self) qui retourne la température en Fahrenheit avec la formule suivante: F = C * 9/5 + 32
    • Implémenter la méthode __str__ qui affiche la prévision de cette façon: Jour 1: 20°C | 68°F
    • Instancier une liste de de 30 prévisions pour chacun des jours avec des températures aléatoires sous forme d'entier entre -10 et 40 degrés.
    • Calculer les résultats suivants sans utiliser avg, min, max.
      • La température moyenne sur les 30 jours.
      • La température la plus faible ainsi que les jours où cette température a été enregistrée. Par exemple si la température la plus faible est -10, on doit afficher les jours où -10 a été enregistré.
      • La température la plus élevée ainsi que les jours où cette température a été enregistrée.
      • Afficher les prévisions triées par température croissante avec et sans utiliser les méthodes de tri prédéfinies.
      • Afficher les prévisions triées par température décroissante avec et sans utiliser les méthodes de tri prédéfinies.
      • Le nombre de jours où la température est supérieure à 20°C et le nombre de jours où la température est inférieure à 0°C.
      • Un dictionnaire qui compter le nombre prévisions qui ont les mêmes températures. Par exemple si 5 jours ont une température de 20°C, et les reste des jours ont -5°C le dictionnaire sera {"20°C": 5, "-5°C": 25}.
  6. Générateur de personnage RPG et un simulateur de combat.
    • Définir la classe Character avec les attributs name, level, attack et health. Implémenter une méthode attack(self, target) (où target est un Character) qui diminue la santé de la cible. Ajouter une méthode is_alive(self) qui retourne True si le personnage est vivant, False sinon. Implémenter le constructeur qui initialise les attributs name, level, attack (entier aléatoire entre 1 et 5) et health (entier aléatoire entre 10 et 20). Implémenter une méthode heal(self) qui rétablit health de 5 + level // 10 (// est la division entière).
    • Définir la fonction fight(character1, character2) qui simule un combat entre deux personnages. Le combat se déroule de la façon suivante:
      • Tant que les deux personnages sont vivants, ils s'attaquent à tour de rôle.
      • Le personnage 1 attaque le personnage 2, puis le personnage 2 attaque le personnage 1.
      • L'attaque de p1 envers p2 diminue le health de p2 avec la formule suivante p2.health - p1.attack * (réel aléatoire entre 0.75 et 1.25).
      • Le déroulement du combat est affiché au fur et à mesure.
      • health doit toujours être >= 0
    • Définir la fonction fight_alternative(character1, character2) qui reprend le même algo que fight, mais permet au joueur actuel d'appeler sa méthode heal en plus d'attaquer. La méthode heal est appelée si un nombre aléatoire en 50 et 100 généré au moment où son tour débute est strictement inférieur à son (numéro du tour * level) / health. Par exemple, si le joueur 1 a un niveau de 5 et 2 points de vie et qu'au tour 30, le nombre aléatoire est 60, alors le joueur 1 peut se soigner car (30 * 5) / 2 = 75 est supérieur à 60. Par contre, si le nombre aléatoire généré était de 75 ou plus, alors le joueur ne peut pas se soigner.
  7. Définir une classe Book avec les propriétés suivantes Python: nb_pages: number,title: string,author: string,isbn: string (ISBN (The International Standard Book Number)) et marked_page: number.
    • Dans la classe Book, définir une méthode mark_page(self, page) qui permet de mettre à jour la valeur de marked_page avec la valeur de l'argument page. ⚠ bien vérifier que page soit < à nb_pages.
    • Créer une classe Library (Bibliothèque) qui contient un tableau de livres qui est passé au constructeur. ⚠ Il y'a une relation entre Library et Book mais ce n'est pas une relation d'héritage. Pour ce cas, on dit que c'est une agrégation.
    • Dans la classe Library, définir une méthode list_authors() qui retourne un tableau contenant uniquement les noms des auteurs.
    • Dans la classe Library, définir une méthode sum_of_marked_pages() qui retourne la somme des marked_page de tous les livres.
    • Instancier une Library avec trois livres
    • Afficher les résultats des appels des méthodes list_authors() et sum_of_marked_pages().
    • Est-ce que la classe Book peuvent exister et être utilisée indépendamment de Library ?
Solution Person
class Person:
  def __init__(self, first_name: str, last_name: str, age: int):
    self.first_name = first_name
    self.last_name = last_name
    self.age = age

  def __str__(self):
    return f"Person(name: {self.first_name} {self.last_name}, age: {self.age})"

  def __eq__(self, other: object) -> bool:
    return isinstance(other, Person) and self.first_name == other.first_name \
      and self.last_name == other.last_name and self.age == other.age


p1 = Person("Ram", "Bo", 40)
p2 = Person("Ryuuji", "Itadori", 17)
p3 = Person("Ryuuji", "Itadori", 17)

print(p1)
print(p2)
print(p3)

print(p1 == 10)
print(p1 == p2)
print(p2 == p3)
Solution Retro games and consoles
class VideoGameConsole:
  def __init__(self, name: str, manufacturer: str, release_year: int):
    self.name = name
    self.release_year = release_year
    self.manufacturer = manufacturer

  def __str__(self):
    return f"The {self.name} was release in {self.release_year} and manufactured by {self.manufacturer}"

  def __repr__(self):
    return self.__str__()

class VideoGame:
  def __init__(self, name: str, developer: str, release_year: int):
    self.name = name
    self.release_year = release_year
    self.developer = developer

  def __str__(self):
    return f"The game {self.name} was release in {self.release_year} and was developed by {self.developer}"

  def __repr__(self):
    return self.__str__()

consoles: list[VideoGameConsole] = [
  VideoGameConsole("DegaDrive", "Dega", 1992),
  VideoGameConsole("Satourne", "Dega", 1995),
  VideoGameConsole("Super Nontendo", "Nontendo", 1991),
  VideoGameConsole("Nontendo", "Nontendo", 1983),
  VideoGameConsole("Ponystation", "Pony", 1996)
]

games: list[VideoGame] = [
  VideoGame("Sanic", "Dega", 1991),
  VideoGame("Spodermin", "Morvel", 1992),
  VideoGame("Y-Men", "Morvel", 1993),
  VideoGame("Nario", "Nontendo", 1985),
  VideoGame("Zebda", "Nontendo", 1986),
  VideoGame("First Fantasy", "Rectangle", 1987),
  VideoGame("Paper Gear", "Bonami", 1987)
]

nontendo_consoles: list[VideoGameConsole] = []
ninety_consoles : list[VideoGameConsole] = []
for console in consoles:
  if console.manufacturer == "Nontendo":
    nontendo_consoles.append(console)
  if console.release_year >= 1990:
    ninety_consoles.append(console)

print("La liste des consoles fabriquées par Nontendo", nontendo_consoles)
# Syntaxe de la Compréhension
print("La liste des consoles fabriquées par Nontendo méthode 2", [c for c in consoles if c.manufacturer == "Nontendo"])

print("La liste des consoles >= 1990", ninety_consoles)
print("La liste des consoles >= 1990 méthode 2", [c for c in consoles if c.release_year >= 1990])

morvel_games: list[VideoGame] = []
eighty_five_consoles : list[VideoGame] = []
for game in games:
  if game.developer == "Morvel":
    morvel_games.append(game)
  if game.release_year >= 1985:
    eighty_five_consoles.append(game)

print("La liste des jeux développés par Morvel", morvel_games)
print("La liste des jeux développés par Morvel méthode 2", [g for g in games if g.developer == "Morvel"])

print("La liste des jeux >= 1990", eighty_five_consoles)
print("La liste des jeux >= 1990 méthode 2", [g for g in games if g.release_year >= 1985])

Série 2

  1. Donner deux exemples de relation d'héritage à un niveau.
    • Par exemple l'étudiant est une personne.
  2. Donner deux exemples de relation d'héritage à deux niveaux.
    • Par exemple : un téléphone est un appareil électronique, un appareil électronique est un appareil.
  3. Trouver les relations d'héritage possibles pour chacun de ses groupes de classes (traiter chaque ligne indépendamment):
    • Être vivant, Homme, Animal
    • Meuble, Fauteuil, Armoire, Table.
    • Aliment, Pâtisserie, Mille-Feuilles, Croissant, Pain, Fruit, Pomme, Banane
    • Aucun code python n'est demandé.
  4. Donner une classe parente pour chaque groupe de classes (traiter chaque ligne indépendamment):
    • Ordinateur portable, ordinateur fixe
    • Ordinateur portable, ordinateur fixe, Switch, Xbox, Playstation
    • Voiture, Camion, Vélo, Trottinette
  5. Un fermier élève des animaux et cultive des arbres fruitiers. Il a un nom et peut arroser. Chaque animal a une date de naissance et un état de santé. Chaque Arbre fruitier a une hauteur Le fermier a deux vaches, 4 poules, un pommier et deux poiriers. Définir les classes en UML et les coder en Python.
  6. Un garage répare des véhicules et vend des pièces. Il répare des voitures, des camions, des vélos et des trottinettes. Il vend des pneus, des moteurs, des batteries et des phares. Définir les classes en UML et les coder en Python.
  7. Répondre aux questions suivantes :
    • L'héritage correspondant à une relation être ou avoir ?
    • Donner deux avantages de l'héritage.
    • Est-ce que t est un objet, classe ou une instance t = Shell() ?
    • Que représente super() en Python ?
Solutions
  • Exemples de relation d'héritage à un niveau :
    • Personne hérite de Être vivant.
    • Homme hérite de Personne.
  • Exemples de relation d'héritage à deux niveaux :
    • Téléphone hérite de Appareil électronique qui hérite de Appareil.
    • Véhicule hérite de Moyen de transport qui hérite de Objet mouvant.
  • Relations d'héritage possibles :
    • Être vivant est une classe parente de Homme et Animal.
    • Meuble est une classe parente de Fauteuil, Armoire et Table.
    • Aliment est une classe parente de Pâtisserie, Fruit et Pain.
  • Classe parente pour chaque groupe de classes :
    • Ordinateur est une classe parente de Ordinateur portable et Ordinateur fixe.
    • Appareil électronique est une classe parente de Ordinateur portable, Ordinateur fixe, Switch, Xbox et Playstation.
    • Objet mouvant est une classe parente de Voiture, Camion, Vélo et Trottinette.
  • Questions
    • L'héritage correspondant à une relation être.
    • Deux avantages de l'héritage:
      • Réutilisation du code.
      • Factorisation du code.
    • t est une instance de la classe Shell ou un objet de type Shell.
    • super() permet d'appeler une méthode de la classe parente.

Fermier

Fermier
class Animal:
    def __init__(self, date_naissance, etat_sante):
        """etat_stante est égal à 0 si tout va bien et 1 si l'animal est malade"""
        self.date_naissance = date_naissance
        self.etat_sante = etat_sante

    def __repr__(self):
        return f"({self.__class__.__name__}: {self.date_naissance})"


class Vache(Animal):
    pass


class Poule(Animal):
    pass


class ArbreFruitier:
    def __init__(self, hauteur):
        self.hauteur = hauteur

    def __repr__(self):
        return f"({self.__class__.__name__}: {self.hauteur})"


class Pommier(ArbreFruitier):
    pass


class Poirier(ArbreFruitier):
    pass

class Fermier:
    def __init__(self, nom, animaux = [], arbres_fruitiers = []):
        self.nom = nom
        self.animaux = animaux
        self.arbres_fruitiers = arbres_fruitiers

    def arroser(self):
        print("💦🌱")

    def __str__(self):
        return f"{self.nom}. Animaux: {self.animaux}. Fruits: {self.arbres_fruitiers}"



animaux = [
    Vache("2024-01-10", 0),
    Vache("2023-05-11", 1),
    Poule("2001-06-02", 0),
    Poule("2020-06-24", 0),
    Poule("2023-05-11", 0),
    Poule("2023-05-11", 0),
]

arbres_fruitiers = [
    Pommier(10),
    Poirier(50),
    Poirier(25)
]

fermier = Fermier("Fave", animaux, arbres_fruitiers)
print(fermier)

Série 3

  1. Définir la classe StringUtils qui contient les méthodes statiques suivantes:
    • is_palindrome(word: str) -> bool qui retourne True si le mot est un palindrome, False sinon
    • count_vowels(word: str) -> int qui retourne le nombre de voyelles dans le mot
    • count_uppercase(word: str) -> int qui retourne le nombre de majuscules dans le mot
    • count_lowercase(word: str) -> int qui retourne le nombre de minuscules dans le mot
    • Appeler les différentes méthodes de cette classe
  2. Définir la classe TaxUtils qui contient les méthodes statiques suivantes:
    • compute_ttc(price_ht: float, vat_rate: float) -> float qui retourne le montant TTC
    • compute_tva(price: float, vat_rate: float) -> float qui retourne le montant de la TVA
    • Appeler les différentes méthodes de cette classe
  3. Définir la classe abstraite Shape qui contient les méthodes abstraites suivantes:
    • area() -> float qui retourne l'aire de la forme
    • perimeter() -> float qui retourne le périmètre de la forme
    • Définir les classes Rectangle, Circle et Triangle qui héritent de Shape et implémentent les méthodes abstraites.
Solutions
StringUtils.py
class StringUtils:

    @staticmethod
    def is_palindrome_method_1(s: str) -> bool:
        return s == s[::-1]

    @staticmethod
    def is_palindrome_method_2(s: str) -> bool:
        for i in range(len(s) // 2):
            if s[i] != s[-i-1]:
                return False
        return True

    @staticmethod
    def count_vowels_method_1(s: str) -> int:
        count = 0
        for c in s.lower():
            if c == "a" or c == "e" or c == "i" or c == "o" or c == "u" or c == "y":
                count += 1
        return count

    @staticmethod
    def count_vowels_method_2(s: str) -> int:
        count = 0
        for c in s.lower():
            if c in "aeiouy":
                count += 1
        return count

    @staticmethod
    def count_vowels_method_3(s: str) -> int:
        return len([c for c in s.lower() if c in "aeiouy"])

    @staticmethod
    def count_uppercase_method_1(s: str) -> int:
        count = 0
        upper_s = s.upper()
        for i in range(len(s)):
            # sans le deuxième test (le grand and), la méthode n'est très efficace car elle ne gère pes les string qui contiennt autre chose que des lettres
            if s[i] == upper_s[i] and ("A" <= s[i] <= "Z" or "a" <= s[i] <= "z"):
                count += 1 # équivalent à count = count + 1
        return count

    @staticmethod
    def count_uppercase_method_2(s: str) -> int:
        count = 0
        for i in range(len(s)):
            if s[i] in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
                count += 1 # équivalent à count = count + 1
        return count

    @staticmethod
    def count_uppercase_method_3(s: str) -> int:
        count = 0
        for i in range(len(s)):
            # ordre du dictionnaire de Français
            if "A" <= s[i] <= "Z":
                count += 1 # équivalent à count = count + 1
        return count


print(StringUtils.is_palindrome_method_1("Hello"))
print(StringUtils.is_palindrome_method_1("kayak"))

print(StringUtils.is_palindrome_method_2("Hello"))
print(StringUtils.is_palindrome_method_2("kayak"))

print(StringUtils.count_vowels_method_1("Python"), 
      StringUtils.count_vowels_method_2("Python"),
      StringUtils.count_vowels_method_3("Python"))

print(StringUtils.count_uppercase_method_1("I Love Python"), 
      StringUtils.count_uppercase_method_2("I Love Python"),
      StringUtils.count_uppercase_method_3("I Love Python"))

print("slicing")
numbers = [2, 5, -1, 6, 9, 11, 21]
print(numbers[::], numbers[::2], numbers[::-1], numbers[::-3])
print(numbers[1:5],numbers[1:], numbers[:4], numbers[:-2])
print(numbers[1:5:2], numbers[1:5:-1], numbers[5:1:-1], numbers[-2:0:-2])
GeometricShapes.py
from abc import ABC, abstractmethod
import math

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        pass

    @abstractmethod
    def perimeter(self) -> float:
        pass

class Rectangle(Shape):
    def __init__(self, length: float, width: float):
        super().__init__()
        self.lenght = length
        self.width = width

    def area(self) -> float:
        return self.width * self.lenght

    def perimeter(self) -> float:
        return (self.width + self.lenght) * 2

class Circle(Shape):
    def __init__(self, radius: float):
        super().__init__()
        self.radius = radius

    def area(self) -> float:
        return (self.radius ** 2) *  math.pi

    def perimeter(self) -> float:
        return self.radius * 2 * math.pi

class Triangle(Shape):
    """On suppose qu'on donne une base et une hauteur ainsi que les deux autres côté.
    A faire en exercice: se baser uniquement sur les côtés"""
    def __init__(self, b: float, h: float, c2: float, c3: float):
        super().__init__()
        self.b = b
        self.h = h
        self.c2 = c2
        self.c3 = c3

    def perimeter(self) -> float:
        return self.b + self.c2 + self.c3

    def area(self) -> float:
        return (self.b * self.h) / 2

shapes = (Rectangle(10, 2.5), Rectangle(9, 3), Circle(9), Circle(1.8), Triangle(10, 10, 10, 14.142))
for shape in shapes:
    print("area", shape.area(), "perimeter", shape.perimeter())

Série 4

  1. Nous souhaitons définir les classes d'un futur jeu MOBA qui va cartonner sévère. Le jeu sera en 2D en vue du dessus. Dans ce jeu nous aurons des héros qui affrontent des monstres.
    • Les héros et les monstres ont tous des HP (points de vie), des MP (points de magie), un nom, une position dans la carte et une hitbox (rectangle de détection des dégâts).
    • Un héro peut être soit un tank, soit un mage, soit un soigneur ou soit un guerrier. Chaque héro a un niveau et une compétence propre (le héros fait 10 dégâts, le mage fait 20 dégâts et perd 5 mp, le tank donne 2 de ses hp et le soigneur fait gagner 30 hp et perd 8 mp). Tous les héros commencent au niveau 1 avec des valeurs de HP et MP aléatoires compris entre 100 et 200 et entre 50 et 100 respectivement. Les guerriers commencent avec 0 MP.
    • Un monstre peut être soit un minion, soit un buldozer. Les monstres ont 0 MP et ont des hp aléatoires compris entre 30 et 70.
    • Représenter les différentes classes en UML et les coder en Python.
Solution moba

solution uml moba

import random
from abc import abstractmethod, ABC

class Point:
    def __init__(self, x, y) -> None:
        self.x = x
        self.y = y


class Rectangle:
    def __init__(self, p: Point, width: int, height: int) -> None:
        self.p = p
        self.width = width
        self.height = height


class Character:
    def __init__(self, name, hp, mp, position: Point, hitbox: Rectangle) -> None:
        self.name = name
        self.hp = hp
        self.mp = mp
        self.position = position
        self.hitbox = hitbox

    def __str__(self) -> str:
        """ " __nom__ -> dunder (jargon python)"""
        return (
            f"{self.__class__.__name__} (name={self.name}, hp={self.hp}, mp={self.mp})"
        )

    def __repr__(self):
        """print d'une liste appelle cette méthode au lieu de __str__"""
        return self.__str__()


class Hero(Character, ABC):
    def __init__(self, name, position, hitbox) -> None:
        hp = random.randint(100, 200)
        mp = random.randint(50, 100)
        super().__init__(name, hp, mp, position, hitbox)
        self.level = 1

    def attack(self, monster):
        monster.hp -= 2
        print(self, "inflige 2 de dégâts. Nouvel état du monstre", monster)


    @abstractmethod
    def perform_skill(self, character):
        print(
            self,
            "a appliqué une compétence a un personnage dont le nouvel état est:",
            character,
        )


class Warrior(Hero):
    def __init__(self, name, position, hitbox) -> None:
        super().__init__(name, position, hitbox)
        self.mp = 0

    def perform_skill(self, character):
        character.hp = max(character.hp - 10, 0)
        super().perform_skill(self, character)


class Mage(Hero):
    def perform_skill(self, character):
        if self.mp < 5:
            return
        character.hp = max(character.hp - 20, 0)
        self.mp -= 5
        super().perform_skill(character)


class Tank(Hero):
    def perform_skill(self, character):
        if self.hp < 2:
            return
        character.hp += 2
        self.hp -= 2
        super().perform_skill(character)


class Healer(Hero):
    def perform_skill(self, character):
        if self.mp < 8:
            return
        character.hp += 30
        self.mp -= 8
        super().perform_skill(character)


class Monster(Character):
    def __init__(self, name, position, hitbox) -> None:
        super().__init__(name, random.randint(30, 70), 0, position, hitbox)

    def attack(self, hero):
        hero.hp -= 1
        print(self, "inflige 1 de dégât. Nouvel état du héro", hero)


class Minion(Monster):
    pass


class Buldozer(Monster):
    pass


mage = Mage(
    "Ryze",
    Point(10, 10),
    Rectangle(Point(0, 0), 100, 100),
)

warrior = Warrior(
    "Gerran",
    Point(50, 100),
    Rectangle(Point(0, 0), 100, 100),
)

healer = Healer(
    "Sorakan",
    Point(50, 50),
    Rectangle(Point(0, 0), 100, 100),
)

tanky = Tank(
    "tanky",
    Point(70, 50),
    Rectangle(Point(0, 0), 100, 100),
)

minion1 = Minion(
    "m1",
    Point(200, 500),
    Rectangle(Point(0, 0), 100, 100),
)

minion2 = Minion(
    "m2",
    Point(200, 500),
    Rectangle(Point(0, 0), 100, 100),
)

b1 = Buldozer(
    "b1",
    Point(200, 500),
    Rectangle(Point(0, 0), 100, 100),
)

heroes = [mage, healer, warrior, tanky]
monsters = [minion1, minion2, b1]

print("héros", heroes)
print("monstres", monsters)

warrior.attack(b1)
mage.perform_skill(minion1)
healer.perform_skill(mage)