shell写个贪吃蛇游戏

  • 2020 年 3 月 11 日
  • 筆記

之前看到各位大神们各种语言完成贪吃蛇游戏,不禁想试试shell实现

github

https://github.com/pedroqin/shell_script

实现功能

可通过修改参数实现

  1. 难度调整(调速)
  2. 是否随分数增加加大难度(调速)
  3. 暂停及恢复
  4. 调整游戏界面大小
  5. 穿墙模式
  6. 贪吃蛇外观修改
  7. 游戏重开

实现思路

  1. 为防止整体刷新时闪屏情况发生,采用局部刷新思路,即:只刷新贪吃蛇涉及元素和鸡蛋(贪吃蛇的食物)
  2. 贪吃蛇身体的所有元素的 x,y坐标组成一个数组,贪吃蛇头部为数组第一个元素,然后依次描绘每个坐标即可完成贪吃蛇的打印
  3. 贪吃蛇头部的坐标变化根据目前的方向决定,如,向左时 x减去1,向下时 y加1
  4. 在贪吃蛇前进时,只有数组第一个元素更新为新的坐标,第二个元素获取第一个元素的坐标,第三个元素获取第二个元素的坐标…以此类推,计算前进后每个元素坐标,依次打印每个元素,并在打印完成后,清空前进之前最后一个元素,以达到贪吃蛇前进的效果
  5. 当贪吃蛇吃到鸡蛋时(头部坐标等于鸡蛋坐标),重新绘制上一步最后一个元素,以达到贪吃蛇长度增加的效果
  6. 由于只有头部为新坐标,所以只需检测头部坐标位置即可判定贪吃蛇是否撞墙或撞到自己

方法重点

  1. shell 数组操作,如赋值,获取数组长度
  2. trap 捕获 SIGTERM信号
  3. kill---$PID
  4. tput 进行画面元素的描绘与指针的隐藏
  5. 脚本内部函数后台运行时信号传递(通过flag文件实现)

代码

#!/bin/bash  ###############################################  # Filename    :   snake.sh  # Author      :   PedroQin  # Email       :   [email protected]  # Date        :   2020-02-28 10:47:19  # Description :  # Version     :   1.0.0  ###############################################    # user-defined  begin_direction=left  length=50  height=15  if_go_through_the_wall=1  # snake speed  flash_time=0.40  min_flash_time=0.15  # if the snake speed up per 5 score  more_difficult=1  # when more_difficult=1, current speed = flash_time - score/5 * rising_rate > min_flash_time  rising_rate=0.03  # define the snake  snake_head="+"  snake="@"  egg="@"    ##########################  # trap the kill singal  trap 'clean_env' SIGTERM  # some flag  flag_file=/tmp/snake_game.log  finish_flag=/tmp/snake_game_fail.log    max_blocks=`echo "$length*$height"|bc`  border="#"  space=" "  score=0    # prepare  begin_y=`echo "$height/2"|bc`  begin_x=`echo "$length/2"|bc`  help_msg="  Press 'q' to quit  Press 'c' to retry  Press 'p' to pause and any other key to resume  "  function display_info()  {      display_str="$1  $help_msg"        line_num=`echo "$display_str"|wc -l`      for i in `seq 1 $line_num`;do          str_line=`echo "$display_str"|sed -n "$i"p`          str_len=`expr length "$str_line"`          str_x=`echo "$begin_x - $str_len / 2 "|bc`          str_y=`echo "$begin_y - $line_num / 2 + $i "|bc`          tput cup $str_y $str_x          echo -n "$str_line"      done  }    function clean_env()  {      rm $flag_file 2>/dev/null      # unhide cursor      tput cnorm      # reposit cursor      tput cup $[height+1] 0      echo      echo "finished."      exit  }    function lay_egg()  {      has_egg=0      while [ "$has_egg" -eq 0 ]; do          egg_x=`echo "$RANDOM%$length + 1" |bc`          egg_y=`echo "$RANDOM%$height + 1" |bc`          has_egg=1          # the egg can't in the snake          for i in ${snakes[@]};do              [ "$i" == "$egg_x,$egg_y" ] && has_egg=0          done      done      tput cup $egg_y $egg_x      echo -n "$egg"    }    function finish()  {      touch $finish_flag      display_info "$1"  }    function game()  {      snakes=( "$[begin_x-1],$begin_y" "$begin_x,$begin_y" "$[begin_x+1],$begin_y")      lay_egg      previous_direction="$begin_direction"      while ((1)) ;do          # calculate per loop          pre_snake=${snakes[0]}          x0=${snakes[0]%%,*}          y0=${snakes[0]##*,}          direction=`cat $flag_file 2>/dev/null`          direction="${direction:-$previous_direction}"          [ -e $flag_file ] && rm $flag_file          case ${direction:-$begin_direction} in              up)              y0=$[ ${snakes[0]##*,} - 1 ]              if [ $y0 -le 0 ] ;then                  if [ $if_go_through_the_wall -eq 0 ] ;then                      finish "Your Score: $score"  && return                  else                      let y0=$y0+$height                  fi              fi              ;;                left)              x0=$[ ${snakes[0]%%,*} - 1 ]              if [ $x0 -le 0 ] ;then                  if [ $if_go_through_the_wall -eq 0 ] ;then                      finish "Your Score: $score"  && return                  else                      let x0=$x0+$length                  fi              fi              ;;                down)              y0=$[ ${snakes[0]##*,} + 1 ]              if [ $y0 -gt $height ] ;then                  if [ $if_go_through_the_wall -eq 0 ] ;then                      finish "Your Score: $score"  && return                  else                      let y0=$y0-$height                  fi              fi              ;;                right)              x0=$[ ${snakes[0]%%,*} + 1 ]              if [ $x0 -gt $length ] ;then                  if [ $if_go_through_the_wall -eq 0 ] ;then                      finish "Your Score: $score"  && return                  else                      let x0=$x0-$length                  fi              fi              ;;                pause)              while [ ! -f "$flag_file" ];do                  sleep $flash_time              done              continue              ;;                *)              continue              ;;            esac          previous_direction="$direction"          # clear the last one and draw the first one,like snake crawl          tput cup ${snakes[$[${#snakes[@]}-1]]##*,} ${snakes[$[${#snakes[@]}-1]]%%,*}          echo -n "$space"          # calculate the snakes          snakes[0]="$x0,$y0"          for i in `seq 1 $[ ${#snakes[@]} - 1]`;do              # snake eat itself              [ "${snakes[0]}" == "${snakes[$i]}" ] && finish "Your Score: $score" && return              transition_value=${snakes[$i]}              snakes[$i]=$pre_snake              pre_snake=$transition_value          done          # eat egg and lay egg          if [ "${snakes[0]}" == "$egg_x,$egg_y" ] ;then              snakes[${#snakes[@]}]=$pre_snake              [ "${#snakes[@]}" -eq "$max_blocks" ] && finish "Congratulations, You WIN !" && return              lay_egg              let score++              # when more_difficult=1, current speed = flash_time - score/5* rising_rate > min_flash_time              if [ $more_difficult -ne 0 ] ;then                  speed_tmp=`printf "%.2f" $(echo "$flash_time - $score / 5 * $rising_rate"|bc)`                  [[ $speed_tmp > $min_flash_time ]] &&  cur_speed=$speed_tmp || cur_speed=$min_flash_time              fi          fi          # display the snake          tput cup ${snakes[0]##*,} ${snakes[0]%%,*}          echo -n "$snake_head"          for i in `seq 1 $[${#snakes[@]}-1]`;do              tput cup ${snakes[$i]##*,} ${snakes[$i]%%,*}              echo -n "$snake"          done          sleep $cur_speed      done  }    function input()  {      input_direction=$begin_direction      while (( 1 ));do          read -sn 1 dir          [ -f $flag_file -a ! -f $finish_flag ] && continue          case $dir in              W|w)              [ "$input_direction" == up -o "$input_direction" == down ] && continue              input_direction=up              echo "$input_direction" > $flag_file              ;;                A|a)              [ "$input_direction" == left -o "$input_direction" == right ] && continue              input_direction=left              echo "$input_direction" > $flag_file              ;;                S|s)              [ "$input_direction" == up -o "$input_direction" == down ] && continue              input_direction=down              echo "$input_direction" > $flag_file              ;;                D|d)              [ "$input_direction" == left -o "$input_direction" == right ] && continue              input_direction=right              echo "$input_direction" > $flag_file              ;;                # pause              P|p)              echo "pause" > $flag_file              ;;                # exit              Q|q)              kill -- -$$              ;;                # retry              C|c)              [ ! -f $finish_flag ] && continue              input_direction=$begin_direction              begin &              ;;                # press any key to exit the pause              *)              echo "$dir" > $flag_file              ;;            esac      done  }    function begin()  {      [ -e $flag_file ] && rm $flag_file      [ -e $finish_flag ] && rm $finish_flag      cur_speed=$flash_time      top_border=$border      second_border=$border      for ((i=0;i < length;i++));do          top_border="$top_border$border"          second_border="$second_border$space"      done      top_border="$top_border$border"      second_border="$second_border$border"      # hide cursor      tput civis      clear      # draw border      echo $top_border      for ((i=0;i < height;i++));do          echo "$second_border"      done      echo $top_border      game  }    begin&  input

后续思考

鸡蛋的坐标是通过获取随机值实现,并增加判断以防止鸡蛋坐标在蛇坐标数组内,在实际应用时,当游戏界面足够大,蛇的长度已经足够长时,所剩的空白很少甚至1个时,可能会出现连续取多个鸡蛋坐标都在蛇的坐标数组内,无法及时产生下一个鸡蛋导致卡顿。

解决思路:可更改判定赢得胜利的标准,原为 蛇长度==游戏界面内总元素数,改为 蛇长度==游戏界面内总元素数*80%。即:在出现上诉情况前结束游戏,没有出现issue就是没有issue,掩耳盗铃。。。