Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Flappy bird in 205 bytes (improved!)
<body onload=z=c.getContext`2d`,setInterval(`c.width=W=150,Y<W&&P<Y&Y<P+E|9<p?z.fillText(S++${Y=`,9,9|z.fillRect(p`}*0,Y-=--M${Y+Y},P+E,9,W),P))):p=M=Y=S=6,p=p-6||(P=S%E,W)`,E=49) onclick=M=9><canvas id=c>
@gullyn

This comment has been minimized.

Copy link
Owner Author

@gullyn gullyn commented Nov 29, 2020

Dataurl (enter into search bar to play)
Controls: click to jump

data:text/html,<body onload=z=c.getContext`2d`,setInterval(`c.width=W=150,Y<W&&P<Y&Y<P+E|9<p?z.fillText(S++${Y=`,9,9|z.fillRect(p`}*0,Y-=--M${Y+Y},P+E,9,W),P))):p=M=Y=S=6,p=p-6||(P=S%E,W)`,E=49) onclick=M=9><canvas id=c>

@kimsey0

This comment has been minimized.

Copy link

@kimsey0 kimsey0 commented Nov 29, 2020

Looks like you can save three bytes by replacing logical AND and ORs with their bitwise versions:
data:text/html,<body onload="z=c.getContext`2d`;c.width=c.height=W=401,Q=z.fillRect.bind(z),N=M=>z.fillStyle=M;c.onclick=_=>M=9;M=S=p=0;Y=E=200;setInterval(_=>{!p&&(p=W,P=E*Math.random()),N`red`,Q(0,0,W,W),Y-=M-=.5,p-=8,N`black`,Q(p,0,V=50,P),p<-V?p=0:Q(p,P+E,V,W),((Y<P|Y>P+E)&p<B)|Y>W?(M=S=p=0,Y=E):z.fillText(S++,9,B);Q(0,Y,B,B)},B=24)"><canvas id=c>

@dantheman213

This comment has been minimized.

Copy link

@dantheman213 dantheman213 commented Nov 29, 2020

@nei9ka6

This comment has been minimized.

Copy link

@nei9ka6 nei9ka6 commented Nov 29, 2020

siick

@T-Spoon

This comment has been minimized.

Copy link

@T-Spoon T-Spoon commented Nov 29, 2020

You can save a further 2 bytes by changing the color from black to tan (for a slightly harder to see version):

data:text/html,<body onload="z=c.getContext`2d`;c.width=c.height=W=401,Q=z.fillRect.bind(z),N=M=>z.fillStyle=M;c.onclick=_=>M=9;M=S=p=0;Y=E=200;setInterval(_=>{!p&&(p=W,P=E*Math.random()),N`red`,Q(0,0,W,W),Y-=M-=.5,p-=8,N`tan`,Q(p,0,V=50,P),p<-V?p=0:Q(p,P+E,V,W),((Y<P|Y>P+E)&p<B)|Y>W?(M=S=p=0,Y=E):z.fillText(S++,9,B);Q(0,Y,B,B)},B=24)"><canvas id=c>
@kimsey0

This comment has been minimized.

Copy link

@kimsey0 kimsey0 commented Nov 29, 2020

You can get another five bytes by moving the click event handler to an attribute:
data:text/html,<body onload="z=c.getContext`2d`;c.width=c.height=W=401,Q=z.fillRect.bind(z),N=M=>z.fillStyle=M;M=S=p=0;Y=E=200;setInterval(_=>{!p&&(p=W,P=E*Math.random()),N`red`,Q(0,0,W,W),Y-=M-=.5,p-=8,N`black`,Q(p,0,V=50,P),p<-V?p=0:Q(p,P+E,V,W),((Y<P|Y>P+E)&p<B)|Y>W?(M=S=p=0,Y=E):z.fillText(S++,9,B);Q(0,Y,B,B)},B=24)"><canvas id=c onclick=M=9>

(You could even get another byte by adding that attribute without a space after the onload on body, but that changes the behavior slightly by extending the clickable area.)

@kimsey0

This comment has been minimized.

Copy link

@kimsey0 kimsey0 commented Nov 29, 2020

An alternative to tan which doesn't change the behavior but still saves a byte is #000. It has to be URL encoded in the data: URI, though:

data:text/html,<body onload="z=c.getContext`2d`;c.width=c.height=W=401,Q=z.fillRect.bind(z),N=M=>z.fillStyle=M;M=S=p=0;Y=E=200;setInterval(_=>{!p&&(p=W,P=E*Math.random()),N`red`,Q(0,0,W,W),Y-=M-=.5,p-=8,N`%23000`,Q(p,0,V=50,P),p<-V?p=0:Q(p,P+E,V,W),((Y<P|Y>P+E)&p<B)|Y>W?(M=S=p=0,Y=E):z.fillText(S++,9,B);Q(0,Y,B,B)},B=24)"><canvas id=c onclick=M=9>

@kimsey0

This comment has been minimized.

Copy link

@kimsey0 kimsey0 commented Nov 29, 2020

A pair of unnecessary parentheses can be removed for 330 bytes:

data:text/html,<body onload="z=c.getContext`2d`;c.width=c.height=W=401,Q=z.fillRect.bind(z),N=M=>z.fillStyle=M;M=S=p=0;Y=E=200;setInterval(_=>{!p&&(p=W,P=E*Math.random()),N`red`,Q(0,0,W,W),Y-=M-=.5,p-=8,N`%23000`,Q(p,0,V=50,P),p<-V?p=0:Q(p,P+E,V,W),(Y<P|Y>P+E)&p<B|Y>W?(M=S=p=0,Y=E):z.fillText(S++,9,B);Q(0,Y,B,B)},B=24)"><canvas id=c onclick=M=9>

@jmromrell

This comment has been minimized.

Copy link

@jmromrell jmromrell commented Nov 29, 2020

You can also reduce the coordinate space. This ends up saving characters in several places:

  1. Width and height reduced from 3 to 2 digits
  2. Half-height / player starting Y reduced from 3 to 2 digits
  3. Player block dimensions reduced from 2 to 1 digit, making it shorter to inline the value rather than store in variable B
  4. X velocity of walls (V) reduced to 1 digit, making storage as a variable unecessary
  5. Probably other savings I forget

Incorporating these and all changes from comments above reduces to 321 bytes:

<body onload="z=c.getContext`2d`;c.width=c.height=W=99,Q=z.fillRect.bind(z),N=M=>z.fillStyle=M;M=S=p=0;Y=E=49;setInterval(_=>{!p&&(p=W,P=E*Math.random()),N`tan`,Q(0,0,W,W),Y-=M-=.3,p-=4,N`red`,Q(p,0,9,P),p<-9?p=0:Q(p,P+E,9,W),(Y<P|Y>P+E)&p<5|Y>W?(M=S=p=0,Y=E):z.fillText(S++,0,9);Q(0,Y,5,5)},45)"onclick=M=4><canvas id=c>

Difficulty can be tweaked by adjusting ms/frame (currently 45), gravity (currently M-=.3), X velocity (currently p-=4), or impulse Y velocity (currently M=4). I set them to values that felt reasonable for these dimensions, but did not attempt to perfectly mimic the original difficulty.

JSFiddle: https://jsfiddle.net/d6qwktf8/

@ParkerM

This comment has been minimized.

Copy link

@ParkerM ParkerM commented Nov 29, 2020

You can use this trick to replace E*Math.random() with new Date%E (encoded: new Date%25E) to save a few more bytes.

@cers

This comment has been minimized.

Copy link

@cers cers commented Nov 29, 2020

Using some of the tricks from above (though not all), and some of my own, here is a version in 270b

<body onload="z=c.getContext`2d`,Q=z.fillRect.bind(z),M=S=p=0,P=Y=E=99,setInterval('c.height=W=c.width,Y-=M-=.5,p-=8,p<-50?(p=W,P=new Date%E):((Y<P|Y>P+E)&p<B)|Y>W?(M=S=0,Y=E,p=W):z.fillText(S++,9,B),Q(0,Y,B,B),Q(p,0,50,P),Q(p,P+E,50,W)',B=24)"><canvas id=c onclick=M=9>

@jmromrell

This comment has been minimized.

Copy link

@jmromrell jmromrell commented Nov 29, 2020

262 bytes.

Modified cers' solution to have a single-digit player and wall width, removed unnecessary parentheses, and moved onclick to body:

<body onload="z=c.getContext`2d`,Q=z.fillRect.bind(z),M=S=p=0,P=Y=E=99,setInterval('c.height=W=c.width,Y-=M-=.5,p-=8,p<-9?(p=W,P=new Date%E):(Y<P|Y>P+E)&p<9|Y>W?(M=S=0,Y=E,p=W):z.fillText(S++,0,9),Q(0,Y,9,9),Q(p,0,9,P),Q(p,P+E,9,W)',24)"onclick=M=9><canvas id=c>

https://jsfiddle.net/38be10vy/

@gullyn

This comment has been minimized.

Copy link
Owner Author

@gullyn gullyn commented Nov 29, 2020

@jmromrell I removed a pair of unnecessary brackets to get it down to 262 bytes.

<body onload="z=c.getContext`2d`,Q=z.fillRect.bind(z),M=S=p=0,P=Y=E=99,setInterval('c.height=W=c.width,Y-=M-=.5,p-=8,p<-9?(p=W,P=new Date%E):(Y<P|Y>P+E)&p<9|Y>W?(M=S=0,Y=E,p=W):z.fillText(S++,0,9),Q(0,Y,9,9),Q(p,0,9,P),Q(p,P+E,9,W)',24)"onclick=M=9><canvas id=c>

@jmromrell

This comment has been minimized.

Copy link

@jmromrell jmromrell commented Nov 29, 2020

@gullyn Had actually just caught that in an edit right before you commented. :)

@MadonnaMat

This comment has been minimized.

Copy link

@MadonnaMat MadonnaMat commented Nov 30, 2020

<body onload="z=c.getContext`2d`,Q=z.fillRect.bind(z),M=S=p=7,P=Y=E=98,setInterval('c.height=W=c.width,Y-=M-=.1,!(p-=6)?(p=W,P=new Date%E):(Y<P|Y>P+E)&p<7|Y>W?(M=S=0,Y=E,p=W):z.fillText(S++,0,7),Q(0,Y,7,7),Q(p,0,7,P),Q(p,P+E,7,W)',14)"onclick=M=3><canvas id=c>

By reducing the interval rate to 14 so that by setting p to 6 it is guaranteed to hit 0 on the default canvas size of 300 (along with some other creature conforts) was able to get rid of the p-=8,p<-9? in favor of a !(p-=6)?
Reduces to an even 260

@HouQiming

This comment has been minimized.

Copy link

@HouQiming HouQiming commented Nov 30, 2020

242 bytes with optimized initialization and Q formulation:

<body onload="z=c.getContext`2d`,p=Y=Q=',9|z.fillRect(',setInterval('c.height=W=c.width,p?Y<W&&Y>P&Y<P+E|p>9?z.fillText(S++,0'+Q+'0,Y-=M-=.5,9'+Q+'p-=8,0'+Q+'p,P+E,9,W),P))):(p=M=S=0,Y=E=99):(p=W+4,P=new Date%E)',24)"onclick=M=9><canvas id=c>
@gullyn

This comment has been minimized.

Copy link
Owner Author

@gullyn gullyn commented Nov 30, 2020

Got it down to 239 by using template strings (eg. "foo"+Q+"bar" -> `foo${Q}bar`)

<body onload="z=c.getContext`2d`,p=Y=Q=',9|z.fillRect(',setInterval(`c.height=W=c.width,p?Y<W&&Y>P&Y<P+E|p>9?z.fillText(S++,0${Q}0,Y-=M-=.5,9${Q}p-=8,0${Q}p,P+E,9,W),P))):(p=M=S=0,Y=E=99):(p=W+4,P=new Date%E)`,24)"onclick=M=9><canvas id=c>

@gullyn

This comment has been minimized.

Copy link
Owner Author

@gullyn gullyn commented Nov 30, 2020

And 232 by changing new Date%E to S%E, not completely random now, but it's basically the same

<body onload="z=c.getContext`2d`,p=Y=Q=',9|z.fillRect(',setInterval(`c.height=W=c.width,p?Y<W&&Y>P&Y<P+E|p>9?z.fillText(S++,0${Q}0,Y-=M-=.5,9${Q}p-=8,0${Q}p,P+E,9,W),P))):(p=M=S=0,Y=E=99):(p=W+4,P=S%E)`,24)"onclick=M=9><canvas id=c>

@gullyn

This comment has been minimized.

Copy link
Owner Author

@gullyn gullyn commented Nov 30, 2020

Now 228 after changing c.height=W=c.width to c.height=W=300, saves 4 bytes with exact same functionality

<body onload="z=c.getContext`2d`,p=Y=Q=',9|z.fillRect(',setInterval(`c.height=W=300,p?Y<W&&Y>P&Y<P+E|p>9?z.fillText(S++,0${Q}0,Y-=M-=.5,9${Q}p-=8,0${Q}p,P+E,9,W),P))):(p=M=S=0,Y=E=99):(p=W+4,P=S%E)`,24)"onclick=M=9><canvas id=c>

@HouQiming

This comment has been minimized.

Copy link

@HouQiming HouQiming commented Nov 30, 2020

Got to 205 with "redesigned" gameplay.

<body onload=z=c.getContext`2d`,setInterval(`c.width=W=150,Y<W&&P<Y&Y<P+E|9<p?z.fillText(S++${Y=`,9,9|z.fillRect(p`}*0,Y-=--M${Y+Y},P+E,9,W),P))):p=M=Y=S=6,p=p-6||(P=S%E,W)`,E=49) onclick=M=9><canvas id=c>

@kres0345

This comment has been minimized.

Copy link

@kres0345 kres0345 commented Nov 30, 2020

You could change the controls and remove 2 bytes (203 bytes), by using oncut instead of onclick (ctrl+x to jump).

<body onload=z=c.getContext`2d`,setInterval(`c.width=W=150,Y<W&&P<Y&Y<P+E|9<p?z.fillText(S++${Y=`,9,9|z.fillRect(p`}*0,Y-=--M${Y+Y},P+E,9,W),P))):p=M=Y=S=6,p=p-6||(P=S%E,W)`,E=49) oncut=M=9><canvas id=c>

@plibither8

This comment has been minimized.

Copy link

@plibither8 plibither8 commented Nov 30, 2020

This has got to be one of the funnest gist threads I've read in a while.

@kimsey0

This comment has been minimized.

Copy link

@kimsey0 kimsey0 commented Nov 30, 2020

This has got to be one of the funnest gist threads I've read in a while.

Yeah, it reads more like a chat on codegolf.stackexchange.com

@cerw

This comment has been minimized.

Copy link

@cerw cerw commented Nov 30, 2020

Mad

@uninhm

This comment has been minimized.

Copy link

@uninhm uninhm commented Nov 30, 2020

You could change the controls and remove 2 bytes (203 bytes), by using oncut instead of onclick (ctrl+x to jump).

But that will remove mobile compatibility. It's a too high price for 2 bytes.

@kres0345

This comment has been minimized.

Copy link

@kres0345 kres0345 commented Nov 30, 2020

You could change the controls and remove 2 bytes (203 bytes), by using oncut instead of onclick (ctrl+x to jump).

But that will remove mobile compatibility. It's a too high price for 2 bytes.

I guess you are right, but then again, do the mobile browsers even support data URI's? (e.g. data:text/html,)

@dicer2000

This comment has been minimized.

Copy link

@dicer2000 dicer2000 commented Nov 30, 2020

Making decoding this extra credit for my 201 Programming Languages class. Thanks for the great gist

@spawluk

This comment has been minimized.

Copy link

@spawluk spawluk commented Nov 30, 2020

How does it know that c is canvas indeed?

@ParkerM

This comment has been minimized.

Copy link

@ParkerM ParkerM commented Nov 30, 2020

How does it know that c is canvas indeed?

Here's a succinct explanation that mentions this from the spec:

The HTML5 standard specifies that the window object must have a property key whose value is elem if...

  • there is exactly one DOM element elem whose property id has the value key.
  • there is exactly one DOM element elem whose property name has the value key. elem’s tag must be one of: a, applet, area, embed, form, frame, frameset, iframe, img, object.

So by the first bullet there, <canvas id=c> creates a property window.c referencing the canvas element, which places it in the global scope by default. You can verify this by replacing references to c with this.c since this === window at the top level.

@SylveonBottle

This comment has been minimized.

Copy link

@SylveonBottle SylveonBottle commented Nov 30, 2020

Making decoding this extra credit for my 201 Programming Languages class. Thanks for the great gist

"Decode this and tell us what it does"
Answer: It's flappy bird

@spawluk

This comment has been minimized.

Copy link

@spawluk spawluk commented Nov 30, 2020

@ParkerM I was looking for that. Thank you a lot :)

@MDeiml

This comment has been minimized.

Copy link

@MDeiml MDeiml commented Nov 30, 2020

Maybe it would be possible to make this even shorter by using SVGs instead of canvas. For example this is enough to draw a whole example scene.

<svg><path d="M0 75H9M99 0V60M99 99V999" stroke=tan stroke-width=9>

This also has the added benifit, that we could use onload on the svg and don't need a body

@farhadnowzari

This comment has been minimized.

Copy link

@farhadnowzari farhadnowzari commented Nov 30, 2020

Thats amazing, I'm speechless

@kres0345

This comment has been minimized.

Copy link

@kres0345 kres0345 commented Nov 30, 2020

@ParkerM how come document.getElementById is a thing then?

@jmromrell

This comment has been minimized.

Copy link

@jmromrell jmromrell commented Dec 1, 2020

I've managed to get a SVG solution at 227 characters:

<svg onload="Y=G=T=0;W=99;setInterval('T%W?p.setAttribute(`d`,`M${W-T%W} ${G}h9V0h-9Zv75h9V150h-9ZM0 ${Y}v9h9v-9H0`):Y>G&&Y<G+75?G=75*Math.random():V=Y=T=0;Y-=V-=.03;t.innerHTML=T++',9)"onclick=V=2><path id=p /><text id=t y=11>

https://jsfiddle.net/7wz02hdr/2/

Among other things, I tried making the wall X coordinate time-based (W-T%W) to remove a variable. That might be useful in the other solution as well. This means the possible collision with the wall happens when T%W==0 which also shortens the ternary conditions. It also makes resetting after a loss trivial V=Y=T=0.

It is possible this may beat the canvas solution with some optimization.
Ideas for optimization:

  1. Drawing player/walls using the path stroke instead of enclosed shapes may save characters, but at the cost of including path attributes stroke=red stroke-width=9
  2. Re-arranging order of drawn shapes and/or coordinates within shapes (could perhaps make the two walls identical except starting Y and store in a variable)
  3. May be able to get away with bitwise and/or in places
  4. Getting rid of Math.random() (I had a hard time making new Date%75 appear random enough)
  5. Figuring out how to make a more concise acceleration be playable, currently 3 characters .03
@jmromrell

This comment has been minimized.

Copy link

@jmromrell jmromrell commented Dec 1, 2020

Bitwise and does save a character (now 226):

<svg onload="Y=G=T=0;W=99;setInterval('T%W?p.setAttribute(`d`,`M${W-T%W} ${G}h9V0h-9Zv75h9V150h-9ZM0 ${Y}v9h9v-9H0`):Y>G&Y<G+75?G=75*Math.random():V=Y=T=0;Y-=V-=.03;t.innerHTML=T++',9)"onclick=V=2><path id=p /><text id=t y=11>

https://jsfiddle.net/usgLxtcn/

@jmromrell

This comment has been minimized.

Copy link

@jmromrell jmromrell commented Dec 1, 2020

Reached 212 characters on the SVG version by realizing I could set the innerHTML of this in the SVG to avoid needing to provide IDs for the inner path and text, as well as avoiding having to call setAttribute:

<svg id=c onload="Y=G=T=0;W=99;setInterval('T%W?0:Y>G&Y<G+75?G=75*Math.random():V=Y=T=0;Y-=V-=.03;c.innerHTML=`<path d=\'M${W-T%W} ${G}h9V0h-9Zv75h9V150h-9ZM0 ${Y}v9h9v-9H0\' /><text y=11>${T++}`',9)"onclick=V=2>

https://jsfiddle.net/ek4g0rb2/1/

@uninhm

This comment has been minimized.

Copy link

@uninhm uninhm commented Dec 1, 2020

do the mobile browsers even support data URI's? (e.g. data:text/html,)

Yes

@jmromrell

This comment has been minimized.

Copy link

@jmromrell jmromrell commented Dec 1, 2020

Since the other solution does it, I guess I can change the SVG one to use new Date%N instead of N*Math.random() to save another 5 bytes.
This brings the SVG version so 207 bytes (2 bytes larger than the canvas version):

<svg id=c onload="Y=G=T=0;W=99;setInterval('T%W?0:Y>G&Y<G+75?G=new Date%75:V=Y=T=0;Y-=V-=.03;c.innerHTML=`<path d=\'M${W-T%W} ${G}h9V0h-9Zv75h9V150h-9ZM0 ${Y}v9h9v-9H0\' /><text y=11>${T++}`',9)"onclick=V=2>

https://jsfiddle.net/nt12Lewp/

@jmromrell

This comment has been minimized.

Copy link

@jmromrell jmromrell commented Dec 1, 2020

SVG version is now 205 bytes (tied with canvas version):

<svg id=c onload="Y=G=T=0;setInterval('T%99?0:Y>G&Y<G+75?G=new Date%75:V=Y=T=0;Y-=V-=.03;c.innerHTML=`<path d=\'M${99-T%99} ${G}h9V0h-9Zv75h9V150h-9ZM0 ${Y}v9h9v-9H0\' /><text y=11>${T++}`',9)"onclick=V=2>

https://jsfiddle.net/nt12Lewp/1/

I inlined the width variable W, saving 2 characters.

@liamstrilchuk

This comment has been minimized.

Copy link

@liamstrilchuk liamstrilchuk commented Dec 1, 2020

@jmromrell It can get down to 198 if you change new Date%N to T%75, which makes it not completely random now, but close enough

Also set the text y position to 9, and remove a space that's not needed at the end of the path element

https://jsfiddle.net/3fjaL0k6/

@liamstrilchuk

This comment has been minimized.

Copy link

@liamstrilchuk liamstrilchuk commented Dec 1, 2020

@jmromrell and 196 if you inline the W variable like you said.

https://jsfiddle.net/7qL2zd6x/

@jmromrell

This comment has been minimized.

Copy link

@jmromrell jmromrell commented Dec 1, 2020

I don't like leaving the text obscured. I personally feel it is a buggy implementation of flappy bird if the score is off the screen, and is worth eating a character for. I think the other changes are great though.

197 bytes if the score is left on screen:

<svg id=c onload="Y=G=T=0;setInterval('T%99?0:Y>G&Y<G+75?G=T%75:V=Y=T=0;Y-=V-=.04;c.innerHTML=`<path d=\'M${99-T%99} ${G}h9V0h-9Zv75h9V150h-9ZM0 ${Y}v9h9v-9H0\'/><text y=11>${T++}`',9)"onclick=V=2>

https://jsfiddle.net/z3bmtg5u/

@gullyn

This comment has been minimized.

Copy link
Owner Author

@gullyn gullyn commented Dec 1, 2020

@jmromrell might as well make the text y 13, so it is fully on the screen, unfortunately it's no longer dataurl safe now

@jmromrell

This comment has been minimized.

Copy link

@jmromrell jmromrell commented Dec 1, 2020

Reached 195 bytes by concatenating the score to the string instead of templating:

<svg id=c onload="Y=G=T=0;setInterval('T%99?0:Y>G&Y<G+75?G=T%75:V=Y=T=0;Y-=V-=.04;c.innerHTML=`<path d=\'M${99-T%99} ${G}h9V0h-9Zv75h9V150h-9ZM0 ${Y}v9h9v-9H0\'/><text y=11>`+T++',9)"onclick=V=2>

https://jsfiddle.net/z3bmtg5u/1/

@jmromrell

This comment has been minimized.

Copy link

@jmromrell jmromrell commented Dec 1, 2020

I'm fine with text at y=13. Y=11 was on-screen for me. I guess default font is platform dependent. 😄
Didn't realize we lost dataurl compatibility along the way. Wonder what broke that. :/

@gullyn

This comment has been minimized.

Copy link
Owner Author

@gullyn gullyn commented Dec 1, 2020

@jmromrell I'm guessing either the slashes or the backslashes. The website I looked at said & is not dataurl safe and we need to encode it, not sure how we got away with that

@pratyushmittal

This comment has been minimized.

Copy link

@pratyushmittal pratyushmittal commented Dec 1, 2020

Can someone please post an unminified version too? Would love to read and understand the brilliant algorithm.

@jmromrell

This comment has been minimized.

Copy link

@jmromrell jmromrell commented Dec 1, 2020

@pratyushmittal Happy to explain!

The SVG solution actually fails to run if you put line breaks in it since most of it is a string, but I'll add them anyways for explanation purposes:

<svg id=c                             //Give the SVG an ID for reference later
     onload="Y=G=T=0;                 //Must initialize Y (player Y location), G (gap distance from top), and T (time) to 0, each assignment returns the new result (0) which is used for the next assignment
             setInterval('            //setInterval will repeatedly call the following function, acting as the game loop
                                      //The following is particularly ordered to avoid needing parens https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
               T%99                   //Zero is treated a "falsey", so this will be "false" whenever T is divisible by 99 (happens at the moment the walls and player are both at Y=0)
                 ? 0                  //Ternary operator X ? Y : Z will return Y when X is truthy or Z when X is falsey, I'm using it as a concise if statement and don't care about the true case, so just return zero
                 : (Y>G) & (Y<(G+75)) //If player is below the top of the gap and above the bottom of the gap (single & is bitwise and, which works fine here)
                     ? G=T%75         //If the player is in the gap, set a new gap height (not chosen randomly, instead is equal to the remainder of the current time/score divided by 75)
                     : V=Y=T=0;       //If the player hit a wall, reset V (player velocity), Y (player height), and T (time/score) to zeros to restart
               Y-=V-=.04;             //Set V (player velocity) to V-.04 (velocity changed by gravity), returning the new V, THEN set Y to Y-newV to make the player move by the current velocity
               c.innerHTML=`          //Set the children of element with ID c (the svg) to the following path and text elements:
                 <path d=\'           //Start path element, d (draw?) string quote must be escaped as we have already nested unescaped ", ', and `
                                      //Path commands used below documented here: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Path_commands
                   M ${99-T%99} ${G}  //Move to location X=(99-T%99) Y=G, X will count down from 99 to 0 over and over as time increments, G is the distance from top of screen to the gap in the wall
                   h 9                //Move right 9 pixels relative to current location
                   V 0                //Move vertically to Y=0
                   h -9               //Move left 9 pixels relative to current location
                   Z                  //Return to starting location (completed top wall rectangle, automatically fills the completed shape with black)
                   v 75               //Move 75 pixels down (height of gap) from current location, now at top left corner of bottom wall
                   h 9                //Move right 9 pixels relative to current location
                   V 150              //Move vertically to Y=150 (bottom of image)
                   h -9               //Move left 9 pixels
                   Z                  //Move to starting location (overshoots to top left of top wall, filling in the now-enclosed bottom wall)
                   M 0 ${Y}           //Move to location X=0 Y=Y (Y variable is the player's Y location)
                   v 9                //Move down 9 pixels
                   h 9                //Move right 9 pixels
                   v -9               //Move up 9 pixels
                   H 0                //Move horizontally to X=0, enclosing the player's 9x9 square (9 chosen for player and wall width due to being largest single-digit value)
                   \'/>               //End of path string/element
                 <text y=11>          //Start of text, y must be provided or text is off-screen, setting y=9 causes text to be partially off-screen but saves a byte
                 `+T++',              //Increment time and concat as body for text, end function string as first argument of setInterval
             9)"                      //Remainder of onload is the second argument to setInterval, setting an interval of 9ms (111 fps, chosen due to it being slowest one-digit rate)
     onclick=V=2>                     //When you click on the SVG it sets the current velocity to 2 pixels/frame upwards

Maybe this explanation might help you find more improvements. 😉

@pratyushmittal

This comment has been minimized.

Copy link

@pratyushmittal pratyushmittal commented Dec 1, 2020

@jmromrell Thanks a lot for the detailed explanation. It is very interesting.

@addyosmani

This comment has been minimized.

Copy link

@addyosmani addyosmani commented Dec 1, 2020

A small riff on @jmromrell's excellent version allows this to scale better for 'full-screen' play but takes us back closer to 240 bytes.

<svg height="100%" width="100%" viewBox="0 0 290 190" id=c onload="Y=G=T=0;setInterval('T%99?0:Y>G&Y<G+75?G=T%75:V=Y=T=0;Y-=V-=.04;c.innerHTML=`<path d=\'M${99-T%99} ${G}h9V0h-9Zv75h9V150h-9ZM0 ${Y}v9h9v-9H0\'/><text y=11>${T++}`',9)"onclick=V=2>

It may be possible to address viewport scaling in fewer bytes with transform: scale(X) (e.g #c { transform: scale(2);} if we could avoid needing to set height/width/viewBox.

@emu-byte

This comment has been minimized.

Copy link

@emu-byte emu-byte commented Dec 2, 2020

@kres0345 Mobile browsers do support Data URIs as shown on this Can I Use page.
Although another two bytes would be nice, I don't think that is enough of a reward for mobile compatibility.

@MDeiml

This comment has been minimized.

Copy link

@MDeiml MDeiml commented Dec 28, 2020

You can actually just remove the "Z" and the "H0" from the path:

<svg id=c onload="Y=G=T=0;setInterval('T%99?0:Y>G&Y<G+75?G=T%75:V=Y=T=0;Y-=V-=.04;c.innerHTML=`<path d=\'M${99-T%99} ${G}h9V0h-9Zv75h9V150h-9M0 ${Y}v9h9v-9\'/><text y=11>`+T++',9)"onclick=V=2>

which is 192 bytes. This means the path is no longer closed, but it displays the same way because it is closed implicitly when it gets filled. The downside is that this is not very well defined behavior - I think - so some browsers could display it differently.

@MDeiml

This comment has been minimized.

Copy link

@MDeiml MDeiml commented Mar 30, 2021

<svg id=c onload="Y=G=T=0;setInterval('T%99?Y-=V-=.1:Y>G&Y<G+60?G=T%75:V=Y=T=0;c.innerHTML=`<path d=\'M${99-T%99} ${G}h9V0h-9Zv60h9V150h-9M0 ${Y}v9h9v-9\'/><text y=11>`+T++',9)"onclick=V=3>

189 bytes. But a bit more difficult (like the real flappy bird)

@mwaitzman

This comment has been minimized.

Copy link

@mwaitzman mwaitzman commented Mar 30, 2021

it's been so long that I forgot how to run this...

@MDeiml

This comment has been minimized.

Copy link

@MDeiml MDeiml commented Mar 30, 2021

You can try this URL:

data:text/html,%3Csvg id%3Dc onload%3D"Y%3DG%3DT%3D0%3BsetInterval('T%2599%3FY-%3DV-%3D.1%3AY%3EG%26Y%3CG%2B60%3FG%3DT%2575%3AV%3DY%3DT%3D0%3Bc.innerHTML%3D`%3Cpath d%3D%5C'M%24%7B99-T%2599%7D %24%7BG%7Dh9V0h-9Zv60h9V150h-9M0 %24%7BY%7Dv9h9v-9%5C'%2F%3E%3Ctext y%3D11%3E`%2BT%2B%2B'%2C9)"onclick%3DV%3D3%3E
@mwaitzman

This comment has been minimized.

Copy link

@mwaitzman mwaitzman commented Mar 30, 2021

thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment