Épreuve écrite · 2026 · Jour 1

Bac NSI 2026 centres étrangers groupe 1 jour 1

Le sujet confronte deux méthodes de classement, construit un chiffrement par grille et programme un démineur. Les corrections détaillent les structures manipulées et signalent les incohérences des exemples imprimés du démineur afin de conserver un modèle exact. Les trois exercices indépendants durent ensemble 3 h 30, sans calculatrice.

Dans ce sujet

Un corrigé pédagogique pour comprendre et justifier vos réponses. Les conseils de rédaction ne constituent pas un barème officiel détaillé.

Exercice 1 · 6 points

Classement d’athlètes : tri par sélection et ABR

Une compétition interscolaire regroupe 100 m, saut en longueur, lancer du poids et 1500 m. Chaque athlète reçoit un score global, somme des points convertis selon les règles du sujet :

ÉpreuvePoints
100 m(20 - temps en secondes) × 10
Longueurdistance en mètres × 20
Poidsdistance en mètres × 10
1500 m500 - temps en secondes

On souhaite classer les athlètes du plus grand au plus petit score, d’abord avec dictionnaires et tri puis avec objets et arbre.

Question 1

#

Alex réalise 13,0 s au 100 m, 5,2 m en longueur, 9,0 m au poids et 310,0 s au 1500 m. Calculer son score.

Indice

Convertissez séparément chaque performance avant de sommer.

Comprendre la correction

70 + 104 + 90 + 190 = 454 points. Les courses récompensent un temps plus court, tandis que les lancers et sauts récompensent une distance plus grande. La formule est celle du problème, pas un barème officiel d’athlétisme.

Voir la question dans le sujet PDF, p. 2 (nouvel onglet)
def nb_points(epreuve, valeur):
    points = 0
    if epreuve == "100m":
        points = (20 - valeur) * 10
    elif epreuve == "longueur":
        ...
    elif epreuve == "poids":
        ...
    elif epreuve == "1500m":
        ...
    return ...

Question 2

#

Compléter nb_points aux lignes 6,8,10,11.

Indice

Les deux courses diminuent lorsque le temps augmente.

Comprendre la correction
def nb_points(epreuve, valeur):
    points = 0
    if epreuve == "100m":
        points = (20 - valeur) * 10
    elif epreuve == "longueur":
        points = valeur * 20
    elif epreuve == "poids":
        points = valeur * 10
    elif epreuve == "1500m":
        points = 500 - valeur
    return points

Chaque étiquette sélectionne sa formule. Le return final commun renvoie les points calculés. Avec le squelette donné, une étiquette inconnue renverrait 0 ; une validation explicite serait une extension possible, mais les quatre épreuves sont ici garanties.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)
l_athletes = [
 {"nom":"Alex", "performances":{"100m":13.0,"longueur":5.2,"poids":9.0,"1500m":310.0}, "score":0},
 {"nom":"Anna", "performances":{"100m":12.2,"longueur":5.8,"poids":8.5,"1500m":230.0}, "score":0},
 {"nom":"Rayan", "performances":{"100m":13.5,"longueur":5.0,"poids":9.5,"1500m":205.0}, "score":0}
]
def score(athlete):
    total = 0
    performances = athlete[...]
    for epreuve in performances:
        valeur = performances[...]
        total += ...
    athlete["score"] = total

Question 3

#

Compléter score(athlete), qui calcule et enregistre le score du dictionnaire reçu.

Indice

Descendez d’abord dans le sous-dictionnaire performances.

Comprendre la correction
performances = athlete["performances"]
# Dans la boucle :
valeur = performances[epreuve]
total += nb_points(epreuve, valeur)

Le dictionnaire externe stocke nom, performances et score ; performances est lui-même un dictionnaire indexé par épreuve. La fonction appelle nb_points pour chaque paire épreuve/valeur puis écrit le total dans athlete["score"]. Elle modifie l’objet reçu et ne renvoie pas le score. Il faut calculer tous les scores avant de trier les athlètes.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)
def classer(l):
    n = len(l)
    for i in range(n):
        max_index = i
        for j in range(i + 1, n):
            if ...:
                max_index = j
        temp = l[i]
        l[i] = l[max_index]
        l[max_index] = temp

Question 4

#

Compléter le test de classer(l) pour trier les scores en ordre décroissant.

Indice

Cherchez le maximum de la partie encore non classée.

Comprendre la correction
if l[j]["score"] > l[max_index]["score"]:

max_index désigne le meilleur score trouvé dans la zone non triée. On compare la clé score de deux dictionnaires, pas les dictionnaires eux-mêmes. Après la boucle interne, cet athlète est échangé avec la position i, qui devient définitive.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Question 5

#

Identifier ce tri parmi insertion, sélection, bulles et fusion.

Indice

Le nom vient de la recherche répétée du meilleur élément restant.

Comprendre la correction

Tri par sélection : à chaque position, on sélectionne le maximum parmi les éléments restants puis on le place. On n’insère pas progressivement l’élément courant parmi ses prédécesseurs, comme le ferait le tri par insertion.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Question 6

#

Donner le nombre de comparaisons d’éléments pour une liste de taille n.

Indice

Comptez la longueur de range(i+1,n) pour chaque i.

Comprendre la correction

(n-1)+(n-2)+…+1+0 = n(n-1)/2. La première boucle interne compare n-1 candidats, la suivante n-2, etc. Le nombre ne dépend pas de l’ordre initial des scores : même une liste déjà triée subit ces comparaisons. Le coût est donc quadratique, Θ(n²), pour cette mesure.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Partie B : classement par objets et ABR

class Athlete:
    def __init__(self, nom, m100, longueur, poids, m1500):
        self.nom = nom
        self.m100 = m100
        self.longueur = longueur
        self.poids = poids
        self.m1500 = m1500
        self.score = self.calculer_score()

    def calculer_score(self):
        ...

Question 7

#

Créer l’objet alex avec les performances d’Alex.

Indice

Respectez la signature du constructeur.

Comprendre la correction
alex = Athlete("Alex", 13.0, 5.2, 9.0, 310.0)

L’ordre des arguments est nom, 100 m, longueur, poids, 1500 m. Le constructeur calculera son score par la méthode, donc on ne fournit pas 454 comme argument supplémentaire.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Question 8

#

Écrire calculer_score.

Indice

Le constructeur attend une valeur de retour, pas uniquement une modification de l’instance.

Comprendre la correction
def calculer_score(self):
    return ((20 - self.m100) * 10
            + self.longueur * 20
            + self.poids * 10
            + (500 - self.m1500))

La méthode renvoie un résultat pour l’affectation self.score du constructeur. Elle lit les performances déjà initialisées ; elle ne doit pas lire self.score avant sa création. Si les performances changent plus tard, il faudra recalculer ce score stocké pour que le classement reste cohérent.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Propriété imposée : tous les scores du sous-arbre gauche sont strictement plus petits ; ceux du droit sont supérieurs ou égaux.

OrdreNomScore
1Martin354
2Lena351
3Rayan350
4Yanis355
5Ninon351
6Ana356

Question 9

#

Dessiner l’ABR après insertion successive de Martin 354, Lena 351, Rayan 350, Yanis 355, Ninon 351, Ana 356.

Indice

L’égalité 351 doit être placée dans la branche droite de Lena.

Comprendre la correction
Martin 354
├─ gauche : Lena 351
│  ├─ gauche : Rayan 350
│  └─ droite : Ninon 351
└─ droite : Yanis 355
   └─ droite : Ana 356

Les scores strictement plus petits vont à gauche ; les scores égaux ou plus grands vont à droite. Ninon 351 passe donc à gauche de Martin puis à droite de Lena. Les noms ne départagent pas l’égalité : seule la règle sur le score décide.

Voir la question dans le sujet PDF, p. 5 (nouvel onglet)
class Noeud:
    def __init__(self, athlete):
        self.valeur = athlete
        self.gauche = None
        self.droite = None

    def inserer(self, athlete):
        if athlete.score < self.valeur.score:
            if self.gauche == ...:
                self.gauche = Noeud(athlete)
            else:
                ...
        else:
            if self.droite == ...:
                self.droite = Noeud(athlete)
            else:
                ...

Question 10

#

Compléter Noeud.inserer.

Indice

Il faut créer au premier emplacement vide, mais descendre si l’emplacement est déjà occupé.

Comprendre la correction
def inserer(self, athlete):
    if athlete.score < self.valeur.score:
        if self.gauche is None:
            self.gauche = Noeud(athlete)
        else:
            self.gauche.inserer(athlete)
    else:
        if self.droite is None:
            self.droite = Noeud(athlete)
        else:
            self.droite.inserer(athlete)

Le choix de branche est effectué à chaque nœud rencontré. Dès qu’un enfant manque, le nouvel athlète est enveloppé dans un Noeud et rattaché à l’arbre. Sinon, l’appel récursif descend dans le sous-arbre choisi. La branche else inclut l’égalité de scores et applique donc la convention du sujet.

Voir la question dans le sujet PDF, p. 5 (nouvel onglet)
def classer(self, classement=None):
    if classement is None:
        classement = []
    if self.droite is not None:
        self.droite.classer(classement)
    classement.append(self.valeur.nom)
    if self.gauche is not None:
        self.gauche.classer(classement)
    return classement

Question 11

#

Expliquer le parcours de classer et pourquoi il donne un classement décroissant.

Indice

Le parcours commence par les grands scores et place la racine entre ses sous-arbres.

Comprendre la correction

C’est un parcours en profondeur infixe inversé : droite, nœud, gauche. Les scores les plus grands du sous-arbre droit sont ajoutés avant le score courant, puis les plus petits du gauche. Pour l’arbre précédent : Ana, Yanis, Martin, Ninon, Lena, Rayan, scores 356,355,354,351,351,350. Ninon précède Lena à score égal du fait de sa position à droite ; ce parcours n’assure donc pas la stabilité chronologique des ex æquo.

La liste classement est créée seulement lors de l’appel initial où le paramètre vaut None, puis partagée par les sous-appels. Ils y ajoutent leurs noms sans recréer de liste vide. Chaque nœud est visité une seule fois.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)

Question 12

#

Donner un avantage et un inconvénient de chaque approche : dictionnaires avec tri, puis objets avec ABR.

Indice

Distinguez le coût d’insertion, le coût de parcours et le risque d’arbre déséquilibré.

Comprendre la correction
ApprocheAvantageInconvénient
Liste de dictionnaires + sélectionReprésentation simple, directement parcourable et facile à afficherReclasser par cette sélection coûte Θ(n²), même si la liste est presque triée
Objets + ABRInsertion localisée et parcours de classement en O(n), avec organisation des données dans des objetsL’arbre non équilibré peut dégénérer : insertion O(n), construction O(n²), mémoire de liens supplémentaire

Si l’ABR reste équilibré, chaque insertion coûte O(log n), mais cette propriété n’est pas garantie par le code. La comparaison dépend aussi du tri choisi : un tri fusion sur liste peut donner O(n log n) sans arbre. Il faut comparer les algorithmes précis du sujet plutôt que déclarer les objets intrinsèquement plus rapides.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)
L’ordre d’insertion change-t-il le classement ou le coût ?Un atelier pour expérimenter

Insérez les six athlètes dans différents ordres. Le modèle reconstruit l’ABR, mesure sa hauteur et affiche le classement droite-nœud-gauche.

Lire le résultat de l’expérience initiale

Hauteur de l’ABR : 3

Les scores restent décroissants au parcours, mais la forme de l’arbre et le coût des insertions changent. Les égalités suivent toujours la branche droite.

RangAthlèteScore
1Ana356
2Yanis355
3Martin354
4Ninon351
5Lena351
6Rayan350

Un ABR ordonne les valeurs, mais ne s’équilibre pas spontanément. La performance dépend de sa hauteur.

Revoir les notions de cet exercice

Exercice 2 · 6 points

Carré de Polybe : générer une grille et convertir les coordonnées

Le chiffrement de Polybe du sujet remplace chaque lettre majuscule ou chiffre par son couple (ligne,colonne) dans une grille 6 × 6. Les coordonnées commencent à 1. La grille d’exemple est :

Ligne123456
1Q7AX2J
29EH0RM
3LZ4WDO
46VNB8K
5PY1STF
6GC3IU5

N est en (4,3). Le message NSI devient (4,3),(5,4),(6,4). Les 26 lettres et 10 chiffres occupent une case chacun.

Question 1

#

Déchiffrer (6,2),(3,6),(3,5),(2,2) avec cette grille.

Indice

Le premier entier est la ligne, le second la colonne.

Comprendre la correction

CODE : C est ligne 6 colonne 2, O ligne 3 colonne 6, D ligne 3 colonne 5, E ligne 2 colonne 2. Inverser ligne et colonne donnerait un autre message.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)

On construit une grille en écrivant d’abord la clé, composée ici de caractères tous distincts, puis les lettres A à Z absentes, puis les chiffres 0 à 9 absents. Bien que le texte dise « lettres », les exemples de clé incluent aussi des chiffres. Pour 2048ALGORITHMES, l’ordre est 2048ALGORITHMESBCDFJKNPQUVWXYZ135679 :

Ligne123456
12048AL
2GORITH
3MESBCD
4FJKNPQ
5UVWXYZ
6135679

Question 2

#

Chiffrer BAC avec la clé SECURITY1024.

Indice

Complétez l’alphabet en sautant les caractères déjà placés par la clé.

Comprendre la correction

L’ordre d’insertion est SECURITY1024ABDFGHJKLMNOPQVWXZ356789. La grille commence par SECURITY1024 puis les lettres et chiffres encore absents. B est en (3,2), A en (3,1) et C en (1,3), donc [(3,2),(3,1),(1,3)].

Ligne123456
1SECURI
2TY1024
3ABDFGH
4JKLMNO
5PQVWXZ
6356789
Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

Question 3

#

Pourquoi le chiffrement est-il symétrique ?

Indice

Les deux participants doivent partager le même secret de construction de grille.

Comprendre la correction

La même clé permet à l’émetteur et au destinataire de reconstruire la même grille, utilisée pour chiffrer et déchiffrer. Elle doit rester secrète pour les tiers. Ce procédé de substitution reste un exemple pédagogique : il conserve les répétitions de caractères et ne fournit pas une sécurité adaptée aux échanges modernes.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)
def generer_ordre(cle):
    ordre_insertion = cle
    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    for lettre in alphabet:
        if lettre not in ordre_insertion:
            ordre_insertion += lettre
    return ordre_insertion

Question 4

#

Que renvoie generer_ordre("AXU7") ?

Indice

Le 7 placé dans la clé ne réapparaît pas parmi les chiffres de fin.

Comprendre la correction
AXU7BCDEFGHIJKLMNOPQRSTVWYZ012345689

AXU7 est conservé en tête. On parcourt ensuite A à Z puis 0 à 9, en ignorant A,X,U,7 déjà présents. Chaque caractère autorisé apparaît exactement une fois, soit 36 caractères.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

Question 5

#

Écrire grille_vide(n), n lignes de n chaînes vides.

Indice

Une grille modifiable demande des lignes indépendantes.

Comprendre la correction
def grille_vide(n):
    return [["" for _ in range(n)] for _ in range(n)]

La compréhension externe crée des lignes distinctes. [[""]*n]*n partagerait la même liste entre toutes les lignes et la modification d’une case aurait plusieurs effets. Pour n=3, on obtient [["","",""],["","",""],["","",""]].

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)
def generer_grille(cle):
    ordre_insertion = generer_ordre(cle)
    grille = grille_vide(6)
    indice = 0
    for i in range(...):
        for j in range(...):
            grille[i][j] = ...
            indice += 1
    return grille

Question 6

#

Compléter generer_grille(cle).

Indice

i choisit la ligne, j la colonne, indice parcourt la chaîne de manière continue.

Comprendre la correction
for i in range(6):
    for j in range(6):
        grille[i][j] = ordre_insertion[indice]
        indice += 1

Les deux boucles parcourent la grille ligne par ligne, comme la construction définie. indice prend les valeurs 0 à 35. La fonction suppose une clé valide, sans répétition ni caractère étranger à l’alphabet, condition nécessaire pour que generer_ordre produise bien 36 symboles uniques.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)
def dechiffrer(cle, message):
    resultat = ""
    grille = generer_grille(cle)
    for t in message:
        resultat = resultat + grille[...][...]
    return resultat

Question 7

#

Compléter l’accès de dechiffrer pour des coordonnées numérotées à partir de 1.

Indice

Transformez séparément numéro de ligne et numéro de colonne en indices Python.

Comprendre la correction
resultat = resultat + grille[t[0] - 1][t[1] - 1]

Les couples chiffrés vont de 1 à 6, mais les indices Python de 0 à 5. Il faut retirer 1 à chacune des deux composantes. Avec 2048ALGORITHMES, (4,4),(3,3),(2,4) donne NSI. Un simple -1 sur la ligne seulement laisserait la colonne décalée.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)

Avec 2048ALGORITHMES, le début du dictionnaire est {"2":(1,1),"0":(1,2),"4":(1,3),"8":(1,4),"A":(1,5),…}.

Question 8

#

Écrire generer_dico(cle), associant chaque caractère à ses coordonnées à partir de 1.

Indice

La clé est le caractère et la valeur son couple de coordonnées.

Comprendre la correction
def generer_dico(cle):
    grille = generer_grille(cle)
    dico = {}
    for i in range(6):
        for j in range(6):
            dico[grille[i][j]] = (i + 1, j + 1)
    return dico

Cette fois on passe des indices internes aux coordonnées externes : on ajoute 1. La grille ne contenant aucun doublon, chaque caractère a une unique position. Le dictionnaire permet ensuite une recherche directe par caractère, au lieu de parcourir jusqu’à 36 cases pour chaque lettre du message.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)

Question 9

#

Écrire chiffrer(cle,message), qui renvoie une liste de tuples.

Indice

La fonction renvoie une liste de coordonnées, pas une chaîne de nombres concaténés.

Comprendre la correction
def chiffrer(cle, message):
    dico = generer_dico(cle)
    resultat = []
    for caractere in message:
        resultat.append(dico[caractere])
    return resultat

On construit le dictionnaire une fois, puis on le réutilise pour chaque symbole. Le message est supposé composé de lettres majuscules et chiffres disponibles. Les espaces, accents et minuscules ne sont pas dans ce modèle ; il faudrait annoncer une règle de traitement plutôt que les supprimer silencieusement. Le message vide produit une liste vide.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)

Alice et Bob veulent changer chaque jour la clé de Polybe et envisagent un échange asymétrique.

Question 10

#

Distinguer chiffrement symétrique et asymétrique pour l’échange quotidien d’une clé entre Alice et Bob.

Indice

La paire publique/privée évite de transmettre le secret de déchiffrement lui-même.

Comprendre la correction

Le chiffrement symétrique utilise le même secret partagé pour chiffrer et déchiffrer. Le chiffrement asymétrique utilise une paire de clés : une clé publique peut servir à chiffrer pour un destinataire, qui déchiffre avec sa clé privée. Ainsi Alice peut protéger la clé de Polybe avec la clé publique authentifiée de Bob ; lui seul possède la clé privée correspondante. L’identité associée à la clé publique doit être vérifiée pour éviter une substitution. Changer la clé de Polybe chaque jour ne transforme pas pour autant cette substitution simple en chiffrement moderne robuste.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)
Changer la clé déplace-t-il tout le message ?Un atelier pour expérimenter

Choisissez une clé valide et un message. La grille est reconstruite, le message chiffré puis relu par coordonnées. Repérez les caractères placés dès le début par la clé.

Lire le résultat de l’expérience initiale

BAC → (3,2) (3,1) (1,3)

Le premier nombre désigne la ligne et le second la colonne, de 1 à 6. Pour indexer un tableau Python, on retire 1 à chacun.

Ligne123456
1SECURI
2TY1024
3ABDFGH
4JKLMNO
5PQVWXZ
6356789

Une convention de coordonnées doit être réversible : +1 au chiffrement, -1 au déchiffrement.

Revoir les notions de cet exercice

Exercice 3 · 8 points

Démineur : voisinage, propagation et scores en ligne

Le champ de mines est une grille. Une mine est codée -1 ; une case libre indique le nombre de mines parmi ses huit voisines au maximum. Cliquer une case numérotée la révèle ; cliquer une case à zéro révèle aussi ses voisines libres, puis propage tant que des zéros sont rencontrés. Une mine fait perdre ; révéler toutes les cases libres fait gagner. La figure 1 donne cette grille 6 × 7 et ses huit mines :

Ligne0123456
01-121211
1123-12-11
212-12211
32-122110
4-1211-121
5110112-1

(1,2) vaut 3 à cause des mines (0,1),(2,2),(1,3). (3,6) vaut 0 car aucune case voisine ne contient de mine. Les coordonnées sont (ligne,colonne) à partir de zéro.

class Demineur:
    def __init__(self, hauteur, largeur, pourcentage_mines):
        # 15,6 % se donne sous la forme 0.156
        assert ..., "Le pourcentage de mines doit être compris entre 10% et 30%."
        ...hauteur = hauteur
        ...largeur = largeur
        ...pourcentage_mines = pourcentage_mines

Question 1

#

Compléter l’assertion imposant entre 10 % et 30 % de mines.

Indice

Le constructeur reçoit une fraction entre 0 et 1.

Comprendre la correction
assert 0.10 <= pourcentage_mines <= 0.30, "Le pourcentage de mines doit être compris entre 10% et 30%."

Le paramètre est une proportion : 15,6 % est représenté par 0.156, pas 15.6. Les deux bornes sont incluses. Une grille de dimensions invalides demanderait une autre validation, distincte de cette assertion.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)

Question 2

#

Compléter les trois affectations d’attributs du constructeur.

Indice

Les attributs doivent appartenir à l’objet.

Comprendre la correction
self.hauteur = hauteur
self.largeur = largeur
self.pourcentage_mines = pourcentage_mines

self désigne l’instance créée. Les paramètres locaux sont conservés dans des attributs pour être réutilisés par les méthodes de génération et de voisinage.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)
NiveauGrilleMinesProportion indicative
Débutant8×81015,6 %
Intermédiaire16×164015,6 %
Expert30×169920,6 %

Question 3

#

Créer demineur_intermediaire selon les recommandations.

Indice

Le constructeur attend hauteur, largeur, puis proportion.

Comprendre la correction
demineur_intermediaire = Demineur(16, 16, 0.156)

La recommandation est une grille de 256 cases pour environ 40 mines, soit 15,6 %. Le code de placement donné atteint 40, car il incrémente jusqu’à atteindre ou dépasser 256×0.156 = 39.936. On peut aussi donner exactement 40/256 si l’on souhaite représenter le ratio exact.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)

Exemple 3 × 5 : [[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]].

def grille_demineur_vide(self):
    return [[0 for _ in range(...)] for _ in range(...)]

Question 4

#

Compléter grille_demineur_vide.

Indice

L’ordre largeur à l’intérieur, hauteur à l’extérieur correspond à liste de lignes.

Comprendre la correction
def grille_demineur_vide(self):
    return [[0 for _ in range(self.largeur)]
            for _ in range(self.hauteur)]

La boucle interne construit une ligne de largeur cases ; l’externe répète cette création hauteur fois. Les lignes sont indépendantes. Le constructeur peut ensuite affecter self.grille_demineur = self.grille_demineur_vide().

Voir la question dans le sujet PDF, p. 11, 12 (nouvel onglet)

randint(a,b) renvoie un entier aléatoire entre a et b inclus.

def placer_mines(self):
    compteur_mines = 0
    nombre_bombes = self.largeur * self.hauteur * self.pourcentage_mines
    while compteur_mines < nombre_bombes:
        ligne = randint(0, self.hauteur - 1)
        colonne = randint(0, ...)
        if self.grille_demineur[...][...] == 0:
            self.grille_demineur[...][...] = -1
            compteur_mines = ...

Question 5

#

Compléter placer_mines aux lignes 14 à 17.

Indice

Une mine n’est comptée qu’après placement sur une case libre.

Comprendre la correction
colonne = randint(0, self.largeur - 1)
if self.grille_demineur[ligne][colonne] == 0:
    self.grille_demineur[ligne][colonne] = -1
    compteur_mines += 1

Les bornes de randint sont incluses, donc le dernier indice est largeur-1. Une case déjà minée ne doit pas incrémenter le compteur : cela compterait deux fois la même mine. On suppose une grille encore vide et que la méthode est appelée une seule fois avant le calcul des nombres.

La quantité nombre_bombes du squelette est un flottant. La condition compteur_mines < nombre_bombes provoque un arrondi à l’entier supérieur si le produit n’est pas entier. Pour une politique de nombre de mines explicite, on préférera calculer un entier avec round ou une autre convention annoncée avant la boucle. Cette nuance devient visible dans l’exemple de la question suivante.

Voir la question dans le sujet PDF, p. 12 (nouvel onglet)

voisines((ligne,colonne)) renvoie une liste de tuples valides. Pour une grille 3×5 : voisines((0,0)) donne [(1,1),(1,0),(0,1)] ; voisines((1,2)) donne [(0,1),(0,3),(0,2),(2,1),(2,3),(2,2),(1,1),(1,3)].

Question 6

#

Écrire nombre_voisines_avec_mines, en utilisant voisines déjà disponible.

PythonCompter les mines autour d’une case, bords comprisÉcrivez votre solution et mettez-la à l’épreuve

Complétez nombre_voisines_avec_mines dans la classe Demineur. La classe de support fournit les attributs hauteur, largeur, grille_demineur et la méthode voisines(coordonnees_case), qui exclut la case centrale et les positions hors grille. Une mine vaut -1 ; les autres nombres ne sont pas des mines. Le générateur aléatoire et l’interface graphique sont volontairement absents : les tests installent des grilles déterministes pour vérifier cette méthode seule.

class Demineur(DemineurSupport):
    def nombre_voisines_avec_mines(self, coordonnees_case):
        # Utilisez self.voisines
        pass

Les cas de test proposés :

  • Un coin avec une mine en diagonale : Le voisinage contient les diagonales, même dans un coin.
  • Huit mines autour de la case centrale : Une case intérieure a huit voisines, pas seulement quatre.
  • Les nombres affichés ne sont pas des mines : Compter des cases non nulles ou additionner leurs valeurs donnerait un faux résultat.
  • Une grille rectangulaire : Les limites de ligne et de colonne ne sont pas interchangeables.
  • La case centrale ne se compte pas et la grille ne change pas : La méthode de voisinage exclut déjà le centre. La méthode demandée lit la grille sans la recalculer.
Indice

Les informations numériques et les mines se distinguent par la valeur -1.

Comprendre la correction
def nombre_voisines_avec_mines(self, coordonnees_case):
    nombre = 0
    for ligne, colonne in self.voisines(coordonnees_case):
        if self.grille_demineur[ligne][colonne] == -1:
            nombre += 1
    return nombre

La méthode voisines fournit seulement les coordonnées valides du voisinage, en excluant la case centrale. On compte les -1 et non les valeurs positives déjà calculées. La somme des nombres des cases voisines ne donnerait pas le nombre de mines, et pourrait compter plusieurs fois une même mine.

Voir la question dans le sujet PDF, p. 13 (nouvel onglet)

Exemple de grille 6×6 imprimée avec dix positions de mines, reproduit comme donnée source :

Ligne012345
02-12121
12-14-12-1
223-1221
32-12211
4-1322-12
512-122-1

Il est présenté après demineur_nsi = demineur(6,6,0.278), puis generer_demineur().

Question 7

#

Écrire generer_demineur pour donner à chaque case non minée son nombre de mines voisines.

Indice

Ne remplacez jamais une mine par son nombre de voisines.

Comprendre la correction
def generer_demineur(self):
    self.placer_mines()
    for ligne in range(self.hauteur):
        for colonne in range(self.largeur):
            if self.grille_demineur[ligne][colonne] != -1:
                self.grille_demineur[ligne][colonne] = (
                    self.nombre_voisines_avec_mines((ligne, colonne)))

L’appel place les mines sur la grille initialement vide, puis toutes les cases libres sont annotées. Si les mines ont déjà été placées par le constructeur, cet appel initial doit être omis : il ne faut pas placer les mines deux fois. Les -1 restent intacts, donc le calcul des cases suivantes ne dépend pas des chiffres déjà écrits.

L’exemple imprimé utilise demineur(6,6,0.278) mais la classe s’appelle Demineur. Il montre dix mines, alors que le placement fourni ferait ceil(36×0.278)=11. Pour obtenir dix mines, utiliser le ratio exact 10/36 ou une règle d’arrondi explicite. De plus, certains nombres de voisinage affichés dans les figures sont incohérents ; un programme doit les recalculer à partir des mines, pas recopier ces chiffres comme attendus.

Voir la question dans le sujet PDF, p. 13 (nouvel onglet)

Partie C : interface du jeu

La grille de visibilité commence entièrement à False ; True signifie une case révélée. Une mine révèle toutes les cases. Les figures 3/4 proposent cette grille de données, un écran initial entièrement caché, puis un clic (0,3) :

Ligne012345
0-110000
1221011
21-111-11
3111111
41-11000
5111000

Figure 4, visibilité imprimée après clic (à distinguer de la propagation corrigée expliquée ci-dessous) :

Ligne012345
0falsetruetruetruetruetrue
1falsetruetruetruetruetrue
2falsefalsetruetruefalsefalse
3falsefalsefalsefalsefalsefalse
4falsefalsefalsefalsefalsefalse
5falsefalsefalsefalsefalsefalse

Le constructeur général initialise self.grille_visibilite = [[False for _ in range(self.largeur)] for _ in range(self.hauteur)].

Question 8

#

Écrire visibilite(coordonnees_case) pour mettre à jour la grille de visibilité après un clic, éventuellement récursivement.

Indice

La visibilité sert aussi de marque de visite pour la propagation.

Comprendre la correction
def visibilite(self, coordonnees_case):
    ligne, colonne = coordonnees_case
    if self.grille_visibilite[ligne][colonne]:
        return
    if self.grille_demineur[ligne][colonne] == -1:
        for i in range(self.hauteur):
            for j in range(self.largeur):
                self.grille_visibilite[i][j] = True
        return
    self.grille_visibilite[ligne][colonne] = True
    if self.grille_demineur[ligne][colonne] == 0:
        for voisine in self.voisines(coordonnees_case):
            self.visibilite(voisine)

On marque une case avant de visiter ses voisines, sinon les appels pourraient revenir indéfiniment sur le même zéro. Une case numérotée est révélée mais ne propage pas. Un zéro révèle sa composante de zéros et sa frontière de cases numérotées. Une mine cliquée révèle toute la grille comme demandé. Depuis un zéro correctement calculé, aucune voisine n’est une mine, donc la propagation ne déclenche pas de défaite.

Les figures 3 et 4 imprimées ne sont pas cohérentes avec leurs mines : en (1,3), la mine (2,4) impose le nombre 1, et non 0. Les cases (3,0),(3,1),(3,2) devraient valoir 2, non 1. Avec les mines réellement dessinées, cliquer (0,3) révèle les colonnes 1 à 5 des lignes 0 et 1, soit dix cases. La correction et l’atelier utilisent ces nombres recalculés ; ils ne prolongent pas la propagation à partir du faux zéro imprimé.

Voir la question dans le sujet PDF, p. 15 (nouvel onglet)

Partie D : scores en ligne

Le schéma pédagogique stocke les comptes et les meilleurs scores par niveau :

id_joueurpseudomot_de_passe (données fictives du sujet)
1GrimdalEgxGB6a3bRinllon
2RaptorJ17NtfMS3Dudjjln
3PetiteFéeUuukBSvj01VrGoGD
4Kirna7NcDFPNl0Xy1MEPb
joueurniveauscoretemps
1facile120072
2expert1196366
3intermédiaire997230
2intermédiaire997200
4expert1097400
Demineur.niveaudimensionpourcentage mines
facile8x815.6
intermédiaire16x1615.6
expert30x1620.6

Question 9

#

Donner les clés étrangères de Meilleur_score et leurs clés primaires cibles.

Indice

Les tables de définition des joueurs et des niveaux sont les tables cibles.

Comprendre la correction

Meilleur_score.joueur référence Joueur.id_joueur ; Meilleur_score.niveau référence Demineur.niveau. Un meilleur score doit être associé à un compte et à un niveau existants.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)

Question 10

#

Donner une requête renvoyant les lignes (expert,1196) et (intermédiaire,997).

SQLExtraire les résultats de RaptorÉcrivez votre solution et mettez-la à l’épreuve

Le tableau demandé par la question regroupe les niveaux et scores du joueur 2, Raptor. Écrivez une requête donnant ses colonnes niveau et score, quel que soit le niveau auquel il a joué.

SELECT ...
FROM ...
WHERE ...;

Les cas de test proposés :

  • Scores officiels du démineur : Les comptes et scores fictifs sont ceux de l’énoncé. Le score 997 existe aussi pour une autre personne : filtrer seulement les nombres ne sélectionne pas correctement le joueur.
  • Cas complémentaire : les scores évoluent : Jeu complémentaire pédagogique, distinct des données officielles. Les résultats ne sont plus ceux de l’extrait. Le critère reste le joueur 2, pas une liste de scores recopiée.
Indice

Cherchez le joueur commun aux deux scores affichés.

Comprendre la correction
SELECT niveau, score
FROM Meilleur_score
WHERE joueur = 2;

Ces deux lignes sont les meilleurs scores du joueur 2, Raptor. La requête ne fixe pas d’ordre ; si l’affichage doit reproduire exactement expert avant intermédiaire, on peut ajouter ORDER BY niveau. D’autres requêtes accidentellement équivalentes sur cet extrait ne décriraient pas aussi clairement le même besoin.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)

Question 11

#

Kirna remplace son mot de passe par cGhhxDE4. Écrire la mise à jour du schéma fourni.

Indice

Ciblez le compte par sa clé primaire.

Comprendre la correction
UPDATE Joueur
SET mot_de_passe = 'cGhhxDE4'
WHERE id_joueur = 4;

L’identifiant 4 cible Kirna sans modifier les autres comptes. Cette écriture répond au schéma simplifié du sujet ; un service réel ne stocke pas un mot de passe en clair mais une empreinte de mot de passe salée calculée par un mécanisme adapté.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)
SELECT pseudo FROM Joueur
JOIN Meilleur_score ON Joueur.id_joueur = Meilleur_score.joueur
WHERE niveau = 'expert' AND score > 1000 AND temps < 400;

Question 12

#

Donner le résultat de la requête sur les experts avec score >1000 et temps <400.

Indice

L’égalité à 400 n’est pas acceptée par temps < 400.

Comprendre la correction
pseudo
Raptor

Raptor a un score expert 1196 et un temps 366 : les trois conditions sont remplies. Kirna a bien plus de 1000 points mais son temps vaut exactement 400, exclu par le test strict. Les autres lignes ne sont pas expert.

Voir la question dans le sujet PDF, p. 17 (nouvel onglet)

Question 13

#

Corriger le niveau facile pour qu’il soit désigné débutant dans la base.

Indice

Renommer une clé primaire exige de maintenir les références pendant l’opération.

Comprendre la correction
INSERT INTO Demineur (niveau, dimension, "pourcentage mines")
SELECT 'débutant', dimension, "pourcentage mines"
FROM Demineur WHERE niveau = 'facile';

UPDATE Meilleur_score
SET niveau = 'débutant'
WHERE niveau = 'facile';

DELETE FROM Demineur WHERE niveau = 'facile';

Cette séquence préserve les références même avec des clés étrangères contrôlées immédiatement : créer d’abord le niveau cible, déplacer ensuite les scores, puis retirer l’ancien niveau. Deux UPDATE naïfs, parent puis enfants ou inversement, peuvent violer la contrainte intermédiaire si la mise à jour n’est pas en cascade ou différée. Les trois commandes doivent être exécutées ensemble dans une transaction.

Le tableau du sujet écrit « pourcentage mines » avec un espace. Les guillemets doubles ci-dessus citent cet identifiant SQL ; si le schéma implémenté utilise pourcentage_mines, il faut employer ce nom réel. La séquence suppose que débutant n’existe pas déjà. Avec une clé étrangère ON UPDATE CASCADE explicitement configurée, une mise à jour du niveau parent aurait aussi suffi, mais cette configuration n’est pas annoncée.

Voir la question dans le sujet PDF, p. 17 (nouvel onglet)
Révéler une zone de démineur sans traverser une mineUn atelier pour expérimenter

Choisissez une case de la grille 6×6 des figures 3 et 4. Les nombres sont recalculés à partir des quatre mines du dessin. Un zéro propage, un nombre s’arrête, une mine révèle tout.

Lire le résultat de l’expérience initiale

10 case(s) révélée(s)

La propagation utilise un ensemble de cases déjà visitées. Les chiffres de la grille source comportaient des erreurs ; ici ils sont recalculés pour respecter exactement les mines (0,0), (2,1), (2,4), (4,1).

Ligne012345
0Cachée10000
1Cachée21111
2CachéeCachéeCachéeCachéeCachéeCachée
3CachéeCachéeCachéeCachéeCachéeCachée
4CachéeCachéeCachéeCachéeCachéeCachée
5CachéeCachéeCachéeCachéeCachéeCachée

La propagation doit reposer sur des voisins et des nombres exacts. Le marquage avant exploration empêche les retours infinis.

Revoir les notions de cet exercice

Du sujet à la méthode

Votre prochaine séance de révision

  • Annoncez le sens de tri et la règle des ex æquo avant de construire l’ABR.
  • Pour toute conversion de coordonnées, vérifiez une case du bord pour repérer un décalage de 1.
  • Au démineur, recalculer les nombres depuis les mines constitue une vérification indépendante des figures imprimées.

Retrouver ces notions dans d’autres sujets

Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027

Énoncé : sujet 26-NSIJ1G11 (PDF). Corrigé et explications pédagogiques proposés par Sofien.