flat assembler
Message board for the users of flat assembler.

Index > Heap > 2048 in pure bash [linux shell]

Author
Thread Post new topic Reply to topic
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 17352
Location: In your JS exploiting you and your system
revolution
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?
Code:
#!/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    
Post 21 Apr 2020, 23:43
View user's profile Send private message Visit poster's website Reply with quote
redsock



Joined: 09 Oct 2009
Posts: 360
Location: Australia
redsock
This is a thing of beauty, no two ways about it. Thanks for sharing.

_________________
2 Ton Digital - https://2ton.com.au/
Post 22 Apr 2020, 08:32
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 17352
Location: In your JS exploiting you and your system
revolution
The hardest part was figuring out how to avoid killing the console echo if using "read -s ...". If the user typed CTRL-C then restoring echo to normal would have required using the external terminal mode setting program.
Post 22 Apr 2020, 09:25
View user's profile Send private message Visit poster's website Reply with quote
Furs



Joined: 04 Mar 2016
Posts: 1485
Furs
Very nice Smile
Post 23 Apr 2020, 13:04
View user's profile Send private message Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You can attach files in this forum
You can download files in this forum


Copyright © 1999-2020, Tomasz Grysztar. Also on YouTube, Twitter.

Website powered by rwasa.