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
}