#!/bin/bash # snake - Snake game written in bash, to be used in terminal # (C) 2026 F-Hauri.ch - http://www.f-hauri.ch # Licensed under terms of GPL v3. www.gnu.org # Version: 0.1.2 -- Last update: Tue Jun 30 15:26:36 2026 # Require bash version >= 5.0 ! # This script is mostly pure bash except some call to `tput`, `clear` and `stty` # at initialisation and end of game. # Use strongly ANSI and control sequences from XTerm documentation: # https://invisible-island.net/xterm/ctlseqs/ctlseqs.html # Require debian: ncurses-bin, coreutils. # Usage: /path/to/snake.sh # Move: # Up: `w`, `i`, `u` or # Down: `x`, `m`, `n`, `,` (comma) or # Left: `a`, `j`, `h` or # Right: `d`, `l`, `k` or # Quit: `q` or if (($#)); then sed -ne "/^# Usage/,/^$/{s@\(Usage: \).*@\1 $0@;s/^# \?//;p}" "$0"; exit 0; fi shapes=( \\U25B6 \\U25BC \\U25C0 \\U25B2 ) # Triangles for snake's head dirs=( "2,0" "0,1" "-2,0" "0,-1" ) # x,y move for directions 0-3 body=\\U25CF keys=() # Keyboard buffer crtApple='' snake=("3,2") # snake body (array of dots) declare -A iSnake=([${snake[0]}]='') # dots indexed body of snake declare -i crtLen=5 crtDir=0 appleValue=0 appleHit=0 appleCnt=0 appleColor getBgValue() { # Get terminal background Value (max of R, G or B). local _array _string IFS= read -d$'\\' -srp $'\033]11;?\033\\' _string IFS=$'\e/' read -ra _array <<<"${_string#*:}" _array=("${_array[@]%??}") printf -v _string '[%d]= ' "${_array[@]/#/0x}" declare -a "_array=($_string)" _array=("${!_array[@]}") printf -v "${1:-TERM_BG_VALUE}" '%d' "${_array[-1]}" } apple() { # Place new apple local -i x y collision=1 local shape if [[ $crtApple ]]; then IFS=, read -r x y <<<${crtApple} printf '\e[%d;%dH ' $y $x fi while (( collision )) ;do x=$((RANDOM%40*2+1)) y=$((RANDOM%24+1)) [[ -v "iSnake[$x,$y]" ]] || collision=0 done appleValue=$(( RANDOM % 10 )) printf -v shape '\\U%X' $(( appleValue +10122 )) # 10102 printf '\e[38;5;%dm\e[%d;%dH%b\e[0m' "$appleColor" "$y" "$x" "$shape" crtApple="$x,$y" appleCnt+=1 } eatApple() { # Snake eat apple crtLen+="appleValue+1" appleHit+=1 printf \\a crtApple= apple } step() { # One snake step chgDir || return 1 local -i x y IFS=, read -r x y <<<"${snake[0]}" printf '\e[%d;%dH%b' $y $x "$body" # Replace head by body char. x+=${dirs[crtDir]%,*} # Place new head with shape y+=${dirs[crtDir]#*,} # regarding direction. printf '\e[%d;%dH%b' $y $x "${shapes[crtDir]}" (( x < 1 || x > 80 || y < 1 || y > 25 )) || # border collision [[ -v "iSnake[$x,$y]" ]] && # body collision return 1 [[ $crtApple == "$x,$y" ]] && eatApple snake=("$x,$y" "${snake[@]}") iSnake["$x,$y"]='' if ((${#snake[@]} > crtLen ));then # Remove last body dot... IFS=, read -r x y <<<"${snake[${#snake[@]}-1]}" printf '\e[%d;%dH ' $y $x # from terminal snake=("${snake[@]::${#snake[@]}-1}") # from snake array unset "iSnake[$x,$y]" # from dot indexed array fi } buffKeys() { # Bufferize user interactions local crtslp key subkey nextStep nsec=150000 nextStep=$(( (${EPOCHREALTIME/.}/nsec+1)*nsec )) # sleep 0.15 sec, while while ((${EPOCHREALTIME/.}24)); then # Add bottom border if not printf '\e[25;80H\n' printf '%.0s\U2592\U2592' {3..42} # Just like to use 42 somewhere ;-) fi printf '\e[%d;80H\U2592\e[H' {0..25} # Fill last column with grey pattern getBgValue appleColor # Choose dark or light apple color appleColor="appleColor>128?125:213 " # to match the background color tput civis # Hide cursor oTTy=$(stty -g) # Stop ECHO on terminal stty -icanon -echo # Main gameTime=$EPOCHREALTIME apple while step; do ((RANDOM<100)) && apple # move apple spontaneously, sometime buffKeys # bufferize user interactions. done # End of game gameTime=00000$(( ${EPOCHREALTIME/.} - ${gameTime/.} )) printf -v gameTime '%.0fm%05.2fsec' $(( 10#${gameTime::-6} / 60 )) \ $(( 10#${gameTime::-6} % 60 )).${gameTime: -6} printf '\e[25;80H\n\a' printf '%.0s\U2592\U2592' {3..42} filled=000$(( ${#snake[@]} * 100000 /960 )) printf '\nYou loose!\nScore: %d, Filled %.2f%%, Apple: %d/%d, time: %s.\n' \ $((crtLen-5)) ${filled::-3}.${filled: -3} $appleHit $appleCnt "$gameTime" for _ in 1 2; do # 2 more beeps read -rsn1 -t .18 _ printf '\a' done tput cnorm stty "$oTTy"