3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License version 2, June 1991.
6 # This program is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # GNU General Public License for more details.
11 # You should have received a copy of the GNU General Public License
12 # along with this program. If not, see <https://www.gnu.org/licenses/gpl-2.0.html>,
13 # or in pdf format at <http://www.dhampir.no/stuff/private/gpl-2.0.pdf>
15 # Copyright 2012 - Øyvind 'bolt' Hvidsten <bolt@dhampir.no>
19 # v1.07, 2012.03.26 - Steve McMurphy
20 # v1.11, 2014.09.30 - Øyvind A. Holm
25 # ShellShock is a top-down space shooter written for Bash 3 / Bash 4
26 # Tested on Linux (Debian, RedHat and CentOS)
28 # Please note: A game in Bash is very demanding on resources.
29 # This script requires a modern computer to run at a decent speed.
30 # This script uses a tab width of 4, which is automatically applied
31 # if you're using vim with modelines enabled (see bottom line).
33 # For updates, please see <http://www.dhampir.no/stuff/bash/shellshock.bash>.
35 # Comments welcome, improvements/patches twice as welcome.
38 # Releases / Changelog:
40 # v1.00, 2012.03.18 - Initial v1.0 release
41 # * All intended functionality implemented
43 # v1.01, 2012.03.19 - Bash 3
44 # * Tweaks to run on Bash 3
46 # v1.05, 2012.03.25 - Several changes based on feedback
47 # * Added score display
48 # * Added increasing difficulty
50 # v1.06, 2012.03.26 - More feedback
51 # * Ship aft collision improved
52 # * Added pause option
53 # * Added simple high score storage
54 # * Runs on Bash 3 again
56 # v1.07, 2012.03.26 - OSX
57 # * Added rudimentary support for Mac OS X
59 # v1.08, 2012.03.26 - Cleanup
60 # * Improved some comments and made clearer error messages
62 # v1.09, 2012.03.27 - Ubuntu
63 # * Various fixes for Ubuntu
65 # v1.10, 2012.03.29 - Pause
66 # * Improved the pause function to avoid problems with timers
68 # v1.11, 2014.09.30 - Better cleanup
69 # * Using "stty sane" to clean up terminal settings on exit
75 # queue temp files for deletion on script exit
76 function rm_queue
{ _rm_queue
[${#_rm_queue[*]}]="$1"; }
77 function rm_process
{ local file; for file in "${_rm_queue[@]}"; do rm "$file"; done; }
79 # check if tput actually outputs something useful for this terminal
80 if [[ -z "$(tput sgr0)" ]] ||
[[ -z "$(tput bold)" ]]; then
81 echo 'Error: tput is not working as expected with your current terminal settings!' >&2
82 echo 'Try setting your $TERM to something more standard?' >&2
86 # read a single character
87 function readc
{ IFS
= read -r -n1 -s "$@" c
; }
89 # variable variables :)
93 # list of files to be removed on cleanup
99 [[ -z "$gamepid" ]] ||
{ kill -TERM "$gamepid"; wait "$gamepid"; } 2>/dev
/null
100 rm_process
# remove temp files
101 tput sgr0
# reset color
102 clear # clear the screen
103 tput cnorm
# show the cursor
104 stty
echo # show input
105 stty sane
# reset terminal
106 ! $saved_term ||
tput rmcup
# restore the terminal view
116 # TODO: better solution for inter-thread communication. must work on bash3
119 comm=$
(mktemp
/tmp
/shellshock.XXXXXX
)
125 if [[ -n "$comm" ]]; then
128 echo "Error: Communications file creation failed!" >&2
133 tput smcup
&& saved_term
=true
# save the current terminal view
134 tput civis
# hide the cursor
140 trap 'input=true' USR1
141 trap 'exit 0' TERM INT HUP
146 kill -0 $$
2>/dev
/null ||
exit 1
155 # running this in a loop with 2>/dev/null to avoid "interrupted system call" messages
156 while { ! builtin echo -n "${posarray[$((y*cols+x))]}$@"; } 2>/dev
/null
; do testparent
; done
160 # running this in a loop with 2>/dev/null to avoid "interrupted system call" messages
161 while { ! builtin echo -n "${posarray[$((y*cols+x))]}$@"; } 2>/dev
/null
; do testparent
; done
168 # running this in a loop with 2>/dev/null to avoid "interrupted system call" messages
169 while { ! builtin printf "${posarray[$((y*cols+x))]}$@"; } 2>/dev
/null
; do testparent
; done
173 # running this in a loop with 2>/dev/null to avoid "interrupted system call" messages
174 while { ! builtin printf "${posarray[$((y*cols+x))]}$@"; } 2>/dev
/null
; do testparent
; done
177 # called when the player fires his/her weapons
178 function fire
# no parameters
180 (( ff_ammo_current
> 0 )) ||
return
182 if ${ff_alive[ff_next]}; then
183 xyprintf $
((origo_x
+ff_x
[ff_next
])) $
((origo_y
+ff_y
[ff_next
])) " "
186 if ((++ff_total
%2 == 0)); then
187 ff_x
[ff_next
]=$
((dynel_cx
[0]-3))
188 ff_y
[ff_next
]=$
((dynel_cy
[0]-8))
190 ff_x
[ff_next
]=$
((dynel_cx
[0]+3))
191 ff_y
[ff_next
]=$
((dynel_cy
[0]-8))
194 if ! outofbounds
"$((origo_x+ff_x[ff_next]))" "$((origo_y+ff_y[ff_next]))"; then
196 ff_symbol
[ff_next
]="|"
198 ff_alive
[ff_next
]=true
201 (( --ff_ammo_current ))
202 ff_next
=$
(( ++ff_next
< ff_count_max ? ff_next
: 0 ))
205 if ((ff_total
%3 == 0)); then
206 if ${ff_alive[ff_next]}; then
207 xyprintf $
((origo_x
+ff_x
[ff_next
])) $
((origo_y
+ff_y
[ff_next
])) " "
210 if ((ff_total
%2 == 0)); then
211 ff_x
[ff_next
]=$
((dynel_cx
[0]+5))
212 ff_y
[ff_next
]=$
((dynel_cy
[0]-7))
214 ff_x
[ff_next
]=$
((dynel_cx
[0]-5))
215 ff_y
[ff_next
]=$
((dynel_cy
[0]-7))
218 if ! outofbounds
"$((origo_x+ff_x[ff_next]))" "$((origo_y+ff_y[ff_next]))"; then
220 ff_symbol
[ff_next
]="¤"
222 ff_alive
[ff_next
]=true
225 ff_next
=$
(( ++ff_next
< ff_count_max ? ff_next
: 0 ))
230 #######################################################
231 ## BEGIN ASCII ART SECTION - NO TABS, NO INDENTATION ##
232 #######################################################
238 _________.__ .__ .__ _________.__ __ ._.
239 / _____/| |__ ____ | | | | / _____/| |__ ____ ____ | | _| |
240 \_____ \ | | \_/ __ \| | | | \_____ \ | | \ / _ \_/ ___\| |/ / |
241 / \| Y \ ___/| |_| |__/ \| Y ( <_> ) \___| < \|
242 /_______ /|___| /\___ >____/____/_______ /|___| /\____/ \___ >__|_ \__
243 \/ \/ \/ \/ \/ \/ \/\/
247 ascii_keybindings_w=74 #21 - fake width to print off-center
248 ascii_keybindings_h=5
259 # dead meat - written in quotes because all the parenthesis confuse vim's syntax highlighting
260 # - this also means the first line has to be offset, so the ascii looks ugly in code
264 ' ______ _______ _______ ______
265 ( __ \ ( ____ \( ___ )( __ \
266 | ( \ )| ( \/| ( ) || ( \ )
267 | | ) || (__ | (___) || | ) |
268 | | | || __) | ___ || | | |
269 | | ) || ( | ( ) || | ) |
270 | (__/ )| (____/\| ) ( || (__/ )
271 (______/ (_______/|/ \|(______/'
277 -= Press q to quit to the title screen! =-
281 ascii_press_fire_w=27
285 -= Press SPACE to start
! =-
297 ascii_pauseframe_w=25
301 *************************
305 *************************
310 ascii_playerdynel_w
=21
311 ascii_playerdynel_h
=9
319 00] |_____. | | ._____|
394 #######################################################
395 ## END ASCII ART SECTION ##
396 #######################################################
399 color_background
=$
(tput setab
0)
400 color_reset
="$(tput sgr0)${color_background}"
401 color_black
="${color_reset}$(tput setaf 0)"
402 color_red
="${color_reset}$(tput setaf 1)"
403 color_green
="${color_reset}$(tput setaf 2)"
404 color_orange
="${color_reset}$(tput setaf 3)"
405 color_blue
="${color_reset}$(tput setaf 4)"
406 color_magenta
="${color_reset}$(tput setaf 5)"
407 color_cyan
="${color_reset}$(tput setaf 6)"
408 color_light_gray
="${color_reset}$(tput setaf 7)"
409 color_dark_gray
="${color_reset}$(tput bold)$(tput setaf 0)"
410 color_light_red
="${color_reset}$(tput bold)$(tput setaf 1)"
411 color_light_green
="${color_reset}$(tput bold)$(tput setaf 2)"
412 color_yellow
="${color_reset}$(tput bold)$(tput setaf 3)"
413 color_light_blue
="${color_reset}$(tput bold)$(tput setaf 4)"
414 color_light_magenta
="${color_reset}$(tput bold)$(tput setaf 5)"
415 color_light_cyan
="${color_reset}$(tput bold)$(tput setaf 6)"
416 color_white
="${color_reset}$(tput bold)$(tput setaf 7)"
418 # specific colors for stuff
419 color_debug
=$color_orange # debug prints (FPS, seconds, rocks count, etc)
420 color_ship
=$color_white # player's ship
421 color_fire
=$color_light_magenta # player's missiles
422 color_engine_1
=$color_light_red # player's engines (blinking)
423 color_engine_2
=$color_red # player's engines (blinking)
424 color_border
=$color_red # border
425 color_score_result
=$color_yellow # score result (death screen)
426 color_origo
=$color_green # origo (bottom center)
427 color_rock_healthy
=$color_light_cyan # healthy rocks
428 color_rock_damaged
=$color_cyan # damaged rocks
429 color_death_1
=$color_light_red # death animation stage 1
430 color_death_2
=$color_red # death animation stage 2
431 color_death_3
=$color_dark_gray # death animation stage 3
432 color_death_4
=$color_black # death animation stage 4 (erase)
433 color_title
=$color_yellow # ShellShock!
434 color_pause_1
=$color_light_red # pause text (blinking)
435 color_pause_2
=$color_red # pause text (blinking)
436 color_pauseframe
=$color_red # frame around pause text
437 color_youaredead
=$color_light_red # YOU ARE DEAD
438 color_keybindings
=$color_green # keybindings...
439 color_pressfire_1
=$color_blue # press space to start (blinking)
440 color_pressfire_2
=$color_light_blue # press space to start (blinking)
441 color_pressq_1
=$color_dark_gray # press q for title screen (blinking)
442 color_pressq_2
=$color_light_gray # press q for title screen (blinking)
444 # score display (top right) (drawn on white background)
445 color_score
="${color_black}$(tput setab 7)"
446 # ammo display (drawn with spaces on background color in the top left)
447 color_ammo_1
="${color_black}$(tput setab 5)"
448 color_ammo_2
="${color_black}$(tput setab 7)"
453 # has the size changed?
456 if (( cols
!= $
(tput cols
) )) ||
(( rows
!= $
(tput lines
) )); then
471 # "tput clear" doesn't fill with background color in bash3, screen, etc.
472 # must write a shitload of spaces instead
473 safeprintf
"${color_reset}${home}%$((cols*rows))s" ""
475 # everything needs redrawing after this
479 # get the current time in microseconds
482 # this is slow. very slow.
484 local time=$
(python
<<<"import time; print \"%.6f\" % time.time();")
490 local time=$(date +%s%N)
491 echo -n ${time:0:((${#time}-3))}
496 # build tput position array
497 # moving using this 10x faster than running tput
500 function buildposarray
502 if ((cols == _pos_cols)) && ((rows == _pos_rows)); then
512 local e=$(echo -e "\e")
513 if [[ "$(tput cup 0 0)" = "${e}[1;1H" ]]; then
514 # standard terminal movement commands - quick generation
518 local string="Building position array for ${cols}x${rows}... "
519 local pos=$(tput cup 0 ${#string})
522 safeecho "${color_debug}${home}${string}"
523 for ((x=0; x < cols; x++)); do
528 echo -n "${pos}$((x*100/cols))%"
529 for ((y=0; y < rows; y++)); do
531 posarray[$((y*cols+x))]="${e}[$((y+1));$((x+1))H"
533 posarray[$((y*cols+x))]=$(tput cup "$y" "$x")
541 # print something at a spot
542 function catc { cat "$@"; } # draw centered (x coordinate specifies width, not x pos)
543 function catd { cat "$@"; } # draw a dynel (supports black outline)
546 local x=$1 y=$2 i=0 cy dynel=false first=true offset=0
548 case "${FUNCNAME[1]}" in
551 x=$(( (cols - x) / 2)) # center
556 while IFS= read -r line; do
559 offset="$(( 10#${line:0:2} ))"
560 line=${line:3+offset}
565 (( cx < cols-1 )) || continue # don't write on or outside the right border
566 (( cy > 0 )) || continue # don't write on or above the top border
567 (( cy < rows-1 )) || break # don't write on or below the bottom border
568 (( cx >= 1 )) || { line=${line:1-cx}; cx=1; } # cut to fit inside left border
569 line=${line:0:cols-1-cx} # cut to fit inside right border
571 xyecho $cx $cy "$line"
578 safeecho $color_border
580 # no printf -v on bash3 :(
582 while { ! line=$(builtin printf "%${cols}s" ""); } 2>/dev/null; do :; done
588 for (( y=1; y<rows-1; y++ )); do
590 xyecho $((cols-1)) $y "#"
593 xyecho 0 $((rows-1)) "$line"
596 # is something outside the screen?
597 function outofbounds # $1 - x coordinate, $2 - y coordinate
601 (( x < 1 )) || (( x >= cols-1 )) ||
602 (( y < 1 )) || (( y >= rows-1 ))
609 # pushes a dynel until it's within the right and left borders
610 function restrict_xaxis # $1 - dynel_* index
613 dynel_x[i]=${dynel_cx[i]}
614 while ! canmoveright $i; do (( dynel_cx[i]-- )); done;
615 while ! canmoveleft $i; do (( dynel_cx[i]++ )); done;
616 (( dynel_cx[i] < dynel_x[i] )) && (( dynel_cx[i]++ ))
617 (( dynel_cx[i] > dynel_x[i] )) && (( dynel_cx[i]-- ))
618 dynel_x[i]=${dynel_cx[i]}
621 # pushes a dynel until it's within the top and bottom borders
622 function restrict_yaxis # $1 - dynel_* index
625 dynel_y[i]=${dynel_cy[i]}
626 while ! canmoveup $i; do (( dynel_cy[i]++ )); done;
627 while ! canmovedown $i; do (( dynel_cy[i]-- )); done;
628 (( dynel_cy[i] < dynel_y[i] )) && (( dynel_cy[i]++ ))
629 (( dynel_cy[i] > dynel_y[i] )) && (( dynel_cy[i]-- ))
630 dynel_y[i]=${dynel_cy[i]}
633 # collides dynels based on simple squares (width, height)
634 function squarecollide # $1 - dynel_* index
637 for j in "${!dynel_alive[@]}"; do
638 ${dynel_alive[j]} || continue # don't check dead dynels
639 (( j != i )) || continue # don't check yourself
640 (( j >= rock_pos )) || continue # don't check the playership
641 local distance_x=$((dynel_cx[i] > dynel_cx[j] ? dynel_cx[i]-dynel_cx[j] : dynel_cx[j]-dynel_cx[i]))
642 local distance_y=$((dynel_cy[j] - dynel_cy[i]))
644 (( distance_x < (dynel_w[i]+dynel_w[j])/2 )) &&
646 if (( distance_y < 0 )); then
647 # j (compare dynel) is above i
648 (( -distance_y < dynel_h[j] ))
650 # j (compare dynel) is below i
651 (( distance_y < dynel_h[j] ))
664 # collides the player's ship
665 # basically the same as square collision, but with some tweaks to make it feel better
666 function shipcollide # no parameters
669 for j in "${!dynel_alive[@]}"; do
670 ${dynel_alive[j]} || continue # don't check dead dynels
671 (( j != i )) || continue # don't check yourself
672 local distance_x=$((dynel_cx[i] > dynel_cx[j] ? dynel_cx[i]-dynel_cx[j] : dynel_cx[j]-dynel_cx[i]))
673 local distance_y=$((dynel_cy[j] - dynel_cy[i]))
675 (( distance_x + 2 < (dynel_w[i]+dynel_w[j])/2 )) && # make the ship a little narrower
677 if (( distance_y < 0 )); then
678 # j (compare dynel) is above i
679 (( -distance_y < dynel_h[j] )) &&
680 (( (distance_y + dynel_h[i]) > distance_x - 4 )) # make a somewhat cone-shaped ship
682 # j (compare dynel) is below i
683 (( distance_y + 2 < dynel_h[j] )) && # make the ship a little shorter
685 ((dynel_h[j] - distance_y - 2 > 2)) || # 2 lines into ship from bottom
686 ((4 + (dynel_h[j] - distance_y - 2) > distance_x)) # engine hit
700 # runs hit tests on friendly fire and damages any rocks encountered
701 function ffhit # $1 - ff_* index
703 local x=${ff_x[$1]} y=${ff_y[$1]} i j
704 for i in "${!dynel_alive[@]}"; do
705 (( i >= rock_pos )) || continue # only check rocks
706 ${dynel_alive[i]} || continue # don't check dead dynels
708 ((y > dynel_y[i]-2)) || # haven't reached rock yet - miss
709 ((y < dynel_y[i]-dynel_h[i])) || # behind the rock - miss
710 (( (x > dynel_x[i] ? x-dynel_x[i] : dynel_x[i]-x) > dynel_w[i]/2 )) # simple square collision
714 ((dynel_hp[i] -= ff_damage[$1]))
715 if ((dynel_hp[i] > 0)) && ((dynel_hp[i] < rock_hp/2)); then
716 dynel_color[i]=$color_rock_damaged
719 (( score_current += score_rockshot * ff_damage[$1] ))
729 # changes the color of a dynel several times until it's finally drawn with black to disappear
730 function deathanimation # $1 - dynel_* index
733 # it helps to read this backwards :)
734 case "${dynel_color[i]}" in
735 "$color_death_4") return 1 ;;
736 "$color_death_3") dynel_color[i]=$color_death_4 ;;
737 "$color_death_2") dynel_color[i]=$color_death_3 ;;
738 "$color_death_1") dynel_color[i]=$color_death_2 ;;
739 *) dynel_color[i]=$color_death_1 ;;
744 # limit the amount of rocks
747 rock_count_max=$(((rows*cols) / 720))
750 # update the amount of score you get for stuff
753 score_rockshot=10 # score per damage point that hits a rock
754 score_deadrock=$((500000 / (rows*cols))) # score per dead rock (off screen or shot to pieces)
755 case "$state_current" in
758 score_current=0 # current score
759 score_last=-1 # last drawn sore
760 score_second=0 # score per second passed
765 # movable? # $1 - dynel_* index
766 function canmoveup { (( origo_y+dynel_cy[$1]-dynel_h[$1] > 0 )); }
767 function canmovedown { (( origo_y+dynel_cy[$1] < rows )); }
768 function canmoveright { (( origo_x+dynel_cx[$1]+(dynel_w[$1]/2)+1 < cols )); }
769 function canmoveleft { (( origo_x+dynel_cx[$1]-(dynel_w[$1]/2) > 0 )); }
771 # tput position array
772 posarray=() # position array for faster cursor movement
773 sizechanged # run console size check (will always have changed)
776 function registerpausetimer
778 pausetimers[${#pausetimers[*]}]="$1"
784 for timer in "${pausetimers[@]}"; do
786 if (( value != 0 )); then
787 (( value -= time_now ))
791 IFS= read -r $timer <<< "$value"
800 for timer in "${pausetimers[@]}"; do
802 if [[ value != "zero" ]]; then
803 (( value += time_now ))
807 IFS= read -r $timer <<< "$value"
813 # ammo line - no printf -v on bash3 :(
814 ff_ammo_max=30 # maximum ammunition
815 while { ! ff_line=$(builtin printf "%${ff_ammo_max}s" ""); } 2>/dev/null; do :; done
818 time_start=$(microtime) # time the game was started
819 time_last=$time_start # time of last game loop
820 time_now=$time_start # current time
821 timer_resize=0 # resize check timer
822 timerd_resize=1000000 # resize check timer delta
823 state_current="title" # current game state
824 state_last="" # game state last loop
825 movespeed_x=7 # how fast the player ship moves horizontally
826 movespeed_y=3 # how fast the player ship moves vertically
827 blink_pressfire="" # blink status for the "press fire" text on title screen
828 blink_pressq="" # blink status for the "press q" text on death screen
829 blink_engines="" # blink status for the ship's engines
830 blink_pause="" # blink status for the pause message
831 messageheight=4 # how far away from the top we print the title and such
832 cpusavesleep=0.2 # time to sleep if saving cpu (dead, paused, menu)
833 redraw=true # should we redraw everything? (size probably changed)
839 dynel_img=( "ascii_playership" ) # drawing
840 dynel_x=( 0 ) # current screen position (last drawn)
841 dynel_y=( 1 ) # current screen position (last drawn)
842 dynel_cx=( ${dynel_x[0]} ) # actual position
843 dynel_cy=( ${dynel_y[0]} ) # actual position
844 dynel_ydiv=( 0 ) # automatic movement (for non-player dynels)
845 dynel_w=( $ascii_playerdynel_w ) # width
846 dynel_h=( $ascii_playerdynel_h ) # height
847 dynel_hp=( 1 ) # health
848 dynel_color=( $color_ship ) # color
849 dynel_redraw=( true ) # needs redrawing or not
850 dynel_alive=( true ) # dynel exists
853 rock_pos=${#dynel_alive[*]} # rock position in dynel array
854 rock_count=0 # current number of live rocks
855 rock_hp=12 # rock health
856 rock_total=0 # total number of rocks spawned
857 rock_add=0 # additional rocks for difficulty
858 limitrocks # set the max rock count
861 ff_x=() # screen position
862 ff_y=() # screen position
863 ff_ydiv=() # speed divisor
864 ff_new=() # when new, don't move, only draw
865 ff_symbol=() # symbol to draw
866 ff_damage=() # how much damage this shot does
867 ff_count=0 # current number of live shots
868 ff_count_max=64 # max shot count at any given time
869 ff_total=0 # total number of shots fired
870 ff_next=0 # next shot
871 ff_alive=() # shot exists
872 ff_ammo_current=$((ff_ammo_max/2)) # current ammo
873 ff_ammo_last=0 # last drawn ammo
874 counter_fire=0 # number of shots fired
875 for (( i=0; i<ff_count_max; i++ )); do ff_alive[i]=false; done
879 timer_ammo=$time_now # timer for ammo generation
880 timerd_ammo=450000 # timer delta for above
881 registerpausetimer "timer_ammo"
882 timer_autofire=$time_now # timer for the autofire function
883 timerd_autofire=200000 # timer delta for above
884 registerpausetimer "timer_autofire"
885 timer_manualfire=$time_now # timer for manual fire
886 timerd_manualfire=100000 # timer delta for above
887 registerpausetimer "timer_manualfire"
888 timer_fire=$time_now # timer for update/drawing of fire
889 timerd_fire=50000 # timer delta for above
890 registerpausetimer "timer_fire"
891 timer_rocks=$time_now # timer for update/drawing of rocks
892 timerd_rocks="" # timer delta for above
893 registerpausetimer "timer_rocks"
894 timer_playerdeath=0 # used for drawing the player ship death animation
895 timerd_playerdeath=500000 # timer delta for above
896 registerpausetimer "timer_playerdeath"
897 seconds_last=0 # runtime in seconds (last printed)
899 # blinking - allows blinking text and stuff to blink in sync
901 timer_blink_fast=$time_start
903 timer_blink_medium=$time_start
905 timer_blink_slow=$time_start
908 autofire=false # is autofire enabled?
909 runshipcollision=false # run ship collision this frame?
910 pause=false # is the game paused?
911 pause_last=false # was the game paused last frame?
917 time_now=$(microtime)
918 seconds=$(((time_now-time_start)/1000000))
919 framecounter=${framecounter:-0}
920 fps=${fps:-$framecounter}
924 if (( timer_blink_fast + 200000 < time_now )); then
925 timer_blink_fast=$time_now
926 $blink_fast && blink_fast=false || blink_fast=true
928 if (( timer_blink_medium + 600000 < time_now )); then
929 timer_blink_medium=$time_now
930 $blink_medium && blink_medium=false || blink_medium=true
932 if (( timer_blink_slow + 999999 < time_now )); then
933 timer_blink_slow=$time_now
934 $blink_slow && blink_slow=false || blink_slow=true
939 [[ "$state_current" != "$state_last" ]] ||
940 [[ "$pause" != "$pause_last" ]] ||
942 (( timer_resize + timerd_resize < time_now )) &&
944 timer_resize=$time_now
945 # this check is frakkin' expensive - do it only once per second
950 state_last=$state_current
953 # clean up and resize
954 wipe # wipe the screen
955 buildposarray # build new position array to move about
956 limitrocks # update the number of rocks we should have
957 updatescore # update how much score you get
959 # push dynels inwards
960 for i in "${!dynel_alive[@]}"; do
961 ${dynel_alive[i]} || continue
970 safeecho $color_origo
971 xyecho $origo_x $origo_y "#"
975 safeecho $color_debug
976 if (( seconds > seconds_last )); then
979 case "$state_current" in
981 xyprintf $((cols-2-9)) 2 "Dynl: %3d" "${#dynel_alive[*]}"
982 xyprintf $((cols-2-9)) 3 "Rock: %3d" "$rock_count"
983 xyprintf $((cols-2-9)) 4 "Shot: %3d" "$ff_count"
984 xyprintf $((cols-2-6)) 5 "%6d" "$timerd_rocks"
985 # xyecho $((cols-2-${#seconds})) 6 "$seconds"
989 xyprintf $((cols-10)) 1 "FPS: %3d" "$fps"
995 case "$state_current" in
999 if ! $pause && ((dynel_hp[0] > 0)); then
1002 for (( i=0; i<movespeed_y; i++ )); do
1003 canmoveup 0 && dynel_redraw[0]=true && runshipcollision=true || break && (( dynel_cy[0]-- ))
1007 for (( i=0; i<movespeed_y; i++ )); do
1008 canmovedown 0 && dynel_redraw[0]=true && runshipcollision=true || break && (( dynel_cy[0]++ ))
1012 for (( i=0; i<movespeed_x; i++ )); do
1013 canmoveright 0 && dynel_redraw[0]=true && runshipcollision=true || break && (( dynel_cx[0]++ ))
1017 for (( i=0; i<movespeed_x; i++ )); do
1018 canmoveleft 0 && dynel_redraw[0]=true && runshipcollision=true || break && (( dynel_cx[0]-- ))
1025 if ! $pause && ((dynel_hp[0] > 0)) && ! $autofire && (( timer_manualfire + timerd_manualfire < time_now )); then
1026 timer_manualfire=$time_now
1039 if ((dynel_hp[0] > 0)); then
1040 $autofire && autofire=false || autofire=true
1046 state_current="title"
1060 state_current="ingame"
1062 'q') kill -TERM $$; exit 0; ;;
1067 'q') resetgame; state_current="title" ;;
1076 case "$state_current" in
1079 safeecho $color_title
1080 catc $ascii_title_w $messageheight <<<"${ascii_title}"
1083 safeecho $color_keybindings
1084 catc $ascii_keybindings_w $((messageheight + ascii_title_h + 3)) <<<"${ascii_keybindings}"
1086 if $redraw || [[ "$blink_pressfire" != "$blink_medium" ]]; then
1087 blink_pressfire=$blink_medium
1088 $blink_medium && safeecho $color_pressfire_1 || safeecho $color_pressfire_2
1089 catc $ascii_press_fire_w $((messageheight + ascii_title_h + 1)) <<<"$ascii_press_fire"
1097 safeecho $color_youaredead
1098 catc 7 $messageheight <<<"YOU ARE"
1099 catc $ascii_dead_w $((messageheight + 1)) <<<"${ascii_dead}"
1102 safeecho $color_score_result
1103 catc $((19+${#score_current})) $((messageheight + ascii_dead_h + 2)) <<<"You scored $score_current points!"
1106 # reading and writing this high score is sensitive to signal interruption and is somewhat error prone
1107 highscore=$(command cat "${HOME}/.shellshock" 2>/dev/null)
1108 if ((score_current > highscore)); then
1109 echo -n "$score_current" >"${HOME}/.shellshock"
1111 safeecho $color_score_result
1112 if ((score_current >= highscore)); then
1113 catc 15 $((messageheight + ascii_dead_h + 3)) <<<"NEW HIGH SCORE!"
1115 catc $((12+${#highscore})) $((messageheight + ascii_dead_h + 3)) <<<"High Score: $highscore"
1118 if $redraw || [[ "$blink_pressq" != "$blink_slow" ]]; then
1119 blink_pressq=$blink_slow
1120 $blink_slow && safeecho $color_pressq_1 || safeecho $color_pressq_2
1121 catc $ascii_press_q_w $((messageheight + ascii_dead_h + 5)) <<<"$ascii_press_q"
1129 # need to run ship collision?
1130 runshipcollision=false
1132 # speed up and add score every second
1133 if (( seconds > seconds_last )); then
1134 timerd_rocks=$(( timerd_rocks > 250 ? timerd_rocks - 250 : 0))
1135 (( score_current += ++score_second ))
1136 rock_add=$((rock_count_max * score_current / 200000))
1140 # move and impact friendly fire
1145 (( timer_fire + timerd_fire < time_now ))
1149 timer_fire=$time_now
1150 (( counter_fire++ ))
1152 safeecho $color_fire
1153 for (( i=0; i<ff_count_max; i++ )); do
1154 if ${ff_alive[i]}; then
1157 outofbounds "$((origo_x+ff_x[i]))" "$((origo_y+ff_y[i]))"
1161 elif ${ff_new[i]}; then
1167 xyecho $((origo_x+ff_x[i])) $((origo_y+ff_y[i])) "${ff_symbol[i]}"
1171 xyecho $((origo_x+ff_x[i])) $((origo_y+ff_y[i])) "${ff_symbol[i]}"
1173 if ! $pause && (( counter_fire % ff_ydiv[i] == 0 )); then
1174 xyecho $((origo_x+ff_x[i])) $((origo_y+ff_y[i])) " "
1175 if (( origo_y + --ff_y[i] == 0 )); then
1183 xyecho $((origo_x+ff_x[i])) $((origo_y+ff_y[i])) "${ff_symbol[i]}"
1194 if $autofire && (( timer_autofire + timerd_autofire < time_now )); then
1195 timer_autofire=$time_now
1196 timer_manualfire=$time_now
1201 if ((dynel_hp[0] > 0)) && ((timer_rocks + timerd_rocks < time_now)); then
1202 timer_rocks=$time_now
1207 if ((dynel_hp[0] > 0)); then
1209 for i in "${!dynel_alive[@]}"; do
1210 (( i >= rock_pos )) || continue
1211 ${dynel_alive[i]} || { first_dead=${first_dead:-$i}; continue; }
1213 # outside bottom of screen by entire height
1214 (( dynel_y[i] - dynel_h[i] > 0 ))
1216 dynel_alive[i]=false
1218 first_dead=${first_dead:-$i}
1219 (( score_current += score_deadrock ))
1221 # should be moved now
1222 (( timer_rocks == time_now )) &&
1223 (( rock_total % dynel_ydiv[i] == 0 ))
1225 if ((dynel_hp[i] <= 0)); then
1226 if ! deathanimation $i; then
1227 dynel_alive[i]=false
1229 first_dead=${first_dead:-$i}
1230 (( score_current += score_deadrock ))
1234 dynel_redraw[i]=true
1235 runshipcollision=true
1239 if ((rock_count < rock_count_max + rock_add)); then
1240 i=${first_dead:-${#dynel_alive[*]}}
1241 dynel_w[i]="ascii_rock$((i%5))_w"
1242 dynel_w[i]=${!dynel_w[i]}
1243 dynel_h[i]="ascii_rock$((i%5))_h"
1244 dynel_h[i]=${!dynel_h[i]}
1245 dynel_x[i]=$(( (RANDOM % (cols-2-dynel_w[i])) - ( (cols-2)/2 ) ))
1246 dynel_y[i]=$((-origo_y))
1247 dynel_cx[i]=${dynel_x[i]}
1248 dynel_cy[i]=${dynel_y[i]}
1250 if ! squarecollide $i; then
1251 dynel_ydiv[i]=$((RANDOM%3+1))
1252 for j in "${!dynel_alive[@]}"; do
1253 (( j >= rock_pos )) || continue
1255 ${dynel_alive[j]} &&
1256 ((dynel_ydiv[i] < dynel_ydiv[j] )) &&
1257 (( (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) ))
1259 dynel_ydiv[i]=${dynel_ydiv[j]}
1262 dynel_hp[i]=$rock_hp
1263 dynel_color[i]=$color_rock_healthy
1264 dynel_redraw[i]=false
1265 dynel_img[i]="ascii_rock$((i%5))"
1273 if $runshipcollision && shipcollide; then
1276 if ((dynel_hp[0] <= 0)) && ((timer_playerdeath + timerd_playerdeath < time_now)); then
1277 timer_playerdeath=$time_now
1278 dynel_redraw[0]=true
1279 if ! deathanimation 0; then
1280 state_current="dead"
1285 if (( timer_ammo + timerd_ammo + (timerd_rocks*3) < time_now )) && ((ff_ammo_current < ff_ammo_max)); then
1286 timer_ammo=$time_now
1287 (( ++ff_ammo_current ))
1293 xyecho 0 0 "${color_ammo_1}${ff_line:0:$ff_ammo_current}"
1294 xyecho ${ff_ammo_current} 0 "${color_ammo_2}${ff_line:$ff_ammo_current:$ff_ammo_max}"
1296 if ((ff_ammo_current < ff_ammo_last)); then
1297 xyecho $ff_ammo_current 0 "${color_ammo_2}${ff_line:0:$((ff_ammo_last-ff_ammo_current))}"
1298 elif ((ff_ammo_current > ff_ammo_last)); then
1299 xyecho $ff_ammo_last 0 "${color_ammo_1}${ff_line:0:$((ff_ammo_current-ff_ammo_last))}"
1304 if $redraw || (( score_current != score_last )); then
1305 score_last=$score_current
1306 xyecho $((cols-${#score_current})) 0 "${color_score}${score_current}"
1309 # blink engines of player ship
1310 if ! $pause && ((dynel_hp[0] > 0)) && ! ${dynel_redraw[0]} && [[ "$blink_engines" != "$blink_fast" ]]; then
1311 blink_engines=$blink_fast
1312 $blink_fast && safeecho $color_engine_1 || safeecho $color_engine_2
1313 xyecho $((origo_x+dynel_cx[0]+3)) $((origo_y+dynel_cy[0]-2)) "@"
1314 xyecho $((origo_x+dynel_cx[0]-3)) $((origo_y+dynel_cy[0]-2)) "@"
1317 # draw/move dynels one step at a time to their current position
1319 for i in "${!dynel_alive[@]}"; do
1320 ${dynel_alive[i]} && lastalive=$i || continue
1321 $redraw || ${dynel_redraw[i]} || continue
1323 dynel_redraw[i]=false
1325 safeecho ${dynel_color[i]}
1327 if (( dynel_x[i] < dynel_cx[i] )); then (( dynel_x[i]++ ))
1328 elif (( dynel_x[i] > dynel_cx[i] )); then (( dynel_x[i]-- )); fi
1329 if (( dynel_y[i] < dynel_cy[i] )); then (( dynel_y[i]++ ))
1330 elif (( dynel_y[i] > dynel_cy[i] )); then (( dynel_y[i]-- )); fi
1333 $(( origo_x-(dynel_w[i]/2)+dynel_x[i] )) \
1334 $(( origo_y-dynel_h[i]+dynel_y[i] )) \
1335 <<<"${!dynel_img[i]}"
1337 # fake do-while condition
1338 (( dynel_x[i] != dynel_cx[i] )) ||
1339 (( dynel_y[i] != dynel_cy[i] ))
1344 if $pause_last; then # this uses pause_last to avoid drawing the pause blinker before the frame
1346 safeecho ${color_pauseframe}
1347 catc $ascii_pauseframe_w $((messageheight + 4)) <<<"$ascii_pauseframe"
1349 if $redraw || [[ "$blink_pause" != "$blink_slow" ]]; then
1350 blink_pause=$blink_slow
1351 $blink_slow && safeecho $color_pause_1 || safeecho $color_pause_2
1352 catc $ascii_pause_w $((messageheight + 6)) <<<"$ascii_pause"
1360 size=${#dynel_alive[*]}
1362 for ((i=lastalive; i<size; i++)); do
1363 unset dynel_alive[i]
1368 ff_ammo_last=$ff_ammo_current
1369 seconds_last=$seconds
1374 # exit normally on several signals
1375 trap 'exit 0' TERM INT HUP
1384 [[ "$c" = '[' ]] || continue
1388 builtin echo "$c" >"$comm"
1389 kill -USR1 "$gamepid"
1394 builtin echo "$c" >"$comm"
1395 kill -USR1 "$gamepid"
1400 # vim: tabstop=4:softtabstop=4:shiftwidth=4:noexpandtab