Maths & NSI

Baccalauréat — Épreuve pratique — 2026 — NSI

Épreuve pratique NSI 2026 — Sujet 14 : simulation de l'évacuation d'une pièce

Sujet

Épreuve pratique de NSI, session 2026 — sujet n°14 de la banque nationale. Durée : 1 heure, sur ordinateur. Le candidat dispose de l'énoncé et de fichiers de code et de données ; il agit en autonomie et, aux « appels professeur » indiqués, présente son travail à l'examinateur ou le sollicite en cas de difficulté.

Simulation de l'évacuation d'une pièce

Lors de la construction d'un bâtiment, d'un lieu culturel ou sportif, le respect des normes de sécurité amène à se poser la question du nombre judicieux de sorties, de leurs emplacements et du temps nécessaire pour l'évacuation totale des occupants.

Ce sujet propose de finaliser une application permettant de simuler l'évacuation d'une pièce rectangulaire. Cette pièce sera une instance de la classe Piece dont le code est dans le fichier simulation_evacuation.py du dossier fourni. Le constructeur de cette classe permet de définir la profondeur et la largeur de la pièce.

La méthode ajouter_occupants(self, i, j, nb) permet d'ajouter jusqu'à nb occupants dans la case située ligne i et colonne j, sachant que le nombre d'occupants d'une case est obligatoirement compris entre 0 et 5.

La méthode ajouter_sortie(self, direction, position) permet d'ajouter une sortie à la pièce bien que, pour l'instant, seules les directions "N" (pour le nord) et "O" (pour l'ouest) soient prises en compte. Lors de l'affichage d'une pièce, les sorties sont représentées par la lettre P.

Voici un exemple d'utilisation de cette classe. Le programme

p1 = Piece(5, 7)
p1.ajouter_occupants(2, 0, 4)
p1.ajouter_occupants(3, 4, 1)
p1.ajouter_occupants(0, 5, 2)
p1.ajouter_sortie("N", 5)
print(p1)

produit l'affichage console :

                 P
 [0, 0, 0, 0, 0, 2, 0]
 [0, 0, 0, 0, 0, 0, 0]
 [4, 0, 0, 0, 0, 0, 0]
 [0, 0, 0, 0, 1, 0, 0]
 [0, 0, 0, 0, 0, 0, 0]

La méthode alerter permet de simuler une alerte : chaque occupant essaie de se rapprocher d'une sortie en se déplaçant d'une case (vers le nord, le sud, l'est ou l'ouest) ; chaque sortie ne laisse passer qu'une seule personne par alerte. Il n'est pas nécessaire de comprendre, ni de modifier, le code de cette méthode. Voici, par exemple, trois alertes successives sur la pièce précédente :

                 P                       P                       P
 [0, 0, 0, 0, 0, 1, 0]   [0, 0, 0, 0, 0, 0, 0]   [0, 0, 0, 0, 0, 0, 0]
 [0, 0, 0, 0, 0, 0, 0]   [0, 4, 0, 0, 0, 0, 0]   [0, 4, 0, 0, 0, 1, 0]
 [0, 4, 0, 0, 0, 0, 0]   [0, 0, 0, 0, 0, 1, 0]   [0, 0, 0, 0, 0, 0, 0]
 [0, 0, 0, 0, 0, 1, 0]   [0, 0, 0, 0, 0, 0, 0]   [0, 0, 0, 0, 0, 0, 0]
 [0, 0, 0, 0, 0, 0, 0]   [0, 0, 0, 0, 0, 0, 0]   [0, 0, 0, 0, 0, 0, 0]

Question 1. Écrire le corps de la méthode nb_occupants_restants de la classe Piece. Comme son nom l'indique, cette méthode doit renvoyer le nombre d'occupants restants dans la pièce. La fonction test_nb_occupants_restants présente dans le fichier simulation_evacuation.py vous permettra d'effectuer une première série de tests.

Appel professeur — Appeler le professeur pour lui présenter votre réponse ou en cas de difficulté.

Question 2. Écrire le corps de la fonction evacuation afin qu'elle simule l'évacuation complète de la pièce et renvoie le nombre de tours nécessaire. On pourra, dans cette fonction, faire appel à la méthode alerter qui simule un tour et renvoie True si des déplacements ont pu avoir lieu, False sinon. En complément de la pièce à évacuer, la fonction evacuation a un paramètre silencieux dont la valeur par défaut est True. Si ce paramètre vaut False, l'état de la pièce doit être affiché à chaque tour dans la console. La fonction test_evacuation présente dans le fichier simulation_evacuation.py vous permettra d'effectuer une série de tests.

Appel professeur — Appeler le professeur pour lui présenter votre réponse ou en cas de difficulté.

Question 3. Modifier la méthode ajouter_sortie(self, direction, position) afin qu'il soit aussi possible d'ajouter une sortie dans les directions qui ne sont pour l'instant pas prises en compte : "S" (pour le sud) et "E" (pour l'est). Le paramètre position désigne l'indice de la case sur le côté correspondant.

La fonction test_ajouter_sortie vous permettra d'effectuer une première série de tests. Vous vérifierez également qu'il est maintenant possible d'ajouter des sorties dans les quatre directions via l'interface homme-machine (IHM), sans modifier le code de celle-ci. Lorsqu'une pièce a été créée dans l'IHM, un clic en périphérie de cette pièce déclenche automatiquement un appel à la méthode ajouter_sortie et fait apparaître la porte ajoutée. Cependant, une seule porte sera utilisée lors des alertes tant que la question suivante n'aura pas été traitée.

Appel professeur — Appeler le professeur pour lui présenter votre réponse ou en cas de difficulté.

On s'aperçoit que, lorsqu'une pièce possède plusieurs sorties, seule la première est utilisée par les occupants. Le problème vient de la méthode choix_sortie(self, i, j) qui renvoie la sortie à utiliser pour une personne positionnée sur la ligne i et la colonne j.

Question 4. Identifier l'erreur logique et la variable non définie dans le code de cette méthode, puis effectuer les corrections nécessaires afin qu'elle renvoie la sortie la plus proche. La fonction test_choix_sortie vous permettra d'effectuer une première série de tests. Vous poursuivrez vos tests avec l'IHM (sans la modifier).

Appel professeur — Appeler le professeur pour lui présenter votre réponse ou en cas de difficulté.

Fichiers fournis

Le dossier comporte une version PDF de l'énoncé, le code source à compléter et corriger simulation_evacuation.py et un programme IHM_evacuation.py permettant d'ouvrir une IHM qui facilitera les tests, à utiliser sans modification. Les bibliothèques random, copy et tkinter doivent être disponibles.

simulation_evacuation.py

from random import randint, shuffle
from copy import deepcopy
 
 
class Piece:
 
    def __init__(self, profondeur, largeur):
        self.grille = [[0 for _ in range(largeur)] for _ in range(profondeur)]
        self.i_max = profondeur-1
        self.j_max = largeur-1
        self.capacite = profondeur * largeur * 5
        self.sorties = []
 
    def ajouter_occupants(self, i, j, nb):
        ''' permet d'ajouter jusqu'à nb occupants dans la case située ligne i et colonne j.
            Le nombre d'occupants ajoutés est limité par la capacité d'accueil de la case (5).
            Cette méthode renvoie le nombre d'occupants effectivement ajoutés.
        '''
        nb_add = min(nb, 5 - self.grille[i][j])
        if nb_add > 0:
            self.grille[i][j] = self.grille[i][j] + nb_add
        return nb_add
 
    def nb_occupants_restants(self):
        ''' renvoie le nombre d'occupants restants dans la pièce.
            A FAIRE (QUESTION 1)
        '''
        pass
 
    def ajouter_sortie(self, direction, position):
        ''' permet d'ajouter des sorties à la pièce.
            A COMPLETER (QUESTION 3) (Pour l'instant, on n'utilise que deux directions !)
        '''
        if direction == "N":
            self.sorties.append((0, position))
        elif direction == "O":
            self.sorties.append((position, 0))
 
    def choix_sortie(self, i, j):
        ''' renvoie la sortie à utiliser pour une personne positionnée sur la ligne i et la colonne j.
            A CORRIGER (QUESTION 4) (Pour l'instant, seule la 1ère sortie est utilisée !)
        '''
        assert len(self.sorties) > 0, "Aucune sortie"
        choix = self.sorties[0]
        distance = abs(i - choix[0]) + abs(j - choix[1])
        for k in range(1, len(self.sorties)):
            autre_sortie = self.sorties[k]
            if k < 0:
                choix = autre_sortie
                distance = d2
        return choix
 
    def deplacer(self, i, j, nb, direction, silencieux=True):
        ''' effectue le déplacement dans la direction demandée d'au maximum
            nb occupants actuellement en ligne i et colonne j.
            Le déplacement est limité par la capacité d'accueil (5) de la case visée.
            Cette fonction renvoie le nombre d'occupants déplacés.
            IL N'EST PAS NECESSAIRE DE COMPRENDRE LE CODE DE CETTE METHODE.
        '''
        d = {"N": (-1, 0), "S": (1, 0), "E": (0, 1), "O": (0, -1)}
        nv_i, nv_j = i + d[direction][0], j + d[direction][1]
        nb_dep = min(nb, 5 - self.grille[nv_i][nv_j], self.grille[i][j])
        if nb_dep > 0:
            if not silencieux:
                print("déplacement de ", nb_dep,
                      " occupant(s) (", i, ",", j, ") vers ", direction)
            self.grille[i][j] = self.grille[i][j] - nb_dep
            self.grille[nv_i][nv_j] = self.grille[nv_i][nv_j] + nb_dep
        return nb_dep
 
    def alerter(self, silencieux=True):
        ''' permet de simuler une alerte : chaque occupant se déplace d'une case
            vers la sortie qui lui est conseillée par la méthode choix_sortie.
            Cette méthode renvoie True si des déplacements ont pu avoir lieu, False sinon.
            IL N'EST PAS NECESSAIRE DE COMPRENDRE LE CODE DE CETTE METHODE.
        '''
        old_grille = deepcopy(self.grille)
        modif = False
        for i in range(len(self.grille)):
            for j in range(len(self.grille[i])):
                if old_grille[i][j] > 0:
                    sortie_i, sortie_j = self.choix_sortie(i, j)
                    dx, dy = sortie_j-j, sortie_i-i
                    if dx == 0 and dy == 0:
                        if not silencieux:
                            print("évacuation d'un occupant (", i, ",", j, ")")
                        self.grille[i][j] = self.grille[i][j] - 1
                        nb_dep = 1
                    else:
                        mvt_possibles = []
                        if dx > 0:
                            mvt_possibles.append("E")
                        elif dx < 0 and j > 0:
                            mvt_possibles.append("O")
                        if dy > 0:
                            mvt_possibles.append("S")
                        elif dy < 0 and i > 0:
                            mvt_possibles.append("N")
                        shuffle(mvt_possibles)
                        nb_dep = self.deplacer(
                            i, j, old_grille[i][j], mvt_possibles[0], silencieux)
                        if nb_dep == 0 and len(mvt_possibles) > 1:
                            nb_dep = self.deplacer(
                                i, j, old_grille[i][j], mvt_possibles[1], silencieux)
                    if nb_dep > 0:
                        modif = True
        return modif
 
    def __str__(self):
        ''' Cette méthode permet de convertir une pièce en chaîne de caractères.
            Ainsi, si p1 est une pièce, l'instruction print(p1) permettra d'afficher l'état actuel de la pièce dans la console.
            IL N'EST PAS NECESSAIRE DE COMPRENDRE LE CODE DE CETTE METHODE.
        '''
        s = "  "
        for j in range(self.j_max+1):
            if (0, j) in self.sorties:
                s = s + "P  "
            else:
                s = s + "   "
        s = s + "\n"
        for i in range(len(self.grille)):
            if (i, 0) in self.sorties:
                s = s + "P"
            else:
                s = s + " "
            s = s + str(self.grille[i])
            if i != 0 and i != self.i_max and (i, self.j_max) in self.sorties:
                s = s + "P\n"
            else:
                s = s + "\n"
        s = s + "  "
        for j in range(self.j_max+1):
            if (self.i_max, j) in self.sorties:
                s = s + "P  "
            else:
                s = s + "   "
        return s + "\n"
 
 
def evacuation(p, silencieux=True):
    ''' simule l'évacuation de la pièce et renvoie le nombre de tours nécessaire.
        A chaque tour, chacun des occupants se déplace, si possible, d'une case
        vers la sortie la plus proche. Si le paramètre silencieux vaut false,
        l'état de la pièce à chaque tour est affiché dans la console.
        A FAIRE EN QUESTION 2
    '''
    pass
 
 
def test_nb_occupants_restants():
    ''' Jeux de tests proposés pour la méthode nb_occupants_restants de la classe Piece.
    '''
    p1 = Piece(5, 7)
    p1.ajouter_sortie("N", 5)
    reussite = True
    if p1.nb_occupants_restants() != 0:
        print("La méthode nb_restants devrait renvoyer 0 quand la pièce est vide.")
        reussite = False
    n1 = randint(1, 5)
    cases_occupees = {(0, 3): 4, (0, 1): 2, (3, 4): 3, (4, 0): n1, (4, 3): 2}
    for c in cases_occupees:
        p1.ajouter_occupants(c[0], c[1], cases_occupees[c])
    if p1.nb_occupants_restants() != 11 + n1:
        print("La méthode nb_restants renvoie",
              p1.nb_occupants_restants(), " au lieu", 11 + n1)
        reussite = False
    if reussite == True:
        print("Pas de problème détecté pour l'instant avec nb_occupants_restants. Il faudra vérifier que l'IHM affiche maintenant le bon nombre d'occupants restants.")
 
 
def test_evacuation(silencieux: bool = True):
    ''' Jeux de tests proposés pour la fonction evacuation.
    '''
    p1 = Piece(5, 7)
    p1.ajouter_sortie("N", 5)
    situations = [{"nom": "essai1", "cases_occupees": {(0, 3): 3, (1, 1): 1, (3, 2): 5}, "temps_attendu": 11},
                  {"nom": "essai2", "cases_occupees": {
                      (0, 3): 4, (0, 1): 2, (3, 4): 3, (4, 0): 1, (4, 3): 2}, "temps_attendu": 14},
                  {"nom": "essai3", "cases_occupees": {(0, 3): 1, (0, 1): 2, (3, 4): 1, (4, 0): 3, (4, 3): 5}, "temps_attendu": 15}]
    verif = True
    for s in situations:
        for c, nb in s["cases_occupees"].items():
            p1.ajouter_occupants(c[0], c[1], nb)
        nbT = evacuation(p1, silencieux)
        if nbT != s["temps_attendu"]:
            print("La fonction evacuation renvoie ", nbT,
                  " au lieu de ", s["temps_attendu"], " pour ", s["nom"])
            verif = False
    if verif:
        print("Pas de problème détecté pour l'instant avec l'évacuation. Il faudra vérifier avec l'IHM que les évacuations n'échouent plus.")
 
 
def test_ajouter_sortie():
    ''' Jeux de tests proposés pour tester les modifications apportées à la méthode ajouter_sortie de la classe Piece.
    '''
    p1 = Piece(5, 7)
    p1.ajouter_sortie("N", 5)
    n1 = randint(1, 5)
    p1.ajouter_sortie("S", n1)
    n2 = randint(1, 5)
    p1.ajouter_sortie("E", n2)
    p1.ajouter_sortie("O", 1)
    if p1.sorties == [(0, 5), (4, n1), (n2, 6), (1, 0)]:
        print("Pas de problème détecté avec le jeu de tests pour la méthode ajouter_sortie. Il faudra vérifier que l'ajout de sortie à l'est ou au sud de la pièce est maintenant possible via l'IHM.")
    else:
        print("L'ajout des sorties ne fonctionne pas correctement.")
 
 
def test_choix_sortie():
    ''' Jeux de tests proposés pour tester les modifications apportées à la méthode choix_sortie de la classe Piece.
    '''
    p1 = Piece(5, 7)
    # Afin de pouvoir tester choix_sortie indépendamment de ajouter_sortie,
    # on effectue ici une modification directe de l'attribut sorties de p1
    p1.sorties = [(0, 5), (4, 1), (3, 6), (1, 0)]
    try:
        assert p1.choix_sortie(0, 3) == (0, 5)
        assert p1.choix_sortie(0, 1) == (1, 0)
        assert p1.choix_sortie(1, 2) == (1, 0)
        assert p1.choix_sortie(3, 4) == (3, 6)
        assert p1.choix_sortie(4, 0) == (4, 1)
        assert p1.choix_sortie(4, 3) == (4, 1)
        print("Pas de problème détecté avec le jeu de tests pour la méthode choix_sortie. Il faudra vérifier avec l'IHM que les occupants n'utilisent plus uniquement la première sortie lors des alertes.")
    except:
        print("La méthode choix_sortie ne renvoie pas la réponse attendue sur au moins l'un des tests.")
 
 
if __name__ == "__main__":
    test_nb_occupants_restants()
    test_evacuation(False)
    test_ajouter_sortie()
    test_choix_sortie()

IHM_evacuation.py

from simulation_evacuation import Piece, evacuation
from tkinter import *
from random import randint
 
################################################################################
# Il n'est pas nécessaire de comprendre (ni modifier) le code de ce programme. #
# Son execution ouvre une interface graphique qui facilitera vos tests.        #
################################################################################
 
 
def creation_piece():
    global choix_largeur, choix_profondeur, choix_nboccupants, piece_test
    global dessin, dernier_affichage, nb_tour_evac
    piece_test = Piece(choix_profondeur.get(), choix_largeur.get())
    n = min(choix_nboccupants.get(), piece_test.capacite)
    while n > 0:
        i = randint(0, piece_test.i_max)
        j = randint(0, piece_test.j_max)
        nb = piece_test.ajouter_occupants(i, j, randint(1, min(5, n)))
        n = n - nb
    dernier_affichage = [[[None, None] for _ in range(
        piece_test.j_max + 1)] for _ in range(piece_test.i_max + 1)]
    dessin.delete(ALL)
    nb_tour_evac.configure(text="")
 
 
def affichage_grille():
    global piece_test, dessin, mode_daltonien, dernier_affichage, nb_occ_restants
    if piece_test is not None:
        couleurs = ["white", "blue", "green", "yellow", "orange", "red"]
        for (lg, cl) in piece_test.sorties:
            if lg == 0:
                dessin.create_text(15*cl+22, 7, text="P")
            elif cl == 0:
                dessin.create_text(7, 15*lg+22, text="P")
            elif lg == piece_test.i_max:
                dessin.create_text(15*cl+22, 15*lg+37, text="P")
            else:
                dessin.create_text(15*cl+37, 15*lg+22, text="P")
        for lg in range(piece_test.i_max + 1):
            for cl in range(piece_test.j_max + 1):
                nb = piece_test.grille[lg][cl]
                case = dernier_affichage[lg][cl]
                if case[0] is None:
                    case[0] = dessin.create_rectangle(
                        15*cl+15, 15*lg+15, 15*cl+30, 15*lg+30, fill="white")
                if case[1] is None:
                    case[1] = dessin.create_text(15*cl+22, 15*lg+22, text="")
                if dessin.itemcget(case[0], "fill") != couleurs[nb]:
                    dessin.itemconfig(case[0], fill=couleurs[nb])
                if mode_daltonien.get() == "oui" and dessin.itemcget(case[1], "text") != str(nb):
                    dessin.itemconfig(case[1], text=str(nb))
                if mode_daltonien.get() == "non" and dessin.itemcget(case[1], "text") != "":
                    dessin.itemconfig(case[1], text="")
        nb_occ_restants.configure(text=str(piece_test.nb_occupants_restants()))
    dessin.after(100, affichage_grille)
 
 
def clic_gauche(event):
    global piece_test
    if piece_test is not None:
        cl, lg = event.x // 15, event.y // 15
        if lg == 0:
            # ajout d'une sortie au nord
            piece_test.ajouter_sortie("N", min(cl-1, piece_test.j_max))
        elif cl == 0:
            # ajout d'une sortie à l'ouest
            piece_test.ajouter_sortie("O", min(lg-1, piece_test.i_max))
        elif lg > piece_test.i_max:
            # ajout d'une sortie au sud
            piece_test.ajouter_sortie("S", min(cl-1, piece_test.j_max))
        elif cl > piece_test.j_max:
            # ajout d'une sortie à l'est
            piece_test.ajouter_sortie("E", min(lg-1, piece_test.i_max))
        else:
            # ajout d'occupants
            piece_test.ajouter_occupants(lg-1, cl-1, 5)
 
 
def alerter_occupants():
    global piece_test
    if piece_test is not None and piece_test.sorties != []:
        nb_tour_evac.configure(text="")
        piece_test.alerter()
 
 
def evacuer_occupants():
    global piece_test, nb_tour_evac
    if piece_test is not None and piece_test.sorties != []:
        nbT = evacuation(piece_test)
        if piece_test.nb_occupants_restants() == 0:
            nb_tour_evac.configure(
                text="Evacuation effectuée en " + str(nbT) + " tours.")
        else:
            nb_tour_evac.configure(text="Echec de l'évacuation.")
 
 
if __name__ == "__main__":
    global fen, choix_largeur, choix_profondeur, choix_nboccupants
    global piece_test, dessin, mode_daltonien, nb_occ_restants, nb_tour_evac
    piece_test = None
    # création de la fenêtre
    fen = Tk()
    fen.title("IHM de simulation d'évacuation")
    fen.geometry("430x650")
    # ajout des zones de saisie permettant de paramétrer la simulation
    Label(fen, text="Largeur de la pièce").grid(row=1, column=1, columnspan=2)
    choix_largeur = Scale(fen, from_=10, to=20, orient=HORIZONTAL)
    choix_largeur.set(10)
    choix_largeur.grid(row=1, column=3)
    Label(fen, text="Profondeur de la pièce").grid(
        row=2, column=1, columnspan=2)
    choix_profondeur = Scale(fen, from_=10, to=20, orient=HORIZONTAL)
    choix_profondeur.set(10)
    choix_profondeur.grid(row=2, column=3)
    Label(fen, text="Nombre d'occupants placés aléatoirement \n(dans la limite de capacité de la pièce)").grid(
        row=3, column=1, columnspan=2)
    choix_nboccupants = Scale(fen, from_=10, to=2000, orient=HORIZONTAL)
    choix_nboccupants.set(200)
    choix_nboccupants.grid(row=3, column=3)
    Label(fen, text="Affichage des nombres en plus des couleurs \n (mode daltonien)").grid(
        row=4, column=1, columnspan=2)
    mode_daltonien = StringVar()
    Checkbutton(fen, text="", var=mode_daltonien, onvalue="oui",
                offvalue="non").grid(row=4, column=3)
    mode_daltonien.set("non")
    btn_grille = Button(fen, text="Créer la pièce", command=creation_piece)
    btn_grille.grid(row=5, column=2)
    # ajout du canvas où sera dessinée la pièce
    Label(fen, text="Un clic sur un côté de la pièce permet d'ajouter une sortie. \nPour ajouter des occupants, cliquer dans la pièce.").grid(
        row=6, column=1, columnspan=3)
    dessin = Canvas(fen, bg="grey", height=330, width=330)
    dessin.grid(row=7, column=1, columnspan=3)
    dessin.bind("<Button-1>", clic_gauche)
    dessin.after(100, affichage_grille)
    Label(fen, text="Nombre d'occupants actuellement dans la pièce :").grid(
        row=8, column=1, columnspan=2)
    nb_occ_restants = Label(fen, text="")
    nb_occ_restants.grid(row=8, column=3)
    # ajout des boutons d'alerte et d'évacuation
    btn_alerte = Button(
        fen, text="Alerter (un pas vers la sortie la plus proche)", command=alerter_occupants)
    btn_alerte.grid(row=9, column=1, columnspan=2)
    btn_evacuer = Button(fen, text="Evacuer", command=evacuer_occupants)
    btn_evacuer.grid(row=9, column=3)
    nb_tour_evac = Label(fen, text="")
    nb_tour_evac.grid(row=10, column=1, columnspan=3)
    # affichage de la fenêtre
    fen.mainloop()

Corrigé

Corrigé réservé aux abonnés Premium.

Créez un compte gratuit : votre première correction est offerte.

Sujet officiel de la banque nationale de sujets 2026 de l'épreuve pratique de NSI (ministère de l'Éducation nationale, sujets.examens-concours.gouv.fr). Corrigé rédigé pour ce site.