Bash を使ったタイピングゲームの作成

LinuxBeginner
オンラインで実践に進む

はじめに

このプロジェクトでは、シェルスクリプトを使用して簡単なタイピングゲームを作成する方法を学びます。このゲームは画面上にランダムな文字を表示し、あなたの目標はそれらが消える前にタイプすることです。ゲームには難易度の異なるさまざまなモードが用意されています。数字、文字、その両方の混合、または独自のカスタム単語をタイプする練習を選択できます。

👀 プレビュー

Shell Typing Game

🎯 タスク

このプロジェクトでは、以下を学びます。

  • プロジェクトファイルを作成し、コードエディタで開く方法
  • 特殊文字と色を使用してウェルカムインターフェイスを表示する方法
  • 難易度レベルを選択するためのモード選択メニューを実装する方法
  • 練習する文字の種類を選択するためのタイピングカテゴリ選択メニューを実装する方法
  • タイピングインターフェイスの枠を描画し、背景色を塗りつぶす関数を作成する方法
  • タイピングゲーム用のランダムな文字と数字を生成する方法
  • ユーザー入力の処理や精度の計算を含むタイピング機能を実装する方法
  • 特殊なシグナルを処理するための適切な終了関数を作成する方法

🏆 達成目標

このプロジェクトを完了した後、以下ができるようになります。

  • シェルスクリプトの基本を示すことができる
  • ターミナル出力で特殊文字と色を使用することができる
  • シェルスクリプトでユーザーからの入力を読み取ることができる
  • シェルスクリプトでメニューとユーザーインターフェイスを実装することができる
  • シェルスクリプトで特殊なシグナルを処理することができる

プロジェクトファイルを作成する

まず、shell_typing_game.sh という名前の新しいファイルを作成し、好みのコードエディタで開いてください。

cd ~/project
touch shell_typing_game.sh

ウェルカムインターフェイスを表示する

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

冒頭の長い文字列 010101... は、表示するゲームのウェルカムインターフェイスです。直接見ると、何を表しているのかわからないかもしれません。そこで、この文字列を gedit エディタにコピーし、ショートカット Ctrl+F を押して検索します。1 を入力すると、すぐに理解できるでしょう。ここでは echo コマンドに注目しましょう。画面に表示するために echo コマンドを使用する必要があります(ここでは Xfce ターミナルを指します。Linux では、標準入力、標準出力、標準エラー出力はすべてデフォルトでターミナルに接続されています)。指定された位置に印刷したり、表示文字の色を設定したり、ゲーム全体の背景を印刷したりするなど、いくつかの特殊な機能を実現するには、対応するコマンドの特殊なパラメータを使用する必要があります。以下のコードのようになります。

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

コメントを元に、おおよその機能は理解できるでしょう。では、具体的に何を意味するのでしょうか。まず、-n パラメータは、現在の行に印刷し、出力後に改行しないことを意味します。次に、出力文字の色とカーソル位置を設定します。このうち、\033[前景色;背景色 m で、その後に \033[行;列 H が続きます。ただし、echo がこれらの特殊なエスケープシーケンスを認識するには、-e パラメータが必要です。そうしないと、echo はこれらの文字をそのまま出力します。

前景色:30 - 39、背景色:40 - 49。

前景色 背景色範囲:40 - 49
30 黒色の前景を設定する 40 黒色の背景を設定する
31 赤色の前景を設定する 41 赤色の背景を設定する
32 緑色の前景を設定する 42 緑色の背景を設定する
33 茶色の前景を設定する 43 茶色の背景を設定する
34 青色の前景を設定する 44 青色の背景を設定する
35 紫色の前景を設定する 45 紫色の背景を設定する
36 シアン色の前景を設定する 46 シアン色の背景を設定する
37 白色の前景を設定する 47 白色の背景を設定する
38 デフォルトの前景色に下線を設定する 49 デフォルトの黒色の背景を設定する
39 デフォルトの前景色の下線を解除する

echo がこのように使えるとは思わなかったでしょう!もう一度知り新たな感じがしますよね。さて、このコードをターミナルでテストしてみましょう。

cd ~/project
bash shell_typing_game.sh

Shell Typing Game

モード選択メニューを表示する

## 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
}

この関数を定義する前に、まず declare -i コマンドを使用して整数変数 time を宣言しました。その後、続く case 文では、ユーザーが選択した難易度に基づいて time 変数に異なる値を設定します。難易度が高いほど、値は小さくなります。この変数は、実際には後続のタイピング処理関数で使用されます。シェルスクリプトでは、関数の内部または外部で宣言または定義された変数は、関数内で local キーワードを使用して変数を宣言しない限り、スクリプトファイル全体をスコープとするグローバル変数として扱われることを思い出すかもしれません。メニューの表示効果については、引き続き echo コマンドを使用し、その後 read コマンドを使用してユーザーの入力を変数 mode に読み込みます。

Shell Typing Game

タイピングカテゴリ選択メニューを表示する

function display_menu() {
  while [ 1 ]; do
    draw_border
    echo -e "\033[8;30H1) Practice typing numbers"
    echo -e "\033[9;30H2) Practice typing letters"
    echo -e "\033[10;30H3) Practice typing alphanumeric characters"
    echo -e "\033[11;30H4) Practice typing words"
    echo -e "\033[12;30H5) Quit"
    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
}

この関数では、まず case 分岐で呼び出される 2 つの関数について説明します。まず、draw_border 関数はタイピングインターフェイスの枠を描画するために使用され、これについては後で説明します。次に、main 関数が呼び出されます。この main 関数は、Java や C などの言語の main 関数のような特別な役割を持っているわけではなく、単に main という名前が付けられており、プログラム全体で主要な役割を果たすことを示しています。各 main の後に引数があることに気づいたかもしれません。そうです、これは関数に渡されるパラメータです。シェルでは、多くの他の言語とは異なり、パラメータは関数名の直後の括弧内に書かれません。また、case 文の 4 番目の分岐、つまり以下の行に注意してください。

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

メニューのプロンプトから、この分岐は単語のタイピング練習用であることがわかります。このプログラムでは、ユーザーがカスタムの単語ファイル(一定の書式要件があるテキストファイルで、1 行に 1 単語。オンラインで単語リストファイルをダウンロードし、awk コマンドで抽出することができます)を使用して練習することができます。そのため、まずユーザーが入力したファイル名を読み込んで単語ファイルを選択する必要があります。次に、exec コマンドを使用してファイルを指すパイプラインを作成します。つまり、ファイルの出力をファイルディスクリプタ 4 (fd) にリダイレクトします。exec は bash の組み込みコマンドであるため、man コマンドを使用して exec のドキュメントを見ることはできません。exec を使用した入出力リダイレクトは通常 fd と関係しており、シェルは通常 0 から 9 までの 10 個の fd を持っています。一般的に使用される fd は 3 つで、0 (stdin、標準入力)、1 (stdout、標準出力)、2 (stderr、標準エラー出力) です。今はその意味を理解しておけば十分です。

Shell Typing Game

タイピングインターフェイスに枠を描画する

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
}
  • draw_border() 関数では、2 つの整数変数 widthhigh が宣言されており、それぞれターミナルウィンドウの幅と高さを表します。
  • コマンド echo -e "\033[37;40m" は表示色を設定するために使用されます。ネストした for ループを使ってターミナルの各行と各列を繰り返し処理し、背景を塗りつぶします。
  • 内側のループでは、ANSI エスケープコードを使ってターミナル内の目的の座標にカーソルを移動させます。例えば、echo -e "\033["$j";"$i"H" はカーソル位置を j 行 i 列に設定します。
  • ターミナルの背景を塗りつぶした後、特定の文字を使って装飾的な枠線を描画します。使用される文字には +-| が含まれ、これらは一般的に枠線を描画するために使われます。これらの文字の位置は行と列の値によって決まり、ANSI エスケープコードを使って印刷されます。例えば、echo -e "\033[1;1H+"+ 文字をターミナルの左上隅に配置します。

要するに、draw_border() 関数はターミナルをクリアし、背景色を黒に設定し、ターミナルを空白で埋めて背景を作成します。最後に、特定の位置に文字(角には +、線には -| など)を配置することで、見栄えの良い枠線を描画します。

Shell Typing Game

タイピングインターフェイスの背景色を塗りつぶす

## 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. clear_all_area() 関数:
  • この関数は、文字の着地エリア全体をクリアするために使用されます。具体的には、文字の着地エリア内の文字を背景色(黒)で塗りつぶすことで、ターミナルウィンドウ内の文字を消去します。
  • ネストしたループを使用して、ij のすべての組み合わせを繰り返し処理します。ここで、i は行を表し、j は列を表します。
  • 内側のループ内では、ANSI エスケープコードを使用してカーソル位置を指定された行と列に設定し、echo -e "\033[44m\033["$i";"$j"H " を使用してその位置に空白文字を印刷しますが、背景色は黒(コード 44)に設定されます。
  • ループが終了した後、関数は echo -e "\033[37;40m" を使用して、テキスト色と背景色をデフォルト値(白文字、黒背景)に戻します。
  1. clear_line() 関数:
  • この関数は、文字の着地エリア内の特定の列をクリアするために使用されます。通常は、ユーザーが正しい文字を入力した後、その列の文字のパスをクリアするために使用されます。
  • この関数は 1 つの引数 $1 を受け取り、これはクリアする列番号を表します。
  • ループを使用して、文字の着地エリア内の行と列を繰り返し処理し、その列の文字のパスを背景色(黒)で塗りつぶします。
  • clear_all_area() と同様に、この関数も ANSI エスケープコードを使用してカーソル位置を指定された行と列に設定し、echo -e "\033[44m\033["$i";"$j"H " を使用してその位置に空白文字を印刷しますが、背景色は黒に設定されます。
  • 最後に、関数は echo -e "\033[37;40m" を使用して、テキスト色と背景色をデフォルト値に戻します。

ランダムな文字と数字を生成する

## 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]}
}

シェルの文字列変数は直接インデックスをサポートしていないため、すべての文字と数字をインデックス付きの配列に格納する必要があります。これが putarray 関数の目的です。以下の get_random_char 関数は、ランダムな文字と数字を生成するために使用されます。この関数は、システムの乱数環境変数 RANDOM を使用してランダムなインデックスを取得し、その後配列から対応する文字を読み取ります。

タイピングの実装

すべての準備が整ったので、ついにタイピング機能の実装を始めることができます。以下のコードを見てみましょう。

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
}

ここで、単一文字のタイピングと単語のタイピングの方法をそれぞれ説明します。これらは少し異なっています。

単一文字のタイピングでは、同時に 5 つの文字が表示され、設定された時間間隔で落下し続け、一定の高さに達すると(最初に選択したタイムアウト期間によって決まります)消えます。消える前にヒットしなかった場合(ユーザーが対応する正しい文字を入力しなかった場合)、その列に新しい文字が表示されます。そのため、まずループを使って文字シーケンスを初期化し、配列に格納します。配列のインデックスはターミナルの列番号を表します。これにより、各列の文字がタイムアウトしたか、ヒットしたか、どこに配置するかを独立して管理できるという利点があります。ただし、比較的大きな配列を作成することになりますが、取り扱いができないほど大きくはありません。シェルでは配列インデックスのサイズに基づいてメモリを割り当てないため、これは問題ありません。最初の for ループがこの処理を担当し、各メインループの最後に列が空かどうかをチェックし、空の場合はその列に新しい文字が表示されます。

次は長い if...else 文です。main 関数を呼び出す際に渡されるパラメータを覚えていますか?最も外側の部分は、単一文字のタイピングか単語のタイピングかを区別するために使われます。まず、単一文字のタイピングを見てみましょう。ここに重要な行があります。

if read -n 1 -t 0.5 tmp

read コマンドの -n パラメータは、読み取る文字の長さを指定します。ここでは 1 が指定されているため、ユーザーが 1 文字入力するとすぐに入力が終了し、Enter キーを押す必要はありません。-t パラメータは入力のタイムアウト時間を指定します。ユーザーがタイムアウト期間内に入力しないか、入力が完了しない場合、読み取りはすぐに終了します。read コマンドは、ユーザー入力を読み取る際に同じシェルに対して同期操作であるため、文字の落下の実装はこのタイムアウトに依存しています(間接的に落下の遅延を実装しています)。ここでは 0.5s が設定されています。ほとんどのユーザーは 0.5s 以内に 1 文字の入力を完了できるからです。ユーザー入力を読み取った後、for ループを使って各列に対応する文字配列を比較し、一致するものがあれば clear_line 関数を呼び出して現在の列の文字を消去し、フラグ変数 ifchar[$line]0 に設定して消去されたことを示します。

単語のタイピングの流れは基本的に文字のタイピングと同じですが、長さが不定の単語をユーザーが入力するのにかかる時間を見積もることができないため、read コマンドに入力のタイムアウト時間を設定することができません。このタイムアウトがないと、単語のタイピングの最終的な実装は文字のタイピングのように自動的に落下しない場合があり、代わりに単語を入力して Enter キーを押すと 1 行落下します。もちろん、他の方法を使って文字のタイピングと同じまたはそれ以上の効果を達成することもできます。

また、各列の新しい単語の取得の処理は少し異なります。これは、ランダムな単語を生成するのではなく、ファイルから読み取っているためです。前に作成した、単語ファイルを指すファイルディスクリプタ 4 を覚えていますか?ここでそのファイルディスクリプタを使っています。

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

ここでも read コマンドを使っており、-u パラメータを使うことで、どのファイルディスクリプタからファイルを 1 行ずつ 変数に読み取るかを指定できます。その後の空チェック文は、ファイルの末尾に達したときにファイルを指すファイルディスクリプタを再作成し、read がファイルの先頭から再度読み取れるようにするためのものです。

少し複雑に感じましたか?心配しないでください。まだ終わっていません。次に、main 関数の最後から 2 番目の行に注意してください。

trap " doexit " 2 ## Capture special signals

trap コマンドは、シェル内の特殊なシグナル(Ctrl+CCtrl+DCtrl+ZESC など)をキャプチャするために使われます。元々はこれらの特殊なシグナルはシェル自身が処理します。ゲームを終了する際によりグレースフルに終了させたいため、2 番目の特殊なシグナルである Ctrl+C をインターセプトして、カスタマイズされた処理を実装することができます。例えば、ここでは 2 番目のシグナルをキャプチャした後、以下の doexit 関数を呼び出してゲームをグレースフルに終了させます。

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

メインコードの流れ

各関数モジュールのコードがすでに用意されていますが、プログラムを実行するには、これらの関数を呼び出すメインコードフローが必要です。

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

このコードは、文字落下ゲームのエントリーポイントを作成します。まず、ウェルカム画面とゲーム開始のプロンプトを表示し、ゲームを開始するかどうかのユーザー入力を待ちます。ユーザーがゲームを開始することを選択した場合、ゲームの難易度モードを選択するように促されます。ユーザーがゲームを終了することを選択した場合、または無効な入力を行った場合、ゲームは開始されません。

実行とテスト

次に、シェルタイピングゲームを実行できます。

cd ~/project
bash shell_typing_game.sh

Shell Typing Game

まとめ

あなたは、シェルで簡単なタイピングゲームを作成するプロジェクトを完成させました。これらの手順に従って、独自のタイピングゲームプロジェクトを作成し、異なるゲームモードと難易度を選択することができます。このプロジェクトは、シェルスクリプトとターミナルベースのゲーム開発の実践的な経験を提供します。コースで使用されている一部のコマンド、例えば echo の様々な出力、exec による入出力のリダイレクト、特殊シグナルをキャプチャする trap などに不慣れな場合は、もっと練習して使い方を習得することができます。

✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習