Criando um Jogo de Digitação com Bash

LinuxBeginner
Pratique Agora

Introdução

Neste projeto, você aprenderá como criar um jogo de digitação simples usando um script de shell. O jogo exibe caracteres aleatórios na tela, e seu objetivo é digitá-los antes que desapareçam. O jogo oferece diferentes modos com níveis de dificuldade variados. Você pode escolher praticar a digitação de números, letras, uma mistura de ambos, ou até mesmo suas próprias palavras personalizadas.

👀 Pré-visualização

Shell Typing Game

🎯 Tarefas

Neste projeto, você aprenderá:

  • Como criar um arquivo de projeto e abri-lo em um editor de código
  • Como exibir uma interface de boas-vindas usando caracteres especiais e cores
  • Como implementar um menu de seleção de modo para escolher o nível de dificuldade
  • Como implementar um menu de seleção de categoria de digitação para escolher o tipo de caracteres a serem praticados
  • Como criar funções para desenhar uma borda e preencher a cor de fundo da interface de digitação
  • Como gerar letras e números aleatórios para o jogo de digitação
  • Como implementar a funcionalidade de digitação, incluindo o tratamento da entrada do usuário e o cálculo da precisão
  • Como criar uma função de saída elegante para lidar com sinais especiais

🏆 Conquistas

Após concluir este projeto, você será capaz de:

  • Demonstrar os fundamentos de scripting de shell
  • Usar caracteres especiais e cores na saída do terminal
  • Ler a entrada do usuário em scripts de shell
  • Implementar menus e interfaces de usuário em scripts de shell
  • Lidar com sinais especiais em scripts de shell
Este é um Lab Guiado, que fornece instruções passo a passo para ajudá-lo a aprender e praticar. Siga as instruções cuidadosamente para completar cada etapa e ganhar experiência prática. Dados históricos mostram que este é um laboratório de nível intermediário com uma taxa de conclusão de 57%. Recebeu uma taxa de avaliações positivas de 93% dos estudantes.

Criar Arquivo do Projeto

Para começar, por favor, crie um novo arquivo chamado shell_typing_game.sh e abra-o no seu editor de código preferido.

cd ~/project
touch shell_typing_game.sh
✨ Verificar Solução e Praticar

Exibir Interface de Boas-Vindas

#!/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

A longa string no início 010101... é a interface de boas-vindas do jogo a ser exibida. Se você olhar para ela diretamente, pode não entender o que é. Então, você pode copiar esta string para um editor gedit e, em seguida, pressionar o atalho Ctrl+F para encontrar. Quando você digitar 1, você entenderá imediatamente. Vamos nos concentrar no comando echo. Precisamos usar o comando echo porque queremos exibi-lo na tela (aqui se refere ao terminal Xfce. A entrada padrão, a saída e a saída de erro padrão no Linux são todas conectadas ao terminal por padrão). Para obter algumas funções especiais, como imprimir em uma posição especificada e definir a cor do caractere de exibição, e imprimir o fundo de todo o jogo, precisamos usar alguns parâmetros especiais do comando correspondente, conforme mostrado no código a seguir:

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

Com base nos comentários, podemos entender aproximadamente sua função. Mas o que isso significa exatamente? Primeiro, o parâmetro -n significa imprimir na linha atual sem quebra de linha após a saída. Em seguida, defina a cor do caractere de saída e a posição do cursor. Entre eles, \033[cor do primeiro plano;cor do fundo m, e o seguinte é \033[linha;coluna H. No entanto, para fazer com que echo reconheça essas sequências de escape especiais, precisamos do parâmetro -e, caso contrário, echo irá gerar esses caracteres como estão.

Cores do primeiro plano: 30-39, cores do fundo: 40-49.

Valor Cores do primeiro plano Valor Faixa de cores do fundo: 40 - 49
30 Definir primeiro plano preto 40 Definir fundo preto
31 Definir primeiro plano vermelho 41 Definir fundo vermelho
32 Definir primeiro plano verde 42 Definir fundo verde
33 Definir primeiro plano marrom 43 Definir fundo marrom
34 Definir primeiro plano azul 44 Definir fundo azul
35 Definir primeiro plano roxo 45 Definir fundo roxo
36 Definir primeiro plano ciano 46 Definir fundo ciano
37 Definir primeiro plano branco 47 Definir fundo branco
38 Definir sublinhado na cor padrão do primeiro plano 49 Definir fundo preto padrão
39 Definir sublinhado desligado na cor padrão do primeiro plano

Não esperava que echo fosse usado assim! Você sente que se reapresentou a ele? Ok, agora vamos testar este trecho de código no terminal.

cd ~/project
bash shell_typing_game.sh
Shell Typing Game
✨ Verificar Solução e Praticar
## Declare the variable 'time' to represent the typing timeout, where different difficulty levels correspond to different timeout values
declare -i time
function modechoose() {
  ## Select from three different modes
  echo -e "\033[8;30H1) easy mode"
  echo -e "\033[9;30H2) normal mode"
  echo -e "\033[10;30H3) difficult mode"
  echo -ne "\033[22;2HPlease input your choice: "
  read mode
  case $mode in
    "1")
      time=10
      dismenu ## Call the menu selection function
      ;;
    "2")
      time=5
      dismenu
      ;;
    "3")
      time=3
      dismenu
      ;;
    *)
      echo -e "\033[22;2HYour choice is incorrect, please try again"
      sleep 1
      ;;
  esac
}

Antes de definir esta função, primeiro declaramos uma variável inteira 'time' usando o comando declare -i. Em seguida, na instrução case a seguir, definimos valores diferentes para a variável 'time' com base na seleção de dificuldade do usuário. Quanto maior a dificuldade, menor o valor. Essa variável é realmente usada na função de processamento de digitação subsequente. Você pode se lembrar que, em scripts de shell, variáveis declaradas ou definidas dentro ou fora de uma função são tratadas como variáveis globais com um escopo em todo o arquivo de script, a menos que a palavra-chave 'local' seja usada para declarar a variável dentro de uma função. Quanto ao efeito de exibição do menu, ainda usamos o comando echo e, em seguida, usamos o comando read para ler a entrada do usuário na variável 'mode'.

Shell Typing Game
✨ Verificar Solução e Praticar
function display_menu() {
  while [ 1 ]; do
    draw_border
    echo -e "\033[8;30H1) Praticar digitação de números"
    echo -e "\033[9;30H2) Praticar digitação de letras"
    echo -e "\033[10;30H3) Praticar digitação de caracteres alfanuméricos"
    echo -e "\033[11;30H4) Praticar digitação de palavras"
    echo -e "\033[12;30H5) Sair"
    echo -ne "\033[22;2HPlease input your choice: "
    read choice
    case $choice in
      "1")
        draw_border
        ## The next two are function parameters, the first parameter indicates the typing type, and the second is the function for moving characters
        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 "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
        ;;
      "5" | "q" | "Q")
        draw_border
        echo -e "\033[10;25HYou will exit this game now"
        echo -e "\033[39;49m"
        sleep 1
        clear
        exit 1
        ;;
      *)
        draw_border
        echo -e "\033[22;2HYour choice is wrong, please try again"
        sleep 1
        ;;
    esac
  done
}

Nesta função, vamos primeiro explicar as duas funções chamadas no ramo case. Primeiro, a função draw_border é usada para desenhar a borda da interface de digitação, que será mostrada mais tarde. Em seguida, a função main é chamada. Esta função main não tem nenhum papel especial como a função main em linguagens como Java e C, ela simplesmente tem o nome main, que é usado para indicar que ela desempenha um papel importante em todo o programa. Você pode ter notado que há um argumento após cada main, sim, este é o parâmetro passado para a função. Em shell, os parâmetros não são escritos entre parênteses imediatamente após o nome da função, ao contrário de muitas outras linguagens. Deve-se notar também que no quarto ramo da instrução case, que são estas linhas:

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

De acordo com o prompt do menu, podemos saber que este ramo é para praticar a digitação de palavras. Este programa permite que os usuários usem arquivos de palavras personalizados (arquivos de texto com um determinado requisito de formatação, com uma palavra por linha, você pode baixar um arquivo de lista de palavras online e usar o comando awk para extrair) para a prática. Então, primeiro precisamos ler um nome de arquivo de entrada do usuário para selecionar o arquivo de palavras. Em seguida, usamos o comando exec para criar um pipeline que aponta para o arquivo, ou seja, redirecionando a saída do arquivo para o descritor de arquivo 4 (fd). Como exec é um comando embutido no bash, você não poderá ver a documentação de exec usando o comando man. O redirecionamento de E/S com exec geralmente está relacionado a fd, e o shell geralmente tem 10 fd, variando de 0 a 9. Os fd comumente usados são 3, que são 0 (stdin, entrada padrão), 1 (stdout, saída padrão) e 2 (stderr, saída de erro padrão). Apenas entenda seu significado por enquanto.

Shell Typing Game
✨ Verificar Solução e Praticar

Desenhando uma Borda para uma Interface de Digitação

function draw_border() {
  declare -i width
  declare -i high
  width=79 ## terminal default width - 1
  high=23  ## terminal default height - 1

  clear

  ## Set display color to white on black background
  echo -e "\033[37;40m"
  ## Set background color
  for ((i = 1; i <= $width; i = i + 1)); do
    for ((j = 1; j <= $high; j = j + 1)); do
      ## Set display position
      echo -e "\033["$j";"$i"H "
    done
  done
  ## Draw background border
  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
}
  • Na função draw_border(), duas variáveis inteiras width e high são declaradas, que representam a largura e a altura da janela do terminal, respectivamente.
  • O comando echo -e "\033[37;40m" é usado para definir a cor da exibição. Loops for aninhados são usados para iterar por cada linha e coluna do terminal para preencher o fundo.
  • Dentro do loop interno, códigos de escape ANSI são usados para posicionar o cursor nas coordenadas desejadas dentro do terminal. Por exemplo, echo -e "\033["$j";"$i"H" define a posição do cursor para a linha j e a coluna i.
  • Após preencher o fundo do terminal, bordas decorativas são desenhadas usando caracteres específicos. Os caracteres usados incluem +, - e |, que são comumente usados para desenhar bordas. As posições desses caracteres são determinadas por seus valores de linha e coluna, e impressas usando códigos de escape ANSI. Por exemplo, echo -e "\033[1;1H+" coloca o caractere + no canto superior esquerdo do terminal.

Em resumo, a função draw_border() limpa o terminal, define a cor do fundo para preto, preenche o terminal com espaços para criar um fundo. Finalmente, ela desenha uma borda visualmente atraente, colocando caracteres (como + para cantos, - e | para linhas) em posições específicas.

Shell Typing Game
✨ Verificar Solução e Praticar

Preenchendo a Cor de Fundo da Interface de Digitação

## Clear the entire character landing area
function clear_all_area() {
  local i j
  ## Fill the typing area
  for ((i = 5; i <= 21; i++)); do
    for ((j = 3; j <= 77; j = j + 1)); do
      ## Set the display position
      echo -e "\033[44m\033["$i";"$j"H "
    done
  done
  echo -e "\033[37;40m"
}

## Function: Clear a specific column of characters
## Input: The column number to be cleared
## Return: None
function clear_line() {
  local i
  ## Fill the typing area
  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. A função clear_all_area():
  • É usada para limpar toda a área de aterrissagem de caracteres. Sua função é preencher os caracteres na área de aterrissagem de caracteres com a cor de fundo (preto) para limpar os caracteres na janela do terminal.
  • Ela usa loops aninhados para iterar sobre todas as combinações de i e j, onde i representa a linha e j representa a coluna.
  • Dentro do loop interno, ela define a posição do cursor para a linha e coluna especificadas usando códigos de escape ANSI e, em seguida, usa echo -e "\033[44m\033["$i";"$j"H " para imprimir um caractere de espaço nessa posição, mas com a cor de fundo definida como preto (código 44).
  • Após os loops, a função usa echo -e "\033[37;40m" para restaurar a cor do texto e a cor de fundo para seus valores padrão (texto branco, fundo preto).
  1. A função clear_line():
  • Esta função é usada para limpar uma coluna específica na área de aterrissagem de caracteres. Ela é tipicamente usada para limpar o caminho do caractere naquela coluna após o usuário inserir o caractere correto.
  • A função recebe um argumento $1, que representa o número da coluna a ser limpa.
  • Ela usa um loop para iterar sobre as linhas e colunas na área de aterrissagem de caracteres e preenche o caminho do caractere daquela coluna com a cor de fundo (preto).
  • Semelhante a clear_all_area(), a função define a posição do cursor para a linha e coluna especificadas usando códigos de escape ANSI e, em seguida, usa echo -e "\033[44m\033["$i";"$j"H " para imprimir um caractere de espaço nessa posição, mas com a cor de fundo definida como preto.
  • Finalmente, a função usa echo -e "\033[37;40m" para restaurar a cor do texto e a cor de fundo para seus valores padrão.
✨ Verificar Solução e Praticar

Gerando Letras e Números Aleatórios

## Function:  Move the character along the falling path.
## Input:     Parameter 1: The current row of the character (related to the time length away from the character).
##            Parameter 2: The current column of the character.
##            Parameter 3: (Unused parameter).
## Return:    None
function move() {

  local locate_row lastloca
  locate_row=$(($1 + 5))
  ## Display the character to be input.
  echo -e "\033[30;44m\033["$locate_row";"$2"H$3\033[37;40m"
  if [ "$1" -gt "0" ]; then
    lastloca=$(($locate_row - 1))
    ## Clear the previous 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
}

## Function:  Generate a random character of the corresponding type to be converted into a random character.
## Input:     The type of character to be generated.
## Global Var: random_char, array[]
## Return:    None
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]}
}

Devido ao fato de que as variáveis de string do shell não suportam diretamente a indexação, precisamos colocar todas as letras e números em um array indexado. Este é o propósito da função putarray. A função get_random_char abaixo é usada para gerar letras e números aleatórios. Ela usa a variável de ambiente de número aleatório do sistema RANDOM para obter um índice aleatório e, em seguida, lê o caractere correspondente do array.

✨ Verificar Solução e Praticar

Implementação da Digitação

Após todos os preparativos, finalmente podemos começar a implementar a função de digitação. Vamos dar uma olhada no código a seguir:

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

  ## Store the corresponding characters into an array, $1 represents the user-selected character type
  putarray $1

  ## Initialize the game start time
  gamestarttime=$(date +%s)

  while [ 1 ]; do
    echo -e "\033[2;2H  Please enter the letter on the screen before it disappears!"

    echo -e "\033[3;2H Game time:     "
    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 Accuracy: \033[31;40m$accuracy %\033[37;40m"
    echo -ne "\033[22;2H Your input:                         "
    clear_all_area
    ## Loop 10 times to check if a column of characters times out or is hit
    for ((line = 20; line <= 60; line = line + 10)); do
      ## Check if the column of characters has been hit
      if [ "${ifchar[$line]}" == "" ] || [ "${donetime[$line]}" -gt "$time" ]; then
        ## Clear the display in that column
        clear_line $line
        ## Generate a random character
        if [ "$1" == "word" ]; then
          read -u 4 word
          if [ "$word" == "" ]; then ## End of file reading
            exec 4< $file
          fi
          putchar[$line]=$word
        else
          get_random_char $1
          putchar[$line]=$random_char
        fi
        numtotal=$numtotal+1
        ## Set the flag to 1
        ifchar[$line]=1
        ## Reset the timer
        starttime[$line]=$(date +%s)
        curtime[$line]=${starttime[$line]}
        donetime[$line]=$time
        ## Reset the column position to 0
        column[$line]=0
        if [ "$1" == "word" ]; then
          move 0 $line ${putchar[$line]}
        fi
      else
        ## If it has not timed out or been hit, update the timer and current 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" ## Clear the input line characters
      ## Check user input characters and act as a one-second timer
      if read -n 1 -t 0.5 tmp; then
        ## Successful input, loop checks if the input matches a column
        for ((line = 20; line <= 60; line = line + 10)); do
          if [ "$tmp" == "${putchar[$line]}" ]; then
            ## Clear the display in that column
            clear_line $line
            ## If matched, clear the flag
            ifchar[$line]=""
            echo -e "\007\033[32;40m\033[4;62H         right !\033[37;40m"
            numright=$numright+1
            ## Exit the loop
            break
          else
            ## Otherwise, always display an error until timeout
            echo -e "\033[31;40m\033[4;62Hwrong,try again!\033[37;40m"
          fi
        done
      fi
    else
      echo -ne "\033[22;14H" ## Clear the input line characters
      ## Check user input characters and act as a timer
      if read tmp; then
        ## Successful input, loop checks if the input matches a column
        for ((line = 20; line <= 60; line = line + 10)); do
          if [ "$tmp" == "${putchar[$line]}" ]; then
            ## Clear the display in that column
            clear_line $line
            ## If matched, clear the flag
            ifchar[$line]=""
            echo -e "\007\033[32;40m\033[4;62H         right !\033[37;40m"
            numright=$numright+1
            ## Exit the loop
            break
          else
            ## Otherwise, always display an error until timeout
            echo -e "\033[31;40m\033[4;62Hwrong,try again!\033[37;40m"
          fi
        done
      fi
    fi
    trap " doexit " 2 ## Capture special signals
    ## Calculate accuracy
    accuracy=$numright*100/$numtotal
  done
}

Agora, vamos explicar o método de digitação de caracteres e palavras individualmente, pois eles são ligeiramente diferentes.

Para digitar caracteres individuais, queremos que cinco caracteres apareçam ao mesmo tempo, e eles continuarão a cair em um intervalo de tempo definido até que desapareçam quando atingirem uma certa altura (determinada pelo período de tempo limite escolhido no início). Se eles não forem atingidos (o usuário não inserir o caractere correto correspondente) antes de desaparecerem, um novo caractere aparecerá naquela coluna. Portanto, primeiro, fazemos um loop para inicializar uma sequência de caracteres e armazená-la em um array. O índice do array representa o número da coluna do terminal, o que tem a vantagem de gerenciar independentemente cada coluna de caracteres, se ela expirou ou foi atingida, e onde ela deve ser colocada. A desvantagem é que criaremos um array relativamente grande, mas não será tão grande que não possa ser tratado. Isso não importa para o shell, pois ele não aloca memória com base no tamanho do índice do array. O primeiro loop for é responsável por isso, e ele também verifica no final de cada loop principal se uma coluna está vazia e, em seguida, um novo caractere aparecerá naquela coluna.

Em seguida, há uma longa instrução if...else. Você se lembra do parâmetro passado ao chamar a função main? A parte mais externa é usada para distinguir se deve digitar caracteres individuais ou palavras. Vamos primeiro olhar para a digitação de caracteres individuais, que contém uma linha crucial:

if read -n 1 -t 0.5 tmp

O parâmetro -n do comando read especifica o comprimento dos caracteres a serem lidos. Aqui, um comprimento de 1 é especificado, o que significa que a entrada termina imediatamente após o usuário inserir um caractere, sem a necessidade de pressionar a tecla Enter. O parâmetro -t especifica o tempo limite de entrada. Se o usuário não inserir ou a entrada não for concluída dentro do período de tempo limite, a leitura terminará imediatamente. Como o comando read é uma operação síncrona para o mesmo shell ao ler a entrada do usuário, a implementação da queda do caractere depende desse tempo limite (implementando indiretamente um atraso na queda). Aqui, 0.5s é definido porque a maioria dos usuários pode concluir a entrada de um caractere em 0.5s. Após ler a entrada do usuário, ele usa um loop for para comparar cada array de caracteres correspondente a cada coluna e, se houver uma correspondência, ele chama uma função clear_line para limpar o caractere na coluna atual e define a variável de sinalizador ifchar[$line] como 0, o que indica que foi limpo.

O fluxo de digitação de palavras é basicamente o mesmo que o de digitação de caracteres, exceto que, como não podemos estimar o tempo que o usuário leva para inserir uma palavra com um comprimento indefinido, não podemos definir o tempo limite de entrada para o comando read. Sem esse tempo limite, a implementação final da digitação de palavras pode não cair automaticamente como ao digitar caracteres, mas, em vez disso, a palavra cai uma linha depois que inserimos uma palavra e pressionamos Enter. Claro, você pode considerar o uso de outros métodos para obter os mesmos ou melhores efeitos que a digitação de caracteres.

Além disso, o tratamento da obtenção de novas palavras para cada coluna é ligeiramente diferente porque estamos lendo de um arquivo, em vez de gerar palavras aleatórias. Você ainda se lembra do descritor de arquivo 4 que criamos anteriormente, que aponta para um arquivo de palavras? Usamos este descritor de arquivo aqui:

read -u 4 word
if [ "$word" == "" ]; then ## End of file reading
  exec 4< $file
fi
putchar[$line]=$word

Aqui, ainda usamos o comando read e, com o parâmetro -u, podemos especificar de qual descritor de arquivo ler o arquivo linha por linha na variável. A instrução de verificação vazia subsequente é para recriar o descritor de arquivo que aponta para o arquivo quando um arquivo atinge seu fim, para que read possa ler do início do arquivo novamente.

Você acha que é um pouco complicado? Não se preocupe, ainda não terminamos. Em seguida, preste atenção na penúltima linha da função main:

trap " doexit " 2 ## Capture special signals

O comando trap é usado para capturar sinais especiais no shell, como Ctrl+C, Ctrl+D, Ctrl+Z, ESC, etc. Originalmente, esses sinais especiais são tratados pelo próprio shell. Como queremos que o jogo saia de forma mais elegante quando ele sair, podemos interceptar o segundo sinal especial, que é Ctrl+C, para implementar algum processamento personalizado. Por exemplo, aqui, após capturar o segundo sinal, ele chama a função doexit abaixo para sair do programa de forma elegante:

function doexit() {
  draw_border
  echo -e "\033[10;30Hthis game will exit....."
  echo -e "\033[0m"
  sleep 2
  clear
  exit 1
}
✨ Verificar Solução e Praticar

Fluxo Principal do Código

Como o código para cada módulo de função já está em vigor, para fazer o programa rodar, ainda precisamos de um fluxo de código principal para chamar essas funções.

draw_border
dis_welcome
echo -ne "\033[3;30Hstart the game. Y/N : "
read yourchoice
if [ "$yourchoice" == "Y" ] || [ "$yourchoice" == "y" ]; then
  draw_border
  modechoose
else
  clear
  exit 1
fi

exit 1

Este trecho de código cria um ponto de entrada para um jogo de queda de caracteres. Ele primeiro exibe uma tela de boas-vindas e um prompt para iniciar o jogo, depois espera pela entrada do usuário sobre se deve iniciar o jogo. Se o usuário escolher iniciar o jogo, ele será solicitado a escolher o modo de dificuldade do jogo. Se o usuário escolher sair ou inserir uma entrada inválida, o jogo não será iniciado.

✨ Verificar Solução e Praticar

Execução e Testes

Em seguida, podemos executar nosso jogo de digitação em shell:

cd ~/project
bash shell_typing_game.sh
Shell Typing Game
✨ Verificar Solução e Praticar

Resumo

Você acabou de criar um projeto para construir um jogo de digitação simples no shell. Você pode seguir estas etapas para criar seu próprio projeto de jogo de digitação e escolher diferentes modos de jogo e níveis de dificuldade. Este projeto fornece experiência prática em shell scripting e desenvolvimento de jogos baseados em terminal. Se você não estiver familiarizado com alguns dos comandos usados no curso, como as várias saídas de echo, o redirecionamento de E/S de exec e a armadilha (trap) para capturar sinais especiais, você pode praticá-los mais para se familiarizar com seu uso.