Programmer un robot (3)

Série d'exercices

Cet exercice fait partie d'une série :

Il est fortement conseillé d'avoir traité les exercices précédents avant d'entreprendre celui-ci.

Rappels sur le fonctionnement du Robot

On considÚre dans cet exercice un robot se déplaçant sur une grille de dimensions finies. Initialement, il se trouve sur la case en haut à gauche de la grille et est dirigé vers la droite.

Ce robot est représenté en Python par un objet de la classe Robot. L'interface de la classe Robot est la suivante :

  • robot = Robot(4, 5) : instancie un objet de type Robot Ă©voluant dans une grille de 4 cases de haut et 5 de large. Cet objet est affectĂ© Ă  la variable robot ;

  • robot.avance() : fait avancer le robot d'une case dans la direction actuelle. Un dĂ©placement qui ferait sortir le robot de la grille est ignorĂ© ;

  • robot.droite() : fait tourner le robot d'un quart de tour vers la droite ;
  • robot.gauche() : fait tourner le robot d'un quart de tour vers la gauche ;
  • robot.dessine_parcours() : affiche la grille, les cases dĂ©jĂ  parcourues et la position actuelle du robot dans la console.

Un objet de type Robot contient aussi un attribut grille. Il s'agit d'une liste de listes gardant la trace des cases visitées (marquées par "*") ou non (laissées vides " ").

Exemple d'utilisation d'un Robot
>>> robot = Robot(4, 3)       # la grille fait 4 case de haut sur 3 de large
>>> robot.grille              # le robot est en haut Ă  gauche
[['*', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
>>> robot.dessine_parcours()  # le robot pointe vers la droite
┌───┐
│>  │
│   │
│   │
│   │
└───┘
>>> robot.avance()
>>> robot.avance()
>>> robot.droite()
>>> robot.avance()
>>> robot.avance()
>>> robot.avance()
>>> robot.dessine_parcours()
┌───┐
│***│
│  *│
│  *│
│  v│
└───┘
>>> robot.gauche()
>>> robot.avance()
>>> robot.dessine_parcours()
┌───┐
│***│
│  *│
│  *│
│  >│
└───┘
>>> robot.grille
[['*', '*', '*'], [' ', ' ', '*'], [' ', ' ', '*'], [' ', ' ', '*']]
La classe Robot
MOUVEMENTS = ((0, 1), (1, 0), (0, -1), (-1, 0))


class Robot:
    def __init__(self, hauteur, largeur):
        self.hauteur = hauteur
        self.largeur = largeur
        self.grille = [[" " for _ in range(largeur)] for _ in range(hauteur)]
        self.i = 0
        self.j = 0
        self.grille[self.i][self.j] = "*"
        self.direction = 0

    def avance(self):
        """Fait avancer le robot d'une case (seulement si possible)"""
        di, dj = MOUVEMENTS[self.direction]
        if 0 <= self.i + di < self.hauteur and 0 <= self.j + dj < self.largeur:
            self.i += di
            self.j += dj
            self.grille[self.i][self.j] = "*"

    def droite(self):
        """Fait tourner le robot d'un quart de tour vers la droite"""
        self.direction = (self.direction + 1) % 4

    def gauche(self):
        """Fait tourner le robot d'un quart de tour vers la gauche"""
        self.direction = (self.direction - 1) % 4

    def dessine_parcours(self):
        """Affiche les cases parcourues et la position actuelle du robot"""
        affichage = [
            ["" for _ in range(self.largeur + 2)] for _ in range(self.hauteur + 2)
        ]
        for j in range(1, self.largeur + 1):
            affichage[0][j] = "─"
            affichage[-1][j] = "─"
        for i in range(1, self.hauteur + 1):
            affichage[i][0] = "│"
            affichage[i][-1] = "│"
        affichage[0][0] = "┌"
        affichage[-1][0] = "└"
        affichage[0][-1] = "┐"
        affichage[-1][-1] = "┘"
        for i in range(self.hauteur):
            for j in range(self.largeur):
                affichage[1 + i][1 + j] = self.grille[i][j]
        affichage[self.i + 1][self.j + 1] = [">", "v", "<", "^"][self.direction]
        print("\n".join("".join(ligne) for ligne in affichage))

La classe Robot est déjà chargée dans l'éditeur, vous pouvez l'utiliser sans l'importer.

On programme ce robot en lui faisant effectuer différentes actions :

  • le code "A" fait avancer le robot ;
  • le code "D" le fait tourner vers la droite ;
  • le code "G" le fait tourner vers la gauche.

Il est aussi possible d'utiliser des instructions du type [action, n] dans lesquelles :

  • action est une des trois actions dĂ©crites ci-dessus ;
  • n est un nombre entier positif ou nul.

Lors d'une répétition, on exécute un maximum d'actions valides. Si par exemple, le robot ne peut avancer que de 5 cases, les instructions ["A", 8] le feront avancer de 5 cases, seules les trois derniÚres actions seront ignorées.

Fonction decoupe

On fournit une fonction decoupe rudimentaire permettant de transformer une chaĂźne de caractĂšres formant une suite d'instructions en une liste.

def decoupe(instructions):
    return [int(c) if c.isnumeric() else c for c in instructions]

decoupe("A3GD5")  # renvoie ['A', 3, 'G', 'D', 5]

Cette fonction est déjà importée dans l'éditeur. Vous pouvez l'utiliser pour écrire des tests personnels.

Cette fonction ne gÚre que les entiers positifs strictement inférieurs à 10 et ne filtre pas les instructions incorrectes. Ainsi decoupe("Z12") renvoie ['Z', 1, 2].

Le premier exercice de la série a pour objectif d'écrire une version valide de cette fonction.

Les instructions permettant de dessiner un carré de 4 cases de cÎtés sont ["A", 3, "D", "A", 3, "D", "A", 3, "D", "A", 3]. Cette série d'instructions comporte un motif ["A", 3, "D"] répété plusieurs fois.

On autorise désormais les répétitions de motifs en écrivant des instructions au format ["(", <instructions>, ")", n] dans lesquelles :

  • <instructions> est une suite d'instructions valides ;
  • n est un nombre entier positif ou nul.

En utilisant ce format, les instructions construisant le carré deviennent : ["(", "A", 3, "D", ")", 4].

Il est possible d'imbriquer des motifs répétés comme dans ["(", "(", "A", 2, "D", ")", 4, "A", 8, "D", ")", 4] qui permet d'obtenir :

Motifs imbriqués
┌────────┐
│********│
│* *  * *│
│***  ***│
│*      *│
│*      *│
│***  ***│
│* *  * *│
│********│
└────────┘

Si certaines actions d'un motif répété font sortir le robot de la grille elles sont ignorées. Les autres instructions sont par contre exécutées.

Il existe plusieurs façon d'interpréter une série d'instructions de ce type. On souhaite dans cet exercice écrire une fonction deroule permettant de dérouler les motifs répétés avant l'exécution proprement dite. Cette fonction transformera donc ["A", "(", "G", "D", ")", 2, "A"] en ["A", "G", "D", "G", "D", "A"].

Il s'agit d'une fonction récursive qui prend deux paramÚtres :

  • la suite d'instructions Ă  convertir,
  • l'indice de la premiĂšre instruction Ă  considĂ©rer.

Elle renvoie un tuple formé :

  • de la liste des instructions dĂ©roulĂ©es
  • et de l'indice de la prochaine instruction Ă  lire.

Lors de la lecture des instructions, la fonction peut rencontrer :

  • une instruction simple ("A", "D", "G" ou un entier positif ou nul) : la fonction l'ajoute Ă  son rĂ©sultat ;
  • une parenthĂšse fermante ou la fin de la liste d'instructions : la fonction renvoie son rĂ©sultat et l'indice immĂ©diatement suivant ;
  • une parenthĂšse ouvrante : dans ce cas, la fonction s'appelle elle-mĂȘme Ă  partir de l'indice suivant. Cet appel renvoie la suite des instructions entre parenthĂšses et l'indice de l'entier suivant la parenthĂšse fermante. Il ne reste alors qu'Ă  ajouter les instructions rĂ©cupĂ©rĂ©es autant de fois que nĂ©cessaire au rĂ©sultat.

Appels récursifs

Écrire la fonction deroule prĂ©sentĂ©e ci-dessus.

On garantit que :

  • toutes les instructions sont valides ("A", "D", "G", "(", ")" ou un entier positif ou nul),
  • chaque entier suit une action valide ou une parenthĂšse fermante,
  • toute parenthĂšse fermante est suivie d'un entier,
  • la suite d'instructions est correctement parenthĂ©sĂ©e.
Exemples
>>> instructions = ["A", "(", "G", "D", ")", 2, "A"]
>>> resultat, i_final = deroule(instructions, 0)
>>> resultat
['A', 'G', 'D', 'G', 'D', 'A']
>>> i_final
7
>>> resultat, i_final = deroule(instructions, 2)
>>> resultat
['G', 'D']
>>> i_final
5

###(Dés-)Active le code aprÚs la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activĂ©, le texte copiĂ© dans le terminal est joint sur une seule ligne avant d'ĂȘtre copiĂ© dans le presse-papier
Évaluations restantes : 10/10

.128013,:CĂȘkn9DSAvsu8;y7e[620wG]r5_p»ag)R1i/Ă©=m«hb.4x+*odt c(P3qlf050Y0s0Z0F0K0*0m0!0#0*0F0m0m0N010Z0K0D010406050m0n0O0O0F0A0q040j0X0*0n0 0X0g0!020F0O0D0p0!0I0s190A0)0n0s0m050L16181a1c140D041A1H051K0L1K1M1H140Y0K0l0@0_0{0}0Q0K0G0Q0*1!0Q0Z12050/0R0*0s1V0`0|011Z1#1%1#0Z1-1/1+0Z0R0X0Y1c1,0A1I0Z0Q0@1f0m0D0F0g0}0v011;1X010+0;0s0g1n0s1+2c2e2j1?2m1/2p0O2r040a0!0%0A0X0D0X0m0K1i1k0-2a0A0A0s0#2M1A2t0g1I0L282Y0Z2625270Y2v0}1%0g2o2J1+1S1U0^1=2,0K2.0g221T1+0D2R1I2W2Y33152d1k2@2k2|0A190*120!0J2V3713362u391?3b3d3f0v3i2e3k2W2+013p0F3e040!0(3t2X143w3n0}3z3B0!0T3F3v373x3L3f0B3P3H3R3J3y0X3c3A3f0u3W3l381W3o3#3q3C0r3*3I3-3K3/3%3C0o3?3Y3^3!3$3M0h3~3m403T040J0w453,2^413:0J3h1B3j3X464e480J3s4j3u4l4d3a3`3B0J3E4r3G3+3S4w120J3O4A2Y300s2Y2=2#0Y2)3x0#222B0,1T1I4K323j3P054S0-4Z4m2k0f120-0+4#3@4e0x3f4:3 4n0+4-0s2G0n1/4^4*1?11040$514u3o122`0m2$0n2T0K1j1z4I4C3Z540b3P0!5k475a573x540H0c3W0!5z5p4;3a122R160*0/0Z5o5q4e0X120N5K5C53120t0z5y5A5L4+120x1Z504I5B4_5D040K5Q5*1?5N04025H0p5.523K0R122y5t5l12565j5R3K5a0g5c0A5e2M5i3564015v5x4I065A6k5)5`3y66680n5_580}5;5P5(5Y595,675d5f5h5 40540t6F4n5s635/0}545V6i6l5X6e4,5,4/6x6e0g6p5d6s3x6v6w336m6t010m2h04010$016J2k546h336j6S6l6y65040m0X0n0m0C5F4 5I6^5S045n6Y6N6o5,6%3Z6v7j5r040-4~5%6d7g54627s6n6!6A6q6D677b6O127e6+707h5-7f6n5;0V7m4e0O0K4F7D6f120H5W6~6T7g7y0g7P2k7l7L6-7y5b6C6b7U6H7U7-7;126Q6|7Z6k7I7y785H0F5J7+6(120V6*3j6,3S127375771y79827(5:120W8j717%6R6~7I6V0s1%6X7H6Z6#698n016)8B6/12010H6@6M6n6`7Y7|8s5E0.0n0A8p8x7#8Q5G7a8K6-5m8B7@847k868B7R7T8q6S8P048u0m0s7^046{4k7|7!7x8X8h837w6-5;0S7?120F0D0D2o0Y8_7v4!8y7z6$8!5u7W8N7}9h7K8V7M86883u8a3Z8-498N8;2R0Z8S8U897~9081929g7t7F8%6L6|1A4%4L1J311A4N1A0Z4P9Y2%2Z21232#0F1.9T0L4N1G4)6-2R0O0C0+0F0f0s0C0Q0(121s1u1w1y0!8{3u1J3k1H0d0.0Z1:0+1j7B0!9b1h0!0e2$1:4S0n0D0*0M1:0F0l2S0!0_0!af0gah0Y000s0U0M0#1h5g1kaI0A2L1:0Y2e0?0*aF0U4}0#0K0#1:0P5p0F0!0Q2R0+0}0S0S0La.0LaGaXaZ1y0L2G210Z0C0v0L122F0X0G0A1n2A0A0!0n1ka{0X0Z0!0$0v7X0L0F3C0Ea5a81R1T3x1^1$1(1*9;3x2x2o2q122D0d5h1f1:0%0q281j4#4Y3+344!9Sbw3Z0G54020G0Z0pbVbXbZ1q9704bc9K9v7I7*9r7,a00Xa|9e7U9y3V9k607d8,7S04b_939l04bi9Q6ebT12b#c9bWb$b`7n7.697B6c9G6eb-cj9M046Icd4e8F6;0k8Jc1b{7Gcm6ncs6=cv9L8L9N8)40cB0ycDa76e8$cHcr6:010icL2X7IcOb.3xcB8I8_cyb+6e9y4qcw6GcGcY3ZcBcu8_7`4k7Ic75=cbcabX5pcq5+cf6aaL767p741/1yc$9O040C8Bclc(8W7o4}d78^d07c9fcMdhd2chdacP2k9y4bdm7Ec34c3xc`c}bYcbc c,6K8=aWaJadb?dy7hb)dtc/ce6Bcg7:dP5v3*0LbQ9U9-9/9W0.0:0=04.

###(Dés-)Active le code aprÚs la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Alt+: ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activĂ©, le texte copiĂ© dans le terminal est joint sur une seule ligne avant d'ĂȘtre copiĂ© dans le presse-papier
Évaluations restantes : 10/10

.128013,:CĂȘkn9DSAvsu8;y7e[620wG]r5_p»ag)R1i/Ă©=m«hb.4x+*odt c(P3qlf050Y0s0Z0F0K0*0m0!0#0*0F0m0m0N010Z0K0D010406050m0n0O0O0F0A0q040j0X0*0n0 0X0g0!020F0O0D0p0!0I0s190A0)0n0s0m050L16181a1c140D041A1H051K0L1K1M1H140Y0K0l0@0_0{0}0Q0K0G0Q0*1!0Q0Z12050/0R0*0s1V0`0|011Z1#1%1#0Z1-1/1+0Z0R0X0Y1c1,0A1I0Z0Q0@1f0m0D0F0g0}0v011;1X010+0;0s0g1n0s1+2c2e2j1?2m1/2p0O2r040a0!0%0A0X0D0X0m0K1i1k0-2a0A0A0s0#2M1A2t0g1I0L282Y0Z2625270Y2v0}1%0g2o2J1+1S1U0^1=2,0K2.0g221T1+0D2R1I2W2Y33152d1k2@2k2|0A190*120!0J2V3713362u391?3b3d3f0v3i2e3k2W2+013p0F3e040!0(3t2X143w3n0}3z3B0!0T3F3v373x3L3f0B3P3H3R3J3y0X3c3A3f0u3W3l381W3o3#3q3C0r3*3I3-3K3/3%3C0o3?3Y3^3!3$3M0h3~3m403T040J0w453,2^413:0J3h1B3j3X464e480J3s4j3u4l4d3a3`3B0J3E4r3G3+3S4w120J3O4A2Y300s2Y2=2#0Y2)3x0#222B0,1T1I4K323j3P054S0-4Z4m2k0f120-0+4#3@4e0x3f4:3 4n0+4-0s2G0n1/4^4*1?11040$514u3o122`0m2$0n2T0K1j1z4I4C3Z540b3P0!5k475a573x540H0c3W0!5z5p4;3a122R160*0/0Z5o5q4e0X120N5K5C53120t0z5y5A5L4+120x1Z504I5B4_5D040K5Q5*1?5N04025H0p5.523K0R122y5t5l12565j5R3K5a0g5c0A5e2M5i3564015v5x4I065A6k5)5`3y66680n5_580}5;5P5(5Y595,675d5f5h5 40540t6F4n5s635/0}545V6i6l5X6e4,5,4/6x6e0g6p5d6s3x6v6w336m6t010m2h04010$016J2k546h336j6S6l6y65040m0X0n0m0C5F4 5I6^5S045n6Y6N6o5,6%3Z6v7j5r040-4~5%6d7g54627s6n6!6A6q6D677b6O127e6+707h5-7f6n5;0V7m4e0O0K4F7D6f120H5W6~6T7g7y0g7P2k7l7L6-7y5b6C6b7U6H7U7-7;126Q6|7Z6k7I7y785H0F5J7+6(120V6*3j6,3S127375771y79827(5:120W8j717%6R6~7I6V0s1%6X7H6Z6#698n016)8B6/12010H6@6M6n6`7Y7|8s5E0.0n0A8p8x7#8Q5G7a8K6-5m8B7@847k868B7R7T8q6S8P048u0m0s7^046{4k7|7!7x8X8h837w6-5;0S7?120F0D0D2o0Y8_7v4!8y7z6$8!5u7W8N7}9h7K8V7M86883u8a3Z8-498N8;2R0Z8S8U897~9081929g7t7F8%6L6|1A4%4L1J311A4N1A0Z4P9Y2%2Z21232#0F1.9T0L4N1G4)6-2R0O0C0+0F0f0s0C0Q0(121s1u1w1y0!8{3u1J3k1H0d0.0Z1:0+1j7B0!9b1h0!0e2$1:4S0n0D0*0M1:0F0l2S0!0_0!af0gah0Y000s0U0M0#1h5g1kaI0A2L1:0Y2e0?0*aF0U4}0#0K0#1:0P5p0F0!0Q2R0+0}0S0S0La.0LaGaXaZ1y0L2G210Z0C0v0L122F0X0G0A1n2A0A0!0n1ka{0X0Z0!0$0v7X0L0F3C0Ea5a81R1T3x1^1$1(1*9;3x2x2o2q122D0d5h1f1:0%0q281j4#4Y3+344!9Sbw3Z0G54020G0Z0pbVbXbZ1q9704bc9K9v7I7*9r7,a00Xa|9e7U9y3V9k607d8,7S04b_939l04bi9Q6ebT12b#c9bWb$b`7n7.697B6c9G6eb-cj9M046Icd4e8F6;0k8Jc1b{7Gcm6ncs6=cv9L8L9N8)40cB0ycDa76e8$cHcr6:010icL2X7IcOb.3xcB8I8_cyb+6e9y4qcw6GcGcY3ZcBcu8_7`4k7Ic75=cbcabX5pcq5+cf6aaL767p741/1yc$9O040C8Bclc(8W7o4}d78^d07c9fcMdhd2chdacP2k9y4bdm7Ec34c3xc`c}bYcbc c,6K8=aWaJadb?dy7hb)dtc/ce6Bcg7:dP5v3*0LbQ9U9-9/9W0.0:0=04.