Created
April 6, 2016 04:53
-
-
Save SomeCrazyGuy/4c844da3c181912579c0e324f17f39a6 to your computer and use it in GitHub Desktop.
A Platformer game written entirely in bash script without calling any external programs
This file contains hidden or 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
#!/bin/bash --noprofile | |
#extra strict flags | |
set -euo pipefail | |
#screen buffer | |
declare -a level | |
#globals | |
declare -i lv_w=0 #width of level | |
declare -i lv_h=0 #height of level | |
declare -i status_line=0 | |
nextTile='' | |
#constants, added after the fact to improve tweaking the numbers | |
kFPSdelay='0.03' #DEFAULT: 0.03, 33 FPS - good setting | |
kBarrierBlock='#' #DEFAULT: '#' - the block you can stand on | |
kEmptyBlock=' ' #DEFAULT: ' ' - air | |
kCoinBlock='$' #DEFAULT: '$' - it's not a platformer unless you can increment a variable | |
kSpikeBlock='^' #DEFAULT: '^' - instant death when touching this | |
kWinBlock='%' #DEFAULT: '%' - instant win when touching this | |
kUpVelocity=-25 #DEFAULT: -25 - could be too high | |
kLeftVelocity=-5 #DEFAULT: -5 | |
kRightVelocity=5 #DEFAULT: 5 | |
kGravityFPSMod=5 #DEFAULT: 5 - provides a delay when falling | |
kVelocityMod=5 #DEFAULT: 5 | |
#Art is hard | |
you_won='###############################################################\n' | |
you_won+='## __ __ ______ __ __ __ __ __ ______ ______ __ ##\n' | |
you_won+='## || || |====| || || || || || |====| |====| || ##\n' | |
you_won+='## |===| || || || || || || || || || || || || ##\n' | |
you_won+='## || || || || || || || || || || || || ##\n' | |
you_won+='## |===| |====| |====| |===||===| |====| || || [] ##\n' | |
you_won+='## ##\n' | |
you_won+='###############################################################\n' | |
#less ambitious art this time | |
game_over='You fell into the pit, landed on a spike, and became less of an adventurer.\n' | |
game_over+='Game Over' | |
#move the cursor to a specific row and colunm | |
function put_cursor() { | |
echo -en "\e[${1};${2}f" | |
} | |
function init_screen() { | |
local height | |
#get the size of the terminal | |
put_cursor 999 999 | |
echo -en '\e[6n' | |
read -s -r -d'[' | |
read -s -r -d';' height | |
read -s -r -d'R' lv_w | |
lv_h=$((height-1)) | |
status_line=height | |
} | |
#usage: mvprintw $row $col $string | |
function mvprintw() { | |
local row=$(($1+1)) #rows count at 1 in escape sequences, I guess | |
local col=$2 | |
printf "%s%s%s" $'\x1b[' "${row};${col}f" "$3" | |
} | |
#usage: draw_row $row_number | |
function draw_row() { | |
mvprintw "$1" 0 "${level[$1]}" | |
} | |
#use really fancy procedural generation | |
function gen_level() { | |
local rowstr='' | |
local foo=0 | |
for (( y=0; y<lv_h; ++y )) | |
do | |
for (( x=0; x<lv_w; ++x )) | |
do | |
foo=$((RANDOM % 4)) | |
case "$foo" in | |
( 0 ) rowstr+="$kBarrierBlock" ;; | |
( 1 ) rowstr+="$kEmptyBlock" ;; | |
( 2 ) rowstr+="$kEmptyBlock" ;; | |
( 3 ) rowstr+="$kCoinBlock" ;; | |
esac | |
done | |
level[$y]="$rowstr" | |
rowstr='' | |
done | |
} | |
#usage: twidle $row $col $replace_char | |
function twidle() { | |
local row="$1" | |
local col="$2" | |
local chr="$3" | |
local lvrest="${level[$row]:$col}" | |
local lvfirst='' | |
col=$((col-1)) | |
(( col > 0 )) && lvfirst="${level[$row]:0:$col}" | |
level[$row]="${lvfirst}${chr}${lvrest}" | |
} | |
#usage: draw_player $row $col | |
function draw_player() { | |
#Display Attribtes #Foreground Colours #Background Colours | |
#0 Reset all attributes #30 Black #40 Black | |
#1 Bright #31 Red #41 Red | |
#2 Dim #32 Green #42 Green | |
#4 Underscore #33 Yellow #43 Yellow | |
#5 Blink #34 Blue #44 Blue | |
#7 Reverse #35 Magenta #45 Magenta | |
#8 Hidden #36 Cyan #46 Cyan | |
# #37 White #47 White | |
mvprintw "$1" "$2" $'\x1b[32m@\x1b[0m' | |
} | |
function draw_level() { | |
for (( x=0; x<lv_h; ++x )) | |
do draw_row "$x" | |
done | |
} | |
#usage: checktile $row $col | |
function checktile() { | |
local row=$1 | |
local col=$(($2-1)) | |
if (( row < 0 )) || (( col < 0 )) | |
then | |
nextTile="$kBarrierBlock" | |
return | |
elif (( row == lv_h )) | |
then | |
nextTile="$kSpikeBlock" | |
return | |
elif (( col == lv_w )) | |
then | |
nextTile="$kWinBlock" | |
return | |
else | |
nextTile="${level[$row]:$col:1}" | |
return | |
fi | |
return | |
} | |
function main() { | |
local px=1 #player X coordinate | |
local py=0 #player Y coordinate | |
local pxv=0 #player X velocity | |
local pyv=0 #player Y velocity | |
local pog=0 #player is on ground | |
local npx=0 #new player X, needs to be checked | |
local npy=0 #new player Y, needs to be checked | |
local score=0 #player score | |
local fps_mod=0 #to delay animations | |
#disable line wrap, clear screen, disable text cursor | |
echo -en '\e[7l\e[2J\e[?25l' | |
init_screen | |
gen_level | |
#make spawnpoint safe | |
twidle 0 0 ' ' | |
twidle 0 1 ' ' | |
twidle 0 2 ' ' | |
twidle 0 3 ' ' | |
twidle 0 4 ' ' | |
twidle 1 0 ' ' | |
twidle 1 1 ' ' | |
twidle 1 2 ' ' | |
twidle 1 3 ' ' | |
twidle 1 4 ' ' | |
draw_level | |
#main loop | |
while true | |
do | |
#read input | |
if read -r -s -N1 -t "$kFPSdelay" #framerate control | |
then case "$REPLY" in | |
( $'\x1b' ) | |
if read -r -s -N1 -t '0.1' | |
then case "$REPLY" in | |
( '[' ) | |
if read -r -s -N1 -t '0.1' | |
then case "$REPLY" in | |
( 'A' ) (( pog == 1 )) && pyv=$kUpVelocity ;; #up | |
( 'B' ) ;; #down, no use yet | |
( 'C' ) pxv=$kRightVelocity ;; #right | |
( 'D' ) pxv=$kLeftVelocity ;; #left | |
esac | |
fi | |
;; | |
esac | |
fi | |
;; | |
esac | |
fi | |
##advance animation | |
checktile $((py+1)) $px | |
[[ "$nextTile" == "$kBarrierBlock" ]] && pog=1 || pog=0 | |
(( pog == 0 )) && (( (++fps_mod) == kGravityFPSMod )) && pyv=$((pyv+1)) && fps_mod=0 | |
if (( pxv > 0 )) | |
then | |
pxv=$((pxv-1)) | |
if (( (pxv % kVelocityMod) == 0 )) | |
then npx=$((px+1)) | |
fi | |
fi | |
if (( pxv < 0 )) | |
then | |
pxv=$((pxv+1)) | |
if (( (pxv % kVelocityMod) == 0 )) | |
then npx=$((px-1)) | |
fi | |
fi | |
if (( pyv > 0 )) | |
then | |
pyv=$((pyv-1)) | |
if (( (pyv % kVelocityMod) == 0 )) | |
then npy=$((py+1)) | |
fi | |
fi | |
if (( pyv < 0 )) | |
then | |
pyv=$((pyv+1)) | |
if (( (pyv % kVelocityMod) == 0 )) | |
then npy=$((py-1)) | |
fi | |
fi | |
#tile based collision detection | |
checktile $npy $npx | |
case "$nextTile" in | |
( "$kBarrierBlock" ) | |
npx=$px | |
npy=$py | |
;; | |
( "$kCoinBlock" ) | |
twidle $npy $npx ' ' | |
score=$((score+1)) | |
;; | |
( "$kSpikeBlock" ) #lose | |
mvprintw 0 0 $'\x1b[2J' | |
echo -e "$game_over" | |
echo "Final Score: $score" | |
break | |
;; | |
( "$kWinBlock" ) #win | |
mvprintw 0 0 $'\x1b[2J' | |
echo -e "$you_won" | |
echo "Final Score: $score" | |
break | |
;; | |
esac | |
#render player | |
if (( npx != px )) || (( npy != py )) | |
then | |
mvprintw $py $px ' ' | |
draw_player $npy $npx | |
px=$npx | |
py=$npy | |
fi | |
#status line | |
mvprintw "$status_line" 0 $'\x1b[2K' | |
mvprintw "$status_line" 0 "px: $px py: $py pog: $pog score: $score pxv: $pxv pyv: $pyv" | |
done | |
} | |
main "$@" |
How would you run this game ?
This game is written in bash. Most linux distributions including those available on windows subsystem for linux utilize bash as the default command shell. To play the game, save the raw script to a .sh file, then mark it as executable, then you may invoke the script from the bash shell. for more information about how to run bash scripts from the command line of a linux system this stackoverflow post explains things in great detail: https://stackoverflow.com/a/55331703
Test it on https://www.onlinegdb.com/online_bash_shell#, putting on a different file.
Pretty cool.
Shameless plug: https://github.com/gmt4/BashGame/
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How would you run this game ?