├── README.md ├── flappybash.sh ├── minify.py ├── release └── flappybash.sh └── requirements-dev.txt /README.md: -------------------------------------------------------------------------------- 1 | # flappybash 2 | 3 | Can _you_ beat the Unix pipes? 4 | 5 | ![FLAPPY BASH](http://xion.io/images/flappybash.jpg) 6 | 7 | ## Features 8 | 9 | * State-of-the-art terminal graphics 10 | * Procedurally generated sound effects 11 | * Adjustable difficulty to challenge beginners and pros alike 12 | * Hours of engaging gameplay 13 | 14 | ## Requirements 15 | 16 | * Pentium processor or higher 17 | * Unix-like operating system with a modern terminal (xterm, UTF-8) 18 | * bash 4.0+ or compatible shell 19 | 20 | ### Recommended 21 | 22 | * Linux operating system supporting ALSA 23 | * Sound Blaster 16 or higher 24 | * Speakers 25 | 26 | ## Manual 27 | 28 | Run it normally: 29 | 30 | ./flappybash.sh 31 | 32 | Press `SPACE` for things to happen. 33 | 34 | Press `q` to quit. 35 | 36 | You can change the effective screen width by passing an argument: 37 | 38 | ./flappybash.sh 120 39 | 40 | Lower values make the game more difficult. Anything below 80 is probably not for the faint of heart. 41 | 42 | ## Troubleshooting 43 | 44 | #### I only see gibberish, and then the game ends. 45 | 46 | Your bash is too old. This most likely means you're running OSX 47 | and you'd want to use Homebrew to install an up to date one: 48 | 49 | brew install bash 50 | 51 | Check that `bash --version` is at least 4.0 and run the script again. 52 | -------------------------------------------------------------------------------- /flappybash.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # ^ Unlike #!/bin/bash, this shebang will likely find bash 4.0 on OSX if installed. 3 | 4 | # FLAPPY BASH 5 | # by Xion 6 | 7 | W=${1-`tput cols`} 8 | H=${2-`tput lines`} 9 | W2=$[W/2] 10 | H2=$[H/2] 11 | H4=$[H/4] 12 | 13 | DT=0.05 # 20 FPS 14 | G=2 # minimum height of the pipe gap (is double this number) 15 | 16 | K=. # last pressed key (dot is ignored) 17 | X=2 18 | Y=$H2 19 | VY=0 # vertical velocity 20 | AY=99 # vertical acceleration 21 | D=0 22 | # current pipe is stored as an array of $H chars 23 | P=( ) # start as empty array 24 | PX='' # pipe's current X 25 | PVX=-99 26 | S=0 # score 27 | 28 | 29 | # update & draw, called every $DT seconds 30 | a() { 31 | # Update 32 | ######## 33 | 34 | case "$K" in 35 | q) exit ;; 36 | *[!\ ]*) ;; # non-space 37 | *) VY=-64; K=. ;; # space 38 | esac 39 | 40 | VY=`_ "$VY+($AY*$DT)"` 41 | Y=`_ "y=$Y+($VY*$DT);if(y<0)0 else if(y>$H)$H else y"`; tY=`t $Y` 42 | 43 | PX=`_ "$PX+($PVX*$DT)"`; PX=`t $PX` 44 | # the -le check below is bad and I should feel bad, but doing it properly 45 | # (i.e. keeping previous and current value of PX, and checking if X is in between) 46 | # won't fit within the limit 47 | if [ $PX -le $X ] && [ ${P[tY]} = '█' ]; then D=1; exit; fi 48 | if [ $PX -le 0 ]; then 49 | ( s 700 0.1; s 350 0.1 )& # be-boop 50 | S=$[S+1] 51 | np 52 | fi 53 | 54 | # Draw 55 | ###### 56 | 57 | clear 58 | 59 | # draw the pipe 60 | for ((i=1; i<=$H; i++)) do 61 | p $PX $i "\e[1;32;49m${P[i]}" # bold green-on-default 62 | done 63 | 64 | # draw the player 65 | p $X $tY "\e[1;37;41mB" # bold white-on-red 66 | 67 | # draw the score 68 | p `_ "$W2-5"` 2 "\e[1;37;49mScore: $S" # bold white-on-default 69 | 70 | # schedule the next call of this function 71 | ( sleep $DT; kill -14 $$ )& 72 | } 73 | 74 | # create a new pipe and place it all the way to the right 75 | np() { 76 | _u=$[H4+RANDOM%(H2-H4-G)] # upper pipe is < this coordinate 77 | _l=$[H2+G+RANDOM%(H2-H4-G)] # lower pipe is >= this one 78 | 79 | for ((i=1; i<$_u; i++)) do P[i]='█'; done 80 | for ((i=$_u; i<$_l; i++)) do P[i]=' '; done 81 | for ((i=$_l; i<=$H; i++)) do P[i]='█'; done 82 | PX=$[W-1] # start from the right side 83 | } 84 | 85 | # cleanup and exit 86 | q() { 87 | $p "\e[?12l\e[?25h" # cursor on 88 | tput rmcup 89 | $e -en "\e[0m" # reset text style and color 90 | clear 91 | 92 | # handle failure 93 | if [ $D -gt 0 ]; then 94 | $e -en "\a"; # BEL 95 | $p "score:$S\ngit gud\n"; 96 | fi 97 | } 98 | 99 | 100 | # put text at given position: p $x $y $text 101 | p() { $e -en "\e[$2;${1}f$3"; } 102 | # play a sine wave (requires ALSA): s $frequency $duration 103 | s() { ( speaker-test >$n -t sine -f $1 )& _p=$!; sleep $2; kill -9 $_p; } 104 | # wrapper over bc (basic POSIX calculator) that makes all computations shorter 105 | _() { $e "$*"|bc; } 106 | # truncate fractional part 107 | t() { $p "%.*f" 0 $1; } 108 | 109 | 110 | p=printf 111 | e=echo 112 | n=/dev/null 113 | 114 | 115 | # 116 | # Main 117 | # 118 | 119 | exec 2>$n # swallow errors, because obviously they're just noise 120 | tput smcup 121 | $p "\e[?25l" # cursor off 122 | trap q ERR EXIT 123 | 124 | $p "\e]0;FLAPPY BASH\007" # set terminal title 125 | trap a 14 # handler for the ALRM signal 126 | 127 | np # create new pipe 128 | a 129 | while :; do 130 | read -rsn1 K 131 | done 132 | -------------------------------------------------------------------------------- /minify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Script to minify that other script. 4 | """ 5 | from __future__ import print_function 6 | 7 | import os 8 | import re 9 | import sys 10 | 11 | import bs4 12 | import requests 13 | 14 | 15 | DEFAULT_INPUT_FILE = './flappybash.sh' 16 | DEFAULT_OUTPUT_FILE = './release/flappybash.sh' 17 | 18 | MINIFIER_URL = 'http://bash-minifier.appspot.com/' 19 | 20 | LIMIT = 1234 21 | 22 | 23 | def main(argv): 24 | args = argv[1:] 25 | filename = args[0] if args else DEFAULT_INPUT_FILE 26 | with open(filename) as f: 27 | source = f.read() 28 | 29 | response = requests.post(MINIFIER_URL, data={'user_source': source}) 30 | response.raise_for_status() 31 | 32 | html = bs4.BeautifulSoup(response.text, 'html.parser') 33 | textarea = [ta for ta in html.find_all('textarea') 34 | if ta.get('name') != 'user_source'][0] 35 | minified = textarea.text 36 | 37 | # fix minifier bugs: 38 | # * semicolon before function declaration requires additional whitespace 39 | # * ampersand (for background jobs) gets an erroneous semicolon 40 | minified = re.sub( 41 | r';(\w+)\(\)', lambda m: '; %s()' % m.group(1), minified) 42 | minified = minified.replace('&;', '&') 43 | 44 | # copy the original shebang and check final size against the limit 45 | minified = (source.splitlines()[0] + '\n' + minified).encode('utf8') 46 | size = len(minified) 47 | if size < LIMIT: 48 | print("Phew, output is below the size limit (%s < %s)" % ( 49 | size, LIMIT), file=sys.stderr) 50 | else: 51 | print("ZOMG we're hitting the size limit!!!one (%s >= %s)" % ( 52 | size, LIMIT), file=sys.stderr) 53 | 54 | # write it to the file or standard output 55 | output = sys.stdout 56 | if output.isatty(): 57 | output = open(DEFAULT_OUTPUT_FILE, 'w') 58 | os.chmod(DEFAULT_OUTPUT_FILE, 0755) 59 | with output as out: 60 | print(minified, end='', file=out) 61 | 62 | 63 | if __name__ == '__main__': 64 | sys.exit(main(sys.argv) or 0) 65 | -------------------------------------------------------------------------------- /release/flappybash.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | W=${1-`tput cols`};H=${2-`tput lines`};W2=$[W/2];H2=$[H/2];H4=$[H/4];DT=0.05;G=2;K=.;X=2;Y=$H2;VY=0;AY=99;D=0;P=( );PX='';PVX=-99;S=0; a() { case "$K" in q) exit;;*[!\ ]*);;*) VY=-64;K=.;;esac;VY=`_ "$VY+($AY*$DT)"`;Y=`_ "y=$Y+($VY*$DT);if(y<0)0 else if(y>$H)$H else y"`;tY=`t $Y`;PX=`_ "$PX+($PVX*$DT)"`;PX=`t $PX`;if [ $PX -le $X ] && [ ${P[tY]} = '█' ];then D=1;exit;fi;if [ $PX -le 0 ];then ( s 700 0.1;s 350 0.1 )&S=$[S+1];np;fi;clear;for ((i=1;i<=$H;i++)) do p $PX $i "\e[1;32;49m${P[i]}";done;p $X $tY "\e[1;37;41mB";p `_ "$W2-5"` 2 "\e[1;37;49mScore: $S";( sleep $DT;kill -14 $$ )&}; np() { _u=$[H4+RANDOM%(H2-H4-G)];_l=$[H2+G+RANDOM%(H2-H4-G)];for ((i=1;i<$_u;i++)) do P[i]='█';done;for ((i=$_u;i<$_l;i++)) do P[i]=' ';done;for ((i=$_l;i<=$H;i++)) do P[i]='█';done;PX=$[W-1];}; q() { $p "\e[?12l\e[?25h";tput rmcup;$e -en "\e[0m";clear;if [ $D -gt 0 ];then $e -en "\a";$p "score:$S\ngit gud\n";fi;}; p() { $e -en "\e[$2;${1}f$3";}; s() { ( speaker-test >$n -t sine -f $1 )& _p=$!;sleep $2;kill -9 $_p;}; _() { $e "$*"|bc;}; t() { $p "%.*f" 0 $1;};p=printf;e=echo;n=/dev/null;exec 2>$n;tput smcup;$p "\e[?25l";trap q ERR EXIT;$p "\e]0;FLAPPY BASH\007";trap a 14;np;a;while :;do read -rsn1 K;done -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | bs4 2 | requests 3 | --------------------------------------------------------------------------------