Aller au contenu

Révisions pour le Bac Blanc de décembre

Jeu de la bataille (POO et structures de données)

(Adapté du sujet de bac 2022, Centres étrangers, Jour 2, exercice 4)

Cet exercercice porte sur la programmation orientée objet et sur les structures de données.

Le but de l’exercice est de créer programmer en Python en programmation oriente objet le jeu de cartes la bataille pour deux joueurs.

On rapelle ici les règles du jeu de la bataille :

Préparation :

  • On mélange le paquet de cartes.
  • On distribue toutes les cartes du paquet aux deux joueurs.

Déroulement :

  • À chaque tour, chaque joueur dévoile la carte du haut de son tas.
  • Le joueur qui présente la carte ayant la plus haute valeur emporte les deux cartes qu’il place sous son tas.
  • Les valeurs des cartes sont dans l’ordre de la plus forte à la plus faible : As, Roi, Dame, Valet, 10, 9, 8, 7, 6, 5, 4, 3 et 2 (la plus faible)

Si deux cartes sont de même valeur, il y a “bataille” :

  • Chaque joueur pose alors une carte face cachée, suivie d’une carte face visible sur la carte dévoilée précédemment.
  • On recommence l’opération s’il y a de nouveau une bataille sinon, le joueur ayant la valeur la plus forte emporte tout le tas.

Lorsque l’un des joueurs possède toutes les cartes du jeu, la partie s’arrête et ce dernier gagne.

Dans cette exercice, le jeu de la bataille sera implémenté à l’aide des classes Carte et Partie.

Partie A. La classe Carte

Chaque instance de la classe Carte a deux attributs : un pour sa valeur et un pour sa couleur. On donne au valet la valeur 11, à la dame la valeur 12, au roi la valeur 13 et à l’as la valeur 14. La couleur est une chaîne de caractères : 'trèfle', ‘carreau’, ‘cœur’ ou 'pique'.

  1. La classe Carte a deux attributs valeur et couleur.

    1. Recopier et compléter la ligne 4 ci-dessous.

      1
      2
      3
      4
      5
      class Carte:
      
          def __init__(self, val, coul)
              self.valeur = val
              ...
      
      Réponse
      1
      2
      3
      4
      5
      class Carte:
      
          def __init__(self, val, coul)
              self.valeur = val
              self.couleur = coul
      
    2. Donner l’instruction Python permettant de créer une instance de la classe Carte correspondant au 7 de cœur et de l’affecter à une variable c7.

      Réponse

      c7 = Carte(7, 'cœur')

  2. On souhaite maintenant créer le paquet de cartes. Pour cela, on donne ci-après le code incomplet d’une fonction creer_paquet qui ne prend aucun paramètre et renvoie un tableau (type list) contenant 52 instances de la classe Carte qui représentent les 52 cartes du jeu.

    Recopier et compléter les lignes 4 et 5.

    1
    2
    3
    4
    5
    6
    7
    def creer_paquet():
        res = []
        for c in ['cœur', 'carreau', 'trèfle', 'pique']:
            for v in range(...):
                carte = ...
                res.append(carte)
        return res
    
    Réponse attendue
    • L4. for v in range(2, 15):
    • L5. carte = Carte(v, c)

    Remarques/explications.

    • L4. Les valeurs des cartes vont de 2 à 14.
    • L5. Attention à l’ordre des paramètres dans le constructeur de la classe Carte !
    Réponse avec le code complet
    1
    2
    3
    4
    5
    6
    7
    def creer_paquet():
        res = []
        for c in ['cœur', 'carreau', 'trèfle', 'pique']:
            for v in range(2, 15):
                carte = carte(v, c)
                res.append(carte)
        return res
    
  3. On rapelle que dans une partie de bataille, les deux joueurs tirent chacun une carte du dessus de leur tas, et celui qui tire la carte la plus forte remporte les deux cartes et les place en desous de son tas.

    Parmi les structures de données linéaires Tableaux, File et Pile, quelle est celle qui modélise le mieux un tas de cartes dans ce jeu de la bataille ? Justifier votre choix.

    Réponse

    La structure de données File est celle qui modélise le mieux un tas de cartes dans ce jeu de la bataille. En effet, la sortie du tas de carte (dessus) et l’entrée du tas de carte (dessous) sont situées aux extrémités opposées. Il s’agit bien d’une structure FIFO (First In First Out : premier arrivé, premier servi).

    Remarque. (non attendu dans la réponse) On rappelle que LIFO signifie Last In First Out (dernier arrivé, premier servi) ce qui correspond à la structure de données File.

Partie B. La classe Partie

Dans cette partie, on suppose que l’on dispose d’une classe File qui implémente la structure de données File en programmation orientée objet. Les méthodes de cette classe sont :

  • est_vide qui renvoie True si la file est vide et False sinon ;
  • enfiler qui ajoute un élément à la file en queue de file ;
  • defiler qui retire un élément en tête de file et le renvoie. Déclenche une erreur si la file est vide.

On donne ci-dessous une partie du code de la classe Partie :

from random import shuffle

class Partie:

    def __init__(self):
        self.paquet = creer_paquet()
        self.paquet.shuffle() # mélange aléatoirement le paquet
        self.tas1 = File()
        self.tas2 = File()
        self.plis = Pile()

La classe Partie possède 4 attributs :

  • paquet est le tableau (type list) qui contient toutes les cartes du jeu. (voir partie A, question 2.).
  • tas1 (respectivement tas2) est une instance de la classe File modélisant le tas de cartes du joueur 1 (respectivement du joueur 2).
  • plis représente le tas de cartes placé sur la table entre les deux joueur pendant un tour de jeu. Ce tas est remporté par le vaincqueur du tour qui le place sous son propre tas.

tas1, tas2 et sont des instances de la classe File, plis est une instance de la classe Pile.

  1. On donne ci-après le code incomplet de la méthode distribuer qui distribue les cartes du paquet une par une aux deux joueurs en alternant d’un joueur à l’autre. Recopier et compléter les lignes 3, 5 et 6 de la méthode distribuer.

    1
    2
    3
    4
    5
    6
    def distribuer(self):
        i = 0
        while i < ...:
            self.tas1.enfiler(self.paquet[i])
            self.tas2.enfiler(...)
            i = i + ...
    
    Réponse attendue
    • L3. len(self.paquet) - 1
    • L5. self.tas2.enfiler(self.paquet[i + 1])
    • L6. i = i + 2

    Explications. À chaque tour de boucle on traite deux éléments consécutifs aux rangs i et i + 1. Conséquences :

    • i doit donc varier entre 0 et len(self.paquet) - 2 (rang de l’avant dernière case).
    • i est incrémenté de deux à chaque itération de la boucle.
    Réponse avec le code complet
    1
    2
    3
    4
    5
    6
    def distribuer(self):
        i = 0
        while i < len(self.paquet) - 1:
            self.tas1.enfiler(self.paquet[i])
            self.tas2.enfiler(self.paquet[i + 1])
            i = i + 2
    
  2. On donne ci-après le code incomplet de la méthode ramasse_plis qui transfère le contenu du plis vers le tas du joueur indiqué par le paramètre booléen joueur1 qui peut prendre comme valeur :

    • True si le joueur 1 est le bénéficiaire du transfert ou
    • False si le joueur 2 est le bénéficiaire du transfert. On ne se préocuppe pas de l’ordre des cartes à l’issu du transfert. Recopier et Compléter les lignes 5 et 6.
    1
    2
    3
    4
    5
    6
    7
    def ramasse_plis(self, joueur1):
        if joueur1:
            tas = self.tas1
        else:
            ...
        while ...:
            tas.enfiler(self.plis.defiler())   
    
    Réponse attendue
    • L5. tas = self.tas2
    • L6. while not self.plis.est_vide():
    Réponse avec le code complet
    1
    2
    3
    4
    5
    6
    7
    def ramasse_plis(self, joueur1):
        if joueur1:
            tas = self.tas1
        else:
            tas = self.tas2
        while not self.plis.est_vide():
            tas.enfiler(self.plis.defiler())   
    
  3. On rappelle qu’à chaque tour de jeu, les joueurs posent chacun sur la table une carte.

    • si les cartes sont de valeurs différentes, le joueur dont la carte a la valeur la plus forte remporte le plis c’est-à-dire l’ensemble des cartes posées sur la table.
    • en cas d’égalité de valeur des cartes, les joueurs placent chacun une nouvelle carte sur la table face cachée puis placent chacun une nouvelle carte face visible. Les cartes sont alors comparées et le tour continue jusqu’à ce que deux cartes de valeurs différentes apparaissent.

    Un tour de jeu prend fin si deux cartes posées au même moment sont de valeurs différentes : le joueur ayant posé la carte de plus forte valeur remporte le plis. Cependant, si l’un des joueurs pose sa dernière carte sans remporter le plis le tour s’arrête et il est déclaré perdant.

    1. Bien que cela soit très improbable, une partie peut se terminer en match nul. Expliquer comment cela peut se produire ?

      Réponse

      Si les joueurs posent leur dernière carte en même temps et qu’il y a égalité des valeurs des cartes, personne ne peut remporter le dernier plis. Les joueurs n’ayant plus de cartes en main, il n’est pas possible de les départager.

    2. On donne ci-après le code incomplet de la méthode lancer qui permet de lancer la simulation d’une partie. Recopier et compléter les lignes 3, 10, 12, 13, 15, 17 et 21.

      def lancer(self):
          # tant que la partie n'est pas finie
          while ... and ... :
              # les joueurs révèlent chacun une carte.
              carte1 = self.tas1.defiler()
              carte2 = self.tas2.defiler()
              self.plis.enfiler(carte1)
              self.plis.enfiler(carte2)
              # cartes de même valeur
              if ...: 
                  # les joueurs posent chacun une nouvelle carte face cachée.
                  self.plis.enfiler(...)
                  self.plis.enfiler(...)
              # joueur 1 a la meilleure carte.
              elif ...:
                  # joueur 1 ramasse le pli.
                  ...
              # joueur 2 a la meilleure carte.
              else: 
                  # joueur 2 ramasse le plis.
                  ...
      
    Réponse attendue
    • L3. while not self.tas1.est_vide() and not self.tas2.est_vide():
    • L10. if self.carte1.valeur == self.carte2.valeur:
    • L12. self.plis.enfiler(self.tas1.defiler())
    • L13. self.plis.enfiler(self.tas2.defiler())
    • L15. elif self.carte1.valeur > self.carte2.valeur:
    • L17. self.ramasse_plis(joueur1=True)
    • L21. self.ramasse_plis(joueur1=False)
    Réponse avec le code complet
    def lancer(self):
        # tant que la partie n'est pas finie
        while not self.tas1.est_vide() and not self.tas2.est_vide():
            # les joueurs révèlent chacun une carte.
            carte1 = self.tas1.defiler()
            carte2 = self.tas2.defiler()
            self.plis.enfiler(carte1)
            self.plis.enfiler(carte2)
            # cartes de même valeur
            if self.carte1.valeur == self.carte2.valeur:
                # les joueurs posent chacun une nouvelle carte face cachée.
                self.plis.enfiler(self.tas1.defiler())
                self.plis.enfiler(self.tas2.defiler())
            # joueur 1 a la meilleure carte.
            elif self.carte1.valeur > self.carte2.valeur:
                # joueur 1 ramasse le pli.
                self.ramasse_plis(joueur1=True)
            # joueur 2 a la meilleure carte.
            else: 
                # joueur 2 ramasse le plis.
                self.ramasse_plis(joueur1=False)
    
Gestion d’un Zoo (Bases de données et SQL)

(Adapté du sujet de bac 2023, Centres étrangers, Jour 2, exercice 4)

Cet exercice porte sur les bases de données relationnelles et le langage SQL

Par la suite on utilisera les mots-clés du langage SQL suivants : SELECT, FROM, WHERE, JOIN ... ON, UPDATE ... SET, INSERT INTO ... VALUES ..., COUNT, ORDER BY.

La clause ORDER BY suivie d’un attribut permet de trier les résultats par ordre croissant de l’attribut.

SELECT COUNT(*) renvoie e nombre de lignes d’une requête.

Un zoo souhaite pouvoir suivre ses animaux et ses enclos. Tous les représentants d’une espèce sont réunis dans un même enclos. Plusieurs espèces, si elle peuvent cohabiter ensemble, pourront partager le même enclos.

Le zoo crée une base de données utilisant le langage SQL.

La base de données contient une première relation animal qui recense chaque animal du zoo. On donne ci-dessous un eextrait de cette relation (les unités des attributs age, taille et poids sont respectivement ans, m et kg).

animal
id_animal nom age taille poids nom_espece
145 Romy 18 2.3 130 tigre du Bengale
52 Boris 30 1.1 48 bonobo
225 Hervé 10 2.4 130 lama
404 Moris 6 1.7 100 panda
678 Léon 4 0.3 1 varan

La base de données contient une seconde relation enclos dont on donne ci-dessous un extrait (l’unité de l’attribut surface est m2).

enclos
num_enclos ecosysteme surface struct date_entretien
40 banquise 50 bassin 2024-12-04
18 forêt tropicale 200 vitré 2024-12-05
24 savane 300 clôture 2024-12-04
68 désert 2 vivarium 2024-12-05

La base de données contient une dernière relation espece dont on donne un extrait ci-dessous.

espece
nom_espece classe alimentation num_enclos
impala mammifères herbivore 15
ara de Buffon oiseaux granivore 77
tigre du Bengale mammifères carnivore 18
caïman reptiles carnivore 45
manchot empereur oiseaux carnivores 40
lama mammifères herbivore 13

  1. Cette question port sur la lecture et l’écriture de requête SQL simples.

    1. Écrire le résultat de la requête ci-dessous. On suppose qu’un seul animal est prénommé Moris.

      SELECT age, taille, poids
      FROM animal
      WHERE nom = "Moris";
      
      Réponse

      age taille poids
      6 1.7 100

    2. Écrire une requête qui permet d’obtenir le nom et l’âge de tous les animaux de l’espèce bonobo, triés du plus jeune au plus vieux.

      Réponse
      SELECT nom, age
      FROM animal
      WHERE nom_espece = "bonobo"
      ORDER BY age;
      
  2. Cette question porte sur modèle relationnel de la base de données.

    1. Pour la relation espece, citer en justifiant :

      • sa clé primaire
      • sa (ou ses) éventuelle(s) clés étrangères
      Réponse
      • La clé primaire de la relation espece est nom_espece car cette attribut permet d’identifier de manière unique chaque n-uplet de cette relation.
      • L’unique clé étrangère de la relation espece est num_enclos. Cet attribut fait référence à la clé primaire num_enclos de la relation enclos. Ainsi, chaque espèce est associée à un unique enclos.

      Remarque. Attention, une relation possède obligatoirement une unique clé primaire (composite ou non). Par contre rien n’oblige une relation de posséder une clé étrangère. Il y a donc trois cas :

      • les relations ne possèdant aucune clé étrangère ;
      • les relations possédant une seule clé étrangère ;
      • les relations possédant plusieurs clés étrangères.
    2. Donner le modèle relationnel de la base de données du zoo. On soulignera les clés primaires et on fera précéder les clés érangères d’un #.

      Réponse

      On donne le schéma relationnel de chaque relation.

      • animal (
        id_animal : INTEGER,
        nom : TEXT,
        age : INTEGER,
        taille : REAL,
        poids : INTEGER,
        #nom_espece : TEXT
        )

        animal.nom_espece fait référence à espece.nom_espece.

      • enclos (
        num_enclos : INTEGER,
        ecosysteme : TEXT,
        surface : INTEGER,
        struct : TEXT,
        date_entretien : TEXT,
        )

        Cette relation ne possède aucune clé étrangère.

      • espece (
        nom_espece : TEXT,
        classe : TEXT,
        alimentation : TEXT,
        #num_enclos : INTEGER,
        )

        espece.num_enclos fait référence à enclos.num_enclos.

      Conseil. Même si le diagramme de la base de données est rarement demandé, c’est une aide précieuse à préparer au brouillon si vous jugez que vous avez suffisemment de temps. Voici le diagramme de cette base de données.

      Lecture des quantificateurs.

      • Chaque animal appartient à une seule espèce. Inversement, une espèce peut réunir plusieurs animaux.
      • Chaque espèce est domiciliée dans un seul enclos. Inversement, un enclos peut réunir plusieurs espèces.
  3. Cette question porte sur les modifications d’une table.

    L’espèce ornithorynque a été entrée dans la base comme étant de la classe des oiseaux alors qu’il s’agit d’un mammifère.

    1. Écrire la requête qui corrige cette erreur dans la relation espece.

      Réponse
      UPDATE espece
      SET classe = "mammifère"
      WHERE nom_espece = "ornithorynque";
      
    2. Le couple de lamas du zoo vient de donner naisance au petit lama nommé Serge qui mesure 80 cm et pèse 30 kg.

      Écrire une requête qui permet d’enregistrer ce nouveau venu au zoo dans la base de données, sachant que les clés primaires de 1 à 178 sont déjà utilisées.

      Réponse
      INSERT INTO animal
      VALUES (179, "Serge", 0, 0.8, 30, "lama");
      
    3. Citer les trois contraintes d’intégrité qui garantissent la cohérence des données d’une base de données. Proposer trois versions erronées de la requête précédente qui violent successivement chacune de ces trois contraintes d’intégrité.

      Réponse
      • contrainte d’intégrité de clé primaire :

        INSERT INTO animal
        VALUES (178, "Serge", 0, 0.8, 30, "lama");
        

        Explication. 178 est une valeur de la clé primaire id_animal déjà utilisée !

      • contrainte d’intégrité référentielle (ou de clé étrangère) :

        INSERT INTO animal
        VALUES (179, "Serge", 0, 0.8, 30, "lamas");
        

        Explication. "lamas" est une valeur de la clé étrangère animal.nom_espece qui ne fait référence à aucune valeur de la clé primaire espece.nom_espece. Autrement dit, l’espèce de nom "lamas" n’exsite pas dans la relation espece (une erreur d’orthographe avec "s" en trop.)

      • contrainte d’intégrité de domaine :

        INSERT INTO animal
        VALUES (179, "Serge", 0, 0.8, 30.5, "lama");
        

        Explication. La valeur 30.5 (REAL) a été choisie pour l’attribut poids alors que le domaine de poids est INTEGER.

      Remarque. On parle ici d’anomalie d’insertion qui comme on vient de le voir peut concerner les trois contraintes d’intégrité.

    4. Serge doit être retiré de la base de données car il quitte le zoo pour une remise en liberté. Écrire la requête permettant de supprimer Serge de la base de données.

      Réponse
      DELETE FROM animal
      WHERE id_animal = 179;
      

      Remarque. Lorsque cela est possible, il est plus prudent de sélectionner un unique n-iplet en utilisant sa valeur de clé primaire pour éviter le risque d’affecter plusieurs n-uplet si la relation contient des homonymes.

  4. Cette question porte sur la jointure entre plusieurs relations.

    1. Écrire la requête SQL qui permet de recenser le nom et l’espèce de tous les animaux carnivires vivant en vivarium dans le zoo.

      Code à trous
      SELECT ...
      FROM animal
          JOIN espece
              ON ...
          JOIN enclos
              ON ...
      WHERE enclos.sruct = 'vivarium' AND ...;
      
      Réponse
      SELECT nom, nom_espece
      FROM animal
          JOIN espece
              ON animal.nom_espece = espece.nom_espece
          JOIN enclos
              ON enclos.num_enclos = espece.num_enclos
      WHERE struct = "vivarium" AND alimentation = "carnivore";
      
    2. Écrire la requête qui permet de compter le nombre d’oiseaux dans tout le zoo.

      Réponse
      SELECT COUNT(*)
      FROM animal
          JOIN espece
              ON animal.nom_espece = espece.nom_espece
      WHERE classe = "oiseaux"