-
-
Save aheadlead/e086866426cb621d6cf5955d6da1f7ed to your computer and use it in GitHub Desktop.
snake.sh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
declare -ai arr_snake_r=( 5 5 5 5 5 ) | |
declare -ai arr_snake_c=( 9 8 7 6 5 ) | |
declare -ai arr_food_r | |
declare -ai arr_food_c | |
readonly TTY=$(tty) | |
readonly IS_DEBUG=true | |
readonly RET_NEED_DEP=1 | |
readonly RET_TOO_SMALL_TEMRINAL=2 | |
readonly RET_CROSS_BORDER=3 | |
readonly RET_CROSS_BODY=4 | |
food_nr=10 | |
difficulty=3 | |
map_height=22 | |
map_width=48 | |
direction=DOWN | |
### MISC ### | |
function check_dependencies { | |
readonly DEPENDENCIES=( tput ) | |
for dependency in ${DEPENDENCIES[@]}; do | |
command -v "${dependency}" &>/dev/null || { echo "need ${dependency}"; exit ${RET_NEED_DEP}; } | |
done | |
} | |
function randint { | |
local -r from=$1; local -r to=$2 # left-closed, right-open interval | |
echo $(( from + RANDOM % (to - from) )) | |
} | |
function debug { | |
"${IS_DEBUG}" && echo $@ >> debug.log | |
} | |
function repeat { | |
yes "$1" | head -n "$2" | tr -d '\n' | |
} | |
### DRAW ### | |
function canvas_init { | |
local terminal_height=$(tput lines) terminal_width=$(tput cols) | |
if [[ ${terminal_height} -lt $(( 2 + map_height )) ]] || [[ ${terminal_width} -lt $(( 2 + map_width )) ]]; then | |
echo Terminal size is too small. Need at least 80x24. Current: ${terminal_width}x${terminal_height} | |
exit ${RET_TOO_SMALL_TEMRINAL} | |
fi | |
tput smcup # use altertive buffer | |
tput civis # cursor invisable | |
} | |
function canvas_exit { | |
tput cnorm # cursor visable | |
tput rmcup # use original buffer | |
} | |
function draw_border { | |
#debug "draw_border: entering" | |
tput cup 0 0 | |
printf "╔"; repeat "═" "${map_width}"; printf "╗\n" | |
for ((i=0; i<map_height; i++)); do | |
printf "║"; repeat " " "${map_width}"; printf "║\n" | |
done | |
printf "╚"; repeat "═" "${map_width}"; printf "╝\n" | |
} | |
function draw_snake_body { | |
tput bold | |
tput setab 3 # set background color to yellow | |
tput cup "$1" "$2" | |
echo -n 'o' | |
tput sgr0 # turn off all attribute | |
} | |
function draw_snake_head { | |
tput bold | |
tput setab 3 # set background color to yellow | |
tput cup "$1" "$2" | |
echo -n 'O' | |
tput sgr0 # turn off all attribute | |
} | |
function draw_snake_remove { | |
tput cup "$1" "$2" | |
echo -n ' ' | |
} | |
function draw_foods { | |
tput bold | |
tput setaf 3 # set background color to yellow | |
for ((i=0; i<${#arr_food_r[@]}; i++)); do | |
tput cup "${arr_food_r[i]}" "${arr_food_c[i]}" | |
echo -n '$' | |
done | |
tput sgr0 # turn off all attribute | |
} | |
### GAME ### | |
function convert_difficulty_to_tick { | |
case $1 in | |
1|easy) echo 1 ;; | |
2|normal) echo 0.3 ;; | |
3|hard) echo 0.1 ;; | |
4|expert) echo 0.05 ;; | |
?) echo 1 ;; | |
esac | |
} | |
function exit_game { | |
kill -s SIGINT $$ | |
} | |
function handle_input { | |
IFS= <&3 read -s -t 0.01 | |
debug "handle_input: REPLY='${REPLY}'" | |
local -r last_key=${REPLY: -1} | |
case ${last_key} in | |
a|A) [[ ${direction} != RIGHT ]] && direction=LEFT ;; | |
s|S) [[ ${direction} != UP ]] && direction=DOWN ;; | |
d|D) [[ ${direction} != LEFT ]] && direction=RIGHT ;; | |
w|W) [[ ${direction} != DOWN ]] && direction=UP ;; | |
$'\x1b') exit_game ;; # escape key -> exit game | |
?) ;; | |
esac | |
} | |
function handle_food { | |
while [[ ${#arr_food_r[@]} -lt ${food_nr} ]]; do | |
arr_food_r+=( $(randint 1 ${map_height}) ) | |
arr_food_c+=( $(randint 1 ${map_width}) ) | |
done | |
} | |
function handle_move { | |
local -i head_r=${arr_snake_r[0]} head_c=${arr_snake_c[0]} | |
local -i next_r=${head_r} next_c=${head_c} | |
debug direction=${direction} | |
case ${direction} in | |
LEFT) let next_c-=1;; RIGHT) let next_c+=1;; | |
UP) let next_r-=1;; DOWN) let next_r+=1;; | |
esac | |
debug head_r=${head_r} head_c=${head_c} | |
debug next_r=${next_r} next_c=${next_c} | |
# game over ? | |
if [[ ${next_r} -lt 1 ]] || [[ ${next_r} -gt ${map_height} ]] || [[ ${next_c} -lt 1 ]] || [[ ${next_c} -gt ${map_width} ]]; then | |
debug out of border | |
return ${RET_CROSS_BORDER} | |
fi | |
for ((i=0; i<${#arr_snake_r[@]}; i++)); do | |
if [[ ${next_r} -eq ${arr_snake_r[i]} ]] && [[ ${next_c} -eq ${arr_snake_c[i]} ]]; then | |
return ${RET_CROSS_BODY} | |
fi | |
done | |
# eat food ? | |
local ate_food=false | |
for ((i=0; i<${#arr_food_r[@]}; i++)); do | |
if [[ ${next_r} -eq ${arr_food_r[i]} ]] && [[ ${next_c} -eq ${arr_food_c[i]} ]]; then | |
ate_food=true | |
arr_food_r=( "${arr_food_r[@]::i}" "${arr_food_r[@]:i+1}" ) | |
arr_food_c=( "${arr_food_c[@]::i}" "${arr_food_c[@]:i+1}" ) | |
fi | |
done | |
# update array and canvas | |
if ! ${ate_food}; then | |
draw_snake_remove "${arr_snake_r[-1]}" "${arr_snake_c[-1]}" | |
unset arr_snake_r[-1] arr_snake_c[-1] | |
fi | |
draw_snake_head "${next_r}" "${next_c}" | |
arr_snake_r=( "${next_r}" "${arr_snake_r[@]}" ) | |
arr_snake_c=( "${next_c}" "${arr_snake_c[@]}" ) | |
draw_snake_body "${arr_snake_r[1]}" "${arr_snake_c[1]}" | |
} | |
### MAIN ### | |
function clean_up { | |
debug "clean up" | |
kill "${input_loop_pid}" "${game_loop_pid}" | |
canvas_exit | |
exit | |
} | |
function input_loop { | |
debug "input_loop: entering, BASHPID=$BASHPID, PPID=$$, tty=$TTY" | |
exec 0<"${TTY}" | |
while : ; do | |
read -s -n 1 | |
debug "input_loop: got char \"${REPLY}\"" | |
echo -n "${REPLY}" >&3 | |
done | |
} | |
function game_loop { | |
debug "game_loop: entering, BASHPID=$BASHPID, PPID=$$, tty=$TTY" | |
draw_border | |
local -r seconds=$(convert_difficulty_to_tick "${difficulty}") | |
while : ; do | |
handle_input | |
handle_food | |
draw_foods | |
handle_move || break | |
sleep ${seconds} | |
done | |
echo GAME OVER | |
<&3 read -n 1 -s | |
exit_game | |
} | |
function main { | |
check_dependencies | |
canvas_init | |
trap clean_up SIGINT | |
PIPE=$(mktemp -u) && mkfifo ${PIPE} && exec 3<>${PIPE} && rm "${PIPE}" | |
input_loop & input_loop_pid=$! | |
game_loop & game_loop_pid=$! | |
while : ; do | |
wait | |
done | |
} | |
main $@ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment