Implementation of Typing
After all the preparations, we can finally start implementing the typing function. Let's take a look at the following code:
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
}
Now let's explain the method of typing single characters and words separately, as they are slightly different.
For typing single characters, we want five characters to appear at the same time, and they will continue to fall at a set time interval until they disappear when they reach a certain height (determined by the timeout period chosen at the beginning). If they are not hit (the user does not input the corresponding correct character) before disappearing, a new character will appear in that column. Therefore, first, we loop to initialize a character sequence and store it in an array. The index of the array represents the column number of the terminal, which has the advantage of independently managing each column of characters, whether it has timed out or been hit, and where it should be placed. The disadvantage is that we will create a relatively large array, but it will not be so large that it cannot be handled. This doesn't matter for the shell since it doesn't allocate memory based on the size of the array index. The first for
loop is responsible for this, and it also checks at the end of each major loop if a column is empty and then a new character will appear in that column.
Next is a long if...else
statement. Do you remember the parameter passed when calling the main
function? The outermost part is used to distinguish whether to type single characters or words. Let's first look at typing single characters, which contains a crucial line:
if read -n 1 -t 0.5 tmp
The -n
parameter of the read
command specifies the length of the characters to be read. Here, a length of 1
is specified, which means that the input ends immediately after the user enters one character, without the need for pressing the Enter key. The -t
parameter specifies the input timeout time. If the user does not input or the input is not completed within the timeout period, the reading will end immediately. Because the read
command is a synchronous operation for the same shell when reading user input, the implementation of the character falling relies on this timeout (indirectly implementing a delay in falling). Here, 0.5s
is set because most users can complete the input of one character within 0.5s
. After reading the user input, it uses a for
loop to compare each character array corresponding to each column, and if there is a match, it calls a clear_line
function to clear the character in the current column, and sets the flag variable ifchar[$line]
to 0
which indicates it has been cleared.
The flow of typing words is basically the same as typing characters, except that because we cannot estimate the time it takes for the user to input a word with an indefinite length, we cannot set the input timeout time for the read
command. Without this timeout, the final implementation of typing words may not automatically fall like when typing characters, but instead, the word falls one line after we enter a word and press Enter. Of course, you can consider using other methods to achieve the same or better effects as typing characters.
In addition, the handling of obtaining new words for each column is slightly different because we are reading from a file, rather than generating random words. Do you still remember the file descriptor 4
we created earlier, which points to a word file? We used this file descriptor here:
read -u 4 word
if [ "$word" == "" ]; then ## End of file reading
exec 4< $file
fi
putchar[$line]=$word
Here, we still use the read
command, and with the -u
parameter, we can specify from which file descriptor to read the file line by line into the variable. The subsequent empty check statement is to recreate the file descriptor that points to the file when a file reaches its end, so that read
can read from the beginning of the file again.
Do you feel that it's a bit complicated? Don't worry, we're not done yet. Next, pay attention to the penultimate line of the main
function:
trap " doexit " 2 ## Capture special signals
The trap
command is used to capture special signals in the shell, such as Ctrl+C
, Ctrl+D
, Ctrl+Z
, ESC
, etc. Originally, these special signals are handled by the shell itself. Because we want the game to exit more gracefully when it exits, we can intercept the 2
nd special signal, which is Ctrl+C
, to implement some customized processing. For example, here, after capturing the 2
nd signal, it calls the doexit
function below to exit the program gracefully:
function doexit() {
draw_border
echo -e "\033[10;30Hthis game will exit....."
echo -e "\033[0m"
sleep 2
clear
exit 1
}