Introduction
Dans ce projet, vous apprendrez à créer un simple jeu de frappe à l'aide d'un script shell. Le jeu affiche des caractères aléatoires à l'écran, et votre objectif est de les taper avant qu'ils ne disparaissent. Le jeu propose différents modes avec des niveaux de difficulté variables. Vous pouvez choisir de pratiquer la frappe de nombres, de lettres, d'un mélange des deux, ou même de vos propres mots personnalisés.
👀 Aperçu

🎯 Tâches
Dans ce projet, vous apprendrez :
- Comment créer un fichier de projet et l'ouvrir dans un éditeur de code
- Comment afficher une interface de bienvenue en utilisant des caractères spéciaux et des couleurs
- Comment implémenter un menu de sélection de mode pour choisir le niveau de difficulté
- Comment implémenter un menu de sélection de catégorie de frappe pour choisir le type de caractères à pratiquer
- Comment créer des fonctions pour dessiner une bordure et remplir la couleur de fond de l'interface de frappe
- Comment générer des lettres et des nombres aléatoires pour le jeu de frappe
- Comment implémenter la fonctionnalité de frappe, y compris la gestion de l'entrée utilisateur et le calcul de la précision
- Comment créer une fonction de sortie gracieuse pour gérer les signaux spéciaux
🏆 Réalisations
Après avoir terminé ce projet, vous serez en mesure de :
- Démontrez les bases de la programmation shell
- Utilisez des caractères spéciaux et des couleurs dans la sortie du terminal
- Lisez l'entrée de l'utilisateur dans les scripts shell
- Implémentez des menus et des interfaces utilisateur dans les scripts shell
- Gérez les signaux spéciaux dans les scripts shell
Créer le fichier de projet
Pour commencer, veuillez créer un nouveau fichier nommé shell_typing_game.sh et l'ouvrir dans votre éditeur de code préféré.
cd ~/project
touch shell_typing_game.sh
Afficher l'interface de bienvenue
#!/bin/bash
function dis_welcome() {
declare -r str='
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
1000000010000000101111101000000111100111000000100000001000001111
0100000101000001001000001000001000001000100001010000010100001000
0010001000100010001111101000001000001000100010001000100010001111
0001010000010100001000001000001000001000100100000101000001001000
0000100000001000001111101111100111100111001000000010000000101111
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000001111000000100000000001000000010000001111100000000000
0000000000010000000001010000000010100000101000001000000000000000
0000000000010011000011111000000100010001000100001111100000000000
0000000000010001000100000100001000001010000010001000000000000000
0000000000001111001000000010010000000100000001001111100000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000'
declare -i j=0
declare -i row=3 ## Ligne de départ de l'impression.
line_char_count=65 ## Définit la position du saut de ligne, 64 caractères par ligne plus les sauts de ligne, soit un total de 65 caractères.
echo -ne "\033[37;40m\033[5;3H" ## Définit la couleur et la position de départ du curseur.
for ((i = 0; i < ${#str}; i++)); do
## Saut de ligne.
if [ "$((i % line_char_count))" == "0" ] && [ "$i"!= "0" ]; then
row=$row+1
echo -ne "\033["$row";3H"
fi
## Détermine les caractères de premier plan et d'arrière-plan.
if [ "${str:$i:1}" == "0" ]; then
echo -ne "\033[37;40m "
elif [ "${str:$i:1}" == "1" ]; then
echo -ne "\033[31;40m$"
fi
done
}
dis_welcome
La longue chaîne au début 010101... est l'interface de bienvenue du jeu à afficher. Si vous la regardez directement, vous risquez de ne pas comprendre ce que c'est. Vous pouvez donc copier cette chaîne dans l'éditeur gedit, puis appuyer sur le raccourci Ctrl+F pour effectuer une recherche. Lorsque vous entrez 1, vous comprendrez immédiatement. Concentrons-nous sur la commande echo. Nous devons utiliser la commande echo car nous voulons l'afficher à l'écran (ici, il s'agit du terminal Xfce. L'entrée standard, la sortie standard et la sortie d'erreur standard sous Linux sont toutes connectées au terminal par défaut). Pour réaliser certaines fonctions spéciales, telles que l'impression à une position spécifiée, la définition de la couleur des caractères affichés et l'impression de l'arrière-plan de l'ensemble du jeu, nous devons utiliser certains paramètres spéciaux de la commande correspondante, comme indiqué dans le code suivant :
## Définit la couleur et la position de départ du curseur.
echo -ne "\033[37;40m\033[5;3H"
D'après les commentaires, nous pouvons à peu près comprendre sa fonction. Mais que signifie-t-il exactement? Tout d'abord, le paramètre -n signifie d'imprimer sur la ligne actuelle sans saut de ligne après la sortie. Ensuite, définissez la couleur des caractères de sortie et la position du curseur. Parmi eux, \033[couleur de premier plan;couleur d'arrière-plan m, et ensuite \033[ligne;colonne H. Cependant, pour que echo reconnaisse ces séquences d'échappement spéciales, nous avons besoin du paramètre -e, sinon echo affichera ces caractères tels quels.
Couleurs de premier plan : 30 - 39, couleurs d'arrière-plan : 40 - 49.
| Valeur | Couleurs de premier plan | Valeur | Plage de couleurs d'arrière-plan : 40 - 49 |
|---|---|---|---|
| 30 | Définit le premier plan en noir | 40 | Définit l'arrière-plan en noir |
| 31 | Définit le premier plan en rouge | 41 | Définit l'arrière-plan en rouge |
| 32 | Définit le premier plan en vert | 42 | Définit l'arrière-plan en vert |
| 33 | Définit le premier plan en marron | 43 | Définit l'arrière-plan en marron |
| 34 | Définit le premier plan en bleu | 44 | Définit l'arrière-plan en bleu |
| 35 | Définit le premier plan en violet | 45 | Définit l'arrière-plan en violet |
| 36 | Définit le premier plan en cyan | 46 | Définit l'arrière-plan en cyan |
| 37 | Définit le premier plan en blanc | 47 | Définit l'arrière-plan en blanc |
| 38 | Active le soulignement sur la couleur de premier plan par défaut | 49 | Définit l'arrière-plan en noir par défaut |
| 39 | Désactive le soulignement sur la couleur de premier plan par défaut |
Qui aurait pensé que echo pouvait être utilisé de cette manière! Vous avez l'impression de le découvrir à nouveau? Maintenant, testons ce morceau de code dans le terminal.
cd ~/project
bash shell_typing_game.sh

Afficher le menu de sélection de mode
## Déclare la variable 'time' pour représenter le délai d'attente de frappe, où différents niveaux de difficulté correspondent à différentes valeurs de délai
declare -i time
function modechoose() {
## Sélectionner parmi trois modes différents
echo -e "\033[8;30H1) Mode facile"
echo -e "\033[9;30H2) Mode normal"
echo -e "\033[10;30H3) Mode difficile"
echo -ne "\033[22;2HVeuillez entrer votre choix : "
read mode
case $mode in
"1")
time=10
dismenu ## Appelle la fonction de sélection de menu
;;
"2")
time=5
dismenu
;;
"3")
time=3
dismenu
;;
*)
echo -e "\033[22;2HVotre choix est incorrect, veuillez réessayer"
sleep 1
;;
esac
}
Avant de définir cette fonction, nous avons d'abord déclaré une variable entière 'time' en utilisant la commande declare -i. Ensuite, dans la déclaration case suivante, nous avons défini différentes valeurs pour la variable 'time' en fonction de la sélection de difficulté de l'utilisateur. Plus la difficulté est élevée, plus la valeur est petite. Cette variable est en fait utilisée dans la fonction de traitement de frappe ultérieure. Vous vous souvenez peut-être que dans les scripts shell, les variables déclarées ou définies à l'intérieur ou à l'extérieur d'une fonction sont considérées comme des variables globales avec une portée dans tout le fichier de script, à moins que le mot-clé 'local' soit utilisé pour déclarer la variable à l'intérieur d'une fonction. Quant à l'effet d'affichage du menu, nous utilisons toujours la commande echo, puis nous utilisons la commande read pour lire l'entrée de l'utilisateur dans la variable 'mode'.

Afficher le menu de sélection de catégorie de frappe
function display_menu() {
while [ 1 ]; do
draw_border
echo -e "\033[8;30H1) Practiquer la frappe de nombres"
echo -e "\033[9;30H2) Practiquer la frappe de lettres"
echo -e "\033[10;30H3) Practiquer la frappe de caractères alphanumériques"
echo -e "\033[11;30H4) Practiquer la frappe de mots"
echo -e "\033[12;30H5) Quitter"
echo -ne "\033[22;2HVeuillez entrer votre choix : "
read choice
case $choice in
"1")
draw_border
## Les deux suivants sont des paramètres de fonction, le premier paramètre indique le type de frappe, et le deuxième est la fonction pour déplacer les caractères
main digit
echo -e "\033[39;49m"
;;
"2")
draw_border
main char
echo -e "\033[39;49m"
;;
"3")
draw_border
main mix
echo -e "\033[39;49m"
;;
"4")
draw_border
echo -ne "\033[22;2H"
read -p "Quel fichier souhaitez-vous utiliser pour la pratique de frappe : " file
if [! -f "$file" ]; then
display_menu
else
exec 4< $file ## Créer un pipeline de fichier
main word
echo -e "\033[39;49m"
fi
;;
"5" | "q" | "Q")
draw_border
echo -e "\033[10;25HVous allez quitter ce jeu maintenant"
echo -e "\033[39;49m"
sleep 1
clear
exit 1
;;
*)
draw_border
echo -e "\033[22;2HVotre choix est incorrect, veuillez réessayer"
sleep 1
;;
esac
done
}
Dans cette fonction, expliquons d'abord les deux fonctions appelées dans la branche case. Tout d'abord, la fonction draw_border est utilisée pour dessiner la bordure de l'interface de frappe, ce qui sera montré plus tard. Ensuite, la fonction main est appelée. Cette fonction main n'a pas de rôle spécial comme la fonction main dans des langages tels que Java et C, elle a simplement le nom main, qui est utilisé pour indiquer qu'elle joue un rôle majeur dans tout le programme. Vous avez peut-être remarqué qu'il y a un argument après chaque main, oui, ce sont les paramètres passés à la fonction. En shell, les paramètres ne sont pas écrits entre parenthèses immédiatement après le nom de la fonction, contrairement à de nombreux autres langages. Il convient également de noter que dans la quatrième branche de la déclaration case, c'est-à-dire ces lignes :
read -p "Quel fichier souhaitez-vous utiliser pour la pratique de frappe : " file
if [! -f "$file" ]; then
display_menu
else
exec 4< $file ## Créer un pipeline de fichier
main word
echo -e "\033[39;49m"
fi
Selon l'invite du menu, nous savons que cette branche est pour pratiquer la frappe de mots. Ce programme permet aux utilisateurs d'utiliser des fichiers de mots personnalisés (fichiers texte avec certaines exigences de formatage, un mot par ligne, vous pouvez télécharger un fichier de liste de mots en ligne et utiliser la commande awk pour extraire) pour la pratique. Donc, d'abord, nous devons lire le nom d'un fichier entré par l'utilisateur pour sélectionner le fichier de mots. Ensuite, nous utilisons la commande exec pour créer un pipeline qui pointe vers le fichier, c'est-à-dire rediriger la sortie du fichier vers le descripteur de fichier 4 (fd). Étant donné que exec est une commande intégrée dans bash, vous ne pourrez pas voir la documentation de exec en utilisant la commande man. La redirection d'E/S avec exec est généralement liée aux fd, et le shell a généralement 10 fd, allant de 0 à 9. Les fd couramment utilisés sont 3, à savoir 0 (stdin, entrée standard), 1 (stdout, sortie standard) et 2 (stderr, sortie d'erreur standard). Comprenez simplement son sens pour l'instant.

Tracer une bordure pour une interface de frappe
function draw_border() {
declare -i width
declare -i high
width=79 ## Largeur par défaut du terminal - 1
high=23 ## Hauteur par défaut du terminal - 1
clear
## Définir la couleur d'affichage en blanc sur fond noir
echo -e "\033[37;40m"
## Définir la couleur de fond
for ((i = 1; i <= $width; i = i + 1)); do
for ((j = 1; j <= $high; j = j + 1)); do
## Définir la position d'affichage
echo -e "\033["$j";"$i"H "
done
done
## Dessiner la bordure de fond
echo -e "\033[1;1H+\033["$high";1H+\033[1;"$width"H+\033["$high";"$width"H+"
for ((i = 2; i <= $width - 1; i = i + 1)); do
echo -e "\033[1;"$i"H-"
echo -e "\033["$high";"$i"H-"
done
for ((i = 2; i <= $high - 1; i = i + 1)); do
echo -e "\033["$i";1H|"
echo -e "\033["$i";"$width"H|\n"
done
}
- Dans la fonction
draw_border(), deux variables entièreswidthethighsont déclarées, qui représentent respectivement la largeur et la hauteur de la fenêtre du terminal. - La commande
echo -e "\033[37;40m"est utilisée pour définir la couleur d'affichage. Des bouclesforimbriquées sont utilisées pour parcourir chaque ligne et chaque colonne du terminal afin de remplir le fond. - À l'intérieur de la boucle interne, des codes d'échappement ANSI sont utilisés pour positionner le curseur aux coordonnées souhaitées dans le terminal. Par exemple,
echo -e "\033["$j";"$i"H"positionne le curseur à la ligne j et à la colonne i. - Après avoir rempli le fond du terminal, des bordures décoratives sont dessinées en utilisant des caractères spécifiques. Les caractères utilisés incluent
+,-et|, qui sont couramment utilisés pour dessiner des bordures. Les positions de ces caractères sont déterminées par leurs valeurs de ligne et de colonne, et imprimées à l'aide de codes d'échappement ANSI. Par exemple,echo -e "\033[1;1H+"place le caractère+dans le coin supérieur gauche du terminal.
En résumé, la fonction draw_border() efface le terminal, définit la couleur de fond en noir, remplit le terminal avec des espaces pour créer un fond. Enfin, elle dessine une bordure esthétiquement agréable en plaçant des caractères (tels que + pour les coins, - et | pour les lignes) à des positions spécifiques.

Remplir la couleur de fond de l'interface de frappe
## Effacer toute la zone d'atterrissage des caractères
function clear_all_area() {
local i j
## Remplir la zone de frappe
for ((i = 5; i <= 21; i++)); do
for ((j = 3; j <= 77; j = j + 1)); do
## Définir la position d'affichage
echo -e "\033[44m\033["$i";"$j"H "
done
done
echo -e "\033[37;40m"
}
## Fonction : Effacer une colonne spécifique de caractères
## Entrée : Le numéro de colonne à effacer
## Retour : Aucun
function clear_line() {
local i
## Remplir la zone de frappe
for ((i = 5; i <= 21; i++)); do
for ((j = $1; j <= $1 + 9; j = j + 1)); do
echo -e "\033[44m\033["$i";"$j"H "
done
done
echo -e "\033[37;40m"
}
- La fonction
clear_all_area():
- Elle est utilisée pour effacer toute la zone d'atterrissage des caractères. Son rôle est de remplir les caractères de la zone d'atterrissage des caractères avec la couleur de fond (noir) afin d'effacer les caractères dans la fenêtre du terminal.
- Elle utilise des boucles imbriquées pour parcourir toutes les combinaisons de
ietj, oùireprésente la ligne etjreprésente la colonne. - À l'intérieur de la boucle interne, elle positionne le curseur à la ligne et à la colonne spécifiées à l'aide de codes d'échappement ANSI, puis utilise
echo -e "\033[44m\033["$i";"$j"H "pour imprimer un caractère espace à cette position, mais avec la couleur de fond définie sur noir (code 44). - Après les boucles, la fonction utilise
echo -e "\033[37;40m"pour restaurer la couleur du texte et la couleur de fond à leurs valeurs par défaut (texte blanc, fond noir).
- La fonction
clear_line():
- Cette fonction est utilisée pour effacer une colonne spécifique dans la zone d'atterrissage des caractères. Elle est généralement utilisée pour effacer le chemin des caractères dans cette colonne après que l'utilisateur ait entré le bon caractère.
- La fonction prend un argument
$1, qui représente le numéro de colonne à effacer. - Elle utilise une boucle pour parcourir les lignes et les colonnes dans la zone d'atterrissage des caractères et remplit le chemin des caractères de cette colonne avec la couleur de fond (noir).
- De même que
clear_all_area(), la fonction positionne le curseur à la ligne et à la colonne spécifiées à l'aide de codes d'échappement ANSI, puis utiliseecho -e "\033[44m\033["$i";"$j"H "pour imprimer un caractère espace à cette position, mais avec la couleur de fond définie sur noir. - Enfin, la fonction utilise
echo -e "\033[37;40m"pour restaurer la couleur du texte et la couleur de fond à leurs valeurs par défaut.
Génération de lettres et de nombres aléatoires
## Fonction : Déplacer le caractère le long du chemin de chute.
## Entrée : Paramètre 1 : La ligne actuelle du caractère (liée à la durée écoulée depuis le caractère).
## Paramètre 2 : La colonne actuelle du caractère.
## Paramètre 3 : (Paramètre non utilisé).
## Retour : Aucun
function move() {
local locate_row lastloca
locate_row=$(($1 + 5))
## Afficher le caractère à saisir.
echo -e "\033[30;44m\033["$locate_row";"$2"H$3\033[37;40m"
if [ "$1" -gt "0" ]; then
lastloca=$(($locate_row - 1))
## Effacer la position précédente.
echo -e "\033[30;44m\033["$lastloca";"$2"H \033[37;40m"
fi
}
function putarray() {
local chars
case $1 in
digit)
chars='0123456789'
for ((i = 0; i < 10; i++)); do
array[$i]=${chars:$i:1}
done
;;
char)
chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
for ((i = 0; i < 52; i++)); do
array[$i]=${chars:$i:1}
done
;;
mix)
chars='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
for ((i = 0; i < 62; i++)); do
array[$i]=${chars:$i:1}
done
;;
*) ;;
esac
}
## Fonction : Générer un caractère aléatoire du type correspondant pour être converti en un caractère aléatoire.
## Entrée : Le type de caractère à générer.
## Variable globale : random_char, array[]
## Retour : Aucun
function get_random_char() {
local typenum
declare -i typenum=0
case $1 in
digit)
typenum=$(($RANDOM % 10))
;;
char)
typenum=$(($RANDOM % 52))
;;
mix)
typenum=$(($RANDOM % 62))
;;
*) ;;
esac
random_char=${array[$typenum]}
}
Étant donné que les variables de chaîne de caractères en shell ne prennent pas directement en charge l'indexation, nous devons placer toutes les lettres et les nombres dans un tableau indexé. C'est le but de la fonction putarray. La fonction get_random_char ci-dessous est utilisée pour générer des lettres et des nombres aléatoires. Elle utilise la variable d'environnement de nombre aléatoire du système RANDOM pour obtenir un index aléatoire, puis lit le caractère correspondant dans le tableau.
Implémentation de la frappe
Après toutes les préparations, nous pouvons enfin commencer à implémenter la fonction de frappe. Jetons un coup d'œil au code suivant :
function main() {
declare -i gamestarttime=0
declare -i gamedonetime=0
declare -i starttime
declare -i deadtime
declare -i curtime
declare -i donetime
declare -i numright=0
declare -i numtotal=0
declare -i accuracy=0
## Stocker les caractères correspondants dans un tableau, $1 représente le type de caractère sélectionné par l'utilisateur
putarray $1
## Initialiser le temps de début du jeu
gamestarttime=$(date +%s)
while [ 1 ]; do
echo -e "\033[2;2H Veuillez entrer la lettre à l'écran avant qu'elle ne disparaisse!"
echo -e "\033[3;2H Temps de jeu : "
curtime=$(date +%s)
gamedonetime=$curtime-$gamestarttime
echo -e "\033[31;40m\033[3;15H$gamedonetime s\033[37;40m"
echo -e "\033[3;60H Total : \033[31;26m$numtotal\033[37;40m"
echo -e "\033[3;30H Précision : \033[31;40m$accuracy %\033[37;40m"
echo -ne "\033[22;2H Votre saisie : "
clear_all_area
## Boucler 10 fois pour vérifier si une colonne de caractères a expiré ou a été touchée
for ((line = 20; line <= 60; line = line + 10)); do
## Vérifier si la colonne de caractères a été touchée
if [ "${ifchar[$line]}" == "" ] || [ "${donetime[$line]}" -gt "$time" ]; then
## Effacer l'affichage dans cette colonne
clear_line $line
## Générer un caractère aléatoire
if [ "$1" == "word" ]; then
read -u 4 word
if [ "$word" == "" ]; then ## Fin de la lecture du fichier
exec 4< $file
fi
putchar[$line]=$word
else
get_random_char $1
putchar[$line]=$random_char
fi
numtotal=$numtotal+1
## Définir le drapeau à 1
ifchar[$line]=1
## Réinitialiser le minuteur
starttime[$line]=$(date +%s)
curtime[$line]=${starttime[$line]}
donetime[$line]=$time
## Réinitialiser la position de la colonne à 0
column[$line]=0
if [ "$1" == "word" ]; then
move 0 $line ${putchar[$line]}
fi
else
## Si elle n'a pas expiré ou été touchée, mettre à jour le minuteur et la position actuelle
curtime[$line]=$(date +%s)
donetime[$line]=${curtime[$line]}-${starttime[$line]}
move ${donetime[$line]} $line ${putchar[$line]}
fi
done
if [ "$1"!= "word" ]; then
echo -ne "\033[22;14H" ## Effacer les caractères de la ligne de saisie
## Vérifier les caractères saisis par l'utilisateur et agir comme un minuteur d'une seconde
if read -n 1 -t 0.5 tmp; then
## Saisie réussie, boucle pour vérifier si la saisie correspond à une colonne
for ((line = 20; line <= 60; line = line + 10)); do
if [ "$tmp" == "${putchar[$line]}" ]; then
## Effacer l'affichage dans cette colonne
clear_line $line
## Si correspondance, effacer le drapeau
ifchar[$line]=""
echo -e "\007\033[32;40m\033[4;62H correct!\033[37;40m"
numright=$numright+1
## Sortir de la boucle
break
else
## Sinon, afficher toujours une erreur jusqu'à l'expiration
echo -e "\033[31;40m\033[4;62Hfaux, réessayez!\033[37;40m"
fi
done
fi
else
echo -ne "\033[22;14H" ## Effacer les caractères de la ligne de saisie
## Vérifier les caractères saisis par l'utilisateur et agir comme un minuteur
if read tmp; then
## Saisie réussie, boucle pour vérifier si la saisie correspond à une colonne
for ((line = 20; line <= 60; line = line + 10)); do
if [ "$tmp" == "${putchar[$line]}" ]; then
## Effacer l'affichage dans cette colonne
clear_line $line
## Si correspondance, effacer le drapeau
ifchar[$line]=""
echo -e "\007\033[32;40m\033[4;62H correct!\033[37;40m"
numright=$numright+1
## Sortir de la boucle
break
else
## Sinon, afficher toujours une erreur jusqu'à l'expiration
echo -e "\033[31;40m\033[4;62Hfaux, réessayez!\033[37;40m"
fi
done
fi
fi
trap " doexit " 2 ## Capturer des signaux spéciaux
## Calculer la précision
accuracy=$numright*100/$numtotal
done
}
Maintenant, expliquons séparément la méthode de frappe de caractères simples et de mots, car elles sont légèrement différentes.
Pour la frappe de caractères simples, nous voulons que cinq caractères apparaissent en même temps, et qu'ils continuent de tomber à un intervalle de temps défini jusqu'à ce qu'ils disparaissent lorsqu'ils atteignent une certaine hauteur (déterminée par la période d'expiration choisie au début). Si ils ne sont pas touchés (l'utilisateur n'entre pas le bon caractère correspondant) avant de disparaître, un nouveau caractère apparaîtra dans cette colonne. Par conséquent, tout d'abord, nous bouclons pour initialiser une séquence de caractères et la stockons dans un tableau. L'index du tableau représente le numéro de colonne du terminal, ce qui a l'avantage de gérer indépendamment chaque colonne de caractères, si elle a expiré ou a été touchée, et où elle devrait être placée. L'inconvénient est que nous allons créer un tableau relativement grand, mais il ne sera pas si grand qu'il ne puisse être géré. Cela ne pose pas de problème pour le shell car il n'alloue pas de mémoire en fonction de la taille de l'index du tableau. La première boucle for est responsable de cela, et elle vérifie également à la fin de chaque grande boucle si une colonne est vide, puis un nouveau caractère apparaîtra dans cette colonne.
Ensuite, il y a une longue instruction if...else. Vous vous souvenez du paramètre passé lors de l'appel de la fonction main? La partie la plus extérieure est utilisée pour distinguer si l'on frappe des caractères simples ou des mots. Regardons d'abord la frappe de caractères simples, qui contient une ligne cruciale :
if read -n 1 -t 0.5 tmp
Le paramètre -n de la commande read spécifie la longueur des caractères à lire. Ici, une longueur de 1 est spécifiée, ce qui signifie que la saisie se termine immédiatement après que l'utilisateur a entré un caractère, sans avoir besoin d'appuyer sur la touche Entrée. Le paramètre -t spécifie le temps d'expiration de la saisie. Si l'utilisateur n'entre rien ou si la saisie n'est pas terminée dans la période d'expiration, la lecture se terminera immédiatement. Parce que la commande read est une opération synchrone pour le même shell lors de la lecture de la saisie de l'utilisateur, l'implémentation de la chute des caractères repose sur cet expiration (en implémentant indirectement un délai de chute). Ici, 0,5s est défini car la plupart des utilisateurs peuvent terminer la saisie d'un caractère en moins de 0,5s. Après avoir lu la saisie de l'utilisateur, il utilise une boucle for pour comparer chaque tableau de caractères correspondant à chaque colonne, et s'il y a une correspondance, il appelle une fonction clear_line pour effacer le caractère dans la colonne actuelle, et définit la variable de drapeau ifchar[$line] sur 0 ce qui indique qu'il a été effacé.
Le flux de frappe de mots est essentiellement le même que celui de la frappe de caractères, sauf que parce que nous ne pouvons pas estimer le temps qu'il faut à l'utilisateur pour saisir un mot de longueur indéfinie, nous ne pouvons pas définir le temps d'expiration de la saisie pour la commande read. Sans cet expiration, la mise en œuvre finale de la frappe de mots peut ne pas tomber automatiquement comme lors de la frappe de caractères, mais plutôt que le mot tombe d'une ligne après que nous ayons saisi un mot et appuyé sur Entrée. Bien sûr, vous pouvez envisager d'utiliser d'autres méthodes pour obtenir les mêmes ou de meilleurs résultats que la frappe de caractères.
De plus, la gestion de l'obtention de nouveaux mots pour chaque colonne est légèrement différente car nous lisons à partir d'un fichier, plutôt que de générer des mots aléatoires. Vous vous souvenez toujours du descripteur de fichier 4 que nous avons créé plus tôt, qui pointe vers un fichier de mots? Nous avons utilisé ce descripteur de fichier ici :
read -u 4 word
if [ "$word" == "" ]; then ## Fin de la lecture du fichier
exec 4< $file
fi
putchar[$line]=$word
Ici, nous utilisons toujours la commande read, et avec le paramètre -u, nous pouvons spécifier à partir de quel descripteur de fichier lire le fichier ligne par ligne dans la variable. La déclaration de vérification de vide suivante est pour recréer le descripteur de fichier qui pointe vers le fichier lorsqu'un fichier atteint sa fin, afin que read puisse lire à partir du début du fichier à nouveau.
Vous trouvez cela un peu compliqué? Ne vous inquiétez pas, nous n'avons pas fini. Ensuite, faites attention à l'avant-dernière ligne de la fonction main :
trap " doexit " 2 ## Capturer des signaux spéciaux
La commande trap est utilisée pour capturer des signaux spéciaux dans le shell, tels que Ctrl+C, Ctrl+D, Ctrl+Z, ESC, etc. À l'origine, ces signaux spéciaux sont gérés par le shell lui-même. Parce que nous voulons que le jeu quitte plus gracieusement lorsqu'il se termine, nous pouvons intercepter le 2ème signal spécial, qui est Ctrl+C, pour implémenter certains traitements personnalisés. Par exemple, ici, après avoir capturé le 2ème signal, il appelle la fonction doexit ci-dessous pour quitter le programme gracieusement :
function doexit() {
draw_border
echo -e "\033[10;30Hce jeu va se terminer....."
echo -e "\033[0m"
sleep 2
clear
exit 1
}
Flux principal du code
Étant donné que le code de chaque module de fonction est déjà en place, pour faire fonctionner le programme, nous avons encore besoin d'un flux principal de code pour appeler ces fonctions.
draw_border
dis_welcome
echo -ne "\033[3;30HCommencer le jeu. O/N : "
read yourchoice
if [ "$yourchoice" == "O" ] || [ "$yourchoice" == "o" ]; then
draw_border
modechoose
else
clear
exit 1
fi
exit 1
Ce morceau de code crée un point d'entrée pour un jeu de caractères tombants. Il affiche d'abord un écran de bienvenue et une invite pour commencer le jeu, puis attend la saisie de l'utilisateur pour savoir s'il souhaite commencer le jeu. Si l'utilisateur choisit de commencer le jeu, il sera invité à choisir le mode de difficulté du jeu. Si l'utilisateur choisit de quitter ou entre une saisie invalide, le jeu ne démarrera pas.
Exécution et test
Ensuite, nous pouvons exécuter notre jeu de frappe en shell :
cd ~/project
bash shell_typing_game.sh

Résumé
Vous venez de créer un projet pour construire un simple jeu de frappe dans le shell. Vous pouvez suivre ces étapes pour créer votre propre projet de jeu de frappe et choisir différents modes de jeu et niveaux de difficulté. Ce projet offre une expérience pratique en script shell et en développement de jeux basés sur le terminal. Si vous n'êtes pas familier avec certaines des commandes utilisées dans le cours, telles que les diverses sorties de echo, la redirection d'E/S de exec et la capture de signaux spéciaux avec trap, vous pouvez les pratiquer davantage pour vous familiariser avec leur utilisation.



