Implementación de la escritura
Después de todas las preparaciones, finalmente podemos comenzar a implementar la función de escritura. Echemos un vistazo al siguiente código:
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
}
Ahora, explicaremos el método de escritura de caracteres individuales y palabras por separado, ya que son ligeramente diferentes.
Para la escritura de caracteres individuales, queremos que aparezcan cinco caracteres al mismo tiempo, y estos seguirán cayendo a intervalos de tiempo establecidos hasta que desaparezcan cuando alcanzan una cierta altura (determinada por el período de tiempo de espera elegido al principio). Si no son acertados (el usuario no ingresa el carácter correcto correspondiente) antes de desaparecer, aparecerá un nuevo carácter en esa columna. Por lo tanto, primero, recorremos en un bucle para inicializar una secuencia de caracteres y la almacenamos en una matriz. El índice de la matriz representa el número de columna del terminal, lo cual tiene la ventaja de gestionar de forma independiente cada columna de caracteres, ya sea si ha expirado el tiempo o ha sido acertado, y dónde debe colocarse. La desventaja es que crearemos una matriz relativamente grande, pero no será tan grande que no se pueda manejar. Esto no importa para la shell ya que no asigna memoria según el tamaño del índice de la matriz. El primer bucle for
se encarga de esto, y también comprueba al final de cada gran bucle si una columna está vacía y, en ese caso, aparecerá un nuevo carácter en esa columna.
A continuación, hay una larga declaración if...else
. ¿Recuerdas el parámetro pasado cuando se llama a la función main
? La parte más externa se utiliza para distinguir si se deben escribir caracteres individuales o palabras. Veamos primero la escritura de caracteres individuales, que contiene una línea crucial:
if read -n 1 -t 0.5 tmp
El parámetro -n
del comando read
especifica la longitud de los caracteres a leer. Aquí, se especifica una longitud de 1
, lo que significa que la entrada finaliza inmediatamente después de que el usuario ingrese un carácter, sin necesidad de presionar la tecla Enter. El parámetro -t
especifica el tiempo de espera de la entrada. Si el usuario no ingresa nada o no completa la entrada dentro del período de tiempo de espera, la lectura finalizará inmediatamente. Debido a que el comando read
es una operación síncrona para la misma shell al leer la entrada del usuario, la implementación de la caída de los caracteres se basa en este tiempo de espera (implementando indirectamente un retraso en la caída). Aquí, se establece 0.5s
porque la mayoría de los usuarios pueden completar la entrada de un carácter dentro de 0.5s
. Después de leer la entrada del usuario, utiliza un bucle for
para comparar cada matriz de caracteres correspondiente a cada columna, y si hay una coincidencia, llama a una función clear_line
para borrar el carácter en la columna actual, y establece la variable de bandera ifchar[$line]
en 0
, lo que indica que ha sido borrado.
El flujo de escritura de palabras es básicamente el mismo que el de escritura de caracteres, excepto que, debido a que no podemos estimar el tiempo que tardará el usuario en ingresar una palabra de longitud indefinida, no podemos establecer el tiempo de espera de entrada para el comando read
. Sin este tiempo de espera, la implementación final de la escritura de palabras puede no caer automáticamente como cuando se escriben caracteres, sino que la palabra cae una línea después de que ingresamos una palabra y presionamos Enter. Por supuesto, puedes considerar utilizar otros métodos para lograr los mismos o mejores efectos que la escritura de caracteres.
Además, el manejo de la obtención de nuevas palabras para cada columna es ligeramente diferente porque estamos leyendo desde un archivo, en lugar de generar palabras aleatorias. ¿Todavía recuerdas el descriptor de archivo 4
que creamos anteriormente, que apunta a un archivo de palabras? Lo utilizamos aquí:
read -u 4 word
if [ "$word" == "" ]; then ## End of file reading
exec 4< $file
fi
putchar[$line]=$word
Aquí, todavía utilizamos el comando read
, y con el parámetro -u
, podemos especificar desde qué descriptor de archivo leer el archivo línea por línea en la variable. La siguiente declaración de comprobación de vacío es para recrear el descriptor de archivo que apunta al archivo cuando un archivo llega a su final, de modo que read
pueda leer desde el principio del archivo nuevamente.
¿Te parece un poco complicado? No te preocupes, todavía no hemos terminado. A continuación, presta atención a la penúltima línea de la función main
:
trap " doexit " 2 ## Capture special signals
El comando trap
se utiliza para capturar señales especiales en la shell, como Ctrl+C
, Ctrl+D
, Ctrl+Z
, ESC
, etc. Originalmente, estas señales especiales son manejadas por la propia shell. Debido a que queremos que el juego salga de manera más elegante cuando se cierra, podemos interceptar la segunda señal especial, que es Ctrl+C
, para implementar algún procesamiento personalizado. Por ejemplo, aquí, después de capturar la segunda señal, llama a la función doexit
que se muestra a continuación para salir del programa de manera elegante:
function doexit() {
draw_border
echo -e "\033[10;30Hthis game will exit....."
echo -e "\033[0m"
sleep 2
clear
exit 1
}