Dessiner un carré

bonsoir
Le livre de MB Programmation, propose de coder un programme permettant de dessiner un carré…ce que j’ai fait.

Dans la section 4, il propose d’entrer la taille du carré comme argument de la fonction.
Ce que j’ai fait ici :

//entrer la taille du carré en paramètre de la fonction dessinerUnCarre

var taille:Int
print(« quelle sera la taille du carré ? »)
taille = Utilisateur.saisirEntier()

var ligne: String = « * "
func dessinerUnCarre(taille: Int) {
//on définit la longueur d’une ligne selon la taille du carré
func ligneDEtoiles() {
for _ in 2…taille {
ligne.insert( »*", at: ligne.endIndex)
ligne.insert(" « , at: ligne.endIndex)
}
print( »(ligne)")
}

//on imprime le nombre de lignes correspondant à la taille du carré
ligneDEtoiles()
for _ in 2...taille {
    print("\(ligne)")
}

}
dessinerUnCarre(taille: taille)

Mais ensuite, il est demandé de passer aussi le choix du caractère pour dessiner le carré en argument. Et là, ben ça plante.

rUnCarre
var taille:Int
print(« quelle sera la taille du carré ? »)
taille = Utilisateur.saisirEntier()

var choixCharacter:String
print(« Avec quel caractère ? »)
choixCharacter = Utilisateur.saisirTexte()

func dessinerUnCarre(taille: Int, choixCharacter:String) {
//on définit la longueur d’une ligne selon la taille du carré
func ligneDeCaracteres() {
for _ in 2…taille {
choixCharacter.insert("(choixCharacter)", at: choixCharacter.endIndex)
choixCharacter.insert(" « , at: choixCharacter.endIndex)
}
print( »(choixCharacter)")
}

//on imprime le nombre de lignes correspondant à la taille du carré
ligneDeCaracteres()
for _ in 2...taille {
    print("\(choixCharacter)")
}

}
dessinerUnCarre(taille: taille, choixCharacter: choixCharacter)

Playgrounds moi pas connaître ! Je préfère travailler avec le simulateur.

Ton code est un peu étrange. Tu crées une variable globale, que tu modifie ensuite dans le corps d’une fonction. L’idée de la programmation objet est d’isoler au maximum les choses, les unes des autres.

J’ai téléchargé le livre de MB pour voir ce qu’il demande, mais je n’ai pas trouvé. Je présume que le but est d’afficher un rectangle comme ça :

*****
*   *
*   *
*   *
*****

J’ai utilisé ton code, qui me donne ça :

(ligne)
** * * * 
** * * * 
** * * * 
** * * * 

Je crois que tu as oublié un caractère dans le premier print().

print("(ligne)")

C’est mieux comme ça :

print("\(ligne)")

A noter, qu’on peut utiliser une formulation plus simple, pour afficher le contenu d’une variable. Il suffit d’écrire :

print (ligne)

J’en reviens à ton code. Le résultat est encore loin d’être parfait :

** * * * 
** * * * 
** * * * 
** * * * 
** * * * 

Tu utilises la méthode String.insert() pour ajouter un caractère à la fin d’une String.

Le code déclare une chaîne ne contenant qu’un caractère “" et lui ajoute successivement un "” et un " ", d’où l’aspect un peu étrange :

** * * * *

Ce serait plus esthétique en inversant l’ordre des ajouts. Avec un " " et un “*”, tu aurais :

* * * * *

Je te propose une variante, utilisant l’opérateur “+” et la méthode String(repeat:) pour créer une chaîne en répétant n fois le même caractère.

func dessinerUnCarre(taille: Int) {
        let lignePleine = String(repeating: "*", count: taille)
        let ligneVide = "*" + String(repeating: " ", count: taille-2) + "*"
        
        print (lignePleine)
        for _ in 2...taille {
            print (ligneVide)
        }
        print (lignePleine)
    }

Elle est entièrement autonome, utilisant ces propres variables pour faire des manipulations de caractères. Par rapport au reste de l’application c’est une boîte noire opaque remplissant une fonction précise.

Résultat :

*****
*   *
*   *
*   *
*   *
*****

Voici la variante pour faire la même chose avec un caractère quelconque :

func dessinerUnCarre(taille: Int, char:String) {
        let lignePleine = String(repeating: char, count: taille)
        let ligneVide = char + String(repeating: " ", count: taille-2) + char
        
        print (lignePleine)
        for _ in 2...taille {
            print (ligneVide)
        }
        print (lignePleine)
    }

Avec le caractère “X” :

XXXXX
X   X
X   X
X   X
X   X
XXXXX

Pour en revenir à ton code, il y a TROIS problèmes :

UN :

Tu as utilisé une syntaxe bizarroïde pour l’instruction insert.

choixCharacter.insert("(\choixCharacter)", at: choixCharacter.endIndex)

Xcode en perd son latin. Il ne comprend pas pourquoi tu as ajouté tous ces symboles inutiles devant et derrière le nom de la variable. Il faut écrire :

choixCharacter.insert(choixCharacter, at: choixCharacter.endIndex)

DEUX :

choixCharacter est un paramètre de la fonction. TOUS les paramètres sont sensés être constants. Pourtant tu tentes de modifier sa valeur dans ton code. C’est pourquoi Xcode t’insulte avec le message “Cannot use mutating member on immutable value: ‘choixCharacter’ is a ‘let’ constant”.

Si tu veux modifier le contenu d’un paramètre d’une fonction, tu doit en créer une copie mutable (pouvant être modifié - déclaration avec var).

Ceci dit, je ne vois pas l’intérêt de faire ça ici. Pour résoudre ton problème, il suffit de créer une variable temporaire et d’écrire dedans.

var ligne = ""

TROIS :

La fonction insert() permet d’insérer un caractère à la fin d’un String

func insert(_ newElement: Character, at i: String.Index)

Selon le contexte “*” peut être considéré comme un caractère ou une String courte d’un seul caractère. Quand tu tapes :

ligne.insert("*", at: ligne.endIndex)

Xcode, connaissant la syntaxe de insert, comprend que “*” est un caractère et l’utilise comme tel.

Dans la seconde fonction, choixCharacter est un String (paramètre de la fonction), mais il est transmis à insert() à la place d’un Character. Xcode n’apprécie pas et hurle … Pour le calmer, il faut caster (transformer) le String en Character.

let maString = "z"
let char = Character(maString)

Voici une version modifiée de ton code, corrigeant les 3 problèmes :

func dessinerUnCarre2(taille: Int, choixCharacter:String) {
        
        //on définit la longueur d'une ligne selon la taille du carré
        var ligne = choixCharacter + " "
        
        func ligneDeCaracteres() {
            for _ in 2...taille {
                ligne.insert(Character(choixCharacter), at: ligne.endIndex)
                ligne.insert(" ", at: ligne.endIndex)
            }
            print(ligne)
        }
        
        //on imprime le nombre de lignes correspondant à la taille du carré
        ligneDeCaracteres()
        for _ in 2...taille {
            print(ligne)
        }
    }

Résultat avec la lettre “O” :

O O O O O 
O O O O O 
O O O O O 
O O O O O 
O O O O O 

On peut aussi écrire la fonction de manière à lui transmettre directement un Character au lieu d’une String.

func dessinerUnCarre2(taille: Int, choixCharacter:Character) {
        
        //on définit la longueur d'une ligne selon la taille du carré
        var ligne = String(choixCharacter) + " "
        
        func ligneDeCaracteres() {
            for _ in 2...taille {
                ligne.insert(choixCharacter, at: ligne.endIndex)
                ligne.insert(" ", at: ligne.endIndex)
            }
            print(ligne)
        }
        
        //on imprime le nombre de lignes correspondant à la taille du carré
        ligneDeCaracteres()
        for _ in 2...taille {
            print(ligne)
        }
    }

Merci Draken pour ta réponse très détaillée.

Les 2 variables du début permettent d’enregistrer le choix de l’utilisateur et de les utiliser ensuite pour afficher le carré selon ces choix. Peut-on les intégrer dans la fonction ? Si je le fais, cela ne fonctionne plus.

J’ai utilisé la méthode repeating que tu proposes et j’ai modifié pour obtenir un carré plein.

Voilà le résultat : https://www.dropbox.com/s/a3xxiklw7ntg49j/carre%20avec%202%20parametres.mov?dl=0

et le code :

`//entrer la taille du carré en paramètre de la fonction ainsi que le choix du caractère dessinerUnCarre

var taille:Int
print(“quelle sera la taille du carré ?”)
taille = Utilisateur.saisirEntier()

let character:String
print(“Avec quel caractère ?”)
character = Utilisateur.saisirTexte() + " "

func dessinerUnCarre(taille: Int, character:String) {
let lignePleine = String(repeating: character, count: taille)
print (lignePleine)

for _ in 2...taille {
    print (lignePleine)
}
print (lignePleine)

}

dessinerUnCarre(taille: taille, character: character)`

Si ton objectif est de dessiner uniquement des carrés pleins, la méthode de dessin peut être simplifié. Il suffit d’afficher n fois la même chaîne de caractères, en la pré-calculant avant la boucle.

func dessinerCarrePlein(taille:Int, char:String) {
        let ligne = String(repeating: char, count: taille)
        for _ in 1...taille {
            print (ligne)
        }
    }

On peut faire encore plus compact , en fabriquant une nouvelle ligne à chaque affichage :

func dessinerCarrePlein2(taille:Int, char:String) {
        for _ in 1...taille {
            print (String(repeating: char, count: taille))
        }
    }

Juste pour rigoler, les émojis sont des caractéres comme les autres. Donc :

dessinerCarrePlein2(taille: 5, char: "🤡")

🤡🤡🤡🤡🤡
🤡🤡🤡🤡🤡
🤡🤡🤡🤡🤡
🤡🤡🤡🤡🤡
🤡🤡🤡🤡🤡

Non, c’est très bien comme ça. Ce qu’il faut éviter c’est ce que tu avait fait tout au début : écrire une fonction avec des paramètres, dont le fonctionnement interne nécessite une variable définie ailleurs dans l’application.

Je recopie ton code :

var ligne: String = "* "
    
    func dessinerUnCarre(taille: Int) {
        //on définit la longueur d'une ligne selon la taille du carré
        func ligneDEtoiles() {
            for _ in 2...taille {
                ligne.insert("*", at: ligne.endIndex)
                ligne.insert(" ", at: ligne.endIndex)
            }
            print("(ligne)")
        }
        
        //on imprime le nombre de lignes correspondant à la taille du carré
        ligneDEtoiles()
        for _ in 2...taille {
            print("\(ligne)")
        }
    }

Telle qu’elle est écrite, la méthode a besoin de la variable ligne, définie à l’EXTERIEUR de la fonction. C’est une très mauvaise manière de faire. La variable ligne, étant accessible depuis l’extérieur, peut être accidentellement modifiée à la suite d’une erreur humaine. C’est pourquoi il faut sécuriser les fonctions, et passer toutes les informations de fonctionnement dans les paramètres.

Dans le cas de ton code, il suffit de définir la variable ligne DANS la fonction et c’est réglé.

Par le passé, des combinaisons méthodes pas assez sécurisé + erreurs de programmation modifiant un truc qu’il n’aurais pas fallu toucher ont causé des accidents d’avions, des explosions d’engins spatiaux, des accidents industriels, etc … Ce n’est pas pour rien que les langages modernes intègrent des mécanismes de sécurité absents des vieux langages.

Bref, autant prendre de bonnes habitudes dés le début !

1 « J'aime »

Merci Draken, j’y vois plus clair sur les paramètres