Erstellen eines Tippspiels mit Bash

LinuxLinuxBeginner
Jetzt üben

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

Einführung

In diesem Projekt lernen Sie, wie Sie mithilfe eines Shell-Skripts ein einfaches Tippspiel erstellen können. Das Spiel zeigt zufällige Zeichen auf dem Bildschirm an, und Ihr Ziel ist es, sie einzugeben, bevor sie verschwinden. Das Spiel bietet verschiedene Modi mit unterschiedlichen Schwierigkeitsgraden. Sie können wählen, ob Sie die Eingabe von Zahlen, Buchstaben, einer Mischung aus beiden oder sogar Ihrer eigenen benutzerdefinierten Wörter üben möchten.

👀 Vorschau

Shell Typing Game

🎯 Aufgaben

In diesem Projekt lernen Sie:

  • Wie Sie eine Projekt-Datei erstellen und sie in einem Code-Editor öffnen
  • Wie Sie mithilfe von Sonderzeichen und Farben eine Willkommensoberfläche anzeigen
  • Wie Sie ein Modiauswahlmenü implementieren, um den Schwierigkeitsgrad auszuwählen
  • Wie Sie ein Tippkategorie-Auswahlmenü implementieren, um die Art der Zeichen auszuwählen, die Sie üben möchten
  • Wie Sie Funktionen zum Zeichnen einer Umrandung und Füllen der Hintergrundfarbe der Tippoberfläche erstellen
  • Wie Sie zufällige Buchstaben und Zahlen für das Tippspiel generieren
  • Wie Sie die Tippfunktionalität implementieren, einschließlich der Verarbeitung von Benutzereingaben und der Berechnung der Genauigkeit
  • Wie Sie eine gracefull Exit-Funktion erstellen, um spezielle Signale zu verarbeiten

🏆 Errungenschaften

Nach Abschluss dieses Projekts können Sie:

  • Die Grundlagen des Shell-Scriptings demonstrieren
  • Sonderzeichen und Farben in der Terminalausgabe verwenden
  • Eingaben vom Benutzer in Shell-Skripten lesen
  • Menüs und Benutzeroberflächen in Shell-Skripten implementieren
  • Spezielle Signale in Shell-Skripten verarbeiten

Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL linux(("Linux")) -.-> linux/BasicSystemCommandsGroup(["Basic System Commands"]) linux(("Linux")) -.-> linux/BasicFileOperationsGroup(["Basic File Operations"]) linux(("Linux")) -.-> linux/FileandDirectoryManagementGroup(["File and Directory Management"]) linux(("Linux")) -.-> linux/TextProcessingGroup(["Text Processing"]) linux(("Linux")) -.-> linux/VersionControlandTextEditorsGroup(["Version Control and Text Editors"]) linux/BasicSystemCommandsGroup -.-> linux/echo("Text Display") linux/BasicSystemCommandsGroup -.-> linux/clear("Screen Clearing") linux/BasicSystemCommandsGroup -.-> linux/read("Input Reading") linux/BasicSystemCommandsGroup -.-> linux/printf("Text Formatting") linux/BasicSystemCommandsGroup -.-> linux/declare("Variable Declaring") linux/BasicFileOperationsGroup -.-> linux/touch("File Creating/Updating") linux/FileandDirectoryManagementGroup -.-> linux/cd("Directory Changing") linux/TextProcessingGroup -.-> linux/awk("Text Processing") linux/VersionControlandTextEditorsGroup -.-> linux/gedit("Graphical Text Editing") subgraph Lab Skills linux/echo -.-> lab-298847{{"Erstellen eines Tippspiels mit Bash"}} linux/clear -.-> lab-298847{{"Erstellen eines Tippspiels mit Bash"}} linux/read -.-> lab-298847{{"Erstellen eines Tippspiels mit Bash"}} linux/printf -.-> lab-298847{{"Erstellen eines Tippspiels mit Bash"}} linux/declare -.-> lab-298847{{"Erstellen eines Tippspiels mit Bash"}} linux/touch -.-> lab-298847{{"Erstellen eines Tippspiels mit Bash"}} linux/cd -.-> lab-298847{{"Erstellen eines Tippspiels mit Bash"}} linux/awk -.-> lab-298847{{"Erstellen eines Tippspiels mit Bash"}} linux/gedit -.-> lab-298847{{"Erstellen eines Tippspiels mit Bash"}} end

Projekt-Datei erstellen

Um zu beginnen, erstellen Sie bitte eine neue Datei mit dem Namen shell_typing_game.sh und öffnen Sie sie in Ihrem bevorzugten Code-Editor.

cd ~/project
touch shell_typing_game.sh
✨ Lösung prüfen und üben

Willkommensoberfläche anzeigen

#!/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   ## Print start line.
  line_char_count=65 ## Define the newline position, 64 characters per line plus newlines, for a total of 65 characters.

  echo -ne "\033[37;40m\033[5;3H" ## Set the color and cursor start position.

  for ((i = 0; i < ${#str}; i++)); do
    ## Line feed.
    if [ "$((i % line_char_count))" == "0" ] && [ "$i"!= "0" ]; then
      row=$row+1
      echo -ne "\033["$row";3H"
    fi
    ## Determine foreground and background characters.
    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

Die lange Zeichenkette am Anfang 010101... ist die Willkommensoberfläche des Spiels, die angezeigt werden soll. Wenn Sie sie direkt ansehen, verstehen Sie vielleicht nicht, was es ist. Sie können diese Zeichenkette also in einen gedit-Editor kopieren und dann die Tastenkombination Strg+F drücken, um zu suchen. Wenn Sie 1 eingeben, verstehen Sie es sofort. Konzentrieren wir uns jetzt auf den echo-Befehl. Wir müssen den echo-Befehl verwenden, weil wir die Oberfläche auf dem Bildschirm anzeigen möchten (hier bezieht sich dies auf den Xfce-Terminal. Die Standard-Eingabe, -Ausgabe und Standard-Fehlerausgabe unter Linux sind standardmäßig mit dem Terminal verbunden). Um einige spezielle Funktionen zu erreichen, wie z. B. das Drucken an einer bestimmten Position, das Einstellen der Anzeigefarbe der Zeichen und das Drucken des Hintergrunds des gesamten Spiels, müssen wir einige spezielle Parameter des entsprechenden Befehls verwenden, wie im folgenden Code gezeigt:

## Set the color and cursor start position.
echo -ne "\033[37;40m\033[5;3H"

Basierend auf den Kommentaren können wir ungefähr verstehen, was diese Zeile tut. Aber was genau bedeutet sie? Zunächst bedeutet der Parameter -n, dass in der aktuellen Zeile gedruckt wird, ohne nach der Ausgabe eine neue Zeile zu beginnen. Dann wird die Ausgabezeichenfarbe und die Cursorposition festgelegt. Dabei ist \033[Vordergrundfarbe;Hintergrundfarbe m, und danach folgt \033[Zeile;Spalte H. Um jedoch zu ermöglichen, dass echo diese speziellen Escape-Sequenzen erkennt, benötigen wir den Parameter -e, andernfalls wird echo diese Zeichen einfach so ausgeben.

Vordergrundfarben: 30 - 39, Hintergrundfarben: 40 - 49.

Wert Vordergrundfarben Wert Hintergrundfarben (Bereich: 40 - 49)
30 Setzt schwarzen Vordergrund 40 Setzt schwarzen Hintergrund
31 Setzt roten Vordergrund 41 Setzt roten Hintergrund
32 Setzt grünen Vordergrund 42 Setzt grünen Hintergrund
33 Setzt braunen Vordergrund 43 Setzt braunen Hintergrund
34 Setzt blauen Vordergrund 44 Setzt blauen Hintergrund
35 Setzt lila Vordergrund 45 Setzt lila Hintergrund
36 Setzt cyan Vordergrund 46 Setzt cyan Hintergrund
37 Setzt weißen Vordergrund 47 Setzt weißen Hintergrund
38 Setzt Unterstreichung auf Standard-Vordergrundfarbe 49 Setzt Standard-schwarzen Hintergrund
39 Entfernt Unterstreichung von Standard-Vordergrundfarbe

Hätten Sie nicht gedacht, dass echo so verwendet werden kann! Fühlen Sie sich wie neu mit ihm vertraut gemacht? Okay, lassen Sie uns jetzt diesen Codeausschnitt im Terminal testen.

cd ~/project
bash shell_typing_game.sh
Shell Typing Game
✨ Lösung prüfen und üben

Anzeige des Modiauswahlmenüs

## Deklariere die Variable 'time', um das Tippzeitlimit darzustellen, wobei verschiedene Schwierigkeitsgrade unterschiedlichen Zeitlimits entsprechen
declare -i time
function modechoose() {
  ## Wähle aus drei verschiedenen Modi
  echo -e "\033[8;30H1) Leichter Modus"
  echo -e "\033[9;30H2) Normaler Modus"
  echo -e "\033[10;30H3) Schwieriger Modus"
  echo -ne "\033[22;2HBitte geben Sie Ihre Wahl ein: "
  read mode
  case $mode in
    "1")
      time=10
      dismenu ## Rufe die Menüauswahlfunktion auf
      ;;
    "2")
      time=5
      dismenu
      ;;
    "3")
      time=3
      dismenu
      ;;
    *)
      echo -e "\033[22;2HIhre Wahl ist inkorrekt. Bitte versuchen Sie es erneut."
      sleep 1
      ;;
  esac
}

Bevor wir diese Funktion definieren, haben wir zunächst eine Ganzzahlvariable 'time' mit dem Befehl declare -i deklariert. Dann haben wir in der folgenden case-Anweisung basierend auf der Schwierigkeitsauswahl des Benutzers verschiedene Werte für die Variable 'time' festgelegt. Je höher die Schwierigkeit, desto kleiner der Wert. Diese Variable wird tatsächlich in der nachfolgenden Tippverarbeitungsfunktion verwendet. Sie erinnern sich vielleicht, dass in Shell-Skripten Variablen, die innerhalb oder außerhalb einer Funktion deklariert oder definiert werden, als globale Variablen behandelt werden, deren Gültigkeitsbereich sich über die gesamte Skriptdatei erstreckt, es sei denn, das Schlüsselwort 'local' wird verwendet, um die Variable innerhalb einer Funktion zu deklarieren. Was den Anzeigeeffekt des Menüs betrifft, verwenden wir weiterhin den echo-Befehl und dann den read-Befehl, um die Eingabe des Benutzers in die Variable 'mode' zu lesen.

Shell Typing Game
✨ Lösung prüfen und üben

Anzeige des Tippkategorie-Auswahlmenüs

function display_menu() {
  while [ 1 ]; do
    draw_border
    echo -e "\033[8;30H1) Üben Sie das Tippen von Zahlen"
    echo -e "\033[9;30H2) Üben Sie das Tippen von Buchstaben"
    echo -e "\033[10;30H3) Üben Sie das Tippen von alphanumerischen Zeichen"
    echo -e "\033[11;30H4) Üben Sie das Tippen von Wörtern"
    echo -e "\033[12;30H5) Beenden"
    echo -ne "\033[22;2HBitte geben Sie Ihre Wahl ein: "
    read choice
    case $choice in
      "1")
        draw_border
        ## Die nächsten beiden sind Funktionsparameter. Der erste Parameter gibt den Tipptyp an, und der zweite ist die Funktion zum Bewegen von Zeichen.
        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 "Welche Datei möchten Sie für das Tippen üben: " file
        if [! -f "$file" ]; then
          display_menu
        else
          exec 4< $file ## Erstellen Sie eine Dateipipeline
          main word
          echo -e "\033[39;49m"
        fi
        ;;
      "5" | "q" | "Q")
        draw_border
        echo -e "\033[10;25HSie verlassen jetzt das Spiel."
        echo -e "\033[39;49m"
        sleep 1
        clear
        exit 1
        ;;
      *)
        draw_border
        echo -e "\033[22;2HIhre Wahl ist falsch. Bitte versuchen Sie es erneut."
        sleep 1
        ;;
    esac
  done
}

In dieser Funktion erklären wir zunächst die beiden Funktionen, die in der case-Verzweigung aufgerufen werden. Zunächst wird die draw_border-Funktion verwendet, um die Umrandung der Tippoberfläche zu zeichnen, was später gezeigt wird. Dann wird die main-Funktion aufgerufen. Diese main-Funktion hat keine besondere Rolle wie die main-Funktion in Sprachen wie Java und C. Sie hat einfach den Namen main, um anzuzeigen, dass sie in ganzem Programm eine Hauptrolle spielt. Sie haben vielleicht bemerkt, dass es nach jeder main ein Argument gibt. Ja, dies ist der an die Funktion übergebene Parameter. In der Shell werden Parameter nicht wie in vielen anderen Sprachen in Klammern direkt hinter dem Funktionsnamen geschrieben. Es sollte auch beachtet werden, dass in der vierten Verzweigung der case-Anweisung, also in diesen Zeilen:

read -p "Which file would you like to use for typing practice: " file
if [! -f "$file" ]; then
  display_menu
else
  exec 4< $file ## Create a file pipeline
  main word
  echo -e "\033[39;49m"
fi

Gemäß der Menüaufforderung können wir erkennen, dass diese Verzweigung für das Üben des Tippens von Wörtern vorgesehen ist. Dieses Programm ermöglicht es Benutzern, benutzerdefinierte Wortdateien (Textdateien mit bestimmten Formatierungsanforderungen, wobei jedes Wort in einer eigenen Zeile steht. Sie können eine Wortlisten-Datei online herunterladen und den awk-Befehl verwenden, um sie zu extrahieren) für die Übung zu nutzen. Daher müssen wir zunächst einen vom Benutzer eingegebenen Dateinamen einlesen, um die Wortdatei auszuwählen. Dann verwenden wir den exec-Befehl, um eine Pipeline zu erstellen, die auf die Datei zeigt, d. h., wir leiten die Ausgabe der Datei an den Dateideskriptor 4 (fd) um. Da exec ein eingebauter Befehl in bash ist, können Sie die Dokumentation von exec nicht mit dem man-Befehl anzeigen. Die I/O-Umleitung mit exec hängt normalerweise mit fd zusammen, und die Shell hat normalerweise 10 fd, die von 0 bis 9 reichen. Die häufig verwendeten fd sind 3, nämlich 0 (stdin, Standard-Eingabe), 1 (stdout, Standard-Ausgabe) und 2 (stderr, Standard-Fehlerausgabe). Versuchen Sie es einfach zu verstehen.

Shell Typing Game
✨ Lösung prüfen und üben

Zeichnen einer Umrandung für die Tippoberfläche

function draw_border() {
  declare -i width
  declare -i high
  width=79 ## Standardbreite des Terminals - 1
  high=23  ## Standardhöhe des Terminals - 1

  clear

  ## Setze die Anzeigefarbe auf weiß auf schwarzem Hintergrund
  echo -e "\033[37;40m"
  ## Setze die Hintergrundfarbe
  for ((i = 1; i <= $width; i = i + 1)); do
    for ((j = 1; j <= $high; j = j + 1)); do
      ## Setze die Anzeigeposition
      echo -e "\033["$j";"$i"H "
    done
  done
  ## Zeichne die Hintergrundumrandung
  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
}
  • In der Funktion draw_border() werden zwei Ganzzahlvariablen width und high deklariert, die jeweils die Breite und Höhe des Terminalfensters darstellen.
  • Der Befehl echo -e "\033[37;40m" wird verwendet, um die Anzeigefarbe festzulegen. Geschachtelte for-Schleifen werden verwendet, um jede Zeile und Spalte des Terminals zu durchlaufen und den Hintergrund zu füllen.
  • Innerhalb der inneren Schleife werden ANSI-Escape-Codes verwendet, um den Cursor an die gewünschten Koordinaten innerhalb des Terminals zu positionieren. Beispielsweise setzt echo -e "\033["$j";"$i"H" die Cursorposition auf Zeile j und Spalte i.
  • Nachdem der Terminalhintergrund gefüllt wurde, werden dekorative Rahmen mit bestimmten Zeichen gezeichnet. Die verwendeten Zeichen sind +, - und |, die üblicherweise zum Zeichnen von Rahmen verwendet werden. Die Positionen dieser Zeichen werden durch ihre Zeilen- und Spaltenwerte bestimmt und mit ANSI-Escape-Codes ausgegeben. Beispielsweise platziert echo -e "\033[1;1H+" das +-Zeichen in der oberen linken Ecke des Terminals.

Zusammenfassend lässt sich sagen, dass die Funktion draw_border() das Terminal leert, die Hintergrundfarbe auf schwarz setzt, das Terminal mit Leerzeichen füllt, um einen Hintergrund zu erstellen. Schließlich zeichnet sie einen optisch ansprechenden Rahmen, indem sie Zeichen (z. B. + für die Ecken, - und | für die Linien) an bestimmten Positionen platziert.

Shell Typing Game
✨ Lösung prüfen und üben

Füllen der Hintergrundfarbe der Tippoberfläche

## Löscht die gesamte Zeichenlandebahn
function clear_all_area() {
  local i j
  ## Füllt den Tippbereich
  for ((i = 5; i <= 21; i++)); do
    for ((j = 3; j <= 77; j = j + 1)); do
      ## Setzt die Anzeigeposition
      echo -e "\033[44m\033["$i";"$j"H "
    done
  done
  echo -e "\033[37;40m"
}

## Funktion: Löscht eine bestimmte Spalte von Zeichen
## Eingabe: Die zu löschende Spaltennummer
## Rückgabe: Keine
function clear_line() {
  local i
  ## Füllt den Tippbereich
  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"
}
  1. Die clear_all_area()-Funktion:
  • Sie wird verwendet, um die gesamte Zeichenlandebahn zu löschen. Ihre Aufgabe besteht darin, die Zeichen in der Zeichenlandebahn mit der Hintergrundfarbe (schwarz) zu füllen, um die Zeichen im Terminalfenster zu löschen.
  • Sie verwendet geschachtelte Schleifen, um alle Kombinationen von i und j zu durchlaufen, wobei i die Zeile und j die Spalte darstellt.
  • Innerhalb der inneren Schleife wird die Cursorposition auf die angegebene Zeile und Spalte mit ANSI-Escape-Codes festgelegt, und dann wird mit echo -e "\033[44m\033["$i";"$j"H " ein Leerzeichen an dieser Position ausgegeben, wobei die Hintergrundfarbe auf schwarz (Code 44) festgelegt ist.
  • Nach den Schleifen verwendet die Funktion echo -e "\033[37;40m", um die Textfarbe und Hintergrundfarbe auf ihre Standardwerte (weißer Text, schwarzer Hintergrund) zurückzusetzen.
  1. Die clear_line()-Funktion:
  • Diese Funktion wird verwendet, um eine bestimmte Spalte in der Zeichenlandebahn zu löschen. Sie wird normalerweise verwendet, um den Zeichenpfad in dieser Spalte zu löschen, nachdem der Benutzer das richtige Zeichen eingegeben hat.
  • Die Funktion nimmt ein Argument $1 entgegen, das die zu löschende Spaltennummer darstellt.
  • Sie verwendet eine Schleife, um die Zeilen und Spalten in der Zeichenlandebahn zu durchlaufen und den Zeichenpfad dieser Spalte mit der Hintergrundfarbe (schwarz) zu füllen.
  • Ähnlich wie clear_all_area() legt die Funktion die Cursorposition auf die angegebene Zeile und Spalte mit ANSI-Escape-Codes fest und gibt dann mit echo -e "\033[44m\033["$i";"$j"H " ein Leerzeichen an dieser Position aus, wobei die Hintergrundfarbe auf schwarz festgelegt ist.
  • Schließlich verwendet die Funktion echo -e "\033[37;40m", um die Textfarbe und Hintergrundfarbe auf ihre Standardwerte zurückzusetzen.
✨ Lösung prüfen und üben

Generieren von zufälligen Buchstaben und Zahlen

## Funktion: Bewegt das Zeichen entlang des Fallpfads.
## Eingabe:  Parameter 1: Die aktuelle Zeile des Zeichens (bezogen auf die Zeitdauer seit dem Zeichen).
##           Parameter 2: Die aktuelle Spalte des Zeichens.
##           Parameter 3: (Unbenutzter Parameter).
## Rückgabe: Keine
function move() {

  local locate_row lastloca
  locate_row=$(($1 + 5))
  ## Zeigt das einzugebende Zeichen an.
  echo -e "\033[30;44m\033["$locate_row";"$2"H$3\033[37;40m"
  if [ "$1" -gt "0" ]; then
    lastloca=$(($locate_row - 1))
    ## Löscht die vorherige Position.
    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
}

## Funktion: Generiert ein zufälliges Zeichen des entsprechenden Typs, das in ein zufälliges Zeichen umgewandelt werden soll.
## Eingabe:  Der zu generierende Zeichentyp.
## Globale Variable: random_char, array[]
## Rückgabe: Keine
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]}
}

Da Shell-Zeichenkettenvariablen nicht direkt Indizierung unterstützen, müssen wir alle Buchstaben und Zahlen in ein indizierbares Array einfügen. Dies ist der Zweck der putarray-Funktion. Die folgende get_random_char-Funktion wird verwendet, um zufällige Buchstaben und Zahlen zu generieren. Sie verwendet die systemseitige Zufallszahlen-Umgebungsvariable RANDOM, um einen zufälligen Index zu erhalten und dann das entsprechende Zeichen aus dem Array zu lesen.

✨ Lösung prüfen und üben

Implementierung des Tippens

Nach all den Vorbereitungen können wir endlich mit der Implementierung der Tippfunktion beginnen. Schauen wir uns den folgenden Code an:

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

  ## Speichert die entsprechenden Zeichen in einem Array. $1 repräsentiert den vom Benutzer ausgewählten Zeichensatz.
  putarray $1

  ## Initialisiert die Spielstartzeit
  gamestarttime=$(date +%s)

  while [ 1 ]; do
    echo -e "\033[2;2H  Bitte geben Sie den Buchstaben auf dem Bildschirm ein, bevor er verschwindet!"

    echo -e "\033[3;2H Spielzeit:     "
    curtime=$(date +%s)
    gamedonetime=$curtime-$gamestarttime
    echo -e "\033[31;40m\033[3;15H$gamedonetime s\033[37;40m"
    echo -e "\033[3;60H Gesamt: \033[31;26m$numtotal\033[37;40m"
    echo -e "\033[3;30H Genauigkeit: \033[31;40m$accuracy %\033[37;40m"
    echo -ne "\033[22;2H Ihre Eingabe:                         "
    clear_all_area
    ## Schleife 10 Mal, um zu prüfen, ob eine Spalte von Zeichen abgelaufen oder getroffen wurde.
    for ((line = 20; line <= 60; line = line + 10)); do
      ## Prüft, ob die Spalte von Zeichen getroffen wurde.
      if [ "${ifchar[$line]}" == "" ] || [ "${donetime[$line]}" -gt "$time" ]; then
        ## Löscht die Anzeige in dieser Spalte.
        clear_line $line
        ## Generiert ein zufälliges Zeichen.
        if [ "$1" == "word" ]; then
          read -u 4 word
          if [ "$word" == "" ]; then ## Ende des Dateilesens
            exec 4< $file
          fi
          putchar[$line]=$word
        else
          get_random_char $1
          putchar[$line]=$random_char
        fi
        numtotal=$numtotal+1
        ## Setzt das Flag auf 1
        ifchar[$line]=1
        ## Setzt den Timer zurück
        starttime[$line]=$(date +%s)
        curtime[$line]=${starttime[$line]}
        donetime[$line]=$time
        ## Setzt die Spaltenposition auf 0 zurück
        column[$line]=0
        if [ "$1" == "word" ]; then
          move 0 $line ${putchar[$line]}
        fi
      else
        ## Wenn es nicht abgelaufen oder getroffen wurde, aktualisiert den Timer und die aktuelle Position.
        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" ## Löscht die Eingabezeilenzeichen
      ## Prüft die Benutzereingabezeichen und fungiert als Ein-Sekunden-Timer
      if read -n 1 -t 0.5 tmp; then
        ## Erfolgreiche Eingabe, Schleife prüft, ob die Eingabe einer Spalte entspricht.
        for ((line = 20; line <= 60; line = line + 10)); do
          if [ "$tmp" == "${putchar[$line]}" ]; then
            ## Löscht die Anzeige in dieser Spalte.
            clear_line $line
            ## Wenn übereinstimmend, löscht das Flag.
            ifchar[$line]=""
            echo -e "\007\033[32;40m\033[4;62H         richtig!\033[37;40m"
            numright=$numright+1
            ## Beendet die Schleife
            break
          else
            ## Andernfalls zeigt immer einen Fehler an, bis der Zeitablauf erreicht ist.
            echo -e "\033[31;40m\033[4;62Hfalsch, versuchen Sie es erneut!\033[37;40m"
          fi
        done
      fi
    else
      echo -ne "\033[22;14H" ## Löscht die Eingabezeilenzeichen
      ## Prüft die Benutzereingabezeichen und fungiert als Timer
      if read tmp; then
        ## Erfolgreiche Eingabe, Schleife prüft, ob die Eingabe einer Spalte entspricht.
        for ((line = 20; line <= 60; line = line + 10)); do
          if [ "$tmp" == "${putchar[$line]}" ]; then
            ## Löscht die Anzeige in dieser Spalte.
            clear_line $line
            ## Wenn übereinstimmend, löscht das Flag.
            ifchar[$line]=""
            echo -e "\007\033[32;40m\033[4;62H         richtig!\033[37;40m"
            numright=$numright+1
            ## Beendet die Schleife
            break
          else
            ## Andernfalls zeigt immer einen Fehler an, bis der Zeitablauf erreicht ist.
            echo -e "\033[31;40m\033[4;62Hfalsch, versuchen Sie es erneut!\033[37;40m"
          fi
        done
      fi
    fi
    trap " doexit " 2 ## Fängt spezielle Signale ab
    ## Berechnet die Genauigkeit
    accuracy=$numright*100/$numtotal
  done
}

Nun erklären wir die Methode des Tippens von einzelnen Zeichen und Wörtern separat, da sie sich etwas unterscheiden.

Beim Tippen von einzelnen Zeichen möchten wir, dass fünf Zeichen gleichzeitig erscheinen und in einem festgelegten Zeitintervall weiterfallen, bis sie verschwinden, wenn sie eine bestimmte Höhe erreichen (bestimmt durch die am Anfang gewählte Zeitüberschreitung). Wenn sie vor dem Verschwinden nicht getroffen werden (der Benutzer gibt nicht das entsprechende richtige Zeichen ein), erscheint in dieser Spalte ein neues Zeichen. Daher initialisieren wir zunächst in einer Schleife eine Zeichenfolge und speichern sie in einem Array. Der Index des Arrays repräsentiert die Spaltennummer des Terminals. Dies hat den Vorteil, dass wir jede Spalte von Zeichen unabhängig verwalten können, ob sie abgelaufen oder getroffen wurde und wo sie platziert werden soll. Der Nachteil ist, dass wir ein relativ großes Array erstellen werden, aber es wird nicht so groß sein, dass es nicht gehandhabt werden kann. Dies spielt für die Shell keine Rolle, da sie nicht basierend auf der Größe des Arrayindexes Speicher zuweist. Die erste for-Schleife ist dafür verantwortlich, und sie prüft auch am Ende jeder Hauptschleife, ob eine Spalte leer ist, und dann erscheint in dieser Spalte ein neues Zeichen.

Als nächstes folgt eine lange if...else-Anweisung. Erinnern Sie sich noch an den Parameter, der beim Aufruf der main-Funktion übergeben wurde? Der äußerste Teil wird verwendet, um zu unterscheiden, ob einzelne Zeichen oder Wörter getippt werden sollen. Schauen wir uns zunächst das Tippen von einzelnen Zeichen an, das eine entscheidende Zeile enthält:

if read -n 1 -t 0.5 tmp

Der -n-Parameter des read-Befehls gibt die Länge der zu lesenden Zeichen an. Hier wird eine Länge von 1 angegeben, was bedeutet, dass die Eingabe sofort endet, nachdem der Benutzer ein Zeichen eingegeben hat, ohne dass die Eingabetaste gedrückt werden muss. Der -t-Parameter gibt die Eingabezeitüberschreitung an. Wenn der Benutzer innerhalb der Zeitüberschreitung keine Eingabe macht oder die Eingabe nicht abgeschlossen ist, endet das Lesen sofort. Da der read-Befehl beim Lesen der Benutzereingabe für die gleiche Shell eine synchrone Operation ist, basiert die Implementierung des Fallens von Zeichen auf dieser Zeitüberschreitung (indirekt wird eine Verzögerung beim Fallen implementiert). Hier wird 0,5s festgelegt, da die meisten Benutzer die Eingabe eines Zeichens innerhalb von 0,5s abschließen können. Nachdem die Benutzereingabe gelesen wurde, wird eine for-Schleife verwendet, um jedes Zeichenarray, das jeder Spalte entspricht, zu vergleichen. Wenn es eine Übereinstimmung gibt, wird die clear_line-Funktion aufgerufen, um das Zeichen in der aktuellen Spalte zu löschen, und die Flag-Variable ifchar[$line] wird auf 0 gesetzt, was bedeutet, dass es gelöscht wurde.

Der Ablauf des Tippens von Wörtern ist im Grunde der gleiche wie beim Tippen von Zeichen, nur dass wir, da wir die Zeit nicht abschätzen können, die der Benutzer benötigt, um ein Wort mit unbestimmter Länge einzugeben, keine Eingabezeitüberschreitung für den read-Befehl festlegen können. Ohne diese Zeitüberschreitung kann die endgültige Implementierung des Tippens von Wörtern möglicherweise nicht automatisch fallen wie beim Tippen von Zeichen, sondern das Wort fällt eine Zeile nach unten, nachdem wir ein Wort eingegeben und die Eingabetaste gedrückt haben. Natürlich können Sie andere Methoden in Betracht ziehen, um die gleichen oder bessere Effekte wie beim Tippen von Zeichen zu erzielen.

Darüber hinaus ist die Behandlung des Abrufs neuer Wörter für jede Spalte etwas anders, da wir aus einer Datei lesen, anstatt zufällige Wörter zu generieren. Erinnern Sie sich noch an den Dateideskriptor 4, den wir früher erstellt haben, der auf eine Wortdatei zeigt? Wir haben diesen Dateideskriptor hier verwendet:

read -u 4 word
if [ "$word" == "" ]; then ## Ende des Dateilesens
  exec 4< $file
fi
putchar[$line]=$word

Hier verwenden wir weiterhin den read-Befehl, und mit dem -u-Parameter können wir angeben, aus welchem Dateideskriptor die Datei zeilenweise in die Variable gelesen werden soll. Die nachfolgende Leerprüfungsanweisung dient dazu, den Dateideskriptor, der auf die Datei zeigt, neu zu erstellen, wenn eine Datei zu Ende ist, damit read wieder von Anfang der Datei lesen kann.

Fühlen Sie sich ein bisschen überfordert? Keine Sorge, wir sind noch nicht fertig. Achten Sie nun auf die vorletzte Zeile der main-Funktion:

trap " doexit " 2 ## Fängt spezielle Signale ab

Der trap-Befehl wird verwendet, um spezielle Signale in der Shell abzufangen, wie z. B. Ctrl+C, Ctrl+D, Ctrl+Z, ESC usw. Ursprünglich werden diese speziellen Signale von der Shell selbst behandelt. Da wir möchten, dass das Spiel beim Beenden eleganter beendet wird, können wir das 2. spezielle Signal, das Ctrl+C ist, abfangen, um einige benutzerdefinierte Verarbeitungen zu implementieren. Beispielsweise ruft es hier nach dem Abfangen des 2. Signals die folgende doexit-Funktion auf, um das Programm elegant zu beenden:

function doexit() {
  draw_border
  echo -e "\033[10;30HDieses Spiel wird beendet....."
  echo -e "\033[0m"
  sleep 2
  clear
  exit 1
}
✨ Lösung prüfen und üben

Hauptcodeablauf

Da der Code für jedes Funktionsmodul bereits vorhanden ist, benötigen wir noch einen Hauptcodeablauf, um diese Funktionen aufzurufen, damit das Programm läuft.

draw_border
dis_welcome
echo -ne "\033[3;30HSpiel starten. J/N : "
read yourchoice
if [ "$yourchoice" == "J" ] || [ "$yourchoice" == "j" ]; then
  draw_border
  modechoose
else
  clear
  exit 1
fi

exit 1

Dieser Codeabschnitt erstellt einen Einstiegspunkt für ein Zeichenfallspiel. Zunächst wird ein Willkommensbildschirm und eine Aufforderung zum Starten des Spiels angezeigt. Anschließend wartet das Programm auf die Benutzereingabe, ob das Spiel gestartet werden soll. Wenn der Benutzer das Spiel starten möchte, wird er aufgefordert, den Schwierigkeitsgrad des Spiels auszuwählen. Wenn der Benutzer das Spiel beenden möchte oder eine ungültige Eingabe macht, wird das Spiel nicht gestartet.

✨ Lösung prüfen und üben

Ausführen und Testen

Als nächstes können wir unser Shell-Tippspiel ausführen:

cd ~/project
bash shell_typing_game.sh
Shell Typing Game
✨ Lösung prüfen und üben

Zusammenfassung

Sie haben gerade ein Projekt erstellt, um ein einfaches Tippspiel in der Shell zu entwickeln. Sie können diese Schritte befolgen, um Ihr eigenes Tippspielprojekt zu erstellen und verschiedene Spielmodi und Schwierigkeitsgrade auszuwählen. Dieses Projekt bietet praktische Erfahrungen in der Shell-Skripting und der Entwicklung von terminalbasierten Spielen. Wenn Sie mit einigen der im Kurs verwendeten Befehle nicht vertraut sind, wie z. B. den verschiedenen Ausgaben von echo, der I/O-Umleitung von exec und der Falle zum Abfangen spezieller Signale, können Sie diese öfter üben, um sich mit ihrer Verwendung vertraut zu machen.