Skip to content

Instantly share code, notes, and snippets.

@aheadlead
Created April 27, 2020 13:28
Show Gist options
  • Save aheadlead/e086866426cb621d6cf5955d6da1f7ed to your computer and use it in GitHub Desktop.
Save aheadlead/e086866426cb621d6cf5955d6da1f7ed to your computer and use it in GitHub Desktop.
snake.sh
#!/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