juubacca

ShellShock bash script game

Sep 29th, 2014
235
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 40.10 KB | None | 0 0
  1. #!/usr/bin/env bash
  2.  
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program.  If not, see <http://www.gnu.org/licenses/>.
  15.  
  16. # Copyright 2012 - Øyvind 'bolt' Hvidsten   <[email protected]>
  17. # Source: http://www.dhampir.no/stuff/bash/shellshock
  18.  
  19. # Description:
  20. #
  21. # ShellShock is a top-down space shooter written for Bash 3 / Bash 4
  22. # Tested on Linux (Debian, RedHat and CentOS)
  23. #
  24. # Please note: A game in Bash is very demanding on resources.
  25. #              This script requires a modern computer to run at a decent speed.
  26. #              This script uses a tab width of 4, which is automatically applied
  27. #              if you're using vim with modelines enabled (see bottom line).
  28. #
  29. # For updates, please see <http://www.dhampir.no/stuff/bash/shellshock.bash>.
  30. #
  31. # Comments welcome, improvements/patches twice as welcome.
  32. #
  33.  
  34. # Releases / Changelog:
  35. #
  36. # v1.00, 2012.03.18 - Initial v1.0 release
  37. #                   * All intended functionality implemented
  38. #
  39. # v1.01, 2012.03.19 - Bash 3
  40. #                   * Tweaks to run on Bash 3
  41. #
  42. # v1.05, 2012.03.25 - Several changes based on feedback
  43. #                   * Added score display
  44. #                   * Added increasing difficulty
  45. #
  46. # v1.06, 2012.03.26 - More feedback
  47. #                   * Ship aft collision improved
  48. #                   * Added pause option
  49. #                   * Added simple high score storage
  50. #                   * Runs on Bash 3 again
  51. #
  52. # v1.07, 2012.03.26 - Steve
  53. #                   * Added rudimentary support for Mac OS X
  54. #
  55. # v1.08, 2012.03.26 - Cleanup
  56. #                   * Improved some comments and made clearer error messages
  57. #
  58. # v1.09, 2012.03.27 - Ubuntu
  59. #                   * Various fixes for Ubuntu
  60. #
  61. # v1.10, 2012.03.29 - Pause
  62. #                   * Improved the pause function to avoid problems with timers
  63. #
  64.  
  65. # get uname
  66. uname=$(uname -s)
  67.  
  68. # queue temp files for deletion on script exit
  69. function rm_queue { _rm_queue[${#_rm_queue[*]}]="$1"; }
  70. function rm_process { local file; for file in "${_rm_queue[@]}"; do rm "$file"; done; }
  71.  
  72. # check if tput actually outputs something useful for this terminal
  73. if [[ -z "$(tput sgr0)" ]] || [[ -z "$(tput bold)" ]]; then
  74.     echo 'Error: tput is not working as expected with your current terminal settings!' >&2
  75.     echo 'Try setting your $TERM to something more standard?' >&2
  76.     exit 1
  77. fi
  78.  
  79. # read a single character
  80. function readc { IFS= read -r -n1 -s "$@" c; }
  81.  
  82. # variable variables :)
  83. gamepid=""
  84. saved_term=false
  85.  
  86. # list of files to be removed on cleanup
  87. _rm_queue=()
  88.  
  89. # cleanup
  90. function cleanup
  91. {
  92.     [[ -z "$gamepid" ]] || { kill -TERM "$gamepid"; wait "$gamepid"; } 2>/dev/null
  93.     rm_process                  # remove temp files
  94.     tput sgr0                   # reset color
  95.     clear                       # clear the screen
  96.     tput cnorm                  # show the cursor
  97.     stty echo                   # show input
  98.     ! $saved_term || tput rmcup # restore the terminal view
  99.     case "$uname" in
  100.         Darwin)
  101.             reset
  102.         ;;
  103.     esac
  104. }
  105. trap cleanup EXIT
  106.  
  107. # make comm file
  108. # TODO: better solution for inter-thread communication. must work on bash3
  109. case "$uname" in
  110.     Darwin)
  111.         comm=$(mktemp /tmp/shellshock.XXXXXX)
  112.     ;;
  113.     *)
  114.         comm=$(mktemp)
  115.     ;;
  116. esac
  117. if [[ -n "$comm" ]]; then
  118.     rm_queue "$comm"
  119. else
  120.     echo "Error: Communications file creation failed!" >&2
  121.     exit 1
  122. fi
  123.  
  124. # init
  125. tput smcup && saved_term=true   # save the current terminal view
  126. tput civis                      # hide the cursor
  127.  
  128. # game subshell
  129. (
  130.     reset 2>/dev/null
  131.     input=false
  132.     trap 'input=true' USR1
  133.     trap 'exit 0' TERM INT HUP
  134.  
  135.     # test parent shell
  136.     function testparent
  137.     {
  138.         kill -0 $$ 2>/dev/null || exit 1
  139.     }
  140.  
  141.     # how to print stuff
  142.     function xyecho
  143.     {
  144.         local x=$1 y=$2
  145.         shift 2
  146.  
  147.         # running this in a loop with 2>/dev/null to avoid "interrupted system call" messages
  148.         while { ! builtin echo -n "${posarray[$((y*cols+x))]}$@"; } 2>/dev/null; do testparent; done
  149.     }
  150.     function safeecho
  151.     {
  152.         # running this in a loop with 2>/dev/null to avoid "interrupted system call" messages
  153.         while { ! builtin echo -n "${posarray[$((y*cols+x))]}$@"; } 2>/dev/null; do testparent; done
  154.     }
  155.     function xyprintf
  156.     {
  157.         local x=$1 y=$2
  158.         shift 2
  159.  
  160.         # running this in a loop with 2>/dev/null to avoid "interrupted system call" messages
  161.         while { ! builtin printf "${posarray[$((y*cols+x))]}$@"; } 2>/dev/null; do testparent; done
  162.     }
  163.     function safeprintf
  164.     {
  165.         # running this in a loop with 2>/dev/null to avoid "interrupted system call" messages
  166.         while { ! builtin printf "${posarray[$((y*cols+x))]}$@"; } 2>/dev/null; do testparent; done
  167.     }
  168.  
  169.     # called when the player fires his/her weapons
  170.     function fire # no parameters
  171.     {
  172.         (( ff_ammo_current > 0 )) || return
  173.  
  174.         if ${ff_alive[ff_next]}; then
  175.             xyprintf $((origo_x+ff_x[ff_next])) $((origo_y+ff_y[ff_next])) " "
  176.         fi
  177.  
  178.         if ((++ff_total%2 == 0)); then
  179.             ff_x[ff_next]=$((dynel_cx[0]-3))
  180.             ff_y[ff_next]=$((dynel_cy[0]-8))
  181.         else
  182.             ff_x[ff_next]=$((dynel_cx[0]+3))
  183.             ff_y[ff_next]=$((dynel_cy[0]-8))
  184.         fi
  185.  
  186.         if ! outofbounds "$((origo_x+ff_x[ff_next]))" "$((origo_y+ff_y[ff_next]))"; then
  187.             ff_ydiv[ff_next]=1
  188.             ff_symbol[ff_next]="|"
  189.             ff_damage[ff_next]=1
  190.             ff_alive[ff_next]=true
  191.             (( ++ff_count ))
  192.             ff_new[ff_next]=true
  193.             (( --ff_ammo_current ))
  194.             ff_next=$(( ++ff_next < ff_count_max ? ff_next : 0 ))
  195.         fi
  196.  
  197.         if ((ff_total%3 == 0)); then
  198.             if ${ff_alive[ff_next]}; then
  199.                 xyprintf $((origo_x+ff_x[ff_next])) $((origo_y+ff_y[ff_next])) " "
  200.             fi
  201.  
  202.             if ((ff_total%2 == 0)); then
  203.                 ff_x[ff_next]=$((dynel_cx[0]+5))
  204.                 ff_y[ff_next]=$((dynel_cy[0]-7))
  205.             else
  206.                 ff_x[ff_next]=$((dynel_cx[0]-5))
  207.                 ff_y[ff_next]=$((dynel_cy[0]-7))
  208.             fi
  209.  
  210.             if ! outofbounds "$((origo_x+ff_x[ff_next]))" "$((origo_y+ff_y[ff_next]))"; then
  211.                 ff_ydiv[ff_next]=2
  212.                 ff_symbol[ff_next]="¤"
  213.                 ff_damage[ff_next]=4
  214.                 ff_alive[ff_next]=true
  215.                 (( ++ff_count ))
  216.                 ff_new[ff_next]=true
  217.                 ff_next=$(( ++ff_next < ff_count_max ? ff_next : 0 ))
  218.             fi
  219.         fi
  220.     }
  221.  
  222. #######################################################
  223. ## BEGIN ASCII ART SECTION - NO TABS, NO INDENTATION ##
  224. #######################################################
  225. # title
  226. ascii_title_w=78
  227. ascii_title_h=6
  228. ascii_title=$(
  229. cat - <<"EOF"
  230.   _________.__           .__  .__    _________.__                   __   ._.
  231.  /   _____/|  |__   ____ |  | |  |  /   _____/|  |__   ____   ____ |  | _| |
  232.  \_____  \ |  |  \_/ __ \|  | |  |  \_____  \ |  |  \ /  _ \_/ ___\|  |/ / |
  233.  /        \|   Y  \  ___/|  |_|  |__/        \|   Y  (  <_> )  \___|    < \|
  234. /_______  /|___|  /\___  >____/____/_______  /|___|  /\____/ \___  >__|_ \__
  235.         \/      \/     \/                  \/      \/            \/     \/\/
  236. EOF
  237. )
  238. # keybindings
  239. ascii_keybindings_w=74 #21 - fake width to print off-center
  240. ascii_keybindings_h=5
  241. ascii_keybindings=$(
  242. cat - <<"EOF"
  243. Keybindings:
  244.     arrow keys - move
  245.     SPACE      - fire
  246.     z          - toggle autofire
  247.     p          - pause
  248.     q          - quit
  249. EOF
  250. )
  251. # dead meat - written in quotes because all the parenthesis confuse vim's syntax highlighting
  252. #           - this also means the first line has to be offset, so the ascii looks ugly in code
  253. ascii_dead_w=36
  254. ascii_dead_h=8
  255. ascii_dead=\
  256. ' ______   _______  _______  ______
  257. (  __  \ (  ____ \(  ___  )(  __  \
  258. | (  \  )| (    \/| (   ) || (  \  )
  259. | |   ) || (__    | (___) || |   ) |
  260. | |   | ||  __)   |  ___  || |   | |
  261. | |   ) || (      | (   ) || |   ) |
  262. | (__/  )| (____/\| )   ( || (__/  )
  263. (______/ (_______/|/     \|(______/'
  264. # press q
  265. ascii_press_q_w=42
  266. ascii_press_q_h=1
  267. ascii_press_q=$(
  268. cat - <<"EOF"
  269. -= Press q to quit to the title screen! =-
  270. EOF
  271. )
  272. # press fire
  273. ascii_press_fire_w=27
  274. ascii_press_fire_h=1
  275. ascii_press_fire=$(
  276. cat - <<"EOF"
  277. -= Press SPACE to start! =-
  278. EOF
  279. )
  280. # pause
  281. ascii_pause_w=11
  282. ascii_pause_h=1
  283. ascii_pause=$(
  284. cat - <<"EOF"
  285. -= PAUSE =-
  286. EOF
  287. )
  288. # pause frame
  289. ascii_pauseframe_w=25
  290. ascii_pauseframe_h=5
  291. ascii_pauseframe=$(
  292. cat - <<"EOF"
  293. *************************
  294. *                       *
  295. *                       *
  296. *                       *
  297. *************************
  298. EOF
  299. )
  300.  
  301. # player's spaceship
  302. ascii_playerdynel_w=21
  303. ascii_playerdynel_h=9
  304. ascii_playership=$(
  305. cat - <<"EOF"
  306. 08]            
  307. 06]         /^\  
  308. 03]       |/.!.\|    
  309. 01]    _|/_/]=[\_\|_  
  310. 00]  _/     | |     \_  
  311. 00] |_____. | | ._____|
  312. 00]     \_________/    
  313. 04]      |@|   |@|  
  314. 05]                
  315. EOF
  316. )
  317. # rock 1
  318. ascii_rock0_w=9
  319. ascii_rock0_h=7
  320. ascii_rock0=$(
  321. cat - <<"EOF"
  322. 02]      
  323. 01]   __  
  324. 01]  /  \_  
  325. 00] |     \
  326. 01]  \   _|
  327. 01]   \_/  
  328. 02]      
  329. EOF
  330. )
  331. ascii_rock1_w=14
  332. ascii_rock1_h=8
  333. ascii_rock1=$(
  334. cat - <<"EOF"
  335. 06]          
  336. 01]       __  
  337. 00]  ____/  \_  
  338. 00] /         \  
  339. 00] \          |
  340. 00]  \     ___/  
  341. 01]   \___/    
  342. 02]        
  343. EOF
  344. )
  345. ascii_rock2_w=8
  346. ascii_rock2_h=8
  347. ascii_rock2=$(
  348. cat - <<"EOF"
  349. 02]      
  350. 01]   __  
  351. 00]  /  \
  352. 00] |   |  
  353. 00] |    \
  354. 00] |   _/
  355. 00] \__/  
  356. 00]      
  357. EOF
  358. )
  359. ascii_rock3_w=9
  360. ascii_rock3_h=7
  361. ascii_rock3=$(
  362. cat - <<"EOF"
  363. 01]        
  364. 00]  _____  
  365. 00] /     \
  366. 00] |     |
  367. 00] \   _/
  368. 00]  \_/  
  369. 01]      
  370. EOF
  371. )
  372. ascii_rock4_w=13
  373. ascii_rock4_h=8
  374. ascii_rock4=$(
  375. cat - <<"EOF"
  376. 01]      
  377. 00]  ___      
  378. 00] /   \_____  
  379. 00] |        _|
  380. 00] \       /  
  381. 00]  \_    /  
  382. 01]    \__/  
  383. 03]        
  384. EOF
  385. )
  386. #######################################################
  387. ## END ASCII ART SECTION                             ##
  388. #######################################################
  389.  
  390.     # pretty colours
  391.     color_background=$(tput setab 0)
  392.     color_reset="$(tput sgr0)${color_background}"
  393.     color_black="${color_reset}$(tput setaf 0)"
  394.     color_red="${color_reset}$(tput setaf 1)"
  395.     color_green="${color_reset}$(tput setaf 2)"
  396.     color_orange="${color_reset}$(tput setaf 3)"
  397.     color_blue="${color_reset}$(tput setaf 4)"
  398.     color_magenta="${color_reset}$(tput setaf 5)"
  399.     color_cyan="${color_reset}$(tput setaf 6)"
  400.     color_light_gray="${color_reset}$(tput setaf 7)"
  401.     color_dark_gray="${color_reset}$(tput bold)$(tput setaf 0)"
  402.     color_light_red="${color_reset}$(tput bold)$(tput setaf 1)"
  403.     color_light_green="${color_reset}$(tput bold)$(tput setaf 2)"
  404.     color_yellow="${color_reset}$(tput bold)$(tput setaf 3)"
  405.     color_light_blue="${color_reset}$(tput bold)$(tput setaf 4)"
  406.     color_light_magenta="${color_reset}$(tput bold)$(tput setaf 5)"
  407.     color_light_cyan="${color_reset}$(tput bold)$(tput setaf 6)"
  408.     color_white="${color_reset}$(tput bold)$(tput setaf 7)"
  409.  
  410.     # specific colors for stuff
  411.     color_debug=$color_orange                   # debug prints (FPS, seconds, rocks count, etc)
  412.     color_ship=$color_white                     # player's ship
  413.     color_fire=$color_light_magenta             # player's missiles
  414.     color_engine_1=$color_light_red             # player's engines (blinking)
  415.     color_engine_2=$color_red                   # player's engines (blinking)
  416.     color_border=$color_red                     # border
  417.     color_score_result=$color_yellow            # score result (death screen)
  418.     color_origo=$color_green                    # origo         (bottom center)
  419.     color_rock_healthy=$color_light_cyan        # healthy rocks
  420.     color_rock_damaged=$color_cyan              # damaged rocks
  421.     color_death_1=$color_light_red              # death animation stage 1
  422.     color_death_2=$color_red                    # death animation stage 2
  423.     color_death_3=$color_dark_gray              # death animation stage 3
  424.     color_death_4=$color_black                  # death animation stage 4 (erase)
  425.     color_title=$color_yellow                   # ShellShock!
  426.     color_pause_1=$color_light_red              # pause text (blinking)
  427.     color_pause_2=$color_red                    # pause text (blinking)
  428.     color_pauseframe=$color_red                 # frame around pause text
  429.     color_youaredead=$color_light_red           # YOU ARE DEAD
  430.     color_keybindings=$color_green              # keybindings...
  431.     color_pressfire_1=$color_blue               # press space to start (blinking)
  432.     color_pressfire_2=$color_light_blue         # press space to start (blinking)
  433.     color_pressq_1=$color_dark_gray             # press q for title screen (blinking)
  434.     color_pressq_2=$color_light_gray            # press q for title screen (blinking)
  435.  
  436.     # score display (top right) (drawn on white background)
  437.     color_score="${color_black}$(tput setab 7)"
  438.     # ammo display (drawn with spaces on background color in the top left)
  439.     color_ammo_1="${color_black}$(tput setab 5)"
  440.     color_ammo_2="${color_black}$(tput setab 7)"
  441.  
  442.     # home
  443.     home=$(tput home)
  444.  
  445.     # has the size changed?
  446.     function sizechanged
  447.     {
  448.         if (( cols != $(tput cols) )) || (( rows != $(tput lines) )); then
  449.             cols=$(tput cols)
  450.             rows=$(tput lines)
  451.             origo_x=$((cols/2))
  452.             origo_y=$((rows-1))
  453.  
  454.             return 0
  455.         fi
  456.         return 1
  457.     }
  458.  
  459.     # clear
  460.     redraw=false
  461.     function wipe
  462.     {
  463.         # "tput clear" doesn't fill with background color in bash3, screen, etc.
  464.         # must write a shitload of spaces instead
  465.         safeprintf "${color_reset}${home}%$((cols*rows))s" ""
  466.  
  467.         # everything needs redrawing after this
  468.         redraw=true
  469.     }
  470.  
  471.     # get the current time in microseconds
  472.     case "$uname" in
  473.         Darwin)
  474.             # this is slow. very slow.
  475.             function microtime {
  476.                 local time=$(python <<<"import time; print \"%.6f\" % time.time();")
  477.                 echo -n ${time/./}
  478.             }
  479.         ;;
  480.         *)
  481.             function microtime {
  482.                 local time=$(date +%s%N)
  483.                 echo -n ${time:0:((${#time}-3))}
  484.             }
  485.         ;;
  486.     esac
  487.  
  488.     # build tput position array
  489.     # moving using this 10x faster than running tput
  490.     _pos_cols=0
  491.     _pos_rows=0
  492.     function buildposarray
  493.     {
  494.         if ((cols == _pos_cols)) && ((rows == _pos_rows)); then
  495.             return 1
  496.         fi
  497.  
  498.         wipe
  499.         _pos_cols=$cols
  500.         _pos_rows=$rows
  501.         posarray=()
  502.  
  503.         local q=false
  504.         local e=$(echo -e "\e")
  505.         if [[ "$(tput cup 0 0)" = "${e}[1;1H" ]]; then
  506.             # standard terminal movement commands - quick generation
  507.             q=true
  508.         fi
  509.  
  510.         local string="Building position array for ${cols}x${rows}... "
  511.         local pos=$(tput cup 0 ${#string})
  512.  
  513.         local x y
  514.         safeecho "${color_debug}${home}${string}"
  515.         for ((x=0; x < cols; x++)); do
  516.             if sizechanged; then
  517.                 buildposarray
  518.                 return $?
  519.             fi
  520.             echo -n "${pos}$((x*100/cols))%"
  521.             for ((y=0; y < rows; y++)); do
  522.                 if $q; then
  523.                     posarray[$((y*cols+x))]="${e}[$((y+1));$((x+1))H"
  524.                 else
  525.                     posarray[$((y*cols+x))]=$(tput cup "$y" "$x")
  526.                 fi
  527.             done
  528.         done
  529.  
  530.         return 0
  531.     }
  532.    
  533.     # print something at a spot
  534.     function catc { cat "$@"; } # draw centered (x coordinate specifies width, not x pos)
  535.     function catd { cat "$@"; } # draw a dynel (supports black outline)
  536.     function cat
  537.     {
  538.         local x=$1 y=$2 i=0 cy dynel=false first=true offset=0
  539.  
  540.         case "${FUNCNAME[1]}" in
  541.             catd) dynel=true ;;
  542.             catc)
  543.                 x=$(( (cols - x) / 2)) # center
  544.                 (( x > 0 )) || x=1
  545.             ;;
  546.         esac
  547.  
  548.         while IFS= read -r line; do
  549.             cy=$(( y + i++ ))
  550.             if $dynel; then
  551.                 offset="$(( 10#${line:0:2} ))"
  552.                 line=${line:3+offset}
  553.                 cx=$(( x + offset ))
  554.             else
  555.                 cx=$x
  556.             fi
  557.             (( cx < cols-1 )) || continue                   # don't write on or outside the right border
  558.             (( cy > 0 )) || continue                        # don't write on or above the top border
  559.             (( cy < rows-1 )) || break                      # don't write on or below the bottom border
  560.             (( cx >= 1 )) || { line=${line:1-cx}; cx=1; }   # cut to fit inside left border
  561.             line=${line:0:cols-1-cx}                        # cut to fit inside right border
  562.  
  563.             xyecho $cx $cy "$line"
  564.         done
  565.     }
  566.  
  567.     # border drawing
  568.     function border
  569.     {
  570.         safeecho $color_border
  571.  
  572.         # no printf -v on bash3 :(
  573.         local line
  574.         while { ! line=$(builtin printf "%${cols}s" ""); } 2>/dev/null; do :; done
  575.         line=${line// /#}
  576.  
  577.         xyecho 0 0 "$line"
  578.  
  579.         local y
  580.         for (( y=1; y<rows-1; y++ )); do
  581.             xyecho 0 $y "#"
  582.             xyecho $((cols-1)) $y "#"
  583.         done
  584.  
  585.         xyecho 0 $((rows-1)) "$line"
  586.     }
  587.  
  588.     # is something outside the screen?
  589.     function outofbounds # $1 - x coordinate, $2 - y coordinate
  590.     {
  591.         local x=$1 y=$2
  592.         if
  593.             (( x < 1 )) || (( x >= cols-1 )) ||
  594.             (( y < 1 )) || (( y >= rows-1 ))
  595.         then
  596.             return 0
  597.         fi
  598.         return 1
  599.     }
  600.  
  601.     # pushes a dynel until it's within the right and left borders
  602.     function restrict_xaxis # $1 - dynel_* index
  603.     {
  604.         local i=$1
  605.         dynel_x[i]=${dynel_cx[i]}
  606.         while ! canmoveright $i;    do (( dynel_cx[i]-- )); done;
  607.         while ! canmoveleft $i;     do (( dynel_cx[i]++ )); done;
  608.         (( dynel_cx[i] < dynel_x[i] )) && (( dynel_cx[i]++ ))
  609.         (( dynel_cx[i] > dynel_x[i] )) && (( dynel_cx[i]-- ))
  610.         dynel_x[i]=${dynel_cx[i]}
  611.     }
  612.  
  613.     # pushes a dynel until it's within the top and bottom borders
  614.     function restrict_yaxis # $1 - dynel_* index
  615.     {
  616.         local i=$1
  617.         dynel_y[i]=${dynel_cy[i]}
  618.         while ! canmoveup $i;       do (( dynel_cy[i]++ )); done;
  619.         while ! canmovedown $i;     do (( dynel_cy[i]-- )); done;
  620.         (( dynel_cy[i] < dynel_y[i] )) && (( dynel_cy[i]++ ))
  621.         (( dynel_cy[i] > dynel_y[i] )) && (( dynel_cy[i]-- ))
  622.         dynel_y[i]=${dynel_cy[i]}
  623.     }
  624.  
  625.     # collides dynels based on simple squares (width, height)
  626.     function squarecollide # $1 - dynel_* index
  627.     {
  628.         local i=$1
  629.         for j in "${!dynel_alive[@]}"; do
  630.             ${dynel_alive[j]} || continue   # don't check dead dynels
  631.             (( j != i )) || continue        # don't check yourself
  632.             (( j >= rock_pos )) || continue # don't check the playership
  633.             local distance_x=$((dynel_cx[i] > dynel_cx[j] ? dynel_cx[i]-dynel_cx[j] : dynel_cx[j]-dynel_cx[i]))
  634.             local distance_y=$((dynel_cy[j] - dynel_cy[i]))
  635.             if
  636.                 (( distance_x < (dynel_w[i]+dynel_w[j])/2 )) &&
  637.                 {
  638.                     if (( distance_y < 0 )); then
  639.                         # j (compare dynel) is above i
  640.                         (( -distance_y < dynel_h[j] ))
  641.                     else
  642.                         # j (compare dynel) is below i
  643.                         (( distance_y < dynel_h[j] ))
  644.                     fi
  645.                 }
  646.             then
  647.                 # collision!
  648.                 return 0
  649.             fi
  650.         done
  651.  
  652.         # no collision
  653.         return 1
  654.     }
  655.  
  656.     # collides the player's ship
  657.     # basically the same as square collision, but with some tweaks to make it feel better
  658.     function shipcollide # no parameters
  659.     {
  660.         local i=0
  661.         for j in "${!dynel_alive[@]}"; do
  662.             ${dynel_alive[j]} || continue   # don't check dead dynels
  663.             (( j != i )) || continue        # don't check yourself
  664.             local distance_x=$((dynel_cx[i] > dynel_cx[j] ? dynel_cx[i]-dynel_cx[j] : dynel_cx[j]-dynel_cx[i]))
  665.             local distance_y=$((dynel_cy[j] - dynel_cy[i]))
  666.             if
  667.                 (( distance_x + 2 < (dynel_w[i]+dynel_w[j])/2 )) && # make the ship a little narrower
  668.                 {
  669.                     if (( distance_y < 0 )); then
  670.                         # j (compare dynel) is above i
  671.                         (( -distance_y < dynel_h[j] )) &&
  672.                         (( (distance_y + dynel_h[i]) > distance_x - 4 )) # make a somewhat cone-shaped ship
  673.                     else
  674.                         # j (compare dynel) is below i
  675.                         (( distance_y + 2 < dynel_h[j] )) && # make the ship a little shorter
  676.                         {
  677.                             ((dynel_h[j] - distance_y - 2 > 2)) ||              # 2 lines into ship from bottom
  678.                             ((4 + (dynel_h[j] - distance_y - 2) > distance_x))  # engine hit
  679.                         }
  680.                     fi
  681.                 }
  682.             then
  683.                 # collision!
  684.                 return 0
  685.             fi
  686.         done
  687.  
  688.         # no collision
  689.         return 1
  690.     }
  691.  
  692.     # runs hit tests on friendly fire and damages any rocks encountered
  693.     function ffhit # $1 - ff_* index
  694.     {
  695.         local x=${ff_x[$1]} y=${ff_y[$1]} i j
  696.         for i in "${!dynel_alive[@]}"; do
  697.             (( i >= rock_pos )) || continue # only check rocks
  698.             ${dynel_alive[i]} || continue   # don't check dead dynels
  699.             if
  700.                 ((y > dynel_y[i]-2)) ||                                             # haven't reached rock yet - miss
  701.                 ((y < dynel_y[i]-dynel_h[i])) ||                                    # behind the rock - miss
  702.                 (( (x > dynel_x[i] ? x-dynel_x[i] : dynel_x[i]-x) > dynel_w[i]/2 )) # simple square collision
  703.             then
  704.                 continue
  705.             else
  706.                 ((dynel_hp[i] -= ff_damage[$1]))
  707.                 if ((dynel_hp[i] > 0)) && ((dynel_hp[i] < rock_hp/2)); then
  708.                     dynel_color[i]=$color_rock_damaged
  709.                     dynel_redraw[i]=true
  710.                 fi
  711.                 (( score_current += score_rockshot * ff_damage[$1] ))
  712.                 # it's a hit!
  713.                 return 0
  714.             fi
  715.         done
  716.  
  717.         # missed
  718.         return 1
  719.     }
  720.  
  721.     # changes the color of a dynel several times until it's finally drawn with black to disappear
  722.     function deathanimation # $1 - dynel_* index
  723.     {
  724.         local i=$1
  725.         # it helps to read this backwards :)
  726.         case "${dynel_color[i]}" in
  727.             "$color_death_4") return 1 ;;
  728.             "$color_death_3") dynel_color[i]=$color_death_4 ;;
  729.             "$color_death_2") dynel_color[i]=$color_death_3 ;;
  730.             "$color_death_1") dynel_color[i]=$color_death_2 ;;
  731.             *) dynel_color[i]=$color_death_1 ;;
  732.         esac
  733.         return 0
  734.     }
  735.    
  736.     # limit the amount of rocks
  737.     function limitrocks
  738.     {
  739.         rock_count_max=$(((rows*cols) / 720))
  740.     }
  741.  
  742.     # update the amount of score you get for stuff
  743.     function updatescore
  744.     {
  745.         score_rockshot=10                           # score per damage point that hits a rock
  746.         score_deadrock=$((500000 / (rows*cols)))    # score per dead rock (off screen or shot to pieces)
  747.         case "$state_current" in
  748.             ingame) ;;
  749.             title)
  750.                 score_current=0                     # current score
  751.                 score_last=-1                       # last drawn sore
  752.                 score_second=0                      # score per second passed
  753.             ;;
  754.         esac
  755.     }
  756.  
  757.     # movable? # $1 - dynel_* index
  758.     function canmoveup    { (( origo_y+dynel_cy[$1]-dynel_h[$1] > 0 )); }
  759.     function canmovedown  { (( origo_y+dynel_cy[$1] < rows )); }
  760.     function canmoveright { (( origo_x+dynel_cx[$1]+(dynel_w[$1]/2)+1 < cols )); }
  761.     function canmoveleft  { (( origo_x+dynel_cx[$1]-(dynel_w[$1]/2) > 0 )); }
  762.  
  763.     # tput position array
  764.     posarray=()                             # position array for faster cursor movement
  765.     sizechanged                             # run console size check (will always have changed)
  766.  
  767.     # pause and unpause
  768.     function registerpausetimer
  769.     {
  770.         pausetimers[${#pausetimers[*]}]="$1"
  771.     }
  772.     function pause
  773.     {
  774.         if ! $pause; then
  775.             local timer value
  776.             for timer in "${pausetimers[@]}"; do
  777.                 value=${!timer}
  778.                 if (( value != 0 )); then
  779.                     (( value -= time_now ))
  780.                 else
  781.                     value="zero"
  782.                 fi
  783.                 IFS= read -r $timer <<< "$value"
  784.             done
  785.             pause=true
  786.         fi
  787.     }
  788.     function unpause
  789.     {
  790.         if $pause; then
  791.             local timer value
  792.             for timer in "${pausetimers[@]}"; do
  793.                 value=${!timer}
  794.                 if [[ value != "zero" ]]; then
  795.                     (( value += time_now ))
  796.                 else
  797.                     value=0
  798.                 fi
  799.                 IFS= read -r $timer <<< "$value"
  800.             done
  801.             pause=false
  802.         fi
  803.     }
  804.  
  805.     # ammo line - no printf -v on bash3 :(
  806.     ff_ammo_max=30                          # maximum ammunition
  807.     while { ! ff_line=$(builtin printf "%${ff_ammo_max}s" ""); } 2>/dev/null; do :; done
  808.  
  809.     # init
  810.     time_start=$(microtime)                 # time the game was started
  811.     time_last=$time_start                   # time of last game loop
  812.     time_now=$time_start                    # current time
  813.     timer_resize=0                          # resize check timer
  814.     timerd_resize=1000000                   # resize check timer delta
  815.     state_current="title"                   # current game state
  816.     state_last=""                           # game state last loop
  817.     movespeed_x=7                           # how fast the player ship moves horizontally
  818.     movespeed_y=3                           # how fast the player ship moves vertically
  819.     blink_pressfire=""                      # blink status for the "press fire" text on title screen
  820.     blink_pressq=""                         # blink status for the "press q" text on death screen
  821.     blink_engines=""                        # blink status for the ship's engines
  822.     blink_pause=""                          # blink status for the pause message
  823.     messageheight=4                         # how far away from the top we print the title and such
  824.     cpusavesleep=0.2                        # time to sleep if saving cpu (dead, paused, menu)
  825.     redraw=true                             # should we redraw everything? (size probably changed)
  826.  
  827.     # reset the game
  828.     function resetgame
  829.     {
  830.         # dynels
  831.         dynel_img=( "ascii_playership" )    # drawing
  832.         dynel_x=( 0 )                       # current screen position (last drawn)
  833.         dynel_y=( 1 )                       # current screen position (last drawn)
  834.         dynel_cx=( ${dynel_x[0]} )          # actual position
  835.         dynel_cy=( ${dynel_y[0]} )          # actual position
  836.         dynel_ydiv=( 0 )                    # automatic movement (for non-player dynels)
  837.         dynel_w=( $ascii_playerdynel_w )    # width
  838.         dynel_h=( $ascii_playerdynel_h )    # height
  839.         dynel_hp=( 1 )                      # health
  840.         dynel_color=( $color_ship )         # color
  841.         dynel_redraw=( true )               # needs redrawing or not
  842.         dynel_alive=( true )                # dynel exists
  843.  
  844.         # rocks
  845.         rock_pos=${#dynel_alive[*]}         # rock position in dynel array
  846.         rock_count=0                        # current number of live rocks
  847.         rock_hp=12                          # rock health
  848.         rock_total=0                        # total number of rocks spawned
  849.         rock_add=0                          # additional rocks for difficulty
  850.         limitrocks                          # set the max rock count
  851.  
  852.         # friendly fire
  853.         ff_x=()                             # screen position
  854.         ff_y=()                             # screen position
  855.         ff_ydiv=()                          # speed divisor
  856.         ff_new=()                           # when new, don't move, only draw
  857.         ff_symbol=()                        # symbol to draw
  858.         ff_damage=()                        # how much damage this shot does
  859.         ff_count=0                          # current number of live shots
  860.         ff_count_max=64                     # max shot count at any given time
  861.         ff_total=0                          # total number of shots fired
  862.         ff_next=0                           # next shot
  863.         ff_alive=()                         # shot exists
  864.         ff_ammo_current=$((ff_ammo_max/2))  # current ammo
  865.         ff_ammo_last=0                      # last drawn ammo
  866.         counter_fire=0                      # number of shots fired
  867.         for (( i=0; i<ff_count_max; i++ )); do ff_alive[i]=false; done
  868.    
  869.         # timers and stuff
  870.         pausetimers=()
  871.         timer_ammo=$time_now                # timer for ammo generation
  872.         timerd_ammo=450000                  # timer delta for above
  873.         registerpausetimer "timer_ammo"
  874.         timer_autofire=$time_now            # timer for the autofire function
  875.         timerd_autofire=200000              # timer delta for above
  876.         registerpausetimer "timer_autofire"
  877.         timer_manualfire=$time_now          # timer for manual fire
  878.         timerd_manualfire=100000            # timer delta for above
  879.         registerpausetimer "timer_manualfire"
  880.         timer_fire=$time_now                # timer for update/drawing of fire
  881.         timerd_fire=50000                   # timer delta for above
  882.         registerpausetimer "timer_fire"
  883.         timer_rocks=$time_now               # timer for update/drawing of rocks
  884.         timerd_rocks=""                     # timer delta for above
  885.         registerpausetimer "timer_rocks"
  886.         timer_playerdeath=0                 # used for drawing the player ship death animation
  887.         timerd_playerdeath=500000           # timer delta for above
  888.         registerpausetimer "timer_playerdeath"
  889.         seconds_last=0                      # runtime in seconds (last printed)
  890.  
  891.         # blinking - allows blinking text and stuff to blink in sync
  892.         blink_fast=false
  893.         timer_blink_fast=$time_start
  894.         blink_medium=false
  895.         timer_blink_medium=$time_start
  896.         blink_slow=false
  897.         timer_blink_slow=$time_start
  898.  
  899.         # misc
  900.         autofire=false                      # is autofire enabled?
  901.         runshipcollision=false              # run ship collision this frame?
  902.         pause=false                         # is the game paused?
  903.         pause_last=false                    # was the game paused last frame?
  904.     }
  905.     resetgame
  906.  
  907.     # game loop
  908.     while true; do
  909.         time_now=$(microtime)
  910.         seconds=$(((time_now-time_start)/1000000))
  911.         framecounter=${framecounter:-0}
  912.         fps=${fps:-$framecounter}
  913.         (( framecounter++ ))
  914.  
  915.         # blinking
  916.         if (( timer_blink_fast + 200000 < time_now )); then
  917.             timer_blink_fast=$time_now
  918.             $blink_fast && blink_fast=false || blink_fast=true
  919.         fi
  920.         if (( timer_blink_medium + 600000 < time_now )); then
  921.             timer_blink_medium=$time_now
  922.             $blink_medium && blink_medium=false || blink_medium=true
  923.         fi
  924.         if (( timer_blink_slow + 999999 < time_now )); then
  925.             timer_blink_slow=$time_now
  926.             $blink_slow && blink_slow=false || blink_slow=true
  927.         fi
  928.  
  929.         # resize if needed
  930.         if
  931.             [[ "$state_current" != "$state_last" ]] ||
  932.             [[ "$pause" != "$pause_last" ]] ||
  933.             {
  934.                 (( timer_resize + timerd_resize < time_now )) &&
  935.                 {
  936.                     timer_resize=$time_now
  937.                     # this check is frakkin' expensive - do it only once per second
  938.                     sizechanged
  939.                 }
  940.             }
  941.         then
  942.             state_last=$state_current
  943.             pause_last=$pause
  944.  
  945.             # clean up and resize
  946.             wipe            # wipe the screen
  947.             buildposarray   # build new position array to move about
  948.             limitrocks      # update the number of rocks we should have
  949.             updatescore     # update how much score you get
  950.  
  951.             # push dynels inwards
  952.             for i in "${!dynel_alive[@]}"; do
  953.                 ${dynel_alive[i]} || continue
  954.                 restrict_xaxis $i
  955.                 if ((i == 0)); then
  956.                     restrict_yaxis $i
  957.                 fi
  958.             done
  959.         fi
  960.         if $redraw; then
  961.             border
  962.             safeecho $color_origo
  963.             xyecho $origo_x $origo_y "#"
  964.         fi
  965.  
  966.         # fps & counter
  967.         safeecho $color_debug
  968.         if (( seconds > seconds_last )); then
  969.             fps=$framecounter
  970.             framecounter=0
  971.             case "$state_current" in
  972.                 ingame|dead)
  973.                     xyprintf $((cols-2-9)) 2 "Dynl: %3d" "${#dynel_alive[*]}"
  974.                     xyprintf $((cols-2-9)) 3 "Rock: %3d" "$rock_count"
  975.                     xyprintf $((cols-2-9)) 4 "Shot: %3d" "$ff_count"
  976.                     xyprintf $((cols-2-6)) 5 "%6d" "$timerd_rocks"
  977. #                   xyecho $((cols-2-${#seconds})) 6 "$seconds"
  978.                 ;;
  979.             esac
  980.         fi
  981.         xyprintf $((cols-10)) 1 "FPS: %3d" "$fps"
  982.  
  983.         # read input
  984.         if $input; then
  985.             readc <"$comm"
  986.  
  987.             case "$state_current" in
  988.                 ingame)
  989.                     case "$c" in
  990.                         A|B|C|D)
  991.                             if ! $pause && ((dynel_hp[0] > 0)); then
  992.                                 case "$c" in
  993.                                         A) # up
  994.                                             for (( i=0; i<movespeed_y; i++ )); do
  995.                                                 canmoveup       0 && dynel_redraw[0]=true && runshipcollision=true || break && (( dynel_cy[0]-- ))
  996.                                             done
  997.                                         ;;
  998.                                         B) # down
  999.                                             for (( i=0; i<movespeed_y; i++ )); do
  1000.                                                 canmovedown     0 && dynel_redraw[0]=true && runshipcollision=true || break && (( dynel_cy[0]++ ))
  1001.                                             done
  1002.                                         ;;
  1003.                                         C) # right
  1004.                                             for (( i=0; i<movespeed_x; i++ )); do
  1005.                                                 canmoveright    0 && dynel_redraw[0]=true && runshipcollision=true || break && (( dynel_cx[0]++ ))
  1006.                                             done
  1007.                                         ;;
  1008.                                         D) # left
  1009.                                             for (( i=0; i<movespeed_x; i++ )); do
  1010.                                                 canmoveleft     0 && dynel_redraw[0]=true && runshipcollision=true || break && (( dynel_cx[0]-- ))
  1011.                                             done
  1012.                                         ;;
  1013.                                 esac
  1014.                             fi
  1015.                         ;;
  1016.                         ' ')
  1017.                             if ! $pause && ((dynel_hp[0] > 0)) && ! $autofire && (( timer_manualfire + timerd_manualfire < time_now )); then
  1018.                                 timer_manualfire=$time_now
  1019.                                 fire
  1020.                             fi
  1021.                         ;;
  1022.                         'p')
  1023.                             if ! $pause; then
  1024.                                 pause
  1025.                             else
  1026.                                 unpause
  1027.                             fi
  1028.                         ;;
  1029.                         'z')
  1030.                             if ! $pause; then
  1031.                                 if ((dynel_hp[0] > 0)); then
  1032.                                     $autofire && autofire=false || autofire=true
  1033.                                 fi
  1034.                             fi
  1035.                         ;;
  1036.                         'q')
  1037.                             if ! $pause; then
  1038.                                 state_current="title"
  1039.                             else
  1040.                                 unpause
  1041.                             fi
  1042.                         ;;
  1043.                     esac
  1044.                 ;;
  1045.                 title)
  1046.                     case "$c" in
  1047.                         ' ')
  1048.                             resetgame
  1049.                             updatescore
  1050.                             timerd_rocks=100000
  1051.                             autofire=false
  1052.                             state_current="ingame"
  1053.                         ;;
  1054.                         'q') kill -TERM $$; exit 0; ;;
  1055.                     esac
  1056.                 ;;
  1057.                 dead)
  1058.                     case "$c" in
  1059.                         'q') resetgame; state_current="title" ;;
  1060.                     esac
  1061.                 ;;
  1062.             esac
  1063.  
  1064.             input=false
  1065.         fi
  1066.  
  1067.         # move and draw
  1068.         case "$state_current" in
  1069.             title)
  1070.                 if $redraw; then
  1071.                     safeecho $color_title
  1072.                     catc $ascii_title_w $messageheight <<<"${ascii_title}"
  1073.                 fi
  1074.                 if $redraw; then
  1075.                     safeecho $color_keybindings
  1076.                     catc $ascii_keybindings_w $((messageheight + ascii_title_h + 3)) <<<"${ascii_keybindings}"
  1077.                 fi
  1078.                 if $redraw || [[ "$blink_pressfire" != "$blink_medium" ]]; then
  1079.                     blink_pressfire=$blink_medium
  1080.                     $blink_medium && safeecho $color_pressfire_1 || safeecho $color_pressfire_2
  1081.                     catc $ascii_press_fire_w $((messageheight + ascii_title_h + 1)) <<<"$ascii_press_fire"
  1082.                 fi
  1083.  
  1084.                 # sleep to save cpu
  1085.                 sleep $cpusavesleep
  1086.             ;;
  1087.             dead)
  1088.                 if $redraw; then
  1089.                     safeecho $color_youaredead
  1090.                     catc 7 $messageheight <<<"YOU ARE"
  1091.                     catc $ascii_dead_w $((messageheight + 1)) <<<"${ascii_dead}"
  1092.                 fi
  1093.                 if $redraw; then
  1094.                     safeecho $color_score_result
  1095.                     catc $((19+${#score_current})) $((messageheight + ascii_dead_h + 2)) <<<"You scored $score_current points!"
  1096.                 fi
  1097.                 if $redraw; then
  1098.                     # reading and writing this high score is sensitive to signal interruption and is somewhat error prone
  1099.                     highscore=$(command cat "${HOME}/.shellshock" 2>/dev/null)
  1100.                     if ((score_current > highscore)); then
  1101.                         echo -n "$score_current" >"${HOME}/.shellshock"
  1102.                     fi
  1103.                     safeecho $color_score_result
  1104.                     if ((score_current >= highscore)); then
  1105.                         catc 15 $((messageheight + ascii_dead_h + 3)) <<<"NEW HIGH SCORE!"
  1106.                     else
  1107.                         catc $((12+${#highscore})) $((messageheight + ascii_dead_h + 3)) <<<"High Score: $highscore"
  1108.                     fi
  1109.                 fi
  1110.                 if $redraw || [[ "$blink_pressq" != "$blink_slow" ]]; then
  1111.                     blink_pressq=$blink_slow
  1112.                     $blink_slow && safeecho $color_pressq_1 || safeecho $color_pressq_2
  1113.                     catc $ascii_press_q_w $((messageheight + ascii_dead_h + 5)) <<<"$ascii_press_q"
  1114.                 fi
  1115.  
  1116.                 # sleep to save cpu
  1117.                 sleep $cpusavesleep
  1118.             ;;
  1119.             ingame)
  1120.                 if ! $pause; then
  1121.                     # need to run ship collision?
  1122.                     runshipcollision=false
  1123.  
  1124.                     # speed up and add score every second
  1125.                     if (( seconds > seconds_last )); then
  1126.                         timerd_rocks=$(( timerd_rocks > 250 ? timerd_rocks - 250 : 0))
  1127.                         (( score_current += ++score_second ))
  1128.                         rock_add=$((rock_count_max * score_current / 200000))
  1129.                     fi
  1130.                 fi
  1131.  
  1132.                 # move and impact friendly fire
  1133.                 if
  1134.                     $redraw ||
  1135.                     {
  1136.                         ! $pause &&
  1137.                         (( timer_fire + timerd_fire < time_now ))
  1138.                     }
  1139.                 then
  1140.                     if ! $pause; then
  1141.                         timer_fire=$time_now
  1142.                         (( counter_fire++ ))
  1143.                     fi
  1144.                     safeecho $color_fire
  1145.                     for (( i=0; i<ff_count_max; i++ )); do
  1146.                         if ${ff_alive[i]}; then
  1147.                             if
  1148.                                 $redraw &&
  1149.                                 outofbounds "$((origo_x+ff_x[i]))" "$((origo_y+ff_y[i]))"
  1150.                             then
  1151.                                 ff_alive[i]=false
  1152.                                 (( --ff_count ))
  1153.                             elif ${ff_new[i]}; then
  1154.                                 ff_new[i]=false
  1155.                                 if ffhit $i; then
  1156.                                     ff_alive[i]=false
  1157.                                     (( --ff_count ))
  1158.                                 else
  1159.                                     xyecho $((origo_x+ff_x[i])) $((origo_y+ff_y[i])) "${ff_symbol[i]}"
  1160.                                 fi
  1161.                             else
  1162.                                 if $redraw; then
  1163.                                     xyecho $((origo_x+ff_x[i])) $((origo_y+ff_y[i])) "${ff_symbol[i]}"
  1164.                                 fi
  1165.                                 if ! $pause && (( counter_fire % ff_ydiv[i] == 0 )); then
  1166.                                     xyecho $((origo_x+ff_x[i])) $((origo_y+ff_y[i])) " "
  1167.                                     if (( origo_y + --ff_y[i] == 0 )); then
  1168.                                         ff_alive[i]=false
  1169.                                         (( --ff_count ))
  1170.                                     else
  1171.                                         if ffhit $i; then
  1172.                                             ff_alive[i]=false
  1173.                                             (( --ff_count ))
  1174.                                         else
  1175.                                             xyecho $((origo_x+ff_x[i])) $((origo_y+ff_y[i])) "${ff_symbol[i]}"
  1176.                                         fi
  1177.                                     fi
  1178.                                 fi
  1179.                             fi
  1180.                         fi
  1181.                     done
  1182.                 fi
  1183.  
  1184.                 if ! $pause; then
  1185.                     # autofire
  1186.                     if $autofire && (( timer_autofire + timerd_autofire < time_now )); then
  1187.                         timer_autofire=$time_now
  1188.                         timer_manualfire=$time_now
  1189.                         fire
  1190.                     fi
  1191.  
  1192.                     # move rocks?
  1193.                     if ((dynel_hp[0] > 0)) && ((timer_rocks + timerd_rocks < time_now)); then
  1194.                         timer_rocks=$time_now
  1195.                         (( ++rock_total ))
  1196.                     fi
  1197.  
  1198.                     # deal with rocks
  1199.                     if ((dynel_hp[0] > 0)); then
  1200.                         first_dead=""
  1201.                         for i in "${!dynel_alive[@]}"; do
  1202.                             (( i >= rock_pos )) || continue
  1203.                             ${dynel_alive[i]} || { first_dead=${first_dead:-$i}; continue; }
  1204.                             if
  1205.                                 # outside bottom of screen by entire height
  1206.                                 (( dynel_y[i] - dynel_h[i] > 0 ))
  1207.                             then
  1208.                                 dynel_alive[i]=false
  1209.                                 (( --rock_count ))
  1210.                                 first_dead=${first_dead:-$i}
  1211.                                 (( score_current += score_deadrock ))
  1212.                             elif
  1213.                                 # should be moved now
  1214.                                 (( timer_rocks == time_now )) &&
  1215.                                 (( rock_total % dynel_ydiv[i] == 0 ))
  1216.                             then
  1217.                                 if ((dynel_hp[i] <= 0)); then
  1218.                                     if ! deathanimation $i; then
  1219.                                         dynel_alive[i]=false
  1220.                                         (( --rock_count ))
  1221.                                         first_dead=${first_dead:-$i}
  1222.                                         (( score_current += score_deadrock ))
  1223.                                     fi
  1224.                                 fi
  1225.                                 (( dynel_cy[i]++ ))
  1226.                                 dynel_redraw[i]=true
  1227.                                 runshipcollision=true
  1228.                             fi
  1229.                         done
  1230.                        
  1231.                         if ((rock_count < rock_count_max + rock_add)); then
  1232.                             i=${first_dead:-${#dynel_alive[*]}}
  1233.                             dynel_w[i]="ascii_rock$((i%5))_w"
  1234.                             dynel_w[i]=${!dynel_w[i]}
  1235.                             dynel_h[i]="ascii_rock$((i%5))_h"
  1236.                             dynel_h[i]=${!dynel_h[i]}
  1237.                             dynel_x[i]=$(( (RANDOM % (cols-2-dynel_w[i])) - ( (cols-2)/2 ) ))
  1238.                             dynel_y[i]=$((-origo_y))
  1239.                             dynel_cx[i]=${dynel_x[i]}
  1240.                             dynel_cy[i]=${dynel_y[i]}
  1241.                             restrict_xaxis $i
  1242.                             if ! squarecollide $i; then
  1243.                                 dynel_ydiv[i]=$((RANDOM%3+1))
  1244.                                 for j in "${!dynel_alive[@]}"; do
  1245.                                     (( j >= rock_pos )) || continue
  1246.                                     if
  1247.                                         ${dynel_alive[j]} &&
  1248.                                         ((dynel_ydiv[i] < dynel_ydiv[j] )) &&
  1249.                                         (( (dynel_x[i] > dynel_x[j] ? dynel_x[i]-dynel_x[j] : dynel_x[j]-dynel_x[i]) < ((dynel_w[i]+dynel_w[j])/2) ))
  1250.                                     then
  1251.                                         dynel_ydiv[i]=${dynel_ydiv[j]}
  1252.                                     fi
  1253.                                 done
  1254.                                 dynel_hp[i]=$rock_hp
  1255.                                 dynel_color[i]=$color_rock_healthy
  1256.                                 dynel_redraw[i]=false
  1257.                                 dynel_img[i]="ascii_rock$((i%5))"
  1258.                                 dynel_alive[i]=true
  1259.                                 (( ++rock_count ))
  1260.                             fi
  1261.                         fi
  1262.                     fi
  1263.  
  1264.                     # do ship collision
  1265.                     if $runshipcollision && shipcollide; then
  1266.                         (( dynel_hp[0]-- ))
  1267.                     fi
  1268.                     if ((dynel_hp[0] <= 0)) && ((timer_playerdeath + timerd_playerdeath < time_now)); then
  1269.                         timer_playerdeath=$time_now
  1270.                         dynel_redraw[0]=true
  1271.                         if ! deathanimation 0; then
  1272.                             state_current="dead"
  1273.                         fi
  1274.                     fi
  1275.  
  1276.                     # regenerate ammo
  1277.                     if (( timer_ammo + timerd_ammo + (timerd_rocks*3) < time_now )) && ((ff_ammo_current < ff_ammo_max)); then
  1278.                         timer_ammo=$time_now
  1279.                         (( ++ff_ammo_current ))
  1280.                     fi
  1281.                 fi # if ! $pause
  1282.  
  1283.                 # draw ammo
  1284.                 if $redraw; then
  1285.                     xyecho 0 0 "${color_ammo_1}${ff_line:0:$ff_ammo_current}"
  1286.                     xyecho ${ff_ammo_current} 0 "${color_ammo_2}${ff_line:$ff_ammo_current:$ff_ammo_max}"
  1287.                 else
  1288.                     if ((ff_ammo_current < ff_ammo_last)); then
  1289.                         xyecho $ff_ammo_current 0 "${color_ammo_2}${ff_line:0:$((ff_ammo_last-ff_ammo_current))}"
  1290.                     elif ((ff_ammo_current > ff_ammo_last)); then
  1291.                         xyecho $ff_ammo_last 0 "${color_ammo_1}${ff_line:0:$((ff_ammo_current-ff_ammo_last))}"
  1292.                     fi
  1293.                 fi
  1294.  
  1295.                 # score
  1296.                 if $redraw || (( score_current != score_last )); then
  1297.                     score_last=$score_current
  1298.                     xyecho $((cols-${#score_current})) 0 "${color_score}${score_current}"
  1299.                 fi
  1300.  
  1301.                 # blink engines of player ship
  1302.                 if ! $pause && ((dynel_hp[0] > 0)) && ! ${dynel_redraw[0]} && [[ "$blink_engines" != "$blink_fast" ]]; then
  1303.                     blink_engines=$blink_fast
  1304.                     $blink_fast && safeecho $color_engine_1 || safeecho $color_engine_2
  1305.                     xyecho $((origo_x+dynel_cx[0]+3)) $((origo_y+dynel_cy[0]-2)) "@"
  1306.                     xyecho $((origo_x+dynel_cx[0]-3)) $((origo_y+dynel_cy[0]-2)) "@"
  1307.                 fi
  1308.  
  1309.                 # draw/move dynels one step at a time to their current position
  1310.                 lastalive=0
  1311.                 for i in "${!dynel_alive[@]}"; do
  1312.                     ${dynel_alive[i]} && lastalive=$i || continue
  1313.                     $redraw || ${dynel_redraw[i]} || continue
  1314.  
  1315.                     dynel_redraw[i]=false
  1316.  
  1317.                     safeecho ${dynel_color[i]}
  1318.                     while
  1319.                         if   (( dynel_x[i] < dynel_cx[i] )); then (( dynel_x[i]++ ))
  1320.                         elif (( dynel_x[i] > dynel_cx[i] )); then (( dynel_x[i]-- )); fi
  1321.                         if   (( dynel_y[i] < dynel_cy[i] )); then (( dynel_y[i]++ ))
  1322.                         elif (( dynel_y[i] > dynel_cy[i] )); then (( dynel_y[i]-- )); fi
  1323.  
  1324.                         catd \
  1325.                         $(( origo_x-(dynel_w[i]/2)+dynel_x[i] )) \
  1326.                         $(( origo_y-dynel_h[i]+dynel_y[i] )) \
  1327.                         <<<"${!dynel_img[i]}"
  1328.  
  1329.                         # fake do-while condition
  1330.                         (( dynel_x[i] != dynel_cx[i] )) ||
  1331.                         (( dynel_y[i] != dynel_cy[i] ))
  1332.                     do :; done
  1333.                 done
  1334.  
  1335.                 # draw pause anim
  1336.                 if $pause_last; then # this uses pause_last to avoid drawing the pause blinker before the frame
  1337.                     if $redraw; then
  1338.                         safeecho ${color_pauseframe}
  1339.                         catc $ascii_pauseframe_w $((messageheight + 4)) <<<"$ascii_pauseframe"
  1340.                     fi
  1341.                     if $redraw || [[ "$blink_pause" != "$blink_slow" ]]; then
  1342.                         blink_pause=$blink_slow
  1343.                         $blink_slow && safeecho $color_pause_1 || safeecho $color_pause_2
  1344.                         catc $ascii_pause_w $((messageheight + 6)) <<<"$ascii_pause"
  1345.                     fi
  1346.  
  1347.                     # sleep to save cpu
  1348.                     sleep $cpusavesleep
  1349.                 fi
  1350.                
  1351.                 # purge dead dynels
  1352.                 size=${#dynel_alive[*]}
  1353.                 (( ++lastalive ))
  1354.                 for ((i=lastalive; i<size; i++)); do
  1355.                     unset dynel_alive[i]
  1356.                 done
  1357.             ;;
  1358.         esac
  1359.         redraw=false
  1360.         ff_ammo_last=$ff_ammo_current
  1361.         seconds_last=$seconds
  1362.     done
  1363. ) &
  1364. gamepid=$!
  1365.  
  1366. # exit normally on several signals
  1367. trap 'exit 0' TERM INT HUP
  1368.  
  1369. # input loop
  1370. stty -echo
  1371. while true; do
  1372.     readc
  1373.     case "$c" in
  1374.         $'\e')
  1375.             readc
  1376.             [[ "$c" = '[' ]] || continue
  1377.             readc
  1378.             case "$c" in
  1379.                 A|B|C|D)
  1380.                     builtin echo "$c" >"$comm"
  1381.                     kill -USR1 "$gamepid"
  1382.                 ;;
  1383.             esac
  1384.         ;;
  1385.         ' '|p|q|z)
  1386.             builtin echo "$c" >"$comm"
  1387.             kill -USR1 "$gamepid"
  1388.         ;;
  1389.     esac
  1390. done
  1391.  
  1392. # vim: tabstop=4:softtabstop=4:shiftwidth=4:noexpandtab
Advertisement
Add Comment
Please, Sign In to add comment