bash is a common Linux shell used to start programs. But we can also use it to run scripts.
Using nothing but bash internal commands here is the game 2048. You can customise the layout and target score to your liking. There is also a hidden option, can you discover what it does?
#!/usr/bin/env bash
header="2048 in pure bash v0.0"
save_name="${0}.saved"
size=4 # default size
target=11 # default target
STATE_SIZE=0
STATE_TARGET=1
STATE_SCORE=2
STATE_BOARD=3
declare -ia STATE
declare -a colours
colours[0]=30 # black text, black background (invisible)
colours[1]="1;33" # yellow text
colours[2]="1;32" # green text
colours[3]="1;34" # blue text
colours[4]="1;36" # cyan text
colours[5]="1;35" # magenta text
colours[6]="1;31" # red text
colours[7]="1;33;7" # yellow background
colours[8]="1;32;7" # green background
colours[9]="1;34;7" # blue background
colours[10]="1;36;7" # cyan background
colours[11]="1;35;7" # magenta background
colours[12]="1;31;7" # red background
colours[13]="1;48;5;238;33" # grey background yellow text
colours[14]="1;48;5;238;32" # grey background green text
colours[15]="1;48;5;238;34" # grey background blue text
colours[16]="1;48;5;238;36" # grey background cyan text
colours[17]="1;48;5;238;35" # grey background magenta text
function save_game {
printf "${STATE[*]}\n" > $save_name
}
function load_game {
test -f $save_name || return
read -a STATE < $save_name || return
let ${#STATE[@]}==STATE[STATE_SIZE]*STATE[STATE_SIZE]+STATE_BOARD
}
function next_piece {
while true ; do
let pos=RANDOM%\(STATE[STATE_SIZE]*STATE[STATE_SIZE]\)+STATE_BOARD
if ! let STATE[pos] ; then
if test ${seed:0:1} ; then
piece=${seed:0:1}
seed=${seed:1}
else
piece=RANDOM%10?1:2
fi
let STATE[pos]=-piece
return
fi
done
}
function push {
test ! -z $2 && local -n won=$2
local change=1
case $1 in
u)
row_increment=1
start_column=0
column_increment=${STATE[STATE_SIZE]}
;;
d)
row_increment=1
let start_column=(STATE[STATE_SIZE]-1)*STATE[STATE_SIZE]
column_increment=-${STATE[STATE_SIZE]}
;;
r)
row_increment=${STATE[STATE_SIZE]}
let start_column=STATE[STATE_SIZE]-1
column_increment=-1
;;
l)
row_increment=${STATE[STATE_SIZE]}
start_column=0
column_increment=1
;;
esac
row=0
for ((dummy0=STATE[STATE_SIZE] ; dummy0-- ;)) ; do
dest_column=$start_column
let source_column=start_column+column_increment
for ((dummy1=STATE[STATE_SIZE]-1 ; dummy1-- ;)) ; do
if let STATE[row+source_column+STATE_BOARD] ; then
if let STATE[row+dest_column+STATE_BOARD] ; then
#check merge
if let STATE[row+dest_column+STATE_BOARD]==STATE[row+source_column+STATE_BOARD] ; then
test -z $2 && return
let STATE[row+source_column+STATE_BOARD]=0
let STATE[row+dest_column+STATE_BOARD]++
let STATE[row+dest_column+STATE_BOARD]==STATE[STATE_TARGET] && won=0
let "STATE[STATE_SCORE]+=1<<STATE[row+dest_column+STATE_BOARD]"
let dest_column+=column_increment
change=0
else
let dest_column+=column_increment
if let source_column!=dest_column ; then
test -z $2 && return
let STATE[row+dest_column+STATE_BOARD]=STATE[row+source_column+STATE_BOARD]
let STATE[row+source_column+STATE_BOARD]=0
change=0
fi
fi
else
test -z $2 && return
let STATE[row+dest_column+STATE_BOARD]=STATE[row+source_column+STATE_BOARD]
let STATE[row+source_column+STATE_BOARD]=0
change=0
fi
fi
let source_column+=column_increment
done
let row+=row_increment
done
return $change
}
function next_key {
local won=1
while true ; do
local dir=0
read -d "" -n 1
if test "$REPLY" = $'\e' ; then
printf "\b \b\b \b"
read -d "" -n 1 -t1
printf "\b \b"
if test "$REPLY" = "[" ; then
read -d "" -n 1 -t1
printf "\b \b"
case $REPLY in
A) dir=u ;;
B) dir=d ;;
C) dir=r ;;
D) dir=l ;;
esac
fi
else
printf "\b \b"
case $REPLY in
k|w) dir=u ;;
j|s) dir=d ;;
l|d) dir=r ;;
h|a) dir=l ;;
esac
fi
if test "$dir" != "0" ; then
push $dir won && return $won
fi
done
}
function print_state {
target_string=$((1<<STATE[STATE_TARGET]))
eval printf -v bar '%.0s\\u2500' {-1..${#target_string}}
printf "\e[2J\e[1;1H$header target=$target_string score=${STATE[STATE_SCORE]}\n"
printf "\e]0;"${STATE[STATE_SCORE]}"\e\\ \n"
printf "\u250c$bar"
for ((col=STATE[STATE_SIZE]-1 ; col-- ;)) ; do printf "\u252c$bar" ; done
printf "\u2510\n"
for ((row=0 ; row<STATE[STATE_SIZE] ; row++)) ; do
printf "\u2502"
for ((col=0 ; col<STATE[STATE_SIZE] ; col++)) ; do
let pos=row*STATE[STATE_SIZE]+col+STATE_BOARD
piece=${STATE[pos]}
if let "piece < 0" ; then
let piece=-piece
let STATE[pos]=piece
indicator="."
else
indicator=" "
fi
printf " \e[${colours[piece]}m%${#target_string}d\e[0m${indicator}\u2502" $((1<<piece))
done
if let row!=STATE[STATE_SIZE]-1 ; then
printf "\n\u251c$bar"
for ((col=STATE[STATE_SIZE]-1 ; col-- ;)) ; do printf "\u253c$bar" ; done
printf "\u2524\n"
fi
done
printf "\n\u2514$bar"
for ((col=STATE[STATE_SIZE]-1 ; col-- ;)) ; do printf "\u2534$bar" ; done
printf "\u2518\n"
}
function finished {
printf -v end_time "%(%s)T"
let total_time=end_time-start_time
printf "\b\bTime: %02d:%02d:%02d\n" $((total_time/3600)) $((total_time%3600/60)) $((total_time%60))
printf "Score: ${STATE[STATE_SCORE]}\n"
if test "$1" = "won" ; then
printf "Target $((1<<STATE[STATE_TARGET])) achieved\n"
elif test "$1" = "save" ; then
read -sn1 -p "Save this game? [y|N]: "
if test "$REPLY" = "y" || test "$REPLY" = "Y" ; then
save_game
printf "\nGame saved. Use -l option to load this game"
fi
test "$REPLY" = $'\n' || printf "\n"
else
printf "You lost\n"
fi
exit 0
}
function help {
printf "Usage: $0 [-s INTEGER] [-t INTEGER] [-l] [-h]\n"
printf "\n"
printf " -h this help\n"
printf " -l load a previously saved game\n"
printf " -s specify board size, default = $size\n"
printf " -t specify base 2 logarithm of target score to win, default = $target\n"
}
load_flag=0
while getopts "s:t:lhc:" option ; do
case $option in
s ) let "size=OPTARG<2?2:OPTARG";;
t ) target="$OPTARG";;
l ) load_flag=1;;
h ) help ; exit 0;;
c ) seed=$OPTARG;;#secret option
\?|:) printf "try $0 -h\n" >&2 ; exit 1;;
esac
done
if let load_flag && ! load_game ; then
load_flag=0
fi
if ! let load_flag ; then
STATE=()
STATE[STATE_SIZE]=$size
STATE[STATE_TARGET]=$target
STATE[STATE_SCORE]=0
for ((i=STATE[STATE_SIZE]*STATE[STATE_SIZE] ; i-- ;)) ; do STATE[i+STATE_BOARD]=0 ; done
next_piece
next_piece
fi
print_state
printf -v start_time "%(%s)T"
trap "finished save" INT
while true ; do
push u || push d || push r || push l || finished lost
next_key && print_state && finished won
next_piece
print_state
done