Summary

#Golfctober 2017

october 2017

This year, I tried to follow the trend of #inktober or #blocktober, but adapted to JS code golfing.

I bet I could golf (or regolf) one project a day for 31 days, and called it #golfctober.

By chance, the whole Codegolf Team, with some excellent new recruits, joined the game and helped me find some awesome new tricks, and save a lot bytes on projects we couldn't golf any more.

Here's a recap of all the progress and lessons learned!

Cheers to all the authors: @p01, @aemkei, @subzey, @veubeke, @justecorruptio, @mmastrac, @xen_the, @nderscore, @ETHproductions, @benjamin_js, @atesgoral, @adabeezus, @fusselwurm and Senokai !

NB: you can read more about our previous "new golfing tricks" HERE!



Day 1 + day 5: miniCodeEditor

Progress

Previous version (156b)
<body oninput='f.srcdoc=t0[v="value"]+`<script>${t2[v]}</script><style>`+t1[v]'onload='for(i=3;i--;)f.outerHTML+=`<textarea id=t${i} rows=9>`'><iframe id=f>

Get rid of the <body> by using "<iframe onload=...">, use the iframe id as a textarea counter then set it to "f".
<iframe onload='id++<3?outerHTML+=`<textarea oninput="f.srcdoc=t3.value+\`<script>\${t1.value}</script><style>\`+t2.value"id=t${id} rows=9>`:id="f"'>

Make each textarea store its content in f[id] after each input, stop prefixing textareas ids with "t".
<iframe onload='id++<3?outerHTML+=`<textarea oninput="f[id]=value;f.srcdoc=f[3]+\`<script>\${f[1]}</script><style>\`+f[2]"id=${id} rows=9>`:id="f"'>

Final version (142b): remove the textareas id's altogether and directly update f[id] after each input.
<iframe onload='id++<3?outerHTML+=`<textarea oninput="f[${id}]=value;f.srcdoc=f[3]+\`<script>\${f[1]}</script><style>\`+f[2]"rows=9>`:id="f"'>

New tricks





Day 2: MiniBookmarklet

Progress

Previous version (80b)
<p><a id=a>BM<p><textarea oninput=a.href='javascript:(function(){'+value+'})()'> 

New version (64b): use ES6 and include the input in the link
<a id=a>@<input oninput='a.href=`javascript:(a=>{${value}})()`'>

New tricks & reminders





Day 3 + day 14 + day 15: sheet

Sheet (originally 220b long) was entirely rewritten into three versions: a minimal one in 188b, a full-featured one in less than 256b, and a good-looking one (with excel-like headers) in 277b.

source code (full/minimal)

o=b=>{for(i in{}+o)with(M=Math)M[y='ABCD'[i%5]+-~(i/5%6)]=b?document.write(y&&
i<30?`<input placeholder=${y} id=`+y:'<br',` onfocus=value=[l[id]] onblur=l[id
]=value,o()>`):(top[y].value=[/^=/.test(z=l[y])?eval('x'+z):x=z],x-~x?+x:x)};o
(o(l=localStorage))

(o=v=>{for(i in z='<input onblur=o[id]=value;o`.value` id=')y="
ABCD"[i%5]+-~(i/5),v?eval(y+(v+o[y]).replace(/[A-Z]\d/g," +$&"+
v)):document.write(y||'<p ',z,y,' onfocus=value=[o[id]]>')})()

New tricks

The commented source code of each version explains in detail all the (new) tricks we used, like:





Day 4 + day 15: MiniKeyCode

Progress

Previous version (128b)
<body onload='for(i in a=["down","press","up"])eval("onkey"+a[i]+"=function(e){b.innerHTML+=\"<p>key"+a[i]+" \"+e.which}")'id=b>

Use map(), arrow function and temlplate literal
<body onload="['down','press','up'].map(x=>eval(`onkey${x}=e=>b.innerHTML+='<p>key${x} '+e.which`))"id=b>

Use replace() instead of map()
<body onload='eval("down;press;up".replace(/\w+/g,"onkey$&=e=>b.innerHTML+=`<p>key$& `+e.which"))'id=b>

Use innerText and \n instead of innerHTML and <p>
<body onload='eval("down;press;up".replace(/\w+/g,"onkey$&=e=>b.innerText+=`\nkey$& `+e.which"))'id=b>

Use e.type to tell the name of the event
<body onload='onkeydown=onkeyup=onkeypress=e=>b.innerHTML+=e.type+" "+e.which'id=b>

Different approach (a bit longer but more golfable): define three global event listeners
<body onload='onkeydown=onkeyup=onkeypress=e=>b.innerHTML+=`<p>${e.type} `+e.which'id=b>

Create all the event listeners in which the 5th char is "y" (onkey...)
<body onload="for(a in b)b[a[4]=='y'?a:0]=e=>b.innerHTML+=`<p>${e.type} `+e.which"id=b>

Simplify "==y" to ">x", and replace the superfluous ternary test with a "&&"
<body onload="for(a in b)b[a[4]>'x'&&a]=e=>b.innerHTML+=`<p>${e.type} `+e.which"id=b>

Reintroduce innerText
<body onload="for(a in b)b[a[4]>'x'&&a]=e=>b.innerText+=`\n${e.type} `+e.which"id=b>

Replace "\n" with a real line break
<body onload="for(a in b)b[a[4]>'x'&&a]=e=>b.innerText+=`
${e.type} `+e.which"id=b>

Replace the space in each line with a comma (default Array to String behavior)
<body onload="for(a in b)b[a[4]>'x'&&a]=e=>b.innerText+=`
`+[e.type,e.which]"id=b>

Use "let a" in the loop to simplify "e.type" into "a"
<body onload="for(let a in b)b[a[4]>'x'&&a]=e=>b.innerText+=`
`+[a,e.which]"id=b>

New tricks and reminders





Day 6: MiniURI

source code (input/drag&drop)

<input onchange="with(new FileReader)readAsDataURL(files[0]),onload=s=>outerHTML=result"type=file>

<html ondrop="with(new FileReader)readAsDataURL(event.dataTransfer.files[0]),onload=s=>innerHTML=result;return!1"ondragover=return!1>

New tricks

We saved a few bytes here by displaying the URI in plain text instead of putting it inside a <tt> or <input> tag.



Day 7: MiniDragonCurve

Progress

Previous version: dragon punch by p01 (121b)
<body onload=with(c.getContext('2d'))for(m=8e4;m;rotate(--m&-m&m/2?11:-11))fillRect(0,0,1,1),translate(1,0)><canvas id=c>

New version (118b): Use getContext`2d` instead of getContext('2d') and svg instead of body
<canvas id=a><svg onload=with(a.getContext`2d`)for(m=8e4;m;rotate(--m&-m&m/2?11:-11))fillRect(0,0,1,1),translate(1,0)>

Smallest version (112b): no more svg, just a canvas. Click it to start the script
<canvas onclick=with(this.getContext`2d`)for(m=8e4;m;rotate(--m&-m&m/2?11:-11))fillRect(0,0,1,1),translate(1,0)>

New tricks





Day 8: MiniSerpinskiTriangle

Source code

<canvas id=a><svg onload=setInterval('with(a.getContext`2d`)fillText(drawImage(a,(V=t++%3|0)*Z,V%2*Z,150,Z),1,1)',Z=t=75)>

New tricks





Day 9: Lorenz

Progress

Previous version (112b)
<svg onload="setInterval(V=>innerHTML+=`<text x=${Z+=(X+=((Y+=X-(X*Z-Y)/99)-X)/9)*Y/Z-1} y=${40+X}>.`,X=Y=Z=1)">

New version (111b), draws a different attractor
<svg onload="setInterval(V=>innerHTML+=`<text x=${N+=X*(E+=X-(X*N-E)/99)/N-1} y=${N+(X+=(E-X)/9)}>.`,X=E=N=1)">




Day 10: Fill an array with all the numbers from 0 to N

Current record:

[...Array(n).keys()]




Day 11:

Progress

Old nano version (58b)
setInterval('document.title=String.fromCodePoint(i++)',99)

New nano version (54b)
i=0;setInterval(`document.body.innerHTML="&#"+i++`,99)
Old micro version (117b)
<center style="font:50vh/90vh arial"id=a><script>i=0;setInterval('a.innerHTML=String.fromCodePoint(i++)',99)</script>

New micro version (93b)
<center style=font:45vh/2'arial' id=a><svg onload='setInterval(V=>a.innerHTML="&#"+id++,99)'>
Old mini version (208b)
<center id=u><script>i=0;setInterval('u.innerHTML="<div style=\'height:90vh;font:50vh/90vh arial\'>"+String.fromCodePoint(i)+"</div><b>U+"+(1E9+i.toString(16).toUpperCase()).slice((65536>i++)-5)',99)</script>

New mini version (179b)
<center id=u><script>i=0;setInterval(`u.innerHTML="<div style=height:90vh;font:45vh/2'arial'>&#"+i+"</div>U+"+(1E9+i.toString(16).toUpperCase()).slice((65536>i++)-5)`,99)</script>
Old big version (560b)
<body id=u><script>i=q=r=0;k='style="font:4';setInterval('u.'+k+'vh arial;color:#fff;background:hsl("+(i/9+200)+",80%,50%)";w="86JG3eJG32GP8H10O0NG6HQKMOG8NQILJG2HUKKING6HG8H5IG0G0LPTHKIJG6LGcJK0K5PbJ3UdH8H18H7LRI7PJ06G0Q0QG35H5QNNLbS5TK2G0G0P0L0P6eHH7bH95H2Q05eNNU".replace(/[G-U]/g,a=>-9+(a.charCodeAt()-70).toString(16)).split(-9);u.innerHTML=\'<center><div '+k+'5vh/85vh arial;height:85vh">\'+String.fromCodePoint(i)+"</div><b>U+"+(1E9+i.toString(16).toUpperCase()).slice((65536>i++)-5);++q=="0x"+w[r]+0|0&&(r++,i+="0x"+w[r++]+0|0,r++,q=0)',99)</script>

New big version (507b)
<body id=u text=#fff bgcolor=#1ae><script>i=q=r=0;k='style="font:4';setInterval(`u.${k}vh arial";w="86JG3eJG32GP8H10O0NG6HQKMOG8NQILJG2HUKKING6HG8H5IG0G0LPTHKIJG6LGcJK0K5PbJ3UdH8H18H7LRI7PJ06G0Q0QG35H5QNNLbS5TK2G0G0P0L0P6eHH7bH95H2Q05eNNU".replace(/[G-U]/g,a=>-9+(a.charCodeAt()-70).toString(16)).split(-9);u.innerHTML='<center><div ${k}5vh/85vh arial;height:85vh">&#'+i+"</div><b>U+"+(1E9+i.toString(16).toUpperCase()).slice((65536>i++)-5);++q=="0x"+w[r]+0|0&&(r++,i+="0x"+w[r++]+0|0,r++,q=0)`,99)</script>
(huge and epic versions also lost 20 bytes each by using the same techniques)

New tricks





Day 12 + day 15: MiniTetris

The hype has been crazy for this project, just look at the stats on @aemkei's tweet, and the comments on reddit!

Progress

See commit history

Source code

<body id=Z onload="M=e=>P&&(h=0,b=[...B],p=P.map((v,i)=>(E=~i%2,x=eval(e[0]),E?0:b[h|=x<0|x>9|b[y]>>x&1|y>17,y]|=1<<x,y=x)),h),b=C=Array(9),(onkeydown=_=s=>(s?(k=s.which-38)%2-k||M`k?v-!E*k:P[2]-(P[i^1]-P[3])*(E|1)`&&M`v`:G||(P=P||[...'02121303040506161715'.substr(new Date%7*2,8,B=[...C,...b].filter(v=>v^1023||!(S+=s+=100)).slice(-18))],M`v-E`?k=p&=G=M`v`:0,setTimeout(_,k-2?200:20,0)),P=p,S|=b.map(v=>{S+=`
`;for(x=10;x--;)Z.innerText=S+='□■'[G^(v^1023+P&&v>>x&1)]})))(S=P=k=G=0)"style=font:2em/.6'>

New tricks

More information in the project's README and commented source code



Day 13 + day 15 + day 31: Mini ASCII Serpinski

Progress

Previous version: serpinski gasket by p01 (96b)
<body onload=for(A=[],A[49]=i=1;i<3137;)document.write(i++%98?'_#'[A[i]|=A[i-97]^A[i-99]]:'\n')>

Shorter loop, real line breaks, ans use zeros instead of "#" to print black "pixels"
<body onload='for(i=0;i<4<<8;)i++%63>30&&document.write(i%31.5?i&i>>5?`_`:0:`<br>`)'>

Use an svg, with implicit 'document'
<svg onload='for(i=0;i<4<<8;)i++%63>30&&write(i%31.5?i&i>>5?`_`:0:`<br>`)'>

Use %63 instead of %31.5
<svg onload='for(i=0;i<4<<8;)i++%63>30&&write(i%63?i&i>>5?`_`:0:`<br>`)'>

Shorter and simpler loop
<svg onload='for(i=1024;i--;)write(i%63?i%63<32?``:i&i>>5?`_`:0:`<br>`)'>

Simpler test, use <p> instead of <br>
<svg onload='for(i=1024;i--;)write(i%63<32?`<p>`:i&i>>5?`_`:0)'>

Final version (63b): use '&&`_`' instead of '?`_`:0`'
<svg onload='for(i=1024;i--;)write(i%63<32?`<p>`:i&i>>5&&`_`)'>
Bigger output (65b)
<svg onload='for(i=4096;i--;)write(i%127<64?`<p>`:i&i/64?`_`:0)'>

New tricks and reminders





Day 16 + day 17 + 22 + 23: Mini Clifford Attractor + Mini Peter De Jong Attractor + HD versions

Source code

Mini Clifford Attractor (SVG, 220b)
<svg onload="[A,B,C,D]=prompt('A,B,C,D','-1.8,-2,-.5,-.9').split`,`;with(Math)setInterval(V=>{innerHTML+=`<text x=${250+99*(X=sin(A*Y)+C*cos(A*U))} y=${250+99*(Y=sin(B*U)+D*cos(B*Y))}>.`;U=X},U=Y=1)"width=600 height=600>
Mini Clifford Attractor (HD canvas, 280b)
<canvas id=c><svg onload=c.width=c.height=999;z=c.getContext`2d`;[A,B,C,D]=prompt('A,B,C,D','-1.8,-2,-.5,-.9').split`,`;setInterval`with(Math)for(i=1e4;i--;)z.fillStyle='rgba(0,0,0,.01)',z.fillRect(500+199*(X=sin(A*Y)+C*cos(A*U)),500+199*(Y=sin(B*U)+D*cos(B*Y)),1,1${U=Y=1}U=X)`>
Mini Peter De Jong Attractor (SVG, 220b)
<svg onload="[A,B,C,D]=prompt('A,B,C,D','-2.7,-.09,-.86,-2.2').split`,`;with(Math)setInterval(V=>{innerHTML+=`<text x=${250+99*(X=sin(A*Y)-cos(B*U))} y=${250+99*(Y=sin(C*U)-cos(D*Y))}>.`;U=X},U=Y=1)"width=600 height=600>
Mini Peter De Jong Attractor (HD canvas, 280b)
<canvas id=c><svg onload=c.width=c.height=999;z=c.getContext`2d`;[A,B,C,D]=prompt('A,B,C,D','-2.7,-.09,-.86,-2.2').split`,`;setInterval`with(Math)for(i=1e4;i--;)z.fillStyle='rgba(0,0,0,.01)',z.fillRect(500+199*(X=sin(A*Y)-cos(B*U)),500+199*(Y=sin(C*U)-cos(D*Y)),1,1${U=Y=1}U=X)`>

New tricks and reminders





Day 18: Mini Burrows-Wheeler

Source code

Transform
s=>[...s+='ÿ'].map((_,i)=>s[S='slice'](i)+s[S](0,i)).sort().map(x=>x[S](-1)).join``

Untransform
(d,t=d.indexOf`ÿ`)=>[...[...d].keys()].sort((x,y)=>d[a='charCodeAt'](x)-d[a](y)||x-y).map((m,_,i)=>d[t=i[t]]).join``.slice(0,-1)

New tricks





Day 19: atree

Progress

Previous version (137b)
<body onload=setInterval("c.width|=Q=Math.cos;for(i=57;i--;)c.getContext('2d').fillRect(99+i*Q(o),i*3,z=2+Q(o+=14),z)",o=9)><canvas id=c>

New version (134b) use svg instead of body and getcontext`2d` instead of getContext('2d')
<canvas id=c><svg onload=setInterval("c.width|=Q=Math.cos;for(i=57;i--;)c.getContext`2d`.fillRect(99+i*Q(o),i*3,z=2+Q(o+=14),z)",o=9)>

Demake (121b, click to start)
<canvas id=c onclick=setInterval("for(c.width|=i=57;i--;)c.getContext`2d`.fillRect(99+i*Math.cos(o+=14),i*3,i/9,8)",o=1)>




Day 20: Mini Tetris Theme

Source code

with(new AudioContext)with(createOscillator())connect(destination),start(setInterval(V=>frequency.value=`R>AIA>77${d='ARIA>>AIRRA777'}IIWnbWRR${d}`.charCodeAt(++t%64*.75)*6,t=192))

New tricks





Day 21: UTF-8 bytes counter

Progress

v1
s=>encodeURI(s).split(/%..|./).length-1

V2
s=>unescape(encodeURI(s)).length




Day 24: attractorandom

Progress

here

Source code

<a id=l></a><canvas id=a width=900 height=900 style=width:90%;height:80%><svg onload='s=z=>(r=Math.random()*500|0,`+Math.${r&2?"cos":"sin"}(${(r-250)/25}*${"XY"[r&1]})`)
l.href="#"+(l.innerHTML=q=(h=decodeURI(location.hash))?h.slice(1):[s()+s(),s()+s()])
setInterval("for(i=1e4;i--;a.getContext`2d`.fillRect(450+99*X,450+99*Y,.1,.1))[X,Y]=["+q+"]",X=Y=1)'>

New tricks

'Math.random()*N|0' is shorter than '~~(Math.random*N)'



Day 25: Mini Plot

Source code

<input oninput=with(c=a.getContext`2d`)for(c.z=fillRect,c.Z=fillText,z(0,75,W=a.width|=0,1),z(u=150,0,1,W),Z(0,155,70),Z(u,280,70),Z(75,155,9),x=-u;x<u;x+=.01)z(u+x,75-eval(value),1,1) value=y=99/x id=f><p><canvas id=a><svg onload=f.oninput()>

Tricks used

The function is evaluated and drawn 30,000 times in total (for x=0.01, 0.02, 0.03, etc.), to ensure there are no discontinuities in the plot.



Day 26: Mini Tic-Tac-Toe

Source code (full/minimal)

for(i=12;--i;d.write(i%4?`<input type=button value=" "onclick="
value-1?value=d[${i}]='XO'[i++%2]:0;for(b of'1345')for(a of'12359')
for(q of(c=d[a])&&c==d[a-=-b]&c==d[+b+a]?d.all:[])q.value=c"style=
width:30>`:`<br>`))d=document
for(i=12;--i;d.write(i%4?`<a onclick="innerText=d[${i}]=d[${i}]||i
++%2+1;for(b of'1345')for(a of'12359')d[a]&d[a-=-b]&d[+b+a]&&d.
write(d[a])">_</a> `:`<p>`))d=document

Tricks used

There's a very clever way to detect when a player has won, using two loops (one for start, one for offset). More details here!



Day 27: "paste my email here" bookmarklet

Progress

Old version (149b)
javascript:(function(d){d=document.activeElement;if(d.tagName=="INPUT"||d.tagName=="TEXTAREA"||d.contentEditable=="true"){d.value=d.innerHTML="JOHN@DOE.COM"}})()

New version (66b) - stops checking if the focused element is an input, a textarea or if it has contenteditable
javascript:(a=>{with(document.activeElement)value=innerHTML="JOHN@DOE.COM"})()




Day 28: CodegolfIDE

Source code

<link rel=stylesheet href=c.css><textarea id=c><body text=blue id=b onload=b.innerHTML="Hello,World!"></textarea><iframe id=f></iframe><script src=c.js></script><script>E=CodeMirror.fromTextArea(c,{lineNumbers:!0,lineWrapping:!0,mode:"htmlmixed"});(U=z=>f.srcdoc=E.getValue())();E.on("keyup",z=>{console.clear(),document.title=encodeURI(U()).split(/%..|./).length-1+"b"})</script><style>*{margin:0;font-size:12px}.CodeMirror,#f{width:100%;height:50%;border:1px solid;box-sizing:border-box

New tricks and reminders





Day 29: Mini Audio Viz

Progress


Original source code inspired by p01

canvas id=c><script>
x=c.getContext`2d`;
a=new Audio("song.mp3");
a.autoplay=a.loop=!0;
with(new AudioContext)
with(createMediaElementSource(a))
connect(destination),
A=createAnalyser(),
connect(A);
setInterval(z=>{
c.width^=0;
A.getByteFrequencyData(D=new Uint8Array(A.frequencyBinCount));
m=1;
D.map((y,i)=>x.lineTo(i,150-D[i]/150*m++));
x.stroke();
},9)
</script>

(...)

Call the mp3 file "3"
<canvas id=c><svg onload="a=new Audio(3);with(new AudioContext)with(createMediaElementSource(a))connect(destination),connect(A=createAnalyser());setInterval('A.getByteFrequencyData(D=new Uint8Array(c.width=512));D.map((v,i)=>c.getContext`2d`.fillRect(i,150,1,-v*.58))',a.autoplay=a.loop=9)">

Use an audio tag
<canvas id=c><audio src=3 id=a autoplay loop><svg onload="with(new AudioContext)with(createMediaElementSource(a))connect(destination),connect(A=createAnalyser());setInterval('A.getByteFrequencyData(D=new Uint8Array(c.width=512));D.map((v,i)=>c.getContext`2d`.fillRect(i,150,1,-v*.6))',9)">

Replace <svg onload=...> with <audio onplay=...>
<canvas id=c><audio src=3 onplay="with(new AudioContext)with(createMediaElementSource(this))connect(destination),connect(A=createAnalyser());setInterval('A.getByteFrequencyData(D=new Uint8Array(c.width=512));D.map((v,i)=>c.getContext`2d`.fillRect(i,150,1,-v*.6))',9)"autoplay>

Reset the canvas value to its default value, 300, at each loop (c.width^=0), and use it for the size of the frequency data array
<canvas id=c><audio src=3 onplay="with(new AudioContext)with(createMediaElementSource(this))connect(destination),connect(A=createAnalyser());setInterval('A.getByteFrequencyData(D=new Uint8Array(c.width^=0));D.map((v,i)=>c.getContext`2d`.fillRect(i,150,1,-v/2))',9)"autoplay>

New tricks





Day 30: Mini Game Of Life 1 & 2

Progress

ASCII version before (238b)
<body onkeyup="a=b;b=[h='<pre>'];for(i=40;i--;h+='\n')for(j=80;j--;){k=80*i+j;d=0;for(z in e=[1,79,80,81])d+=a[k+e[z]]+a[k-e[z]];h+='<a onclick=innerHTML=q[b['+k+']^=1]>'+q[b[k]=3==d|a[k]&2==d]}innerHTML=h"onload=f.onkeyup(b=q=".@") id=f>

Simplify the loop
<body onkeyup="a=b;b=[h='<pre>'];for(i=k=2048;i--;h+=--k&63?'':'\n'){d=0;for(e of[1,63,64,65])d+=a[k+e]+a[k-e];h+='<a onclick=innerHTML=q[b['+k+']^=1]>'+q[b[k]=3==d|a[k]&2==d]}innerHTML=h"onload=f.onkeyup(b=q=".@") id=f> 

Move d initialization
<body onkeyup="a=b;b=[h='<pre>'];for(i=k=2048;i--;h+=--k&63?'':'\n'){for(e of[d=1,63,64,65])d+=a[k+e]+a[k-e];h+='<a onclick=innerHTML=q[b['+k+']^=1]>'+q[b[k]=[1,a[k]][4-d]|0]}innerHTML=h"onload=f.onkeyup(b=q=".@") id=f>

Move everything
<body onkeyup="a=b;b=[h='<pre>'];for(i=k=2048;d=0,i--;h+=--k&63?'<a onclick=innerHTML=q[b['+k+']^=1]>'+q[b[k]=3==d|a[k]&2==d]:'\n')for(e of[1,63,64,65])d+=a[k+e]+a[k-e];innerHTML=h"onload=f.onkeyup(b=q=".@") id=f> 

Use template literals
<body onkeyup="a=b;b=[h='<pre>'];for(i=k=2048;d=0,i--;h+=--k&63?`<a onclick=innerHTML=q[b[${k}]^=1]>`+q[b[k]=3==d|a[k]&2==d]:'\n')for(e of[1,63,64,65])d+=a[k+e]+a[k-e];innerHTML=h"onload=f.onkeyup(b=q=".@") id=f>

Move everything again and use a line break char inside the <pre tag to make a new line, instead of \n
<body onkeyup="a=b;b=[h='<pre>'];for(i=2048;i--;h+=i&63?`<a onclick=innerHTML=q[b[${i}]^=1]>`+q[b[i]=[1,a[i]][4-d]|0]:`
`)for(e of[d=1,63,64,65])d+=a[i+e]+a[i-e];innerHTML=h"onload=f.onkeyup(b=q=".@") id=f>

New version (200b) - Move the alphabet (.@) before the <pre> tag
<body onkeyup="a=b;b=[h='.@<pre>'];for(i=2048;i--;h+=i&63?`<a onclick=innerHTML=h[b[${i}]^=1]>`+h[b[i]=[1,a[i]][4-d]|0]:` 
`)for(e of[d=1,63,64,65])d+=a[i+e]+a[i-e];innerHTML=h"onload=b.onkeyup() id=b> 
Canvas autoplay version before (182b)
<canvas id=a><svg onload=setInterval('for(a.width=e=[i=1e4];i--;a.getContext`2d`.fillRect(i%k,i/k,1,e[i]=d?d[i]+n==4||n==4:3<i%9))for(jof[n=1,97,k=98,99])n+=d[i+j]+d[i-j];d=e',d=0)>

New version (179b)
<canvas id=a><svg onload=setInterval('for(a.width=e=[i=6e3];i--;a.getContext`2d`.fillRect(i%k,i/k,1,e[i]=(n-1|d[i])==3|i%9&!d))for(jof[n=1,73,k=74,75])n+=d[i+j]+d[i-j];d=e',d=0)>

Tricks used

The vertical tab trick, used here, is explained in this blog post!



Day 31: Tetris

Source code (progression)

<body onload="x=y=4;B=[~!setInterval(onkeyup=e=>{u=[x,y,i=r+4];e&&(g=e.which-38)-2?g?x-=g:r++:y++;C=[...B];for(z=S;--i;(B[b+=24-y]|2049)&(k=1<<a+x)?[x,y,r,u]=u:i<T?Z.innerText=z+=`_#
`[C[b]|=k,i%11?C[i/11+1|0]>>i%11&1:2]+'| ':0)for(a='547690681'[i%4+(i&2&&t)],b=a&3,a>>=2,j=r&3;j--;a=f)f=b,b=3-t/5-a|0;B=u||e?B:C.filter(_=>_-2046?x=y=4:!++S,t=r%7)},r=T=210),t=S=0]"id=Z>

Tricks used

Read the annotated source code!



CSS golfing

Once again, we managed to lose a few bytes in inline CSS code, and once again, it's about the "font" property!





Bonuses





To finish, there's a faux-ami golfing trick that we thought we had discovered earlier this month: leaving the last HTML tag unclosed.

It works on jsfiddle because JSfiddle adds a lot of stuff at the end of your HTML code, which gets merged with your unclosed tag without bad consequences, but in reality, in a standalone demo, it's just broken.



Cheers!
xem & the team!

JS13kGames 2017 making-of: LOSSST

july-september 2017




TL;DR




Introduction

JS13kgames is certainly the most important game jam for code golfers every year, and I wouldn't miss it for any reason!

This progression makes me want to build something even better and bigger every year!

This year, I decided to make a game that uses emoji and CSS3D, like I did for my JS1k 2017 entry Can I haz 1Karrot?. It'll be a game for desktop and mobile. I also want to include a bit of storytelling, a few cinematics and exploration phases between each gameplay session, and if possible, different ambiances. I want it to be speedrunnable. I want a WOW effect when people launch it. Also, I'll try to fill the whole 13kb with HTML, CSS and JS code only. No images, no kilobytes of binary data, only code! It'll be my biggest solo project (in terms of lines of code, hours of work and also in terms of thought) since Super Chrono Portal Maker... and maybe this year it'll be so big that I'll really have to golf it in less than 13kb at the end! That would be cool.



Preparation

Since last JS13k, I've listed a dozen game concepts on paper (mostly puzzle or reflection games, because that's the genre I like the most), and waited for this year's theme to see which idea would suit it the most...

I also worked on many CSS3D tools, techniques and prototypes, to cite a few:

Finally, I took 2 weeks of holidays to be able to work on this entry as much as I wanted!



Week 1

The idea

When the theme was announced (Lost), I picked an idea in my list.

It'll be a game loosely inspired by these japanese games where people need to pass through holes in moving walls.

Instead of humans, the game will feature a snake made of (CSS3D) cubes.

The 3D snake is also an hommage to this extraordinary mobile game called Snaky Snake (free on Android / iOS), and there'll be a little influence of the games The Witness and Polarium DS too (but it'll still be completely original!)

Instead of going through moving walls, the snake will enter a series of puzzles in which it has to fit more and more complex shapes, first in 2D, then with wrap-around, and finally in 3D.

I really like this concept because I've never seen it before in a video game, it's super simple to understand and use, and it allows a big variety of puzzles, from very simple to very complex.

The theme "Lost" will be part of the game's scenario: the game will be labyrinthic, and the hero (the snake) has to find his kid. I made a little intro cinematic with animated emoji eyes, but I'll need to improve it later.

Sketches!

During this week, I sketched on paper most of the game's puzzles, as well as a basic storyboard and a lot of notes. If all goes well, the game will contain about 80 puzzles and a puzzle editor.

First, a puzzle editor!

During this first week, I focused on the development of a good-looking puzzle editor. The first goal of this tool is to easily create all the puzzles of the game, but also to be included as a bonus, so people can create and exchange custom levels on social networks.

It's made in HTML, all the sizes are in "vh" units to make it responsive and playable on vertical screens, and the background colors (including the sun) are made of radial gradients. The snake's shadow is made of semi-transparent squares below each cube, and its head is stylized with Unicode / Emoji characters
( Y, ‿, 👀 ).

Here's the basic feature where you can set the grid's and snake's size, draw a 2D puzzle on the ground, solve it with the arrow keys and share it as an URL:

In the second group of puzzles, you can wrap from left to right and from top to bottom, a la Pac-Man:

In the last group of puzzles, you can move in the 3 dimensions, first to reproduce patterns drawn on the vertical wall:

Then you must match both grids (the wall and the ground) in 3D:

And at the end, you have to match both grids, in 3D, and with wraps:

The checkboxes interactions (to enable or disable the ground, the wall or the wraps) are made in pure CSS, with rules like that:

/* Hide all cells */
.cell {
  opacity: 0;
}

/* Show ground/wall cells if ground/wall checkboxes are enabled */
#ground:checked ~ .puzzle .cell,
#wall:checked ~ .puzzle .cell {
  opacity: 1;
}

/* Show wrap surfaces if wrap checkbox is enabled */
#wrap:checked ~ .puzzle .wrap > div {
  background: rgba(200,200,200,.25);
}

Wrap surfaces have the CSS property pointer-events: none, allowing to draw the puzzles even if they're in the middle.

As you can see in the previous images, I also implemented more boring (but essential) things, like:

Screw you, Firefox!

The most complicated task this week has been to debug Firefox. Firefox (on desktop) is super bad with CSS3D. It got a bit better in april 2017 by allowing more than 100 CSS3D elements to appear at the same time in a page, but they still haven't fixed (and they may never fix) their z-ordering and CSS3D intersections (as you can see here).

I spent hours ordering and reordering the elements in my page to have the best result possible, and added little offsets everywhere to ensure that two shapes never intersect each other. This fixed the most important problems (for example in that gif), but there are still a lot of glitches that I can't fix yet, like the cube's faces that behave normally when the "camera" moves, and disappear randomly as soon as the scene is still:

But in general, in order to have the best result possible (at least on Firefox), the secret is to organize your DOM such as the first elements hide the last ones, or appear in front of them. Sort of doing the z-ordering manually.

I also noticed that most of the glitches that remain on Firefox disappear when my browser is not in fullscreen. Weird.

Anyway, on Webkit it's perfect, so let's stop losing time on this issue for now, there's a game to make!

As in every big project, it took me quite a lot of time to find a good way to organize my code and my files to work properly. At the end of the first week, everything is still chaotic...

Anyway, the byte count is currently 19.5kb (commented and uncompressed), and only 3.4kb (not golfed, but minified and zipped).

At first I thought BabelJS (and its Babili/ES2017 presets) was the new Holy Grail of JS minification, but no! The slow and ugly Google Closure compiler, even if it outputs only ES5 code, is still the best compressor for JS code out there! (its output is around 3% smaller than BabelJS's, and ES5 ensures a better compatibility with older or bad browsers, like the ones on iOS, so it's win-win)

I also thought of a few melodies that may be played in the different sections of the game. We'll see that later.



Week 2

Let's start by fixing a few boring details. First, the controls. I've tried many configuration and finally implemented the one that seems the most natural: arrow keys to move forward, backward, left and right (relative to the camera), shift to go upward, ctrl to go downward. I won't provide a way to just "look up", "look down" like in the moving cube prototype. On mobile, there will be buttons on the screen to simulate these 6 keys.

Another important fix is to make the wraps much clearer visually. Instead of transitioning to the other side of the level, the cubes can now leave the level, disappear, reappear on the other side, and then transition to the right place. Easier said than done, it took me an entire day and a headache to get this result:

The code used to do this animation is super dirty but it'll be rewritten many times during the compo.

And while we're talking of little details, don't forget to hide the snake's shadows when its cubes are not touching the ground!



The hub (and screw you, Chrome!)

Instead of making a game containing only a list of puzzles, I decided to make a "hub", a big room where the game starts and from which the character (the snake) can evolve and access all the puzzles, editor, etc. Though, I soon realized that this hub can't be too big, because on my computer, Chrome crops the backgrounds of CSS3D elements when they exceed a size of about 32k pixels squared. (Ex: this green div measures 900vh * 900vh, and these circles are placed at its center. Chrome seems to crop everything that exceeds ~6500px in width and height).

There's another issue I encountered on Chrome on my laptop: localStorage becomes locked (impossible to read or write) when my PC gets back from hibernation mode. It took me a while to understand that. I searched a bug in my code for a long time, and finally everything got back to normal when I closed and restarted Chrome. (NB: closing a tab is not enough to fix that. All chrome tabs must be killed!)

A last issue specific to Chrome Windows (that I already encountered during JS1k) is that the emoji lose their fill colors when they exceed a certain computed size in pixels. Here, the sizes are in vh, so if I have a screen too big, or a zoom too important, they can glitch quite randomly when the window is resized:

On Mac it's even worse, the trees don't appear at all:

During all the month of the jam, I semi-fixed these issues as well as I could, by changing the camera's zoom, angle, perspective, and by making the trees smaller, but there was no miracle solution... until the very last days! We'll talk about it later.

An important refactoring allowed me to use the same code for controlling the snake in the editor's tests and the "real" game.

The main difference with the puzzle editor is that here, the snake first gets out of the ground, can move but doesn't have a puzzle to solve (yet), and the camera must follow it around the scene.

I rewrote the code of the trees and tree shadows to appear at precise coordinates and with solid hitboxes. The trees are slightly slanted backward from the camera (when I first used this trick, I discovered it was also used in Super Mario 64!).

I also added a few custom transitions and timeouts on the scene and the snake's head to make a mini opening cinematic. Here's a first preview (with camera rotations to show how the trees are tilted backward):

Eat apples, grow longer, and move back!

The snake will have to eat apples to get longer, and thus, solve more and more difficult puzzles. But by doing so, two issues become apparent:

There's a single answer to these two issues: I have to record all the positions of the snake's head since the beginning of the game. By doing so, I can place any new cube where the tail was one move ago, and add a button (Alt ot backspace) to let the snake get back to any previous position when it's stuck.

Here's the result after one day of refactoring:

Side note: my error messages are still very stylish in CSS3D!

It took me a long time to wonder if a collision between the head and the tail could occur when the snake goes back to a previous position, or when it eats an apple. Turns out, it can't happen. Believe me, I tried hard!

Doors, rooms... and autosaves?

To ease the progression of the player, I wanted to implement doors and paths to travel from a room to another. The doors will open if the snake approaches it with a sufficient length, or if it has solved all the puzzles in the room.

The tests (in JS) and animations (in CSS) to open the door were pretty easy (even it it had to be rewritten a few times to work correctly in the four directions), and going in a new room simply consists of loading a different HTML page, but this feature still required a big refactoring to maintain the snake's length and all the doors and apples states when we travel between rooms, and to make the snake spawn at the right place in the new room every time it goes through a door. All these pieces of data are stored in the browser's localStorage. This has the advantage to be persistent between pages, but it also constitutes an automatic save and load feature for when the player quits the game and relaunches it later.

I actually didn't intend to implement autosave/autoload so soon, nor to implement it like that, but this use of localStorage brought it very naturally and basically for free. Awesome!

After a big effort of refactoring, each room just needs to declare the positions and behaviors of its emoji (trees, apples, ...), doors, paths and puzzles, and the game's engine will just do the rest.

Well... after thinking about it a little, loading a different HTML page for each room turned out to be kinda dirty, and caused two issues: when the player loads his/her save data, he/she will land on index.html (the hub), instead of the last visited room. And the other issue is for the music: it'll stop playing and restart from start when the page changes, and I don't want that. So instead of loading a different page when the snake goes through a door, I prefer to reset and redraw everything in the same page (index.html). This page will load the hub by default, or the last visited room if it's saved in localStorage, but it will never reset the music. As a bonus, having a single, generic HTML page saves some hundred bytes in total!

This refactoring wasn't as easy as I thought. At some point, when I passed a door, the result was a mix of the last room and the new room!

But this was fixed quickly (I forgot to empty the objects container before drawing a new room). And with this new organization, I also have camera transitions from a room to the other for free. Cool!

The first 2D puzzles

A new challenge appears: display many puzzles in a room and let the player solve them in any order. Moreover, the camera must focus on the puzzle's grid while the snake is inside, and the game engine must show the solved state of every puzzle and save it in localStorage (in order to avoid solving a puzzle multiple times). Finally, the snake can leave a puzzle before it finishes solving it.

I finished this week by adding apples animations, higher platforms, and explaining how to backtrack and open doors in the hub:

I'm quite happy with this little effect: the platform on the left is not rectangular: it's made of many little cubes, but each cube has a computed background and background-position, which gives an homogeneous radial gradient to the whole surface:
(The performance on Firefox is so terrible that I only kept a little portion of the platform in the final game, but it still has this fancy radial gradient!)

Current progress: 1783 lines of code, 45.6kb uncompressed and commented, 7.19kb minified and zipped.



Week 3

All the 2D puzzles!

After showing the game to two friends, I realized that my 2D puzzles were quite easy and risked to be annoying. So when I made the three other 2D puzzle rooms, I discarded most of my original puzzles designs and replaced them with most difficult ones.

I originally planned to have 4 x 6 2D puzzles, but to spice things up a little, I removed a room completely, and made more apples appear before each new room, which makes six 2D puzzles for a snake of length 8, six for length 11 and six for length 13. I also added cubes here and there to make the puzzles solutions less obvious.

The rooms are arranged in circle to easily return to the hub when they're completed.

I threw away a lot of puzzles, but for a good cause: I want the players (and the judges) to quickly discover the two other sections of the game!

Budget-wise, I'm quite happy that these 3 rooms and 18 puzzles only add 600b to my zip (even though they add nearly 4kb in the source code). Of course they need more decoration but it's alredy a nice compression.

Cleanup!

To continue coding in good conditions, let's clean up all the dead/bad code that piled up during the last two weeks, and organize it such as all the variables names get automatically reduced to 1 char when passed into Closure Compiler.

This cleanup made the project much cleaner, and made the zip 1kb smaller! Yay!

God mode!

When you make a game with a long intro and progression, you quickly get annoyed when you have to replay from the beginning to arrive at a certain point. In that case I highly recommend to create a "god mode": a secret menu containing shortcuts to different places or states in the game. Here's mine:

Adding these shortcuts to load each room with the corresponding snake length, and buttons to rotate the camera or erase all saved data took only a few minutes and really changed my life!

Hints

Some beta-testers are very confused about the gameplay (how to backtrack, how apples/doors/puzzles work, etc...), so let's take some time to add hints explaining how the game works. These signboards only appear when the snake has a given size, allowing to display different messages at the start of the game, and later.

When I added these hints in the game, I realized something with CSS3D sprites (eg. the trees, and apples, and now the hints). They won't appear at the right place if you set this in their CSS: (imagine that W, H, L and T represent respectively a width, a height, a left offset and a top offset in vh)

width: W;
height: H;
left: L;
top: T;
transform-origin: 50% 100%;
transform: rotateX(-90deg);

This doesn't work fine because the "top" property will set an Y offset to the top of the sprite before it's rotated along the X axis. After fiddling a little, I discovered that the good way to do it is:

width: W;
height: H;
left: L;
bottom: 0;
transform-origin: 50% 100%;
transform: translateY(T) rotateX(-90deg);

When you do it this way, the point at the bottom center of the sprite is moved at the exact coordinates L:T in the scene, thanks to the "bottom: 0" that makes it start exactly from the top of the scene!

Access to the editor

Now that the 2D puzzles are done, the hub allows to access the puzzle editor to make other 2D puzzles. (wraps and 3D options are hidden for now.)

The wrap area

The second area of the game contains all the 2D puzzles with wrap, and are playable with a snake of length 14, then 15, then 16 then 20.

It starts with a little hub explaining the principle of the wrap: a wall blocks the way and a puzzle with shiny sides invites the player to follow the black path and arrive to the other side.

Everyone likes this tutorial puzzle. It shows with extreme simplicity how wraps work, and also brings a little "wow" effect.

One particularity of the wrap puzzles is that the snake is trapped inside the shiny walls until it solves the puzzles. It can also backtrack many times to return to a place out of the puzzle, but beta testers told me they wanted to reset a puzzle entirely instead of backtracking, so I added a reset feature when we press the key "R".

The snake can then go into a room containing 12 puzzles. The first room was very complicated to design because its puzzles form a big easter egg, that I won't spoil here. With the help of the beta-testers, I managed to enhance the puzzles and optimize this room so that the easter-egg is more easy to see. If you already saw it, you can see a before/after picture here, and the final version with extra highlight here. It's indeed much clearer like that!

Then, there are just 2 puzzles of size 16 and 2 puzzles of size 20, because of a sudden lack of ideas. I spent hours trying to design other puzzles for these rooms but all of them were too easy or didn't have a solution, so it's a sign that it's time to move to something else!

When these rooms are completed, the puzzle editor gets a new option to draw 2D wrap puzzles.

However, beta tests showed that the difficulty curve was too steep between the tutorial and the 12 first wrap puzzles, so I added an extra room with 4 "easy" wrap puzzles, just after the tutorial, and it seems to be very helpful indeed.

So let's start the final part of the game!

Preparing for the 3D puzzles

The 3D puzzles require a new ability (move up and down), and less cubes (at least 6). But our current snake is now 21 cubes long and doesn't have the ability to move up and down. So it's the good time to find the lost kid snake, that will solve the last puzzles, in 3D. The switch has been quite complex to implement, but it's done!

Basically, the orange snake becomes a pile of immobile CSS3D cubes (but keeps its hitboxes by having all its cubes' coordinates added to the list of ground cubes), a flag (called "son") is set to true, all the snake's data structures are reset, and the function that resets the snake is called. This function is modified to create a new snake, and let the player control it when the "son" flag is set to 1. The flag is also saved in localStorage, to let the player continue playing with this snake when a savefile is loaded.

Gravity

Our little snake can go up and down, but it must not fly! So I added a big bunch of code to test if all the parts of the snake are on the floor or on a cube, and if they're not, all the snake's cubes get instantly lowered.

If the snake backtracks just after being lowered by the gravity, some of its parts can glitch a little because their "history" has been rewritten, but I won't fix that, it's too complex. Let's say it's an hommage to last year's "glitch" theme...

Fast-forward a few days: this gravity system caused me a big headache in a particular edge case that has some chances to happen during a normal gameplay: when the snake is entering a 3D puzzle by the top side, and falls inside the puzzle because it's not tall enough, and if we press R to quit the puzzle, the snake gets stuck in an infinite loop of quitting the puzzle and falling inside it, and it can't move anymore. Eventually, my (dirty) fix was to put a try/catch around the falling code and if it detects an infinite loop, the game rollbacks for a number of moves equal to the snake's size. It'll generally land a few steps before the entrance of the puzzle and won't be stuck anymore. And besides a little freeze, the result is practically unnoticeable! Ex:

And that's the end of week 3!

Current progress: 39 puzzles, 2448 lines of code, 63.3kb uncompressed and commented, 8.49kb minified and zipped.

A word from my fellow musician Anders Kaare maybe?...

"I'm really busy at the moment (secret project!) but if I can write the music as quickly as usual I can probably squeeze you in :)"

A hypothetical music for the very last days of the compo, just like the previous years :D ... Quite scary!



Week 4

The 3D area

The 3D puzzles, that must be solved with the little snake, required a few days to design (designing 2D puzzles was hard, but designing 3D puzzles is super hard!). And yes, still on paper, because I'm more inspired when I'm outside.

In the end there will be about 40 of them, including wall-only puzzles, wall+ground puzzles and wall+ground+wrap puzzles, for a snake of length 6 to length 20. This represent a half of the game! But the first ones are very easy, to avoid discouraging the players (but some of them will be cut and included in the game's bonus, to avoid annoying people too much). Here's how the 3D rooms look like (wip pictures):

By chance, all these new levels compress very well and as far as I tested, they won't add much more than 1kb in my zip. So, plenty of room left for the rest!

I designed the last room with a final easter egg, and developped the end cinematic at the same time. (Here's a gif: spoiler! don't click before playing the game!).

Final puzzle editor

After all these puzzles are completed, the final puzzle editor is finally available for the player. I made a few arrangements to make the snake black and able to go up and down only if the vertical wall is enabled, and orange otherwise.

I also prevented the snake from going through the vertical wall of the 3D puzzles, except if the wrap is enabled.

Finally, I ensured that the editor stays fully unlocked even if the players start a new game.

It's also time to add a level loader and a dedicated room for player-created levels. This room reminds the controls to the player and has a path to return to the "real" game. (wip: the design was simplified before release)

Unexpected fun with boolean golfing

While I was working on the last puzzle editor and hints improvements, I happened to write two interesting tests, which I had fun trying to simplify with a friend. They looked like this:

if((f && mousedown) || !f){ ... }

if((son && hints[i][5]) || (!son && !hints[i][5])){ ... }

Which can be simplified to:

if((A & B) || !A) { ... }

if((A & B) || (!A && !B) { ... }

For the first one we had no idea, but we drew a truth table before realizing that if A is false, it's okay, and otherwise it's necessarily true so we don't have to repeat it when we test B. So, the golfed version is:

if(!A || B){ ... }

For the second one, it was a bit harder. When we drew the truth table, nothing was obvious, except that it was basically the opposite of a XOR. So I tried an assumption: what if it was a XAND? After a quick check on Stack Overflow, it appeared that there's actually no such thing as "XAND" (ha ha! silly me), and what we were looking for was actually a NXOR. Hey, not far! How to do a NXOR in JS, you ask? Easy!

if(!(A ^ B)){ ... }

That was a fun pause! Let's go back to work.

Final name

One idea of title for the game was "pattern-alism", referencing both the patterns constituting the puzzles, and the fact that the father snake must find his son. But it's too complicated.

I wanted to find a pun like "Lost in translations" as a reference to all the CSS3D translate's in my code, or "SNAKE un au-revoir", but they would have been too much geeky/private jokes, so let's go with LOSSST instead.
At least it respects the theme, and it's a reference to the snakes in the game!

Main menu, mobile controls... and screw you, localStorage!

I had honestly no big inspiration fot the title screen so I made something with an exaggerated 3D rotation and a solid text-shadow as a wink to the TV show "LOST".

It took a few days to have the main menu and mobile controls work properly, and to adapt all the signposts to the device used (keyboard for desktop, buttons for mobile).

Here are some traps that made me lose quite a lot of time:

The mobile mode has quite a lot of buttons (to move in all directions, backtrack, exit puzzles and move the camera), but to avoid shocking the player with this ton of buttons, I made them appear progressively, when the player reaches a zone where these buttons become necessary.

OCD challenges

For OCD players, I added a move counter and a time counter, that are able to save and resume their states when the game is played in multiple sessions. The players are invited to beat my best scores and create puzzles when they finish the game

Current zip size: 18.5kb. WHAAAAT? After a few hours stressfully wondering how to lose weight, and make room for the music, I realized I had actually zipped my minified JS plus my unnecessary, unminified JS. Without that dumb mistake, the zip is below 11.5kb. Oof!

Beta-test

This year I have the chance to have a lot of volunteers for testing my game. I gathered a lot of feedback by watching them play IRL, or by interviewing them when they live in other countries.

All went very well, a lot of puzzles, hints and features were added thanks to them. You guys rock!

One sad note though (but it's my fault): almost no one reads my signposts. Most of the testers wondered how to eat the second apple without getting stuck. There's a signpost a few centimeters ahead that tells to press Alt to backtrack, but it's still not clear enough. Meh... I don't know what else I can do if people don't wanna read anything!

Decoration

For decoration, there was not much room available, so I just added an emoji animal in each room, and applied a little CSS animation to the animals and the apples. It's a little detail but it makes the world less static.

When I added the animals, I realized that all my emoji shadows were wrong: they actually needed an extra "scaleX(-1)" and a bigger angle to be symmetrical to the actual emoji. Here are the "emoji shadows 2.0":

Browsers, OS, Devices

To avoid nasty visual glitches, I decided to degrade the visual quality on Firefox, MacOS and mobiles, by hiding all the shadows in the game (except the snake shadows).

I also made two versions of the intro (for Firefox and for the others: on Firefox the snake waits the end of the camera movement to get its head out of the ground, otherwise it's all glitchy) and two versions of the easter-egg animations (less zoomed on mobile and more zoomed on desktop, to ensure that everything is visible even if the mobile is held in portrait mode).

Moreover, on MacOS, the emoji have line-heights different than all the other OS's, so I added specific hacks to have the trees, apples and animals actually touch the ground.

Current size: 87.8kb unminified, 13.1kb minified and zipped. There are a few days left to add music and actually golf the code! Yay!

Trees fix!

Remember my problem of disappearing trees? Webkit browsers (Chrome, Safari, Android) don't like to draw trees exceeding a certain font-size in pixels, so I finally found the right solution. I made all the elements of my game almost two times smaller, and made the camera two times closer to the scene to compensate. It required a lot of edits (in CSS and in JS, especially the cinematics) but it worked like a charm. The trees appear about as big as before, but with a font-size technically two times smaller, they don't glitch anymore!

Lose weight!

The first attempt to lose bytes consisted in replacing all my classNames, ids and localStorage keys with one-char variables. It failed miserably (I forgot that in CSS, ids and classnames were case-insensitive, so my ".a" collided with my ".A", my "#a" with my "#A", and same problem for all the rest of the alphabet), so I reverted everything.

New tactic: rewrite my beloved puzzle editor, but much simpler, and reusing all the code of the game engine to resize the grid or move the snake. I also followed the idea of some beta-testers and removed the ability to draw a puzzle completely. Now to make a puzzle, you just have to place the snake as you want on the grid and click "share":

This editor rewrite saved 900b in my zip. (12.2kb)

(You may wonder why I talked so much about the previous puzzle editor if I removed it from the game... In fact, it's so handy that I decided to included it in the game's bonuses.)

After adding a bit of decoration and byte-consuming visual enhancements (especially for the "shiny wrap walls", the highlighting of the easter-eggs, and spawning a signpost after the first easter-egg to show where the exit is), finishing the puzzle editor, and making the 3D wrap puzzles walls solid after they're completed, nearly 800b were added to the zip size.

After putting all my code in a single file, removing more dead code, and retrying (successfully) to reduce the names of my ids and localStorage keys into 1 or 2 chars, I managed to save plenty of space.

I'm still very close to 13kb... and there's no music yet...



Week 5

Music!

As usual, Anders Kaare saved my life at the end of the last week-end of the compo, and he had a great idea: to play a melody that he composed, but play all the notes one by one, each time the snake moves! That's pretty original and works very well, with a very small overhead. Thanks a lot to him, and to McCoordinator who also kindly offered to make a music for my game. I'm keeping his prototypes and stay in touch for my next games! :D You guys rock too!

Anders shared a few details about how he made the two melodies of the game: (verbatim)

To clear things up (or scare you a little more, depending on how you react to dirty hacks explanations), each sound effect in the game, and each note of the melodies, constitutes a specific "wave shape", that is represented with 44100 floating numbers, per second and sent to the speakers.

For example, here are all the samples for the sound that I used to tell that a puzzle is completed, plotted. This ~150ms sound is made of 16000 floating-point samples! (full list here)

Of course, all these values are not stored in the game's code, but are computed procedurally, placed in a wav file that is created out of thin air, converted in base64'd and played on-the-fly with an Audio tag.

// mkaudio generates samples based on a function passed in parameter, creates a wav file and exposes a "play" method
function mkaudio(fn) {
	var data = [];
	for (var i = 0;;i++) {
		var smp = fn(i);
		if (smp === null) break;
		data.push(smp);
	}
	var l = data.length;
	var l2=l*2;

	var b32 = function (v) {
		var s = 0;
		var b = "";
		for (var i=0; i<4; i++,s+=8) b+=String.fromCharCode((v>>s)&255);
		return b;
	};
	var b16 = function (v) {
		var b = b32(v);
		return b[0]+b[1];
	};
  
	var SR=48e3;
	var b = "RIFF"+b32(l2+36)+"WAVEfmt "+b32(16)+b16(1)+b16(1)+b32(SR)+b32(SR*2)+b16(2)+b16(16)+"data"+b32(l2);
	for (var i in data) b+=b16(data[i]*10e3);
	return new Audio("data:audio/wav;base64,"+btoa(b));
}

P=Math.pow;
S=Math.sin;
function t(i,n) {
	return (n-i)/n;
}

// Here's the "equation" for the win sound
function win(i) {
	var n=1.6e4;
	var c=n/7;
	if (i > n) return null;
	var q=P(t(i,n),2.1);
	return (i < c ? ((i+S(-i/900)*10)&16) : i&13) ?q :-q;
}

Demo:

(don't get me wrong, even if I can kinda explain what happens, this code is still black magic for me)

The demo/code of the melodies he sent me during the last sunday of the jam is available here.

By the way, no new sound effects were made this year, I just reused sounds that Anders had made for my 2016 entry (for example, the "win" sound above was used when a coin was collected in the previous game).

Add stuff, Minify, Golf, Zip, Advzip, Repeat! (and Screw you, Closure Compiler!)

After adding the music and sound effects, and compressing everything, I realized that my zip had reached a size of 13.4kb plus a few bytes. Pretty cool! The music and sounds really create an ambiance in the game and end up taking less than 1/2kb zipped!

However, something less cool occurred. My minified JS code was all buggy, because when Closure Compiler minified my code and renamed all my vars, it didn't rename them in my 44 setTimeout's and 7 setInterval's, in which the JS code was stringified. Ex:

var Song = () => { /*...*/ } // This function gets renamed as "A"

setInterval("Song.forward()",500); // After minification, the browsers throw an error saying "Song is not defined"

Closure Compiler transpiles ES6 to ES5 but doesn't interpret strings passed to setTimeout and setInterval. I didn't know that and it was pretty annoying.

Even if it was a bit late, the solution was to spend a few hours rewriting all my intervals and timeouts using functions instead of strings... The most boring task was to use closures to be able to pass vars to the delayed/repeated functions when they're in for loops. Ex:

// Before
for(var i in apples){
  if(applecanappear[i]){
    setTimeout('top["apple"'+i+'].className="emoji apple"',800);
  }
}

// After
for(var i in apples){
  if(applecanappear[i]){
    setTimeout(
      function(i){
        return function(){
          top["apple"+i].className="emoji apple"
        }
      }(f),90
    );
  }
}

Then, with the kind help of @kuvos, @veubeke, @AdrienGueret and @salvan13, I was able to gather 3 different ways to rewrite a "string interval" as a "non-string" interval. They're listed here.
After a pass in Closure Compiler, two of them are transformed to the same code. The last one becomes a closure. weird. But good to know.

As there was still a lot of bytes to remove, I continued my manual rewriting session, and renamed all the hardcoded classNames and ids as one or 2 chars, by following this rudimentary dictionary.

After a long rename/debug session, and after dropping a few useless signposts, I managed to fall below 13kb 15h before the deadline, only to realize later in the night that my editor was all broken, once again because of Closure Compiler:
Closure Compiler renames all vars, even if they're global, and even if they're declared as "top.varname = 1234;". But I still had dozens of places in my code where I had to call a variable by its id in the top object (ex: top["u"]), but Closure Compiler also didn't interpret that correctly. It renamed the original "u" but kept the top["u"] as-is. The solution was to "force" it to not rename these vars that are required in strings, by being more silly than it:

// Before
u=r=d=l=s=c=B=0;

// After
window["u"]=window["r"]=window["d"]=window["l"]=window["s"]=window["c"]=window["B"]=0

// Then after mnification, I paste the result in my min.html file, and replace the "after" snippet by the "before" one to save bytes.

Last warning: Closure Compiler adds line breaks in minified JS code. This is a good practice for real-life JS code, but for js13kgames, all these line breaks (75 in my case) can and must be removed.

Final scare

I submitted my game at 1h40 AM, less than 12h before the deadline, and went to sleep.

The next morning, I saw it online and tried it. All my "return to main menu" links were broken. Turns out, these links had a href="." attribute, and js13kgames.com didn't support that. So I renamed each "." in "index.html", and everything went back to normal after I sent an updated version to Andrzej at 9h30.

Final version of the game, after all the necessary hacks and renames: ~200h of work (including thinking + drawing + coding + beta-testing), 86.1kb commented, 80 global vars (including functions), 3175 lines of code, 41.6kb minified, 13300 bytes zipped and advzipped. Just 12b below the limit!

BTW, If you don't know advzip, it's a zip recompressor that optimizes any zip as strongly as possible. Without it, my final zip would have been over 13.4kb. A real life saver. How to use it.

Bonus

During my spare time, I prepared a bonus page, on my Github repo, where I could put all the levels I didn't include in the game, and all the levels shared on Twitter plus a link to this making-of / my Github / my Itch page, as well as the game credits, and a trailer and a video of my dev speedrun, recorded september 13 at 9h00, just before updating and sending my updated zip to Andrzej!



End of week 5 (mini-conclusion)

After the release, the game had super nice reviews from a lot of friends and strangers IRL, on Twitter and on Slack. Most of the other contestants really enjoyed the music (maybe because they know how hard it is to make music for a 13kb game, and they don't have the chance to have Anders as a magi-musi-cian).

Some people even told me that they had more fun with the interactive music than with the game itself, heh!

Anders didn't remember the name of the old song that inspired him the second melody of the game, but someone on Twitter found it!

I also went into each room of the game and counted all the HTML elements generated by my game's engine, and dumped all the HTML code generated in a single file.

I teased the result with this poll, then revealed the answer here: 7434 elements and 1.26MB of HTML is generated by my 13kb game! How crazy is that?!

So, I'm very happy with this year's game and by the people's reactions. It's the game I wanted to make when I started, and the lack of extra time only prevented me from trying to fix my code for a better result on Firefox, and on Safari Mac / iOS. Meh. These browsers are not ready for so much CSS3D and so many emoji anyway.



Week 7 (real conclusion)

Here we are, two weeks after the end of the jam.

More details!

First, let's address a few details that I forgot in the rest of the making-of, or dicovered afterwards:

Feedbacks

Before the results, I received A LOT of positive feedback on my game, I'm really impressed by the people's kindness and nice words to describe my game:


Feedbacks about this making-of


My first shootout !

Ryan Malm (a.k.a. Rybar), the dude (and friend) who arrived first in the desktop category with the game Greeble, added a shout-out to me and other golfers who helped him during the development of his game, in his game over screen!

So shout out to him too! If you haven't done it yet, go check out his game!.

Custom puzzles

Nearly a dozen players finished my game at 100% (congrats!) and some players even solved the bonus puzzles (including the glitchy ones), and created very cool custom puzzles. Some of them were hard to solve, even for me! You can find them here: #LOSSSTlevels.

I also couldn't resist the urge to create 26 bonus puzzles representing all the alphabet, because these kind of puzzles were pretty fun to design for the easter-eggs. So now, the bonus page contains 26 "hard-ish" letter-shaped puzzles!

With all the in-game puzzles, and all the bonuses, I designed a total of 111 puzzles for this game!

miniMusic

At least one entry (Polyhedron Runner, by Alex Swan, one of my favourites this year) actually used miniMusic for its theme! Woohoo!... but he had to hack the code to be able to play the melody repeatedly, as Webkit doesn't like when we create more than 6 AudioContexts. The solution is to reuse the same over and over, and destroy it between each use.

I'm gonna enhance this tool and make it more dev-friendly before the next game jam!

Speedrun!

I progressed on the game's speedrun, and managed to beat the game in 742s and 2266 moves. These scores will be the final dev times appearing on the game over screen (I included them when I sent a litte bug's hotfix to Andrzej a few days after the end of the compo).

It's far from perfect: I did a few mistakes during the run, and the route could be simplified for some puzzles solutions (Jupiter Hadley's solution for one of the first puzzles of the game was much easier than the one I used, though I had never thought of using this solution before I saw her video:

Jupiter Hadley's video test

You can see the video test of LOSSST by Jupiter Hadley (one of the judges, and indie game expert) here, at 9:49

I'm also present at the beginning of this compilation video made by one other judge, Agar3s.

Thanks!

Jam results!

They're finally here, and they're very good!

Community awards (I'm #2):

Twitter specials (I'm #1):

Mobile awards (I'm #1):

Desktop awards (I'm #2):

Here are the lovely judge's feedbacks on my entry's page:

I really liked the comparison with Braid, and the typo at the end (the music is synced to whaaaat?)

Here are some kind messages I received from after the results:

Mattia Fortunati also praised my mini canvas framework and LOSSST in his writeup about A day in the life!

The jam results also had a lot of success on Hacker News, where I found these feedbacks about LOSSST:

Thank everyone! Anders Kaare, the testers, the players, everyone who helped me, the judges, and Andrzej. You're all fabulous!

What's next?

First, I'm gonna adapt this entry for AirConsole, and present it to their upcoming game jam. (I "just" moved the mobile controls into a separate page that communicates with the game with WebRTC messages). Here's how it looks in AirConsole's simulator:

And on real hardware (a bit laggy but enjoyable. It even works with two players, controlling the snake together):

And then?

It seems clear that this game deserves some kind of further development, and by chance, I have a ton of new ideas that couldn't be implemented during the compo!

I'm currently gathering people and ideas to work on something much bigger, yet still based on this basic pattern-matching schema.

I don't want to spoil you the surprises , but I'll still leave you with some food for thought...
What if...?

What if the black and white tiles were not just square?

What if the puzzles were not just placed on flat surfaces like floors and walls?

What if the snake had more than one color?

What if there was more than one snake?

The answers to these questions, and probably many others, may be coming on Ssssteam or on an App Sssstore near you in 2018!

Until then, take care and don't forget to support both arrow keys, WASD and ZQSD for keyboard controls!



Week 12

PS: we just received some great stats from Andrzej: the 100 most visited pages since august, and the 500 most visited pages since 2012.

LOSSST and all my/our previous games performed very well!



Andrzej also told me that:





Cheers!
xem

WebGL quest #2: tiny raymarching bootstrap with Distance Estimation functions

may 2017




TL;DR

A simple WebGL raycasting algorithm, accepting any Distance Estimation function and rendering it with distance fog and no distorsion, can be as short as 135b! (DEMO)

void mainImage(out vec4 r,vec2 d){vec3 D=normalize(vec3(d+d-(r.xy=iResolution.xy),r.y));r-=r;for(int i=0;i<50;i++)r+=DE(D*r.x);r=1./r;}

That's for the shader alone. For a complete demo including a WebGL bootstrap in HTML/JS and the shader input "F" as a frame counter, the total is 531b. (DEMO)



Introduction

This article is the sequel of our first WebGL quest, that should be read first.

After JS1k 2017 (where we presented MiniShadertoy based on our 349b WebGL bootstrap), we started reading many articles and tutorials explaining how to draw 3D shapes, with shadows, using raymarching algorithms and distance estimation functions, with WebGL.

Here are some of the best resources we've found on this subject:

Once all that was understood, our goal was to make a WebGL raymarcher compatible with any distance estimation function, and as small as possible.



Raymarching? Distance estimation?

To sum up very quickly (and not exhaustively), this rendering technique consists in sending a ray from the camera, in a 3D scene, for each pixel of the canvas...

The scene is defined with an equation, or the combination of many equations representing each of the objects we want to draw. These equations can be translated in source code, that's what we call Distance Estimation (DE) functions. These functions have the advantage to tell you how far from the object is any point in the 3D space...

Each ray then "marches" (advances in the scene) in many steps, by computing at the beginning, and at each step, its distance from the nearest object of the scene, and progressing that exact distance in the scene until the closest object is hit (or close enough to be considered as hit). In this case, the corresponding pixel of the canvas can be colored with the color of the hit object.

Fog can be applied very easily, based on the hit distance, to give a sense of depth, or hide the objects that are further than the max ray distance.

Shadows and light reflections can also be rendered quite easily by aiming for the the light source from each hit point and seeing if something is placed in-between or not.

If a ray doesn't hit any object, it must be stopped after a certain distance to avoid an infinite loop.

Simple operations can be applied to the DE (Distance Estimation) functions to rotate, translate, scale or twist any object.
Combining a DE function with a modulo operator allows to render a plane or a space filled with "infinite" objects.

DE functions exist for most 3D shapes (and even 3D fractals), and they can be combined pretty easily to generate complex scenes. In "real" projects like video games, scenes are generally only comprised of a multitude of triangles, because triangles are the shapes for which the rendering is the easiest and the most optimizable for a computer.

NB: The canvas can use either a 2D context or a WebGL context, the logic will be the same, but with WebGL, you'll have much better performance (because the computations for each pixel are parallelized on the GPU), and the renderer will need to be written in GLSL instead of JS.



Let's code!

So here's an example of commented shader that does the job with very little code. On the Shadertoy version, you can change the settings in the first lines of code to change the shapes to be drawn.

// ========================================= // SETTINGS: // Shape: 0 = sphere, 1 = box, 2 = torus, 3 = union, 4 = substraction, 5 = intersection int shape = 0; // Infinite: 0 = no, 1 = yes int infinite = 1; // ========================================= // Distance Estimation function float DE(vec3 p){ // Infinite if(infinite == 1){ p = mod(p, 2.0) - 0.5 * 2.0; } // Scaling based on a cosinus wave float scale = ((cos(iGlobalTime) + 2.5) / 2.7); p = p / scale; float r; // Sphere if(shape == 0){ r = length(p) - 0.7; } // Box if(shape == 1){ r = length(max(abs(p) - 0.5, 0.0)); } // Torus if(shape == 2){ vec2 q = vec2(length(p.xy) - 0.5, p.z); r = length(q)-0.1; } // Union if(shape == 3){ r = min(length(p) - 0.7, length(max(abs(p) - 0.55, 0.0))); } // Substraction if(shape == 4){ r = max(-(length(p) - 0.7), length(max(abs(p) - 0.55, 0.0))); } // Intersection if(shape == 5){ r = max(length(p) - 0.7, length(max(abs(p) - 0.5, 0.0))); } // End of scale return r * scale; } // Main function (called for each pixel) void mainImage(out vec4 fragColor,vec2 fragCoord){ // Adjust to canvas size vec2 uv = fragCoord.xy / iResolution.xy; uv = uv * 2.0 - 1.0; uv.x *= iResolution.x / iResolution.y; // Create a ray that goes forward vec3 r = normalize(vec3(uv, 1.0)); // The origin o is the camera's position vec3 o; // Infinite: Make the camera move if(infinite == 1){ o = vec3(0.0, iGlobalTime, iGlobalTime); } // Not infinite: place the camera at (0,0,-2) if(infinite == 0){ o = vec3(0.0, 0.0, -2.0); } // Raymarching loop (calling DE() at each step) float t = 0.0; vec3 p; for(int i = 0; i < 99; i++) { p = (o + r * t);// * 0.3; float d = DE(p); t += d; } // Fog float fog = 1.0 / (1.0 + t * t * 0.3); // Color the current pixel according to the fog fragColor = vec4(vec3(fog), 1.0); }




Let's golf it!

The following code was golfed with the help of the team plus a shadertoy user called coyote. Thanks to them! Here's a live demo.

void mainImage(out vec4 r,vec2 d){
  
  // Direction vector
  vec3 D=normalize(vec3(d+d-(r.xy=iResolution.xy),r.y));
  
  // r represents the current pixel's color, which is proportional to the accumulated distance
  r-=r;
  
  // Loop
  for(int i=0;i<50;i++)
	
  // Add the result of DE to all the components of r (flagColor) to produce a shade of grey
  // Camera origin is implied (0,0,0).
  r+=DE(D*r.x);
	
  // Fog (make the furthest points darker)
  r=1./r;
}
  

If we count the useful chars only, the results fits in 135b!

void mainImage(out vec4 r,vec2 d){vec3 D=normalize(vec3(d+d-(r.xy=iResolution.xy),r.y));r-=r;for(int i=0;i<50;i++)r+=DE(D*r.x);r=1./r;}

It's possible to combine that with our first WebGL bootstrap (source code and demo are available at the beginning of this article)



Further experiments

- Golfing-wise, the raymarching algorithm could have been a byte shorter by reversing the for loop: for(int i=50;i-->0;). This works on Shadertoy, but it triggers a syntax error on other environments like MiniShadertoy, so we preferred not using it.

- It's very fun to play with the numbers and variables contained in the raymarching algorithm and the DE function. Here are some examples:

- If you change the normalization part, for example (d+d-(r.xy=iResolution.xy))*50., the shape distorsion can be quite awesome:


- If you reduce the number of iterations in the loop, the image will get blurry, because the rays won't approach the surface of the scene's objects as accurately as they're supposed to.


- You can reverse the colors in order to have black shapes and white fog. This also saves 2 bytes in the golfed raymarching algorithm.


- Mistakes can be pretty too! (this happened when I made a typo in my raymarching loop)





Conclusion

Of course, these experiments are just scratching the surface of what can be done with raymarching, and a lot of cool stuff is still to discover (shadowing, lighting, coloring, texturing, rendering goddamn Mandelboxes...), but that's for a future episode! I'm already very proud of the team for coming up with this tweetable generic raymarching algorithm! :D

Cheers!

JS1K 2017: some kind of magic (plus a few other news)

febuary 2017




News:

there's now a Slack room dedicated to JS code-golfing: jsgolf.club.
You can fill this form to get an invite!

And a big thumbs up for Dwitter: a platform allowing to make art on a canvas in 140b...

Also, Dwitter inspired this entry by p01, and this 140b clone by nderscore.



Update: results!

MiniShadertoy arrived 7th and Can I Haz 1Karrot? arrived 8th!
More info on js1k.com (many entries of the Codegolf Team members are in the top 10!)



JS1k:

JS1k is certainly the most important rendez-vous for JS code-golfers each year,
and this time a lot of people have been very busy to submit incredible entries with the theme "some kind of magic"!
Here are the 5 entries I worked on, in team and alone, plus some anecdotes and tricks we found while making them!



Mini Shadertoy

made with literallylara, nderscore, innovati, subzey, p01, sqaxomonophonen (and aemkei for the non-js1k version)

- PLAY
- COMMENTED SOURCE
- GITHUB

Our first team entry is inspired by our previous work on WebGL: MiniShadertoy, MiniShadertoyLite and WebGL playground.

We completely reworked these apps to include a split-screen UI, shareable URLs, a perfect compatibility with Shadertoy's syntax and a cool built-in example. (plus a fullscreen view on double-click).

As a result, our entry is able to play demos from shadertoy.com (like Seascape), provided they only contain one shader code, and no extra buffers or channels.



Tricks and lessons learned:




PERIOD1k reloaded

made with innovati and subzey

- PLAY
- COMMENTED SOURCE
- GITHUB

Last year, we submitted PERIOD1k, a periodic table drawn on a canvas.
This time, we rewrote it entirely and the result is much better!
It's rendered in HTML/CSS and generated with a big chunk of ES6 template literals (26kb of generated code in total!)
It contains the definitive names for the last four elements (officialized in june 2016), better responsiveness, and a new information: the state of each element at room temperature (Liquid / Solid / Gas).



Tricks and lessons learned:





If the Moon was 1px

made with innovati

- PLAY
- COMMENTED SOURCE
- GITHUB

This demo is a tribute to If the Moon were only 1px by Josh Worth, reimagined with the hard constraint of 1kb.
We wanted to make an extremely complete and precise solar system, with all the planets, dwarf planets, satellites and belts.
We almost nailed it: all the planets positions are precise with a margin error < 1000px, and their size is pixel-perfect;
the major satellites have pixel-perfect positions and their full names are displayed in the page;
The only important size/accuracy compromises are the belts (not drawn in the page) and the minor satellites (placed at pseudo-random positions and not named).



Tricks and lessons learned:





Can I haz 1Karrot?

Individual entry

- PLAY
- COMMENTED SOURCE
- GITHUB

I always wanted to make something cool with CSS3D and emoji, but never had the good idea... until a week after the start of JS1k, when I realized I could make a game with a rabbit in a hat that could run through a maze to find its carrot, and with a surprising ending. All the other details were improvised during the next ~3 weeks of intensive development.

This demo relies heavily on two CSS and golfing experiments from 2016:
- JS games inputs (to support both arrow keys, WASD and ZQSD in 82 bytes);
- How to make games in CSS3D
(though all the "good practices" of this tutorial were heavily tortured and trespassed in this demo in order to save every byte possible).

Mobile controls would have been cool (moreover, mobile browsers have no problem displaying the demo), but it would have been so heavy to listen to touch events and bypass their default behavior to make the rabbit move (around 200b), that too many features would have been cut out (no animals, bad collisions, bad camera, less trees, etc.) and it was too much of a downgrade to me, so I abandoned it.

Same thing for the sound or the interaction with the animals: they were sacrified to make the main thing more enjoyable.

Big thanks to Keith Clark for his CSS3D expertise, and to the friends from the Codegolf Team for their tests!



Video:

I made a video recording to show the demo played in the best conditions (Windows 10 + Webkit):
(WARNING: SPOILERS!)

Tricks and lessons learned:



WIP screenshots and funny glitches:





1Kind of magic

Individual entry

- PLAY
- COMMENTED SOURCE
- GITHUB

This demo was rushed during the last week of the compo, after seeing @literallylara's entry magic flute. (which has a remarkably readable source code).
As a joke, I asked "what if we played Queen's "A kind of magic" in JS? that'd be a fun interpretation of the theme!".
I ended up handwriting every note of the song and playing them with two JS oscillators (one for the melody and one for the drums starting after 12 seconds).
The remaining room was used to display some emoji at the center of the page, changing the background after each note, and using a little bit of JS speech synthesis to pronounce the first 3 lyrics of the song.
It was also a good occasion to finally make a non-mute demo for js1k!
The Speech Synthesis is inspired by miniSpeechSynthesis.



Tricks and lessons learned:





Bonus: CSS golfing

To finish, I'd like to talk about a code-golf trick that I found while making these demos, and it's about inline CSS code.

It generally saves 1 byte per element, but hey, every byte counts!

Inline CSS looks like "<div style='...'>", and as you may know, the attribute quotes can be omitted if the inline style doesn't contain any space, for example "<div style=color:#fff;background:#000;border-radius:50%>".

My demos contained many CSS properties that generally use spaces, and I discovered I could get rid of most of these spaces, by removing them or replacing them with a "+" or quotes (''). Here are some examples:

The only spaces that can't be removed so far are:

There's a second CSS trick that can save many bytes when you write a lot of CSS functions, but I don't really recommend it because it doesn't work on Safari: all the closing parenthesis at the end of CSS rules (or at the end of inline styles) can be omitted, ex:





Cheers!

Golfing the Game of Life (and its variations)

october 2016




We golfed the Game of Life many times in the past few years, in many different forms:

First, when 140byt.es still existed, we golfed the algorithm alone in less than 140 bytes (135b by aemkei and 126b by me);

Subzey joined us to golf miniGameOfLife, a complete Game of Life simulation in ASCII in 238b, which used the mouse and the keyboard to set the initial state and evolve;

Aemkei golfed a pure HTML version in 176b and he presented many... obfuscated... versions... on stage;

We made MiniGameOfBraille in 389b;

And finally, we ended up golfing a super tiny version on a 100x100px canvas inspired by aemkei's talks, in just 221b, featuring ES6 and a pseudo-random initial state.

<canvas id=a><svg onload=z=a.getContext`2d`;s=!setInterval`p=a;a=[];for(i=1e4;a[i]=s?3==k|p[i]&2==k:3<i%9,k=0,i--;)for((j)of[1,97,v,99])k+=p[i+j]+p[i-j];for(s=1e4;s--;)z[a[s]?'fillRect':'clearRect'](s%v,s/v|0,1${v=98}1)`>

During months, we really thought we couldn't do better than that. We even included this version in our js13kgames entry 26-games-in-1.

We also golfed other cellular automata like miniLangtonAnt, miniLangtonLoops and cellularandom.



This week, we discovered a new, intriguing variation of the Game of Life, that produced a Serpinski triangle fractal while it evolved from a vertical line.

So I tried to reproduce it and golf it from scratch with @sqaxomonophonen @subzey @nderscore and @veubeke. The result is HERE, and it fits in just 188b!

Here are the different steps of this quite epic golfing session, just after we realized that the rules of this game were the same as Game of Life's, except that cells live with 0 or 3 live neighbours instead of 2 or 3 live neighbours:

The commented source code can be found HERE.

// Just minified, not golfed yet.
<canvas id=a><script>d=[];k=0;setInterval(function(){a.width^=0;e=[];for(i=0;i<98*98;i++){if(!k){d[i]=((i%98)==49)?1:0;}else{n=0;for(j of[1,97,98,99])n+=!!d[i+j]+!!d[i-j];if(e[i]=[x=d[i],,x,!x][n])a.getContext`2d`.fillRect(i%98,~~(i/98),1,1);}}if(k)d=e;k++;},9)</script>

// Move k (a flag telling if the game has started or not) into d (an array representing all cells' state). Pass a string to setInterval. Move the rest of the code to remove some curly braces.
<canvas id=a><script>d=[k=0];setInterval(`a.width^=0;e=[];for(i=0;i<98*98;i++){n=0;if(k)for(j of[1,97,98,99])n+=!!d[i+j]+!!d[i-j];else d[i]=e[i]=((i%98)==49)?1:0;if(e[i]=[x=d[i],,x,!x][n])a.getContext("2d").fillRect(i%98,~~(i/98),1,1);}d=e;k++`,9)</script>

// Move some code inside e (an array representing the new state of the grid). Use .map() to iterate on the set of neighbours offsets. 
<canvas id=a><script>d=[k=0];setInterval(`for(e=[a.width^=i=0];i<98*98;i++)n=0,k?[1,97,98,99].map(j=>n+=!!d[i+j]+!!d[i-j]):d[i]=e[i]=i%98-49?0:1,(e[i]=[x=d[i],,x,!x][n])&&a.getContext("2d").fillRect(i%98,~~(i/98),1,1);d=e;k++`,9)</script>

// Reverse the loop (from 10000 to 0) to iterate on all the cells. Move n reset at the end.
<canvas id=a><script>d=[k=0];setInterval(`e=[a.width^=0];for(i=1e4;i--;k?[1,97,98,99].map(j=>n+=!!d[i+j]+!!d[i-j]):d[i]=e[i]=i%98-49?0:1,(e[i]=[x=d[i],,x,!x][n])&&a.getContext("2d").fillRect(i%98,~~(i/98),1,1))n=0;d=e;k++`,9)</script>

// Instead of incrementing k at each loop, just set it to the value of e after the first iteration
<canvas id=a><script>     d=[k=0];setInterval(`e=[a.width^=0];for(i=1e4;i--;k?[1,97,98,99].map(j=>n+=!!d[i+j]+!!d[i-j]):d[i]=e[i]=i%98-49?0:1,(e[i]=[x=d[i],,x,!x][n])&&a.getContext("2d").fillRect(i%98,~~(i/98),1,1))n=0;k=d=e`,9)</script>

// Place all the JS code in an svg onload. Reorder the code a little, and simplify the game's rules (e[i]=!(d[i]?n^2&n:n^3) instead of e[i]=[x=d[i],,x,!x][n])
<canvas id=a><svg onload="d=[k=0];setInterval('e=[a.width^=0];for(i=1e4;i--;k?[1,97,98,99].map(j=>n+=d[i+j]+d[i-j])|(e[i]=!(d[i]?n^2&n:n^3))&&a.getContext`2d`.fillRect(i%98,i/98|0,1,1):e[i]=i%98==49)n=0;k=d=e',9)">

// Set d to zero and use it as the second parameter of setInterval (it's not useful as an array at the beginning ant it's set to the value of e after each iteration)
<canvas id=a><svg onload="setInterval('e=[a.width^=0];for(i=1e4;i--;d?[1,97,98,99].map(j=>n+=d[i+j]+d[i-j])|(e[i]=!(d[i]?n^2&n:n^3))&&a.getContext`2d`.fillRect(i%98,i/98|0,1,1):e[i]=i%98==49)n=0;k=d=e',d=0)">

// k is not needed anymore, the truthiness of d is enough to choose between "initiate the grid" (when d=0) and "iterate" (when d=e)
<canvas id=a><svg onload="setInterval('e=[a.width^=0];for(i=1e4;i--;d?[1,97,98,99].map(j=>n+=d[i+j]+d[i-j])|(e[i]=!(d[i]?n^2&n:n^3))&&a.getContext`2d`.fillRect(i%98,i/98|0,1,1):e[i]=i%98==49)n=0;d=e',d=0)">

// Do not floor the y position of each pixel (at this scale, it's almost not noticeable)
<canvas id=a><svg onload="setInterval('e=[a.width^=0];for(i=1e4;i--;d?[1,97,98,99].map(j=>n+=d[i+j]+d[i-j])|(e[i]=!(d[i]?n^2&n:n^3))&&a.getContext`2d`.fillRect(i%98,i/98,1,1):e[i]=i%98==49)n=0;d=e',d=0)">

// Try to use a boolean in d... but it doesn't work on Firefox (Fx needs an integer as second param of setInterval)
<canvas id=a><svg onload="d=!setInterval('e=[a.width^=0];for(i=1e4;i--;d?[1,97,98,99].map(j=>n+=d[i+j]+d[i-j])|(e[i]=!(d[i]?n^2&n:n^3))&&a.getContext`2d`.fillRect(i%98,i/98,1,1):e[i]=i%98==49)n=0;d=e')">

// Rollback
<canvas id=a><svg onload="setInterval('e=[a.width^=0];for(i=1e4;i--;d?[1,97,98,99].map(j=>n+=d[i+j]+d[i-j])|(e[i]=!(d[i]?n^2&n:n^3))&&a.getContext`2d`.fillRect(i%98,i/98,1,1):e[i]=i%98==49)n=0;d=e',d=0)">

// Move some code inside e[i]=xxx (e[yyy,i]=xxx is valid in JS!)
<canvas id=a><svg onload="setInterval('e=[a.width^=0];for(i=1e4;i--;e[[1,97,98,99].map(j=>n+=d[i+j]+d[i-j]),i]=d?(d[i]?n^2&n:n^3)?0:!a.getContext`2d`.fillRect(i%98,i/98,1,1):i%98==49)n=0;d=e',d=0)">

// Move some code (declaration of e and reset of a.width) inside the for loop and mix it with "i=1e4". Also, save the number 98 in a variable (k) to reuse it many times.
<canvas id=a><svg onload="setInterval('for(a.width|=e=[i=1e4];i--;e[[1,97,k=98,99].map(j=>n+=d[i+j]+d[i-j]),i]=d?(d[i]?n^2&n:n^3)?0:!a.getContext`2d`.fillRect(i%k,i/k,1,1):i%k==49)n=0;d=e',d=0)">

// Simplify the rules with a global "n xor"
<canvas id=a><svg onload="setInterval('for(a.width|=e=[i=1e4];i--;e[[1,97,k=98,99].map(j=>n+=d[i+j]+d[i-j]),i]=d?n^(d[i]?2&n:3)?0:!a.getContext`2d`.fillRect(i%k,i/k,1,1):i%k==49)n=0;d=e',d=0)">

// Moved rules inside of height argument of fillRect.
<canvas id=a><svg onload="setInterval('for(a.width&=e=[i=1e4];i--;a.getContext`2d`.fillRect(i%k,i/k,1,e[i]=d?!(d[i]?n^2&n:n^3):i%k==49))[1,97,k=98,99].map(j=>n+=d[i+j]+d[i-j],n=0);d=e',d=0)">

// Simplify the rules again (reuse the global "n xor" trick) and simplify "i%k==49" into "i%k^49".
<canvas id=a><svg onload="setInterval('for(a.width&=e=[i=1e4];i--;a.getContext`2d`.fillRect(i%k,i/k,1,e[i]=!(d?n^(d[i]?2&n:3):i%k^49)))[1,97,k=98,99].map(j=>n+=d[i+j]+d[i-j],n=0);d=e',d=0)">

// Remove the quotes of the svg onload attribute, and replace the map() with "for(j of[...])". The space is replaced with a vertical tab character (considered as a space in JS but not in HTML)
<canvas id=a><svg onload=setInterval('for(a.width&=e=[i=1e4];i--;a.getContext`2d`.fillRect(i%k,i/k,1,e[i]=!(d?n^(d[i]?2&n:3):i%k^49))){n=0;for(jof[1,97,k=98,99])n+=d[i+j]+d[i-j]}d=e',d=0)>

// Move n=0 at the beginning of the loop to get rid of two more curly braces.
<canvas id=a><svg onload=setInterval('for(a.width&=e=[i=1e4];n=0,i--;a.getContext`2d`.fillRect(i%k,i/k,1,e[i]=!(d?n^(d[i]?2&n:3):i%k^49)))for(jof[1,97,k=98,99])n+=d[i+j]+d[i-j];d=e',d=0)>




With this extremely small code, we tried to re-golf miniGameOfLife with the same principle, and got it down from 221b to 183b! The result is HERE!

<canvas id=a><svg onload=setInterval('for(a.width&=e=[i=1e4];i--;a.getContext`2d`.fillRect(i%k,i/k,1,e[i]=d?d[i]+n==4||n==4:3<i%9))for(jof[n=1,97,k=98,99])n+=d[i+j]+d[i-j];d=e',d=0)>


(to save one extra byte, Subzey also had the idea to rewrite the rule "e[i]=(d[i]&&n==2)||n==3" into "e[i]=d?d[i]+n==4||n==4")



To conclude, I'd like to show that this model can actually be used as a bootstrap for any Game of Life variation, not only these two:
<canvas id=a><svg onload=setInterval('for(a.width&=e=[i=1e4];i--;a.getContext`2d`.fillRect(i%k,i/k,1,e[i]=d?[RULES]:[INITIAL STATE]))for(jof[n=1,97,k=98,99])n+=d[i+j]+d[i-j];d=e',d=0)>

Replace [RULES] with the conditions that make e[i] (the current cell) alive based on d[i] (the previous state of the current cell) and n (the number of live neighbours);
and replace [INITIAL STATE] with the code that defines the initial state of the grid, based on i (the cell counter).

For example, if you use the rules of Life without Death, you can get something like this:

Cheers,
the Team

Web Speech API golfing (plus our new best JS golfing tricks)

october 2016




The Codegolf Team and I constantly make new codegolf-related discoveries, sometimes completely randomly.

Our most recent ones are the following:





We also discovered the Web Speech API, allowing to make your browsers generate voices from any text you input (Speech Synthesis) and vice-versa (Speech Recognition)!

Naturally, we tried to golf these new features as much as possible, and here is the result:

And as many people are curious about how we make things like that, here's the detail, for miniSpeechSynthesis, golfed by @p01, @subzey, @nderscore, @veubeke, @literallylara, @sqaxomonophonen and I:

// Non golfed
<input id=i><button onclick="s=speechSynthesis;u=new SpeechSynthesisUtterance(i.value);u.voice=s.getVoices()[0];s.speak(u)">Speak!

// Use new() to avoid using a space, and remove attribute quotes
<input id=i><button onclick=s=speechSynthesis;u=new(SpeechSynthesisUtterance)(i.value);u.voice=s.getVoices()[0];s.speak(u)>Speak!

// Add speechSynthesis's scope to get rid of "s."
<input id=i><button onclick=with(speechSynthesis)u=new(SpeechSynthesisUtterance)(i.value),u.voice=getVoices()[0],speak(u)>Speak!

//Use ES6 destructuring to set u.voice to getVoices()[0] implicitly
<input id=i><button onclick=with(speechSynthesis)u=new(SpeechSynthesisUtterance)(i.value),[u.voice]=getVoices(),speak(u)>Speak!

// Move a bit of previous code in empty parenthesis
<input id=i><button onclick=with(speechSynthesis)[u.voice]=getVoices(u=new(SpeechSynthesisUtterance)(i.value)),speak(u)>Speak!

// Get rid of the button (speak when the input changes), the voice and the id "i" because it's implied now
<input onchange=speechSynthesis.speak(new(SpeechSynthesisUtterance(value)))>

// Use the onblur event (shorter and more handy: indeed, triggering the speaker at each keystroke was not nice)
<input onblur=speechSynthesis.speak(new(SpeechSynthesisUtterance)(value))>

// Use a VT character between "new" and "SpeechSynthesisUtterance"
<input onblur=speechSynthesis.speak(newSpeechSynthesisUtterance(value))>

Demo:




The other apps use very similar techniques as you can see for example in the source code of miniSpeechRecognition (webkit only):

<svg onload='with(new webkitSpeechRecognition)start(onresult=e=>write(e.results[0][0].transcript))'>

...or its bgColor variant, using the trick described at the beginning of this article:
<svg onload='with(new webkitSpeechRecognition)start(onresult=e=>bgColor=e.results[0][0].transcript)'>

... and as a bonus, an app that repeats with Speech Synthesis what you say in the microphone, interpreted by Speech Recognition! The lite version is only 146b (webkit only)
<svg onload='with(new webkitSpeechRecognition)start(onresult=e=>speechSynthesis.speak(new SpeechSynthesisUtterance(e.results[0][0].transcript)))'>

Cheers!





Update 01/17: a nice article on appendto.com explains these tricks with more detail.

How to make a game in CSS3D

october 2016 - june 2017




This article is aimed at developers that are already familiar with JS game making and CSS3D.

If you don't already know CSS3D, I also recommend reading the following resources:
- An Introduction to CSS 3D Transforms
- Things to watch when working with CSS3D
- Keith Clark's articles and demos about CSS3D engine, lighting, shadowing, etc.
- Super Mario Kart in CSS, it's heavy, old, not optimized, not cross-browser, but that's what inspired me to do this today :)

I won't cover all the features of CSS3D, but instead I'll focus on some discoveries I've made, like how to deal with the rotation and translation axis, how to simulate a camera and how to make CSS3D scenes interactive with a little bit of JS.

In parallel with this article, I'm updating this Github repo with my experiments.



Why?

Why make games in CSS3D? Because it works fine (most of the time), because it's fun and hacky, and also because it's super lightweight! You can literally make a 3D scene interactive with a few lines of HTML, CSS and JS (example) (demo), so it can be useful for lazy coders, or code-golfers trying to work with the size limits of js1k or js13kgames!



Spoiler: 3D games often are just 2D games plus perspective

It seems stupid to say it like that, but it's one of the most important things I realized while experimenting: if you can make a 2D game in JS (for example a chess game or a racing game seen from above, or anything else you can imagine), you literally just need to look at your scene from a slightly different angle and you have turned it into a 3D game "for free".
You can see what I mean with this Mario Kart prototype: the only real difference between the 2D view and the 3D view is rotation of 80 degrees along the X axis!

But we'll see that in greater detail later.



So let's get started with CSS3D!


Nowadays, CSS3D is pretty easy to use. Modern browsers don't require vendor-prefixes anymore and their performances are... decent.

All you have to do is set "perspective: 400px" to a container in your page (avoid the <body> though) - of course you can replace 400px with any length you want; as far as I understand, this length represents the distance between the container and the virtual camera that will "see" the scene in perspective. (the lower the perspective, the more deformed the scene is). Then fill the container with elements, apply all the CSS3D transforms you want to these elements, don't forget to set "transform-style: preserve-3d;" to render them in "real 3D" (otherwise, the browsers will flatten all the scene and make it look as if it's in perspective, but there would be no depth at all in reality), and you're done!

But now, let's dig into some subtleties that you may face while playing with this new toy!



Subtlety #1: HTML elements vs. axis, translations and rotations?


By default, each HTML element of your page is drawn at a certain position according to the flow of the page and the CSS you applied to the element, its neighbours and its ancestors. It has no translation and no rotation, and its default transform-origin it at their top-left corner. This transform-origin is editable, and it's the starting point of all the possible CSS transforms, but six of them are particularly important here: rotateX / Y / Z and translateX / Y / Z. The X axis goes on the right, the Y axis goes downward, and the Z axis goes in depth towards your eyes, as you can see in the following demo:

DEMO!

The demo above allows to move the transform-origin of a square (represented by a red dot), and allows to translate in X,Y and Z axis and rotate along X,Y and Z axis, in this order. All the range inputs go from 0 to 100 (regardless to the units used).

Note that translateX and translateY can be expressed in percent units, but not translateZ. If you try to sate a translateZ in %, the whole transform will be discarded.

Note also that the order of the transforms is extremely important. For example here, you can translate the square then rotate it, but you wouldn't get the same result if you rotated the square before translating it. Transforms are accumulated from the first one to the last one, while the transform-origin can only have one value used by all the transforms. (of course, even if its value never changes, the transform-origin moves with the element when it's translated).

This principle allows, for example, to rotate an element, then translate in, then rotate it back to the original angle, to make an object move around a circle!

Finally, note that the X, Y and Z axis of an element can have different orientations than the ones by default if a parent element is rotated. (for example, if the parent of the square above had a 90 degrees rotation along its X axis, the square would undergo this parent transform and have its Y and Z axis rotated 90 degrees along the X axis of the parent, i.e. the Y axis would behave like a Z axis and the Z axis would behave like X axis.

The other possible CSS transforms include matrix, translate, scale, scaleX, scaleY, skew, skewX, skewY, matrix3d, translate3D, scale3D, rotate3D and perspective, but we won't study them here. ( for the record, matrix3D has very interesting applications!)



Subtlety #2: The HTML structure

Even if a container (with perspective) and its children (with CSS transforms) are enough to test CSS3D, a good HTML structure will make your work (and calculations) much easier, especially if you want to do a CSS3D game. Here's the one I recommend:

First, you need a viewport (the equivalent of a canvas element when you do a canvas-based JS game). For example, use a 600x400px div. The perspective will be applied to this element.

At the center of this viewport, we place a 0x0px div called "camera". It's not REALLY the camera (more details in the next paragraph), but the point that will always be watched by the camera, something like a global transform-origin for the game.

Inside this "camera" point, we can place a scene container with a defined size and inside it, all the scene objects in their order of appearance (for a better browser support - more details at the end of the article). For example, we can put inside the kart, the tree, and finally the circuit.

Here's the template's HTML:

<div id=viewport>
  <div id=camera>
    <div id=scene>
      (...scene content...)
    </div>
  </div>
</div>

And CSS:

* { transform-style: preserve-3d; box-sizing: border-box; }
#viewport { width: 600px; height: 400px; overflow: hidden; perspective: 400px; }
#camera { width: 0px; height: 0px; position: relative; left: 300px; top: 200px; }
#scene { width: 4000px; height: 4000px; transform-origin: 0 0; }
/* scene content... */
#scene { transform: rotateX(80deg) translateZ(-70px); }

All your scene objects can be placed in the scene the same way you'd place them in 2D on a HTML page (using left/top, or margins, or translateX/Y, etc.).

The last CSS line (rotateX and translateZ on the #scene element) enables the 3D view, exactly like in the previous Gif. The translateZ is optional, its goal is to place the camera a little above the scene, and avoid some bugs (more details later).

You can find a demo HERE.

In this demo, the viewport is shown in red, the camera in blue, and the scene in green (you can see the top-left corner of the scene in perspective).

In the screenshot below, you can see what the template looks like before and after adding the last line of CSS (the 3D toggle)

That's all! Everything else depends on your content and your creativity!



Subtlety #3: A "camera"?

In a webpage, there's no "camera", but there's a viewport (the portion of the page visible in browser's window).

In most cases, and even in 2D games, the notion of camera is completely neglected, and we generally call "camera" the current view, i.e. the rectangle in which the user can see your game.

But in 3D, we should never forget that the camera is a point in the space, and the scene is "seen" from this point.

Remember, when you make a scene in CSS3D, you set a given perspective to a container, and it defines how all the children are rendered. Well, here it is! If your scene has a perspective set to "400px", the camera is just 400px away, along the Z axis. Its a virtual point in the air between your screen and your eyes.

So, can you guess what happens if you perform a "translateZ(400px)" transformation on your scene?

Yep, what you get is a first-person view!

Here you can see the translateZ(400px) being enabled and disabled in the Mario Kart prototype (there's also a toggle of the kart's opacity), and of course, you can have this transformation always enabled if you make a CSS3D FPS for example!



Subtlety #4: Interactivity!

Adding interactivity (controls) to a 3D scene is rather simple if you're well organized and keep a few trigonometry basics in mind:

(NB: the following tips mostly apply to CSS3D games with first-person or third-person view, like a FPS or Mario Kart.)

For example, you may want to look around you using the mouse or the keyboard, so you can declare a var called angle_z, and update it every time you move the mouse or press Left/Right arrows.
For more simplicity, this var will contain your Z rotation in radians (one turn = 2 * PI ~= 6.28 radians). It's possible to use degrees but all the JS Math functions work in radians and you'd need to convert units all the time, so I don't recommend it.

You'll probably want to move too, that's why you can declare two vars x_pos and y_pos. If you're surprised by the "Y" axis used to walk forward and backward, remember that our 3D game starts as a 2D game, and in 2D, the Y axis allows you to move up and down, which will become a forward-backward movement after passing in 3D view. These vars can be updated with keyboard inputs for example.

Remark: naively, we can think that it's okay to move along the X axis when we press left and right keys, and move along the Y axis when we press up and down keys, but in first-person or third-person view, it's often not what we want at all. What we want is to go forward according to our current angle! So when we press "up", the right thing to do is probably something like:

pos_x += walk_speed * Math.sin(angle_z);
pos_y += walk_speed * Math.cos(angle_z);

Finally, we want to apply these vars to our scene at each frame, and to do so, we just have to generate a CSS transform string and apply it with JS; something like:

scene.style.transform = "rotateZ(" + angle_z + "rad) translateX(" + pos_x + "px) translateY(" + pos_y + "px)";

All the rest is just a slight variation of this principle. For example, to place the kart at the right position and the right angle, in front of the camera, you have to do something like:

kart.style.transform = "translateX(" + (-pos_x - kart_width / 2) + "px) translateY(" + (-pos_y - kart_width / 2) + "px) translateZ(" + kart_height + "px) rotateZ(" + (-angle_z) + "rad)";

It takes a few fails and retries to get the right values and the right order for all the transforms, but with practice it becomes more and more logical and natural.

Hack: if you're super lazy, your third-person sprite (for example the kart) can be placed over the viewport! You can see a demo HERE. The illusion is perfect... until something passes between the subject and the camera...





Subtlety #5: browser support?

I said earlier that CSS3D works fine... most of the time. Indeed, you may encounter a few glitches, or even some elements that disappear completely while they should be here, but that's the fault of the browsers, and Firefox in particular.

First, some parts of the scene can look broken if the camera is too close to them. It happens with the road for example, as you can see during a few frames of the previous GIF. This happens on both Chrome and Firefox, but for their defense, my camera was VERY close to the road (like, 20px high). The solution is to put the camera a bit higher.

Then, with the great help from Keith Clark, I understood why Firefox hides the kart behind the scene in this case:

As you can see in his super detailed pull request, the order of the elements in the DOM is super important to help the browsers draw the "above" elements above, and the "below" elements below, and not the other way around. He also explains that Firefox completely stops doing any depth-ordering effort if your scene contains more than 100 children. In that case, you need to reorganize your DOM manually at each frame... or more realistically, just completely drop Firefox support for your game!



2017 update:
- My JS1k entry "Can I haz 1Karrot" uses many tricks discussed here plus a lot of new ones. It arrived 8th!
You can read more in the dedicated blog post and detailed source code HERE.

- Since April, Firefox no longer limits CSS3D scenes to a maximum of 100 elements! You can use as many elements as you want, but don't rejoice too fast: Firefox is still very glitchy with CSS3D...
- My JS13KGames entry LOSSST is also entirely rendered in CSS3D (and emoji). It arrived 1st twice and 2nd twice.
There's a complete making of HERE

Happy game making! If you have questions, feel free to get in touch on Twitter, Gitter or Slack!

Cheers,
@MaximeEuziere

Some subtleties of keyboard inputs in JS games

september 2016, january 2017




After JS13kGames 2016, we realized on Slack that many games were only playable with W, A, S, D keys, and many players were bothered by that.

In this article I'll cover this point plus a few other subtleties of keyboard input in JS, and provide a tiny library that you can drop directly in your future games to get these problems out of the way.



The WASD issue

WASD keys can be used as an alternative for arrow keys in a vast majority of keyboards worldwide. All QWERTY and QWERTZ keyboards (and some hybrid keyboards like QWERTY/russian for example) support this pattern just fine. But there's another layout widely used too (especially in France, Belgium and Canada): AZERTY. On this kind of keyboard, WASD becomes ZQSD. Other minor layouts exist but we can ignore them for now.

So the idea is to teach game developpers to natively support not only WASD, but also ZQSD AND arrow keys! Why support only one scheme when we can please everyone with a super light overhead?

To sum up, up arrow must be aliased with W and Z, left arrow with A and Q, right arrow with D, and down arrow by S.



The JS keyboard events issue

As you can see on this page displaying the keyCodes of all your keyboard events, when you press a key, two events are fired by the browser: keydown and keypress. when you release it, another event is fired: keyup. And if you keep a key pressed for a moment (depending on your OS settings), after a short pause during which nothing happens, both keydown and keypress are fired repeatedly. There's also an "input" event, but it happens only in form elements, so it's not really relevant here.

There are many problems with this default behavior: firstly, this pause that happens after pressing a key is not good for a video game. If you play a platform game, you don't want Mario to stay idle for a few frames before running or jumping. And secondly, the keypress event is totally messed up (look at the keyCode returned by this event when you press letter keys or arrow keys, and you'll see absurd values almost everytime, and different absurd values depending on the browser you're using.

So my advice is to avoid relying on keypress events altogther, and also to avoid relying on keydown at each frame to see if a key is down or not, because the pause will bother your players.



The solution

So here's a super short solution to all these issues.

It introduces four global boolean variables to keep in memory the state of each direction.

Of course, feel free to fork it and replace them with non-global vars if you want, but for this example, I'll just keep things simple, and who cares about global vars in code-golfing anyway? Okay, maybe everyone cares, but not me! :p

So here's the code:

// Keys states (false: key is released / true: key is pressed)
up = right = down = left = false;

// Keydown listener
onkeydown = (e) => {

  // Up (up / W / Z)
  if(e.keyCode == 38 || e.keyCode == 90 || e.keyCode == 87){
    up = true;
  }
  
  // Right (right / D)
  if(e.keyCode == 39 || e.keyCode == 68){
    right = true;
  }
  
  // Down (down / S)
  if(e.keyCode == 40 || e.keyCode == 83){
    down = true;
  }
  
  // Left (left / A / Q)
  if(e.keyCode == 37 || e.keyCode == 65 ||e.keyCode == 81){
    left = true;
  }
}

// Keyup listener
onkeyup = (e) => {
    
  // Up
  if(e.keyCode == 38 || e.keyCode == 90 || e.keyCode == 87){
    up = false;
  }
  
  // Right
  if(e.keyCode == 39 || e.keyCode == 68){
    right = false;
  }
  
  // Down
  if(e.keyCode == 40 || e.keyCode == 83){
    down = false;
  }
  
  // Left
  if(e.keyCode == 37 || e.keyCode == 65 || e.keyCode == 81){
    left = false;
  }
}

Then, during you game loop, you just need to rely on the state of these four variables to know if the player is currently pressing a direction (or an alias in WASD or ZQSD) or not!

Want to see a demo? Of course! Just launch the first level of my js13kgames entry. It uses this exact technique :)



P.S: if you want a minified ang golfed version, here it is: only 160b! (the four vars u,r,d,l, are not booleans but just truthy and falsy)

u=r=d=l=0;onkeydown=(e)=>t(e,1);onkeyup=(e)=>t(e);t=(e,v,l,i)=>{for(i in l={u:[38,90,87],r:[39,68],d:[40,83],l:[37,65,81]})if(l[i].includes(e.keyCode))top[i]=v}

P.P.S: Here's a 122b version by @p01, using a radically different idea: a look-up table of keyCodes represented as a string, and ".which" instead of ".keyCode" (because it's the same thing):

u=r=d=l=0;onkeyup=t=(e,v)=>top['lurd************************l**r************l*d***u**u'[e.which-37]]=v;onkeydown=e=>t(e,1)



january 2017 golfing

I made a new version that's only 87b, but it works if the only keys you need to support are arrows, WASD and ZQSD (most of the other keys may collide with one of the four directions):

u=d=l=r=0;onkeyup=t=(e,v)=>top['lurdl*d*l*ur*u'[(e.which-37)%20]]=v;onkeydown=e=>t(e,1)

It fell down to 85b with the help of nderscore:

u=d=l=r=0;onkeydown=e=>(onkeyup=(e,v)=>top['lurdl*d*l*ur*u'[(e.which-37)%20]]=v)(e,1)

then 83, by merging the two event listeners:

u=d=l=r=0;onkeydown=onkeyup=e=>top['lurdl*d*l*ur*u'[(e.which-37)%20]]=e.type[3]<'u'

and finally 82 with Subzey's touch:

u=d=l=r=0;onkeydown=onkeyup=e=>top['lurdl*d*l*ur*u'[(e.which+3)%20]]=e.type[3]<'u'

(DEMO)

Note: there's no collision with the keys "E", "R", "T" "space", "shift" and "Enter", so you can also handle them just fine with just a 100 bytes:

E=R=T=_=s=e=u=d=l=r=0;onkeydown=onkeyup=z=>top['lurdlRdTl*urEu*_e**s'[(z.which+3)%20]]=z.type[3]<'u'

(DEMO)



Cheers,

@MaximeEuziere

JS13kGames 2016: Super Chrono Portal Maker

august-september 2016




TL;DR:

You can play the game HERE! (please share!)

Try also my Codegolf Team's entry "26 games in 1" HERE (please share too)!

You can see all the shared levels (and share yours) HERE.

You can follow the official account @SuperCPMaker HERE.

The Github repo of the game is HERE.

The speedruns of the game are HERE. (warning: spoilers!)

The game was ranked 3rd in the Desktop category!



Preparation

This year, I was really into 2D platforming.

I developed a platform engine during my free time.

We also discussed on JS13k's slack about the idea of a "13kb Mario Maker". This idea made me develop a 1kb level editor with shareable URLs, that all js13k's devs can use in their games...

As I was a bit rusty on 2D maths, I also made this trigonometry cheat-sheet for 2D games that many people found very useful.

I also made a tiny graphics editor, to help devs produce line-based graphics quickly and lightly (like I did for my 2015 entry GeoQuiz).

And finally, @innovati, @p01 and I, made a graphical music composer called miniPiano that lets you draw a melody and outputs a tiny piece of JS code to play it back.

Everything is ready to make a cool game now!



Week-end 1

When JS13kGames 2016 started, I remembered this Mario Maker idea and decided to mix it with two other of my favourite games, Portal (no presentation needed) and Chronotron (a Flash game that lets you rewind time and collaborate with your past self to solve puzzles).

The idea of mixing Mario and Portal also comes from Super Mario Portal by "Moumou" (20Mb, made with RPG Maker!) and Mari0 by StabYourself.

The idea of mixing Portal and time travel also comes from Thinking with Time Machine, a great Portal 2 mod.

Some inspiration can also be found in Braid (for time travel) and Enough Plumbers (for the multiple Mario gameplay).

And for the record, this article is a great resource about 2D portals implementation!

So I wanted to make a game featuring most of the cool mechanics of these three games (Mario, portal gun, time travel, mechanisms, companion cube, traps, and of course a level editor), but also being something completely unseen before.

The theme of js13k this year is "Glitch!", and to include it in my game, I decided to provoke glitches in case of time paradoxes... (and in a few other cases).

Like previous years, my personal challenge was to make a game so big that it would be complicated to make it fit in 13kb. So with this puzzle-maker idea, I believed it would be a hard (and fun) task to achieve. I really wanted to make a game that was a complete system with a lot of interesting rules, and with a quality high enough that no player would guess that its size was limited (and make them wonder how all that can fit in 13kb).

Unlike previous years, my source code before minification will be really readable: well commented, cut into multiple files, and with explicit variable names! I'll do all the optimizations at the end instead. I can't promise that the code will be clean and mainainable, but at least, it'll be readable.

Last detail: last year, half of the zip was used by the game's data (the world map, the names of countries, capitols, famous places, etc), but this year, I'll really focus on filling it with a lot of code and a ton of features, instead of data. So, to sum up, I don't wanna look lazy this year...

During this first week-end, I wrote the game's specs, then I drew all the images of my game into this spritesheet...

... of course, inspired by Super Mario Maker, Chronotron and Portal, but without the copyrights:

I also developed basic menus (title screen, level selection screen...) and started to develop the main features of the level editor (select tiles, place them on the map, etc).

All the following screenshots show the interface at the end of the week-end...

Current code size: ~7kb commented, ~1.5kb minified and gzipped. (plus a 1.8kb PNG)



Week 2

During the first half of this week, I developed the level editor, to give it strict rules (eg. only one starting point, only one end flag, the ability to place green pipes and switches that make them go up and down, the ability to place pairs of balanced platforms, etc...).

To do that, I had to define a precise format for saving the levels, a format that allows to store all the level's tiles in a grid of 40 * 20 ASCII characters (which is the most compressible output I could find while developing the 1k level editor), plus information about linked elements (pipes and switches, pairs of balanced platforms, etc). And all this data needs to be serializable in a string shorter than 4096 bytes, in order to be easily shareable via an URL on twitter.

Here's what the data of a level looks like:

// As a JS object:
level_data = {
  tiles: [
    [0, 0, 1, 2, ...],
    [3, 2, 0, 0, ...],
    ...
  ],
  pipes: [
    [x, height_1, height_2, switch_x, switch_y],
    ...
  ],
  balances: [
    [x1, y1, x2, y2],
    ...
  ]
};

// As an URL:
http://...#{hash:"0012...",I:[[x,height_1,height_2,switch_x,switch_y]...],J:[[x1,y1,x2,y2]...]}

I also added many checks in the code of the editor to prevent to draw overlapping pipes and balances, and also to avoid overwriting important elements of the map (like the pipes or the time machine) to avoid bugs.

The whole code, including the menus, the complete level editor, as well as the "share", "clear" and "exit" buttons, is 19kb commented, and 2.16kb minified and gzipped. (plus the usual 1.8kb PNG)

~

At the end of the week, I designed the first levels of the game, which will be useful for me, to develop the game's engine in "real conditions".



Weeks 3 & 4

During these two weeks I wrote the game's engine, little by little. Here's my TO DO list, plus some GIFs I tweeted while developing it:

General stuff:

Basic controls:

(at this point (and after a little refactoring session), the JS code fits in ~3.6kb minified and gzipped. The zip takes 5.4kb.)

Mechanisms:

(Little refactoring... current zip size with 5 levels: 6.5kb)

Portals:

(Huge refactoring (it took 5 days)... current zip size with 10 levels: 8.2b)

Time travel:

(Another refactoring...current zip size with 15 levels: 10.5kb)



Weeks 5 & 6

These two weeks (actually, one week plus two days) were consacred to the creation of the final levels included in the game, the music, the debugging and the polishing:

(current zip size: 13.9kb)

There's an important discovery made a few days before the deadline: Advzip, a zip optimizer/recompressor is able to save around 1kb out of a 13kb zip. By passing my current zip through Advzip, it only takes 12.8kb. We're still below the limit! Maybe we can have some music...

At this point, my commented source code weighs 114kb (scattered in 12 files and containing more than 3600 lines of code), and shrinks to 72.4kb when minified (including 27kb of level data)

And yes, as incredible as it sounds, once zipped, all that code fits in less than 13kb!

Golfing:

Zip size after this golfing: 11.1kb!

Final touch

With 24 hours left and nearly 2kb available in my zip, I called Anders Kaare to save my life once again. He made me a great collection of sounds plus a gorgeous musical theme in no time!

I also added a hidden, super difficult level and recorded all my developer times as a challenge for speedrunners.

All these latest details were added in a hurry, without any form of optimization or golfing.

Final beta-testing with my colleagues!

Final interface:




Conclusion

This project took me nearly 200h to complete, much more than any of my previous entries, and more than any of my previous personal projects!

There's a lot more that could be done, but I ran out of time and energy... but never ran out of free space! My final zip is just 12.5kb!

I'm still very happy with the result, and hope that a lot of people will enjoy it and create stunning levels!

Thanks js13kgames, and Andrzej, and Anders for the music, and all the people that helped me and supported me during this compo, and see you next year for something even bigger! :D



One week later:

People play and enjoy the game, they also start making cool levels, and they speedrun it too! That's great!

I also took the time to speedrun it as fast as I could (better than my own "dev times" in the game), and you can see the result here (warning, spoilers!):

But even if you create something and think you master it, someone will always be better than you, and it's just awesome!



Two weeks later:

Publication of the final results:

- Super Chrono Portal Maker was ranked 2nd in Community votes. 26 games in 1 was ranked 10th! Thanks guys!

- Super Chrono Portal Maker was ranked 3rd in the Desktop category (and 26-games-in-1 54th)! Thanks jury!

Also, these playtests by Jupiter Hadley:

- Super Chrono Portal Maker

- 26-games-in-1



2017 update: A new speedrun in 6:18.40!



Cheers!
@MaximeEuziere

EQCSS: The Search For The Holy Grail: How I Ended Up With Element Queries, And How You Can Use Them Today

july 2016

A great article written by @innovati about our Element Queries library got published on Smashing Magazine!

You can read it HERE!

Shitty systems & superfluous hacking fatigue

or: How we waste most of our lives struggling to obtain the most basic features from overly complicated and badly designed systems

june 2016

For once, this isn't a regular front-end blog post. It's a rant post, complaining about the fatigue of having to hack our way through the shitty systems that surround us more and more everyday. Heck, I'm an old engineer, I'm used to shitty systems. My work even consists in dealing with some of the most shitty systems out there: Web browsers. But today, I'll complain about what they call "smart" phones. Warning: curses inside.

I won't mention my own phone, as it's a Lumia 735 that comes from a batch built with some shitty component that makes it reboot randomly, and which is not even capable of installing or updating any application since I (painfully) installed Windows 10 on it. So I'm already screwed in this domain.

But as every engineer, I have relatives that think it's my job to "know these things" and my passion to make them work.

Anyway, my mom has a relatively new Samsung Galaxy S5 Mini smartphone, running on Android 4.4. She makes a very basic usage of it, besides calling people: she takes a ton of pictures, "edits" them through the Google Photos app and posts them on Facebook.

It all began when she wanted "more memory", translate: more space to store the photos on the phone, even if they're backed up in the cloud. Sure, whatever, a 64GB Samsung microSD card is less than 25€ nowadays, so let's buy one and install it.

Rightaway, I try to use my PC to transfer all the photos from the internal 16GB memory to the microSD card... For some reason, i'm not allowed to do it, and have to use the embedded shitty file explorer to do it directly on the phone. It takes hours, and in the process, changes the creation date of every file to the present day, which results in many albums being mixed up in Google Photos. Screw you, file explorer.

Fast forward a few days, I get a complaint that the phone freezes every couple hours, and that Google Photos pops a write error everytime we try to edit a photo and save it. And since I'm the last one who touched it, It's my fault, obviously.

So I take the phone back and try to figure out what the fuck is shittening... After a long search, it turns out this version of Android has a default setting that locks writes on the MicroSD card for most non-native applications, for the user's safety or some shit like that. That's why the default camera app could take pictures and save them on the card, but Google Photos, installed via the Play Store, couldn't edit them in place. And even if I have no proof, I'm pretty certain that Google Photos provoked some sort of memory leak because of this card lock, which was responsible of the freezes. But I digress. Also, I won't mention the fact that an OS made by Google forbids an application made by Google to write on my SD card for my safety, because fuck logic.

Before going further, I take a look at the "firmware update" page of the phone settings, which tells me that a 900MB update to Android 5 is downloaded and ready to install, but every attempt to start the upgrade fails lamentably and there's no solution. WHATEVER.

Okay so how can I disable this default SD lock on 4.4? Turns out, it's just a matter of setting a value to "true" in a XML file stored in the system partition of the phone. But it would be too simple if this partition was visible with a file explorer or a PC. No, I have to install one of the dozens "SD unlock" apps of the Play Store, which will do that for me.

But here's the joke, all those apps weigh a few MB and require something called "root access". Yes. All that, just to edit a fucking line in a fucking XML file. Okay whatever Google, tell me how I can root your shitty OS, I'm tired now.

Rooting an S5 Mini, for some reason, is not as easy as any other fucking Android phone. On this one, the regular rooting solutions fail, and tell me to try somewhere else, generally redirecting me to a Windows software with better success rates.

I plug the phone to my laptop using the mini-USB data cable of my Windows Phone (that's the only one I have on me)... Error: Unrecognized peripheral. Moving to the USB3 port... it works. ah, no. Ah, yes. Ah, no. this motherfucker blinks, and alternates between connected and disconnected every second. The fuck! Root tutorials tell me to install a fork of the Samsung USB drivers without some shitty adware that they include in every exe... impossible to find a live download link. Fuck it, I install the official drivers. It changes nothing. FUCK. Just to be sure, I borrow a genuine Samsung mini-USB data cable... oh. well, it seems to work, but I wouldn't call it stable (still makes some disconnection blinks every minute or so.) Modern Android systems don't behave like a hard drive when connected to a PC but use another shitty "multimedia storage" protocol that seem to be the reason why it's so unstable. Whatever I try, I gotta do it very quickly. I start KingoRoot (the only software that seems genuine in the middle of all the scams), and it tells me to enable the "USB debugging" feature of my phone.

Huh? No USB debugging in the settings.

- Me: "Google, WTF?"
- Google: "USB debugging is in the developper settings ( ͡° ͜ʖ ͡°)".
- Me: "Oh okay, cool... wait, Google, where are the developper settings?"
- Google: "They're hidden, for your safety ( ͡° ͜ʖ ͡°)".
- Me: "FUCK YOU GOOGLE, HOW DO I SHOW THEM?"
- Google: "Go in System Settings and tap eight times the 'version number' line ( ͡° ͜ʖ ͡°)".
- Me: "(╯°□°)╯︵ ┻━┻".
- Google: "¯\_(ツ)_/¯"

A few minutes later, KingoRoot agrees to start working, and at 70%, the phone restarts. Wait, what? Is that part of the process? Of course not. KingoRoot hangs, the phone reboots half-rooted, which is as useful as no rooted at all.

Second try: plug the phone, launch KingoRoot, wait until 100%, and both KingoRoot and the phone tell me victoriously that the root has been successful... ly... removed. THE FUCK? all it did was removing the previous root attempt.

Third try: Same process, it arrives to 100%, and this time it tells me that it seems to have worked.

I launch the SD unlock app, it asks me the permission to request root access, I say OK, it says "processing...", then "Failed. Your phone may not be rooted.". EAT MY ASS, PHONE!

I install and launch another 25MB (!) app called Root Checker whose only purpose is to tell me if my phone is rooted, and the answer is yes. GODDAMMIT.

I retry the SD unlock app, allow it to ask root access, "processing...", and then it tells me that it's sorry but my XML file has already been successfully edited. GO BURN IN HELL, APP.

I finally made it, after hours of shitty hacking. Hours of my life wasted because of shitty systems, with shitty protections, shitty drivers, shitty protocols and shitty error handling. And it's not the first time something like that happens to me or to someone I know! But apart from tiring the hell out of me, this adventure made me wonder...

How could a non-engineer, or someone who doesn't have an entire day to waste could have resolved this problem? How could my mother could have done one percent of what I did? She doesn't even have a computer! Are non-tech-savvy people doomed to live with more and more buggy products? Will they soon be rejected by the society, where only the best hackers will be able to make modern devices work? Will we reach a point where even the best hackers, or the people who make these systems, won't event be able to use them?

Or will there be a savior, a company that builds better systems than Google, Apple and Windows, systems that just work, without any hack? Free startup idea for you, guys! How about me? I won this battle, but the shit-hack fatigue got me. I'm gonna live in the wood and tame goats. See ya.

Xem

The quest of the 🌍🔬🕸🇬🇱🎠 (World's Smallest WebGL Playground)

april to june 2016, january 2017




01/2017 update

We made an entry for JS1k 2017 featuring all the tricks of this article, plus a nice UI and great compatibility with Shadertoy.com. It was ranked #7!



You can find the entry HERE.

The commented source code is HERE.



Full Story

During the last few weeks, the codegolf team (including Mathieu 'p01' Henri, Martin 'Aemkei' Kleppe, Subzey, Anders Kaare, ...) has welcomed new members (LiterallyLara & HellMood, authors of JS1K 2016 winning entry Romanesco 2.0) and gave itself a nice challenge: develop the smallest possible HTML/JS (ES6) boilerplate that's able to run a WebGL fragment shader. It's currently as short as 349b, and looks like this:

<canvas id=a><svg onload='for(i in g=a.getContext`webgl`)g[i[0]+i[6]]=g[i];with(g)vA(P=cP(setInterval(x=>dr(6,vertexAttrib1f(1,NO++),3),A=s=>sS(S=cS(FN++),id+"varying lowp vec4 p,t;void main(){gl_"+s)|ce(S)|!aS(P,S))),2,5120,bD(B=ET-3,Int8Array.of(A`FragColor=[SHADER]}`,B,!eV(bf(B,cB(id="attribute vec4 P,T;"))),A`Position=p=P;t=T;}`),B+82),!lo(P),ug(P))'>

See detailed source code below!

With this boilerplate, you have access to a time counter t.x (a frame counter), the clip space coordinates c (representing gl_PointCoord), and all you need to do is replace [SHADER] with the value set by your shader to gl_FragColor, for example: p+sin(atan(p.y,p.x)*9.)*sin(p*t.x/40.+9./dot(p,p)+t.x/40.); (DEMO).

Optional:

- You can set the size of the canvas with things like a.width=a.height=600; or a.width=outerWidth;a.height=outerHeight; at the beinning of the JS code.
- You can change the animation speed by changing the way t is incremented, NO+=.1 (t incremented by 0.1 at each frame) is often a better trade-off than NO++ (t incremented by 1 at each frame).

Here are some gorgeous demos made by LiterallyLara, with this template, in less than 512b.

For code-golfers and demosceners, this boilerplate offers a total of 163b dedicated to the shader in a 512b demo (or 675b in a 1kb demo, not counting the gain of RegPack or gzip): more than never before in the history of WebGL demo'ing!



Making-of & Interactive playground

Between 2014 and 2015, it took us nearly 60 golfing iterations to make the Codepen clone miniCodeEditor fit in 158b. Since then, I talked with Subzey and wondered if we could do something similar with WebGL, maybe a clone of Shadertoy. I tried many times to understand / simplify the source code of shadertoy, but it was too big and too complex (694kb of JS, with 148kb really dedicated to the app's engine.)

So I reversed the problem and decided to start from a finished, milimalist WebGL demo and try to make it editable, like on ShaderToy.

WebGL implementation is very (very) cryptic, but by chance, Lara's post-mortem gave me all the keys to get started.

As a result, we managed to release, and golf MiniShadertoy, including almost all the features of Shadertoy in less than 1kb...


and then MiniShadertoyLite, allowing to fiddle with just a fragment shader in less than 512b.


While we were discussing about it, and how we could golf it even better, Aemkei had the idea to make a "static" boilerplate, basically the same thing as MiniShadertoy, but not editable. Just the strict minimum required to play a hardcoded shader.



You'll find below a sumary of all the golfing steps that led it to its final, almost unreadable form, and a "clean", commented version.
If you're not familiar with WebGL, you can start by reading the commented source code of MiniShadertoyLite HERE.

How we code-golfed it, step by step:

// Step 1: start from MiniShadertoyLite, ignore the textarea, minify, apply RegPack's webgl method hashing, replace all the constants with their numeric value

<body onload='for(i in g=a.getContext(`webgl`))g[i[0]+i[7]+[i[13]]]=g[i];setInterval(`g.uniform1f(g.goa(P,"T"),T++);g.da(6,0,3)`,9);with(g)P=cr(),so(S=ch(35633),`attribute vec2 P;${V="void main(){gl_"}Position=vec4(P,0,1);}`),cS(S),ah(P,S),so(S=ch(35632),`precision lowp float;uniform float T;${V}FragColor=[SHADER]}`),cS(S),ah(P,S),lg(P),ur(P),bf(B=34962,cu()),eet(T=0),vto(0,2,5120,0,0,0),ba(B,new Int8Array([-3,1,1,-3,1,1]),35044)'><canvas id=a>

// Step 2: simplify `gl_Position=vec4(P,0,1)` to `gl_Position.xy=P`

<body onload='for(i in g=a.getContext(`webgl`))g[i[0]+i[7]+[i[13]]]=g[i];setInterval(`g.uniform1f(g.goa(P,"T"),T++);g.da(6,0,3)`,9);with(g)P=cr(),so(S=ch(35633),`attribute vec2 P;${V="void main(){gl_"}Position.xy=P;}`),cS(S),ah(P,S),so(S=ch(35632),`precision lowp float;uniform float T;${V}FragColor=[SHADER]}`),cS(S),ah(P,S),lg(P),ur(P),bf(B=34962,cu()),eet(T=0),vto(0,2,5120,0,0,0),ba(B,new Int8Array([-3,1,1,-3,1,1]),35044)'><canvas id=a>

// Step 3: place the `;` in the template, reuse B at the end

<body onload='for(i in g=a.getContext(`webgl`))g[i[0]+i[7]+[i[13]]]=g[i];setInterval(`g.uniform1f(g.goa(P,"T"),T++);g.da(6,0,3)`,9);with(g)P=cr(),so(S=ch(35633),`attribute vec2 P${V=";void main(){gl_"}Position.xy=P;}`),cS(S),ah(P,S),so(S=ch(35632),`precision lowp float;uniform float T${V}FragColor=[SHADER]}`),cS(S),ah(P,S),lg(P),ur(P),bf(B=34962,cu()),eet(T=0),vto(0,2,5120,0,0,0),ba(B,new Int8Array([-3,1,1,-3,1,1]),B+82)'><canvas id=a>

// Step 4: `with(g)` including the setInterval, arrow function inside setInterval, `int8array.of()`

<body onload='for(i in g=a.getContext(`webgl`))g[i[0]+i[7]+[i[13]]]=g[i];with(g)setInterval(x=>uniform1f(goa(P,"T"),T++)+da(6,0,3),9),P=cr(),so(S=ch(35633),`attribute vec4 P${V=";varying lowp vec4 c;void main(){gl_"}Position=c=P;}`),(A=x=>cS(S)+ah(P,S))(),so(S=ch(35632),`uniform lowp float T${V}FragColor=[SHADER]}`),A(),lg(P),ur(P),bf(B=34962,cu()),eet(T=0),vto(0,2,5120,0,0,0),ba(B,Int8Array.of(-3,1,1,-3,1,1),B+82)'><canvas id=a>

// Step 5: move some reused code inside the `A()` function, and reuse B better

<body onload='for(i in g=a.getContext(`webgl`))g[i[0]+i[7]+[i[13]]]=g[i];with(g)setInterval(x=>uniform1f(goa(P,"T"),T++)+da(6,0,3),9),P=cr(),B=35633,(A=s=>so(S=ch(B--),s)+cS(S)+ah(P,S))(`attribute vec4 P${V=";varying lowp vec4 c;void main(){gl_"}Position=c=P;}`),A(`uniform lowp float T${V}FragColor=[SHADER]}`),lg(P),ur(P),bf(B=34962,cu()),eet(T=0),vto(0,2,5120,0,0,0),ba(B,Int8Array.of(-3,1,1,-3,1,1),B+82)'><canvas id=a>

// Step 6: replace "body onload=..." with "svg onload=..."

<canvas id=a><svg onload='for(i in g=a.getContext(`webgl`))g[i[0]+i[7]+[i[13]]]=g[i];with(g)setInterval(x=>uniform1f(goa(P,"T"),T++)+da(6,0,3),9),P=cr(),B=35633,(A=s=>so(S=ch(B--),s)+cS(S)+ah(P,S))(`attribute vec4 P${V=";varying lowp vec4 c;void main(){gl_"}Position=c=P;}`),A(`uniform lowp float T${V}FragColor=[SHADER]}`),lg(P),ur(P),bf(B=34962,cu()),eet(T=0),vto(0,2,5120,0,0,0),ba(B,Int8Array.of(-3,1,1,-3,1,1),B+82)'>

// Step 7: use a simpler hashing (`g[i[0]+i[6]]` instead of `g[i[0]+i[7]+[i[13]]]`)

<canvas id=a><svg onload='for(i in g=a.getContext(`webgl`))g[i[0]+i[6]]=g[i];with(g)setInterval(x=>uniform1f(gf(P,"T"),T++)+dr(6,0,3),9),P=cP(),B=35633,(A=s=>sS(S=cS(B--),s)+ce(S)+aS(P,S))(`attribute vec4 P${V=";varying lowp vec4 c;void main(){gl_"}Position=c=P;}`),A(`uniform lowp float T${V}FragColor=[SHADER]}`),lo(P),ug(P),bf(B=34962,cB()),eV(T=0),vA(0,2,5120,0,0,0),bD(B,Int8Array.of(-3,1,1,-3,1,1),B+82)'>

// Step 8: replace the first usages of `B` (36532, 36533) with hashed properties (`g.FN` and `g.FN+1`, simplified to `g.FN++` called twice)

<canvas id=a><svg onload='for(i in g=a.getContext(`webgl`))g[i[0]+i[6]]=g[i];with(g)setInterval(x=>uniform1f(gf(P,"T"),T++)+dr(6,0,3),9),P=cP(),(A=s=>sS(S=cS(FN++),s)+ce(S)+aS(P,S))(`uniform lowp float T${V=";varying lowp vec4 c;void main(){gl_"}FragColor=[SHADER]}`),A(`attribute vec4 P${V}Position=c=P;}`),lo(P),ug(P),bf(B=34962,cB()),eV(T=0),vA(0,2,5120,0,0,0),bD(B,Int8Array.of(-3,1,1,-3,1,1),B+82)'>

// Step 9: replace `T` with `g.NO`, which is already set to 0

<canvas id=a><svg onload='for(i in g=a.getContext(`webgl`))g[i[0]+i[6]]=g[i];with(g)setInterval(x=>uniform1f(gf(P,"T"),NO++)+dr(6,0,3),9),P=cP(),(A=s=>sS(S=cS(FN++),s)+ce(S)+aS(P,S))(`uniform lowp float T${V=";varying lowp vec4 c;void main(){gl_"}FragColor=[SHADER]}`),A(`attribute vec4 P${V}Position=c=P;}`),lo(P),ug(P),bf(B=34962,cB()),eV(0),vA(0,2,5120,0,0,0),bD(B,Int8Array.of(-3,1,1,-3,1,1),B+82)'>

// Step 10: simplify the constant `34962` with `g.ET-3`

<canvas id=a><svg onload='for(i in g=a.getContext(`webgl`))g[i[0]+i[6]]=g[i];with(g)setInterval(x=>uniform1f(gf(P,"T"),NO++)+dr(6,0,3),9),P=cP(),(A=s=>sS(S=cS(FN++),s)+ce(S)+aS(P,S))(`uniform lowp float T${V=";varying lowp vec4 c;void main(){gl_"}FragColor=[SHADER]}`),A(`attribute vec4 P${V}Position=c=P;}`),lo(P),ug(P),bf(B=ET-3,cB()),eV(0),vA(0,2,5120,0,0,0),bD(B,Int8Array.of(-3,1,1,-3,1,1),B+82)'>

// Step 11: Subzey joins the game... remove `getContext` parenthesis, replace the second param of `g.dr` (0) with the result of the neighbour function `uniform1f()`. Renamem c into p and T to t

<canvas id=a><svg onload='for(i in g=a.getContext`webgl`)g[i[0]+i[6]]=g[i];with(g)setInterval(x=>dr(6,uniform1f(gf(P,"t"),NO++),3),P=cP(),(A=s=>sS(S=cS(FN++),s)+ce(S)+aS(P,S))(`uniform lowp float t${V=";varying lowp vec4 p;void main(){gl_"}FragColor=[SHADER]}`),A(`attribute vec4 P${V}Position=p=P;}`),lo(P),ug(P),bf(B=ET-3,cB()),eV(0),vA(0,2,5120,0,0,0),bD(B,Int8Array.of(-3,1,1,-3,1,1),B+82))'>

// Step 12: get rid of some more zeros
<canvas id=a><svg onload='for(i in g=a.getContext`webgl`)g[i[0]+i[6]]=g[i];with(g)setInterval(x=>dr(6,uniform1f(gf(P,"t"),NO++),3),P=cP(),(A=s=>sS(S=cS(FN++),s)+ce(S)+aS(P,S))(`uniform lowp float t${V=";varying lowp vec4 p;void main(){gl_"}FragColor=[SHADER]}`),vA(A(`attribute vec4 P${V}Position=p=P;}`),2,5120,lo(P),ug(P),eV(bf(B=ET-3,cB()))),bD(B,Int8Array.of(-3,1,1,-3,1,1),B+82))'>

// Step 13: move `A` declaration into `cP()`

<canvas id=a><svg onload='for(i in g=a.getContext`webgl`)g[i[0]+i[6]]=g[i];with(g)setInterval(x=>dr(6,uniform1f(gf(P,"t"),NO++),3),P=cP(A=s=>sS(S=cS(FN++),s)+ce(S)+aS(P,S)),A(`uniform lowp float t${V=";varying lowp vec4 p;void main(){gl_"}FragColor=[SHADER]}`),vA(A(`attribute vec4 P${V}Position=p=P;}`),2,5120,lo(P),ug(P),eV(bf(B=ET-3,cB()))),bD(B,Int8Array.of(-3,1,1,-3,1,1),B+82))'>

// Step 14: move `vA`, replace the ones in `Int8Array.of()` with neighbor functions

<canvas id=a><svg onload='for(i in g=a.getContext`webgl`)g[i[0]+i[6]]=g[i];with(g)setInterval(x=>dr(6,uniform1f(gf(P,"t"),NO++),3),!vA(P=cP(A=s=>sS(S=cS(FN++),s)+ce(S)+aS(P,S)),2,5120,A(`uniform lowp float t${V=";varying lowp vec4 p;void main(){gl_"}FragColor=[SHADER]}`),eV(bf(B=ET-3,cB())),bD(B,Int8Array.of(-3,1,!A(`attribute vec4 P${V}Position=p=P;}`),-3,!lo(P),!ug(P)),B+82)))'>

// Step 15: move `vA(P=cP(...))` around the `setInterval`

<canvas id=a><svg onload='for(i in g=a.getContext`webgl`)g[i[0]+i[6]]=g[i];with(g)vA(P=cP(setInterval(x=>dr(6,uniform1f(gf(P,"t"),NO++),3),A=s=>sS(S=cS(FN++),s)+ce(S)+aS(P,S))),2,5120,A(`uniform lowp float t${V=";varying lowp vec4 p;void main(){gl_"}FragColor=[SHADER]}`),eV(bf(B=ET-3,cB())),bD(B,Int8Array.of(-3,1,!A(`attribute vec4 P${V}Position=p=P;}`),-3,!lo(P),!ug(P)),B+82))'>

// Step 16: change the Int8Array's vertices to `3,-1,-1,3,-1,-1` and use `~` to turn `null` into `-1`

<canvas id=a><svg onload='for(i in g=a.getContext`webgl`)g[i[0]+i[6]]=g[i];with(g)vA(P=cP(setInterval(x=>dr(6,uniform1f(gf(P,"t"),NO++),3),A=s=>sS(S=cS(FN++),s)+ce(S)+aS(P,S))),2,5120,A(`uniform lowp float t${V=";varying lowp vec4 p;void main(){gl_"}FragColor=[SHADER]}`),eV(bf(B=ET-3,cB())),bD(B,Int8Array.of(3,-1,~A(`attribute vec4 P${V}Position=p=P;}`),3,~lo(P),~ug(P)),B+82))'>

// Step 17: Make `A()` always return `-1`

<canvas id=a><svg onload='for(i in g=a.getContext`webgl`)g[i[0]+i[6]]=g[i];with(g)vA(P=cP(setInterval(x=>dr(6,uniform1f(gf(P,"t"),NO++),3),A=s=>sS(S=cS(FN++),s)|ce(S)|~aS(P,S))),2,5120,0,eV(bf(B=ET-3,cB())),bD(B,Int8Array.of(3,A(`uniform lowp float t${V=";varying lowp vec4 p;void main(){gl_"}FragColor=[SHADER]}`),A(`attribute vec4 P${V}Position=p=P;}`),3,~lo(P),~ug(P)),B+82))'>

// Step 18: change the shape of the triangle, using the return value of `setInterval` (usually between 1 and 127) as first param of `Int8Array`

<canvas id=a><svg onload='for(i in g=a.getContext`webgl`)g[i[0]+i[6]]=g[i];with(g)vA(P=cP(),2,5120,bD(B=ET-3,Int8Array.of(B,setInterval(x=>dr(6,uniform1f(gf(P,"t"),NO++),3),A=s=>sS(S=cS(FN++),s)|ce(S)|aS(P,S)),!A(`uniform lowp float t${V=";varying lowp vec4 p;void main(){gl_"}FragColor=[SHADER]}`),B,!eV(bf(B,cB())),!A(`attribute vec4 P${V}Position=p=P;}`)),B+82),lo(P),ug(P))'>

// Step 19: introduce C and T as attributes, set `t.x=T.x`, and use `vA` (vertexAttrib1f) instead of `uniform1f`

<canvas id=a><svg onload='for(i in g=a.getContext`webgl`)g[i[0]+i[6]]=g[i];with(g)vA(P=cP(),2,5120,bD(B=ET-3,Int8Array.of(B,setInterval(x=>dr(6,vertexAttrib1f(1,NO++),3),A=s=>sS(S=cS(FN++),s)|ce(S)|aS(P,S)),!A(`${V="varying lowp vec4 p,t;void main(){gl_"}FragColor=[SHADER]}`),B,!eV(bf(B,cB())),!A(`attribute vec4 P,T;${V}Position=p=P;t.x=T.x;}`)),B+82),lo(P),ug(P))'>

// Step 20: Set `t=T` directly

<canvas id=a><svg onload='for(i in g=a.getContext`webgl`)g[i[0]+i[6]]=g[i];with(g)vA(P=cP(),2,5120,bD(B=ET-3,Int8Array.of(B,setInterval(x=>dr(6,vertexAttrib1f(1,NO++),3),A=s=>sS(S=cS(FN++),s)|ce(S)|aS(P,S)),!A(`${V="varying lowp vec4 p,t;void main(){gl_"}FragColor=[SHADER]}`),B,!eV(bf(B,cB())),!A(`attribute vec4 P,T;${V}Position=p=P;t=T;}`)),B+82),lo(P),ug(P))'>

// Step 21: Set 5th parameter of vA (i.e. `vertexAttribPointer`), stride, to 1, to  make the coordinate pairs overlap: `int8Array.of(x1 = 1, y1 = x2 = -3, y2 = x3 = 1, y3 = 1)`. Also, the setTimeout is moved into `cP` (i.e. `createProgram`)

<canvas id=a><svg onload='for(i in g=a.getContext`webgl`)g[i[0]+i[6]]=g[i];with(g)vA(P=cP(setInterval(x=>dr(6,vertexAttrib1f(1,NO++),3),A=s=>sS(S=cS(FN++),s)|ce(S)|aS(P,S))),2,5120,bD(B=ET-3,Int8Array.of(!A(`${V="varying lowp vec4 p,t;void main(){gl_"}FragColor=[SHADER]}`),B,!eV(bf(B,cB())),!A(`attribute vec4 P,T;${V}Position=p=P;t=T;}`)),B+82),!lo(P),ug(P))'>

// Step 22: Make `A` return 1 in order to avoid calling `!A()` with a `!` twice

<canvas id=a><svg onload='for(i in g=a.getContext`webgl`)g[i[0]+i[6]]=g[i];with(g)vA(P=cP(setInterval(x=>dr(6,vertexAttrib1f(1,NO++),3),A=s=>sS(S=cS(FN++),s)|ce(S)|!aS(P,S))),2,5120,bD(B=ET-3,Int8Array.of(A(`${V="varying lowp vec4 p,t;void main(){gl_"}FragColor=[SHADER]}`),B,!eV(bf(B,cB())),A(`attribute vec4 P,T;${V}Position=p=P;t=T;}`)),B+82),!lo(P),ug(P))'>

// Step 23: Rearrange `Int8Array.of()` arguments to place `!eV()` first, and move `V` declaration into `cb()`

<canvas id=a><svg onload='for(i in g=a.getContext`webgl`)g[i[0]+i[6]]=g[i];with(g)vA(P=cP(setInterval(x=>dr(6,vertexAttrib1f(1,NO++),3),A=s=>sS(S=cS(FN++),s)|ce(S)|!aS(P,S))),2,5120,bD(B=ET-3,Int8Array.of(!eV(bf(B,cB(V="varying lowp vec4 p,t;void main(){gl_"))),B,A(V+`FragColor=[SHADER]}`),A(`attribute vec4 P,T;${V}Position=p=P;t=T;}`)),B+82),!lo(P),ug(P))'>

// Step 24: use the svg's id (which is an empty string)

<canvas id=a><svg onload='for(i in g=a.getContext`webgl`)g[i[0]+i[6]]=g[i];with(g)vA(P=cP(setInterval(x=>dr(6,vertexAttrib1f(1,NO++),3),A=s=>sS(S=cS(FN++),id+"varying lowp vec4 p,t;void main(){gl_"+s)|ce(S)|!aS(P,S))),2,5120,bD(B=ET-3,Int8Array.of(A`FragColor=[SHADER]}`,B,!eV(bf(B,cB(id="attribute vec4 P,T;"))),A`Position=p=P;t=T;}`),B+82),!lo(P),ug(P))'>

Mini WebGL boilerplate, commented:

<canvas id=a>
<script>

// Canvas methods hashing:
// This loop creates tiny shortcuts for all the webgl context's methods/constants we need:
// g.createProgram => g.cP
// g.shaderSource => g.sS
// g.createShader => g.cS
// g.compileShader => g.ce
// g.attachShader => g.aS
// g.linkProgram => g.lo
// g.useProgram => g.ug
// g.bindBuffer => g.bf
// g.createBuffer => cB
// g.enableVertexAttribArray => g.eV
// g.vertexAttribPointer => g.vA
// g.bufferData => g.bD
// g.getUniformLocation => g.gf
// g.drawArrays => g.dr
// g.NO_ERROR => g.NO (value = 0)
// g.FRAGMENT_SHADER => g.FN (value: 35632)
// g.ELEMENT_ARRAY_BUFFER_BINDING => g.ET (value: 34965)
for(i in g=a.getContext(`webgl`)){
  g[i[0]+i[6]]=g[i];
}

id = "";                                    // The following code is minified and placed in an <svg onload="...">. The variable "id" below is therefore an empty string, until we rename it.

with(g){                                    // Keep g into scope for future function calls (ex: vA == g.vA)
  vA(                                       // Call vA (vertexAttribPointer) with the following params: vertex_index = 0, components_per_vertex_attribute = 2, vertex_attributes_type = g.BYTE = 5120, normalized = 0, stride = 1, offset = 0.
    P=cP(                                   // Call cP (createProgram) to create the program P and return 0 as 1st param of vA
      setInterval(                          // Start the main loop
        x=>dr(6,vertexAttrib1f(1,NO++),3),  // Inside the loop, increase the uniform variable T (by reusing g.NO, initialized at 0) and call g.drawArrays with the params: mode = g.TRIANGLE_FAN = 6, first = 0, count = 3.
        A=s=>{                              // Then define the function A that takes a source string s...
          sS(                               // set the shader's source (sS = shaderSource)...
            S=cS(FN++),                     // with a new shader (cs = createShader), increases g.FN...
            id                              // and use as source the svg's id, plus a constant piece of code, plus s.
            +
            "varying lowp vec4 p,t;void main(){gl_"
            +
            s                               
          )
          |ce(S)                            // compiles the shader (ce = compileShader)...
          |!aS(P,S)                         // attaches the shader to P (aS = attachShader), and return 1 because Firefox needs a positive integer as the 2nd param of setInterval.
        }
      )
    ),                                      
    2,                                      // 2nd param of vA
    5120,                                   // 3rd param of vA
    bD(                                     // Fill bD (bufferData), and return 0 as 4th param of vA
      B=ET-3,                               // Initialize B (1st param of bD) to g.ARRAY_BUFFER = 34962 = g.ET - 3. we can't use g.AB as it's overwritten during hashing, and doesn't correspond to g.ARRAY_BUFFER anymore)
      Int8Array.of(                         // Set the coordinates of a big triangle surrounding the canvas (x1=1, y1=x2=B, y2=x3=1, y3=1) as the 2nd param of bD. The coordinates overlap because the "stride" param of vA is set to 1.
        A`FragColor=[SHADER]}`,             // x1 = A("FragColor=[SHADER]}") = 1. We call A() a first time to define the fragment shader (at this moment, g.FN = g.FRAGMENT_SHADER = 36532)
        B,                                  // y1 = x2 = B = 34962
        !eV(                                // y2 = x3 = !eV(0) = !enableVertexAttribArray(0) = 1
          bf(                               // Use bf (bindBuffer) to bind the buffer created on-the-fly with cB and return 0
            B,                              // g.ARRAY_BUFFER
            cB(                             // g.createBuffer
              id="attribute vec4 P,T;"      // set the id of the svg, as a prefix for the next time we call A()
            )                               
          )
        ),
        A`Position=p=P;t=T;}`               // y3 = A(Position=p=P;t=T;}) = 1. We call A() a second time to create the vertex shader (at this moment, g.FN = 35633 = g.VERTEX_SHADER)
      ),
      B+82                                  // 3rd param of bD: g.STATIC_DRAW = 35044 = 34962 + 82
    ),
    !lo(P),                                 // Link the program P (lo = linkProgram), return 1 as 5th param of vA
    ug(P)                                   // Use the program P (ug = useProgram), return 0 as 6th param of vA
  )
}

</script>




With all this progress, MiniShadertoyLite became a little outdated... so we made a new version including all the previous improvements.
There's now enough space to include a fullscreen canvas and a contenteditable body and remain under 512b!
Here's the last version of MiniShadertoyLite:

And thanks to @innovati for the Emoji title: 🌍🔬🕸🇬🇱🎠 !



Cheers!

The Codegolf team

JS1k 2016

febuary - march 2016

JS1k is certainly code-golfing event that makes me wait for febuary to come, during all the rest of the year.

This year, the theme was EleMental, and I decided to make two chemistry-related demos with the help of my friends Innovati and Subzey (a.k.a. The Codegolf Team), plus a couple of entries by myself.

If you're curious about how they were made or how they can fit in 1kb, take a look at the detailed source code. It's very detailed.

If you want to support us for the "social prize", you can upvote the demos on reddit!



PERIOD1k

Our first challenge was to draw a pretty periodic table of chemical elements with as much information as possible. After long efforts of data compression dead-ends and successes, we released PERIOD1k. It was the first entry of the compo.



- The entry on js1k (1024b)
- The post on reddit
- The project (with bonuses) on Github
- The detailed source code



JSOTOPES

We then gave ourselves another compression challenge: draw a complete table of isotopes (or table of nuclides). This time, there was so much data to include th the app that the JS code used to draw the chart only takes ~10% of the demo, all the rest is used for the position, number and decay types of all the known isotopes.



- The entry on js1k (1023b)
- The post on reddit
- The project (with bonuses) on Github
- The detailed source code

PS: In febuary, researchers discovered the tetraneutron particle. I didn't manage to include it in our demo in less than 1025b. ARRGGH.



UN1kODE

For my first individual entry, I wanted to revisit a Unicode slideshow that I made a few months earlier, called MiniUnicode. It's actually a collection of slideshows made to fit in 64b, 128b, 256b, 512b, 4kb, 512kb, including more and more details at each version. The 512b version only shows the assigned code points, but the 4kb version includes all the Unicode blocks names. I decided to do something in the middle: a demo that shows all the assigned code points, but is also aware of all the blocks sizes, and able to start the animation from any of these blocks. That required a lot of compression and transformation of the original code, but it worked!



- The entry on js1k (1024b)
- The post on reddit
- The project on Github
- The detailed source code



MiniBeautifier

This final challenge came as a surprise, while I was developping my code-golfing IDE. Subzey suggested that I add a code beautifier to see the "unminified" version of our code in real time, so I decided to develop one from scratch. Subzey also told me it was impossible to achieve this task using RegExes, so I took it as a personal challenge, and succeeded. Then I golfed my code a little and realized it could fit in 1kb with some extra compression. And that's what I did.



- The entry on js1k (1022b)
- The post on reddit
- The project on Github
- The detailed source code



Bonus: emulation!

There are some other 1kb projects that I started to prepare before febuary, but they weren't good enough (or small enough) to be submitted. Here they are in exclusivity, just for you:

Here's a Chip8 ROM decompiler (1024b gzipped)




And a Chip8 emulator with sound, packed in a js1k shim (~1040b regpacked, but still buggy)



(Both are inspired by my old Chip8 emulator project, where I had made a PNG-bootstrapped Chip8 emulator, without sound, in 1028b).



Bonus 2: Compilations!


I also prepared two compilations including all the tiny apps and games that I golfed with the team
(@p01, @subzey, @aemkei, @0ndras, @maettig and @ilesinge) during the past year.
Sadly, none of them fits in 1kb but you can try them here!

4 Games (1230b)


(Featuring MiniGameOfBraille, MiniFlappyBraille, Ping & Pacman)



7 Apps (1157b)


(Featuring MiniMandelbrot, MiniMandelbrot ASCII, MiniBookmarklet, Countdown 2017, Xmas trees, MiniKeyCode & MiniUnicode)






Cheers,
xem

Obfusc-a-tweet reloaded

november-december 2015


TL;DR - the app is here: xem.github.io/obfuscatweet-reloaded



Last year, Subzey and I introduced Obfusc-a-tweet, allowing to fit 190 chars of JavaScript in a single 140-chars tweet, by packing a pair of ASCII chars in 95 Unicode symbols and using the other 45 chars to unpack and execute them.

This time, the idea is to take advantage of Twitter's URL shortener!

When you tweet an URL, Twitter currently shrinks it (or lenghtens it) into a 23-chars URL, in the form https://t.co/xxxxxxxxxx.

In the final tweet, your original URL can appear truncated on the left or on the right, or both, according to this rule.

But when you copy-paste such a tweet from Twitter to a text editor or a JS console, you get the entire original URL (instead of the shortened URL or the truncated URL), followed by a space and an "…" ellipsis character.



Testing the limits

I made some experiments so find the limits of this system and here's what I found:




Packing JS code

So, if we want to store a lot of text in a 140-chars tweet, the theorical limit is about 5 x 4084 url-encoded chars (stored into 5 URLs) + 4 spaces or punctuations between each URL + 21 other characters.

But our goal is to have an executable tweet, that can be copied as-is into a JS console and run instantly. Thus, the tweet needs to contain some long URLs plus a small JS unpacker and executer. After a lot of trial and error, here is the most efficient solution I could find: (it contains only 4 URLs and uses all the other chars to unpack and execute the JS code).

eval(decodeURI(" 🖝http://a.fr/WWW http://a.fr/XXX http://a.fr/YYY http://a.fr/ZZZ".replace(/ .{1,14}/g,'')))

... where WWW, XXX, YYY and ZZZ are url-encoded strings up to 4084 chars long.

Note that the four URLs are placed into a long string, surrounded by JS code.

When this message is tweeted, it takes exactly 140 characters.

When it's copy-pasted from Twitter to a JS console, the result is almost the same thing, the only difference being the spaces and ellipsis added after each URL:

eval(decodeURI(" 🖝http://a.fr/WWW … http://a.fr/XXX … http://a.fr/YYY … http://a.fr/ZZZ …".replace(/ .{1,14}/g,'')))

When you execute that into a JS console, here's what happens:

// step 0: copy-paste the tweet

eval(decodeURI(" 🖝http://a.fr/WWW … http://a.fr/XXX … http://a.fr/YYY … http://a.fr/ZZZ …".replace(/ .{1,14}/g,'')))

// step 1: the replace(/ .{1,14}/g,'') transforms the string to keep only the interesting parts
// it removes the beginning: " 🖝http://a.fr/", because it's a space followed by 14 characters.
// The "🖝" is an astral Unicode character. It counts as two chars in JS, but only one on Twitter.
// It also removes the three occurences of " … http://a.fr/" (space + 14 chars)
// And it also removes the end: " …" (space + 1 char).
// After the replace, here's the remaining string:

eval(decodeURI("WWWXXXYYYZZZ"))

// step 2: the remaining string is url-decoded to produce the final code
// for example, any occurrence of "%7D" will be decoded to a tilde ("~"), etc.

eval("final code")

// step 3: the remaining string (our final JS code) is evaluated
  

The 🖝 symbol can be replaced by any astral Unicode symbol (U+10000 - U+10FFFF).

At this point, we can potentially store up to 4 * 4084 = 16336 chars in a single tweet! But this total includes escape sequences for many characters that can't be used into URLs (such sequences take 3 to 12 chars each).



A word about encoded URIs

Here are the 95 printable ASCII chars:

 !"#$%&'()*+,-./0123456789:;<=>?
@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^
_`abcdefghijklmnopqrstuvwxyz{|}~

Here are the same chars after passing them through JS's encodeURI:

%20!%22#$%25&'()*+,-./0123456789:;%3C=%3E?
@ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5D%5E
_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~

Basically, according to JS, the only chars that need to be escaped are space, ", %, <, >, [, \, ], ^, `, {, | and }.

But Twitter has some quirks: it doesn't support URLs containing empty parenthesis like "()" or unbalanced parenthesis like "a(a)a)" or "(a(a)a" and on the other hand, it supports URLs containing %, [, ] and |.

We need to take that into account to build a packer as effective as possible. (We will keep parenthesis and percent signs escaped to avoid weird parsing bugs, and we will keep [, ] and | unescaped because they are safe enough to be used like that in our URLs).



Building the packer

To sum things up, if we want to make a JS packer, the mission is to take a JS string as input, URL-encode it, URL-encode the parenthesis characters that can cause problems on Twitter, decode the three chars that don't need to be encoded ([, ], |), split the result in 4084-char blocks, pack these blocks in URLs, surround these URLs with an unpacker/executer, and output the result as a tweetable message.

Here's the packer! (use the first option to test the above technique)

And here's a demo tweet that alerts a 12641-char text



UPDATE #1: Go further with charcode shifting!

After releasing the first version of this packer, I realized with Anders Kaare that a lot of non-ASCII chars were actually useable in Twitter's URLs. After some manual research, we discovered that there's actually a total of 483 chars that can be used safely.

!#$&'()*+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz[]|~ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀǁǂǃDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿȀȁȂȃȄȅȆȇȈȉȊȋȌȍȎȏȐȑȒȓȔȕȖȗȘșȚțȜȝȞȟȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿɀɁɂɃɄɅɆɇɈɉɊɋɌɍɎɏ

This represents a subset of the Unicode blocks "Basic Latin" and "Latin-1 supplement" plus the whole blocks "Latin-extended-A" and "Latin-extended-B", containing respectively 128 and 256 characters.

So I had the idea to "shift" all the ASCII characters present in the JS source code (U+0000 - U+007F), to represent them with Latin-extended-A characters (U+0100 - U+017F). The advantage here is to avoid all the escape sequences needed to represent the special chars of our JS code in an URL. All we need to do is to add 0x100 to each charcode.

With the previous version, a 14-chars string like "Hello World ^^" was URL-encoded as "Hello%20World%20%5E%5E" (which is 22 chars long). With this "shift", it's converted to "ňťŬŬůĠŗůŲŬŤĠŞŞ" (which is only 14 chars long).

Of course, this shifted code is not directly executable, it needs to be unshifted first. Here's the unshifter I wrote with the help of Subzey:

eval(unescape(escape('...shifted code...').replace(/u../g,'')))

If you're confused by this code, you can read an explanation here. (we used the same principle in the original obfusc-a-tweet app)

We don't have room in the tweet itself to include this unshifter, because the tweet is already filled with the URLs and their unpacker and executer, so we need to place this code directly inside our URLs.

Here's an example of a complete tweet:

eval(decodeURI(" 🖝http://a.fr/eval%28unescape%28escape%28'WWW … http://a.fr/XXX … http://a.fr/YYY … http://a.fr/ZZZ'%29.replace(/u../g,'')%29%29 …".replace(/ .{1,14}/g,'')))

where WWW, XXX, YYY and ZZZ respectively represent shifted code up to 4056, 4084, 4084 and 4055 chars long.

Note that the first URL starts with an URL-encoded version of eval(unescape(escape(' and the last URL ends with an URL-encoded version of ').replace(/u../g,''))).

Here's what happens when this tweet is executed:

// step 0: copy-paste the tweet

eval(decodeURI(" 🖝http://a.fr/eval%28unescape%28escape%28'WWW … http://a.fr/XXX … http://a.fr/YYY … http://a.fr/ZZZ'%29.replace(/u../g,'')%29%29 …".replace(/ .{1,14}/g,'')))

// Unpacker-side:

// step 1: the replace() transforms the string to keep only the interesting parts
// After the replace, here's the remaining string:

eval(decodeURI("eval%28unescape%28escape%28'WWWXXXYYYZZZ'%29.replace(/u../g,'')%29%29"))

// step 2: the remaining string is url-decoded:

eval("eval(unescape(escape('WWWXXXYYYZZZ').replace(/u../g,'')))")

// step 3: the remaining string is evaluated:

eval(unescape(escape('WWWXXXYYYZZZ').replace(/u../g,'')))

// Unshifter-side:

// Step 4: the string is unshifted to produce the final code

eval('final code')

// Step 5: the remaining string is evaluated.

This unshifter consumes 57 chars of our URLs, but it removes all risk of escape sequences. So we're now sure to store up to 16,279 chars of JS in a tweet.

Here's the updated packer! (choose the second option under the input section to use the shifter)

And here's a demo tweet that alerts a 16271-char text



UPDATE #2: Go even further with shift + PNG bootstrapping

So, if we sum up our possibilities right now, we can either execute up to 16,336 chars of JS (including the escape sequences for all the chars that are not URL-safe), or execute up to 16,279 chars of JS (shifted to avoid using any escape sequence). This works fine with minified and even RegPacked code. But what if we could gzip our JS code, embed it into our 16,279 shifted chars, and add a little bit of extra code to extract and execute it?

Well, some people already kinda had this idea, and it's called JsExe. JsExe encodes the chars of any JS code in the pixels of a greyscale PNG image. The PNG format is natively gzipped. The PNG is then saved as a HTML page that includes itself in an IMG, draws it on a canvas, and reads the pixels to retrieve and execute the original JS code. Even though the extre code takes about 200 bytes, this "PNG bootstrapping" technique is generally more efficient than RegPack, even for demos as small as 1kb.

So we decided to try that. First, Subzey and I developed zpng, a pure JS clone of JsExe. It turned out to be even more efficient than JsExe thanks to the use of zopfli compression!

Then I took the code that generates PNG from zpng and included it in obfuscatweet-reloaded.

I made it encode the JS source code into a PNG, converted the PNG bytes into extended ASCII, shifted them, and it gave me something like that:

// Source code
alert("Hello World ^^");

// PNG as dataURI
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAABCAAAAAAOB1pJAAAAIklEQVR4AWNIzEktKtFQ8kjNyclXCM8vyklRiItT0rRmAABw4wfh
  
// PNG's content, as text

‰PNG


IHDRZI"IDATxcHÌI-*ÑPòHÍÉÉWÏ/ÊIQˆ‹SÒ´fpãá
  
// PNG's content, as shifted text
ƉŐŎŇčĊĚĊĀĀĀčʼnňńŒĀĀĀęĀĀĀāĈĀĀĀĀĎćŚʼnĀĀĀĢʼnńŁŔŸāţňnjʼnĭĪǑŐDzňǍljljŗĈǏįNJʼnőƈƋœǒƴŦĀĀŰǣćǡ

Cool, so now the PNG's content is tweetable. In order to use it, we have to unshift it, convert it in dataURI, put it in an image, draw that image on a canvas, read the pixels of the canvas, transform it in JS and execute the JS.

The principle is the following:

var png = "ƉŐŎŇčĊĚĊĀĀĀčʼnňńŒĀĀĀęĀĀĀāĈĀĀĀĀĎćŚʼnĀĀĀĢʼnńŁŔŸāţňnjʼnĭĪǑŐDzňǍljljŗĈǏįNJʼnőƈƋœǒƴŦĀĀŰǣćǡ";
var g;
// (snip) unshift png and put the result in g
z=new Image;
z.src='data:image/png;base64,'+btoa(g);
V=document.createElement('canvas');
C=V.getContext("2d");
for($=_=''; C.drawImage(z,$--,0), X=(q=C.getImageData(0,0,64,32)).data[0]; _+=String.fromCharCode(X)){};
(e=eval)(_);
  

Let's golf it:

(z=new Image).src='data:;base64,'+btoa(g);C=document.createElement('canvas').getContext('2d');for($=_='';C.drawImage(z,$--,0),X=(q=C.getImageData(0,0,64,32)).data[0];_+=String.fromCharCode(X));(e=eval)(_)
  

204 bytes. Great! Now here's the real trick. The PNG content is stored as shifted text. This bootstrap code should also be shifted, to use as few chars as possible. So the idea is to put the two shifted blocks in the same string:

var tmp = 'ĨźĽŮťŷĠʼnŭšŧťĩĮųŲţĽħŤšŴšĺĻŢšųťĶĴĬħīŢŴůšĨŧĮųŬũţťĨIJıĵĩĩĻŃĽŤůţŵŭťŮŴĮţŲťšŴťŅŬťŭťŮŴĨħţšŮŶšųħĩĮŧťŴŃůŮŴťŸŴĨħIJŤħĩĻŦůŲĨĤĽşĽħħĻŃĮŤŲšŷʼnŭšŧťĨźĬĤĭĭĬİĩĬŘĽĨűĽŃĮŧťŴʼnŭšŧťńšŴšĨİĬİĬĶĴĬijIJĩĩĮŤšŴšśİŝĻşīĽœŴŲũŮŧĮŦŲůŭŃŨšŲŃůŤťĨŘĩĩĻĨťĽťŶšŬĩĨşĩ' /*bootstrap*/ + 'ƉŐŎŇčĊĚĊĀĀĀčʼnňńŒĀĀĀęĀĀĀāĈĀĀĀĀĎćŚʼnĀĀĀĢʼnńŁŔŸāţňnjʼnĭĪǑŐDzňǍljljŗĈǏįNJʼnőƈƋœǒƴŦĀĀŰǣćǡ' /* PNG */
  

Then unshift this string, as we already did in the previous chapter, an put the result in g:

g=unescape(escape('ĨźĽŮťŷĠʼnŭšŧťĩĮųŲţĽħŤšŴšĺĻŢšųťĶĴĬħīŢŴůšĨŧĮųŬũţťĨIJıĵĩĩĻŃĽŤůţŵŭťŮŴĮţŲťšŴťŅŬťŭťŮŴĨħţšŮŶšųħĩĮŧťŴŃůŮŴťŸŴĨħIJŤħĩĻŦůŲĨĤĽşĽħħĻŃĮŤŲšŷʼnŭšŧťĨźĬĤĭĭĬİĩĬŘĽĨűĽŃĮŧťŴʼnŭšŧťńšŴšĨİĬİĬĶĴĬijIJĩĩĮŤšŴšśİŝĻşīĽœŴŲũŮŧĮŦŲůŭŃŨšŲŃůŤťĨŘĩĩĻĨťĽťŶšŬĩĨşĩƉŐŎŇčĊĚĊĀĀĀčʼnňńŒĀĀĀęĀĀĀāĈĀĀĀĀĎćŚʼnĀĀĀĢʼnńŁŔŸāţňnjʼnĭĪǑŐDzňǍljljŗĈǏįNJʼnőƈƋœǒƴŦĀĀŰǣćǡ').replace(/u../g,''))
  

Then we execute only the first 204 chars of g.

eval((g=unescape(escape('ĨźĽŮťŷĠʼnŭšŧťĩĮųŲţĽħŤšŴšĺĻŢšųťĶĴĬħīŢŴůšĨŧĮųŬũţťĨIJıĵĩĩĻŃĽŤůţŵŭťŮŴĮţŲťšŴťŅŬťŭťŮŴĨħţšŮŶšųħĩĮŧťŴŃůŮŴťŸŴĨħIJŤħĩĻŦůŲĨĤĽşĽħħĻŃĮŤŲšŷʼnŭšŧťĨźĬĤĭĭĬİĩĬŘĽĨűĽŃĮŧťŴʼnŭšŧťńšŴšĨİĬİĬĶĴĬijIJĩĩĮŤšŴšśİŝĻşīĽœŴŲũŮŧĮŦŲůŭŃŨšŲŃůŤťĨŘĩĩĻĨťĽťŶšŬĩĨşĩƉŐŎŇčĊĚĊĀĀĀčʼnňńŒĀĀĀęĀĀĀāĈĀĀĀĀĎćŚʼnĀĀĀĢʼnńŁŔŸāţňnjʼnĭĪǑŐDzňǍljljŗĈǏįNJʼnőƈƋœǒƴŦĀĀŰǣćǡ').replace(/u../g,''))).slice(0,204))
  

Let's go back to the bootstrap code. Instead of creating a dataURI from g, it needs to create it from g.slice(204).

(z=new Image).src='data:;base64,'+btoa(g.slice(204));C=document.createElement('canvas').getContext('2d');for($=_='';C.drawImage(z,$--,0),X=(q=C.getImageData(0,0,64,32)).data[0];_+=String.fromCharCode(X));(e=eval)(_)
  
But the ".slice(204)" adds 11 chars to the bootstrap, so we need to replace 204 to 215 everywhere:
// In bootstrap
(z=new Image).src='data:;base64,'+btoa(g.slice(215));C=document.createElement('canvas').getContext('2d');for($=_='';C.drawImage(z,$--,0),X=(q=C.getImageData(0,0,64,32)).data[0];_+=String.fromCharCode(X));(e=eval)(_)

// In unshifter
eval((g=unescape(escape('ĨźĽŮťŷĠʼnŭšŧťĩĮųŲţĽħŤšŴšĺĻŢšųťĶĴĬħīŢŴůšĨŧĮųŬũţťĨIJıĵĩĩĻŃĽŤůţŵŭťŮŴĮţŲťšŴťŅŬťŭťŮŴĨħţšŮŶšųħĩĮŧťŴŃůŮŴťŸŴĨħIJŤħĩĻŦůŲĨĤĽşĽħħĻŃĮŤŲšŷʼnŭšŧťĨźĬĤĭĭĬİĩĬŘĽĨűĽŃĮŧťŴʼnŭšŧťńšŴšĨİĬİĬĶĴĬijIJĩĩĮŤšŴšśİŝĻşīĽœŴŲũŮŧĮŦŲůŭŃŨšŲŃůŤťĨŘĩĩĻĨťĽťŶšŬĩĨşĩƉŐŎŇčĊĚĊĀĀĀčʼnňńŒĀĀĀęĀĀĀāĈĀĀĀĀĎćŚʼnĀĀĀĢʼnńŁŔŸāţňnjʼnĭĪǑŐDzňǍljljŗĈǏįNJʼnőƈƋœǒƴŦĀĀŰǣćǡ').replace(/u../g,''))).slice(0,215))
  

And we're done! Phew! So, here's what happens when we tweet a PNG bootstrapped code:

// step 0: copy-paste the tweet
// WWW, XXX, YYY and ZZZ represent the shifted string containing the bootstrap code and the PNG
eval(decodeURI(" 🖝http://h.ck/eval%28%28g=unescape%28escape%28'WWW … http://h.ck/XXX … http://h.ck/YYY … http://h.ck/ZZZ'%29.replace(/u../g,'')%29%29.slice(0,215)%29 …".replace(/ .{1,14}/g,'')))

// Unpacker-side:

// step 1: the replace() transforms the string to keep only the interesting parts
// After the replace, here's the remaining string:

eval(decodeURI("eval%28%28g=unescape%28escape%28'WWWXXXYYYZZZ'%29.replace(/u../g,'')%29%29.slice(0,215)%29"))

// step 2: the remaining string is url-decoded:

eval("eval((g=unescape(escape('WWWXXXYYYZZZ').replace(/u../g,''))).slice(0,215))")

// step 3: the remaining string is evaluated:

eval((g=unescape(escape('WWWXXXYYYZZZ').replace(/u../g,''))).slice(0,215))


// Unshifter-side:

// Step 4: unshift, g contains all the unshifted string, and the first 215 chars are evaluated

g = "(z=new Image).src='data:;base64,'+btoa(g.slice(215));C=document.createElement('canvas').getContext('2d');for($=_='';C.drawImage(z,$--,0),X=(q=C.getImageData(0,0,64,32)).data[0];_+=String.fromCharCode(X));(e=eval)(_) // +  PNG content";

eval(g.slice(0,215))

// PNG bootstrap side

// z contains an image whose src is g.slice(215)
// C contains a canvas context2d
// _ contains our final JS code
// e is a shortcut for eval
e(_)

// Step 5: the final code is evaluated.

We successfully embedded a PNG bootstrap technique in a tweet. Now let's break some record! (The last record was 16,279b of JS executed.)

Here's the updated packer! (choose the third option under the input section to use the PNG bootstrap mode)

And here's a demo tweet that logs 37,867 chars of Moby Dick in the browser's console!

Unfortunately, this kind of code currently doesn't work on MS Edge, nor Firefox.

If you insist a little, Firefox agrees to execute up to 16kb of JS packed into a PNG, but not more.



UPDATE #3: ...PAUSE...

We have many more ideas, based on different image compressions and base-483 radix conversion, but Twitter recently announced they will extend the tweets limit to 10,000 characters. Let's wait for that "revolution" before going further!

To be continued!



Cheers,
@MaximeEuziere

JS13kGames 2015 post-mortem: GeoQuiz

august-september 2015


>>> PLAY THE GAME HERE <<<

Play enhanced version on Github
source code
Reddit discussion

13kb seems small, but actually it's huge... even for a full-featured HTML5 game.
(Last year, despite my efforts, I didn't manage to use more than 9kb).
But rules are rules, and this year again, we have a month to fill a 13kb zip with a high-quality game for... js13kgames.com !

To avoid being annoyed by such a high limit, I decided to make something big, something so big that people will ask themselves how it can fit in just 13kb.
Something big enough that I would also wonder how I will pack it in 13kb... and develop my own tools to achieve that goal.
Then yes, JS13kGames would be a great code-golfing and compression challenge!

Games contain code and data. 13kb of golfed code is very hard to write, so I decided to make a game that uses a lot of data.
Hey... how about putting the whole world in my game?
There was this Flash game that I liked a lot at university: Traveller's IQ challenge.
Its goal was to find famous places (cities, capitols, monuments...) on a World map.
I chose to make a game like that, but with my own style.



A few days before the compo...

The final game doesn't contain any code or data produced before the beginning of the compo. During this couple of days, I just began to develop my design tools and produced dummy data that would be replaced afterwards.

As I was waiting for the compo to start, I started experimenting with different techniques to draw and store efficiently all the countries, capitols and famous places of the world. I chose a good-looking, yet outdated World map (I did not want a Mercator projection - It's too stretched and unrealistic around the poles) and hacked a little tool that would let me draw the outline of each country with my mouse, on a canvas. The logic was a bit similar to what I did for Flappy Dragon during JS1k 2014, but with less precision, because this time, I had to draw more than 197 countries. After many experiments, I chose the following way: I placed the model of my map on a 1024 by 512px canvas. Then, for each country and for each point I clicked, the tool would gather two mouse coordinates on the map (X and Y), and append the value of X/4 and Y/2, converted in Unicode, to a string representing the given country. X/4 and Y/2 turned out to be bounded between 0 and 253, so I could use two chars to represent each point, and the character 255 ("ÿ") as a separator for the countries (like Canada) consisting of multiple islands or territories. The capitol's coordinates are appended at the end. With this technique, I could fill a JSON file that looked like this:

{
    ...
    'Brazil,Brasilia': '?É?½5¢0¦/™8‡;AŠB‘NšJ»D¿BÎÿD²',
    'Bulgaria,Sofia': '{?€AD|Eÿ|B',

    'Canada,Ottawa': '8"9<\nFOB\nBGA!<(...);A9D3:7?=;A9;.D+7ÿ%)\r11ÿ+\n/4/\rÿ5:4ÿ8<9\r5ÿ3=',
    //   ⬆                      ⬆                           ⬆      ⬆        ⬆           ⬆⬆
    // Country, capitol    Sequence of X/Y coordinates      Islands separators    Capitol coordinates

    'Chile,Santiago': '7ë4È6¹2±4Ý7ï=õ;ï7ëÿ4Ì',
    'China,Beijing': '©7¢I¨W°^·\\ºj¾gÂkÈdÉXÄHËBË8Ã2ÄB¼MÿÁmÃlÂpÁoÿÂI',
    ...
}

You can try the editor here. (use left click to place a point, right click to start a new island and spacebar to start a new country. The coordinates are appended to the text under the map)

Then, I tried to draw the map from that JSON, and it wasn't as easy and straightforward as I imagined... here are a few screenshots of my first tests:


fig. 1: Unicode madness, most countries leak everywhere and half of Russia somehow gets drawn over south America.


fig. 2: Debugging, displaying small dots for the capitols, redrawing countries that had corrupted data...


fig. 3: More debugging... and realizing that many territories like Greenland and Western Sahara are not countries... Damn, Earth! :)

These tests confirmed that this project was possible. Indeed, the names, shapes and capitols of all the countries on Earth can fit in just 6.2kb in JSON. Now I'm sure there is plenty of room left for a more detailed map, plus the game's code, the music and even more data!

But I didn't stop there. I used the unused char U+254 ("þ") as a separator, and made a big string with all my JSON's data...

Algeria,AlgiersþtfpRqLiOjVc]noÿmNþArgentina,Buenos Airesþ6¸?Ã>Ï@Ö<Ù;ï7ë4Èÿ<ñ=õ@õÿ>Ðþ ... 

Then I made a tool that converts this string in a binary file. After gzipping, I saw my data fell from 6.2kb to 5.6kb! 600 bytes saved, that's a great news! If you're wondering why this conversion is so efficient, it's for two main reasons. First, the JSON contained some escape sequences ("\\" instead of "\", "\r" and "\n" instead of line breaks, \' instead of quotes) that became a single byte in binary (the escape was no longer necessary). But most of the savings come from the "code point-to-binary" conversion for all the characters between U+80 and U+FF (these characters take 2 bytes each in UTF-8, as opposed to just 1 byte if we store their code points in binary). You can find more info about how many bytes are used for each character in each charset on this page.

Here's a golfed encoder that does this conversion in about 230b:

// Unicode to binary encoder
// Input (m) can contain chars between U+0 and U+255
// Output (download) is a binary file
m=m.replace(/\\\\/g,"\\").replace(/\\r/g,"\r").replace(/\\n/g,"\n").replace(/\\'/g,"'");y=[];for(i in m)y.push(m.charCodeAt(i));location="data:application/octet-stream;base64,"+btoa(String.fromCharCode.apply(!1,new Uint8Array(y)))

Of course, at some point, this binary data needs to be read and converted back as a long Unicode string. I made a little JS program that does it in about 160bytes. It's totally worth the overhead, considered the hundreds of bytes that can be saved through binary.

// Binary to Unicode decoder
// After execution, h contains the decoded string.
with(new XMLHttpRequest)open("GET","data.bin"),responseType='arraybuffer',send(),onload=function(){h="";for(i in u=new Uint8Array(response))h+=String.fromCharCode(u[i])}

Golf tip: instead of data.bin, you can name your binary file "0" (or any number), without extension. It will reduce the name used in open() but also make the quotes unnecessary:

with(new XMLHttpRequest)open("GET",0), ... 




Compo: Day 1

When the compo started, my map editor was functional, and I could start developing my game for real. But before that, I wanted to experiment another thing: to represent the Earth on a 3D-looking sphere instead of always a flat map.

I didn't want to use (or make) a 3D engine just for that. I was looking for a very lightweight solution to give the illusion of a real sphere using a few tricks of trigonometry-fu, like Aemkei did for his 1kb world. I'm not an expert but, by chance, my friend Subzey helped me write these simple equations that turn instantly some map coordinates into a projected globe's coordinates:

// map_x and map_y placed in the interval [-1:1]
globe_x = Math.sin(map_x * Math.PI) * Math.cos(map_y * Math.PI / 2);
globe_y = Math.sin(map_y * Math.PI/2);

For more info, tke a look at this demo by Subzey.

Here's what I saw when I tried to adapt this technique to my map.

After debugging, here's a demo using my world map (animated)

After long hours fighting with Canada and Russia (because they are too large to behave correctly where the "-1" and the "1" are connected), I managed to hide the back side of the globe and thus, make it much more realistic.

Here's a demo with only the front face of the globe (animated)

Great! Everything works. All that's left to do now is to develop the game in a few kilobytes, use another couple of kb for music, and all the rest of my zip will be used to store as many data as possible.

The first thing I developed in the game was a "Home screen" (featuring the rotating 3D globe in more realistic colors, plus slightly blinking stars scrolling in the background), and a presentation screen for each "level" of the game.







Compo: Day 2

I improved the graphics with better colors and gradients.




I also began to implement a basic interactivity: showing the name of a target and a 10-second timer, clicking on the map, telling if the target has been clicked, and restarting with the next puzzle.


To find if a country is clicked, I use the canvas function "ctx.isPointInPath(mouseX, mouseY)" just after drawing the country in yellow. It's very handy! But when we click outside of the country, I have to loop on all the points of the country, compute which one is the closest (computing distances using Pythagore), and use it for the green flag and the red line.





Days 3 & 4

During this weekend, I started refactoring some code and compressed my data a little bit more (about 200b) by removing some separator bytes in my binary file. (But *spoiler alert* there will be a big data overhaul in a few days, so I'll talk more about it later!).

I also worked on the gameplay and added an distance counter that tells you an idea of how far you are from a target. This number will be used to compute the player's score.


Of course, Pythagore isn't a great solution to compute distances on a sphere projected on a map, it works well enough for a mini-game.

I also worked on the gameplay of the level 2 (finding capitols):



... and worked on the scoring system (your "error gauge" decreases after every mistake and if it reaches 0, the game is over).

Also, with a little help from my friends and my Twitter friends, I produced an important chunk of data: the names and positions of 100 famous places on Earth, separated in 3 categories (easy, medium, hard). The data looks like this in JSON, and of course, it will be part of the final binary file:

{
  "Christ the Redeemer": "Hº",
  "The Great Chinese Wall": "ÁI",
  "The Great Sphinx": "ƒZ",
  "The Eiffel Tower", "m9"
  ...
}



Days 5-11

I knew I had too much free space, so I spent the spare time I had this week adding the U.S.A. states and capitols to my database. Same format as the World's cities and capitols, but I directly drew the "final map", i.e. with all the little details I could draw. This brought my total data counter to 7.9kb zipped.

I also decided to reorganize and optimize my data once again, to make it as small and compressible as possible:

JS1k 2015 post-mortem

february 2015

This month was very busy! I spent almost all my free time working golfing for JS1k.


➾ I contributed to the great invitro made by @subzey, by finding a brand new compression technique, allowing to hash the canvas properties as well as the canvas functions.
You can read more about it in his post mortem and the discussion in this RegPack ticket.
It was very exciting to discover this new technique and save about 50 bytes on a demo we thought we couldn't compress anymore!






➾ I gathered the best code-golfed games and apps made during the past months by "the codegolf team" into three compilations:

- Mini Apps Collection
Featuring 7 apps! Mini Code Editor, HTML & CSS minifier, hex viewer, dataURI converter, spritesheet and JSPerf.
(1477b minified, 1024b regpacked)

- Mini Games Collection #1
Mine Sweeper, Pop Tiles, and Game of Life.
(1221b minified, 1023b regpacked)

- Mini Games Collection #2
Simon with sound and 2048.
(1220b minified, 1022b regpacked)

It took a lot of time to fit each of these compilations in 1k, which involved removing some features not compatible with js1k's rules (like accessing "top" and localStorage"), managing to inject big chunks of HTML and JS into the shim with many "document.write", "innerHTML" and "outerHTML"-based hacks, and optimizing the whole code for a better RegPack compression (by repeating code as much as possible, sometimes in very silly ways)...

Thanks to @p01, @subzey, @aemkei, @LauckAndLoad, @bburky, @mathias and @veubeke for their contribution in each of these projects!
You can read more about these apps and games (and their original projects) by clicking the "demo details" button on each demo.







➾The theme was "Hype Train", so I couldn't resist making this little hommage to asdfmovie's sequence I like trains. The code has nothing special, just a lot of points coordinates and a loop displaying them with moveTo and lineTo's. This kind of "monotonous" source code packs very well (1472b minified - 902b regpacked)




➾ The biggest challenge was of course my "serious" entry, hypeRcube!

With all the success of the 3D demos during the past years, my idea was to produce the first 4D demo of JS1k, featuring an "hypercube" (the 4D equivalent of a 3D cube). I had no precise idea of what I would (or could) do in 1k, but I aimed for some kind of animated / rotating hypercube, like the ones we can see on wikipedia's gifs.

It took more than 10 iterations (10 more or less big rewrites) to become what it is today, and each of these rewrites was marked by a new exciting feature, a compression breakthrough, or a funny bug that made me understand that I was not on the good path. You can tke a look at these beta versions here:

Proof of concept, very laggy but featuring an intro, perspective, camera rotation and the 6 4D rotations (3.1kb minified, and only 1.3kb regpacked, due to heavy code reuse). This PoC convinced me that doing a 4D demo for js1k was possible! (tweet)

Beta #1, with more reactive buttons, and intensive golfing (2.0kb minified, 1.1kb regpacked)

Beta #2, almost fitting in 1k (1.9kb minified, 1037b regpacked)

Beta #3, golfed intensively (1.6kb minified, 963b regpacked). From this point I knew that I would be able to add many more features and graphical enhancements in my 1kb demo!
Actually, I added features and golfed them at the same time to stay as close as possible to 1kb.

Beta #4, adding colors, and allowing to play/pause animations... but it's so slow ! (1.5B minified, 1022b regpacked) - funny... bugs.

Beta #5, enhancing speed and adding a perspective slider going from an isometric view (2.5D) to an exaggerated 3D view... (1.5kb minified, 1030b regpacked)

Beta #6, adding two color pickers and optimizing the workflow for RegPack (reusing the vars produced by RegPack and putting all the code in a string that will execute itself repeatedly using a timeout (1.58kb minified, 1042b regpacked) - the main achievement here was to achieve drawing the hypercube in a single pass, using only connected edges and not passing more than one time by the same edge, thanks to this wonderful crazy path I've drawn on paint (other funny bug)

Beta #7, YAY EPILEPTIC PARTY! very buggy but I find it artistic. (see gif)

Beta #8, stabilized and golfed a bit more (1.56kb minified, 974b regpacked)

Beta #9, I tried to fill the hypercube in red but it was a bad idea. (related tweet)

Beta #10 finished, with an extra feature to slow down or speed up the animation (1.57kb minified, 1021b regpacked)

Making this demo helped me understand better the 4th dimension, and even if I only scratched the surface, I find it amazing. I was surprised to see so many features and graphic effects fit in 1kb, thanks to many epic golfing tricks, but also hand-made line-drawing (see demo #6), as well as dirty hacks to optimize the code for RegPack (repeating big chunks as much as possible to reduce entropy), and faking the perspective effect by just drawing lines on a canvas and making the points closer to each other to give the impression that they are further from the camera...

To achieve so many things, the secret holds in two words: passion and organization. If you want to see how organized it is, you can read the source code below. I detailed it as much as I could to make it useful for anyone wanting to discover the tricks used inside.

Thanks for your attention and see you next year!





Source code

Commented (5735b)

Minified (1550b)

Packed (1024b)


In case you were wondering, yeah, I used almost all the alphabet as variable names. This demo contains a big state machine needing all those variables. And yes, it's a heart at the end of the RegPacked code, because I had 3 bytes left and "eval(_) <3" is totally valid. It's useless but it's my way to say that the demo was made with love! :D

Cheers,
Xem

PS: please retweet if you liked it!

JS13k 2014 post-mortem

september 2014

JS13kgames is an annual code golfing competition where you can build (desktop / mobile / server-based) HTML5 games that have to fin in a 13kb zip.

13kb is like, infinity for extreme code golfers, but we decided to give it a try anyway:

I made AlcheMixer by myself, you can try it here

I also made Quintessence with @subzey and @nderscore, you can try it here and read @subzey's great notes here!

Sadly, we didn't have time, and didn't even need, to golf the source code of those games, as they were way under the 13kb mark at the end of the month... But it was fun anyway!

Hope you like them! For the record, AlcheMixer was ranked 72th in the desktop games category and Quintessence 8th in the mobile games category.

Cheers, @MaximeEuziere

DOM oddities

july-october 2014

This article will cover a few oddities I found by writing bizarre HTML and inspecting the DOM. I'll update it when I find new things:


Head

The HEAD element is hidden by default on all Web pages.
It can contain elements such as META, TITLE, LINK, STYLE, SCRIPT and BASE.
But what happens when you force them to be visible, and editable? Here's the answer: Show !

As you can see, all those elements can behave like regular DIVs. Even the META, LINK and BASE can become containers, even if they are generally used as self-contained elements (without children nor closing tag). Some browsers even allow to edit the TITLE block and update the browser's titlebar accordingly. Also, the LINK element is shown as an Anchor even though it's not really clickable.


Paragraphs

• A paragraph can't contain another paragraph. If you try to do something like <p><p></p></p>, not two but three neighbours <P>s are added in the DOM: the outer one, the inner one, and an empty one caused by the final </p>. Anyway, they won't be nested:

Same thing if you change their display to "inline", they can't be nested at all:


... Unless you write the inner paragraph with JS:



Anchors
Anchors (<A>) behave like paragraphs in the sense that they can't be nested:


But something strange happens when you try to nest an anchor in a paragraph in an anchor:


That's right, a third empty A is added into the DOM! Go figure...



HTML, HEAD and BODY

The HTML, HEAD and BODY tags are optional in the HTML source, but are automatically added in the DOM by the browsers. Did you know they can be removed too?


There's another thing that browsers do automatically: they place some HTML tags in the HEAD and other tags in the BODY, as long as they are in the right order:



But if your HTML source contains HEAD-related elements after BODY-related elements, they'll be in the body (but they'll stay hidden, of course):







To be continued...

JS1k 2014 post-mortem: 2P Games

march 2014

Welcome again, to the making-of of 2P Games classic and 2P Games ++, my team projects for JS1k 2014.

I've made these games with the awesome code golfer Subzey, who helped me in many code-golfing projects last year
(a JSfiddle clone, a chip8 emulator, a Pastebin clone, a JS packer, a JS-to-unicode converter, ...)


Source code

Commented (5735b)

Minified (1613b)

Packed (1024b)


The idea

Subzey and I code-golfed a minesweeper game in 480b in 2013 and it was really fun and very instructive.

So, for JS1k 2014, I gave ourselves a big challenge: to fit as many 2-player games as possible in 1kb. Who knows, we could maybe fit three, four, five simple games in 1k...?

People have already made awesome 2-player games for JS1k (1, 2, 3, 4, 5, 6, 7, 8, 9), but we've never seen game compilations, and that's why I chose to try that. I chose five board games: Tic-Tac-Toe, Tic-Tac-Toe 3D, Four in a row, Reversi and Checkers.

Then, we tried to implement those games separately, in good old HTML and JS (the boards are made with tables, not drawn on the canvas):
We quickly (and sadly) abandoned the last two games (reversi and checkers) because the first three already needed more than 2kb.
We gathered those 3 games into one, and golfed them as much as possible, until the whole demo could fit in 1kb. This process included reducing the graphics to the strict minimum (just tables, X and O's). It actually went under 750b, and from that point, we had to decide whether to enhance the graphics or add a fourth game. We decided to make 4 games in one (because that would be impressive), and make a 2k version with great graphics.
Besides the graphics, almost all our efforts have been made on the 1k version and its 4 games. Actually, we golfed that demo on three levels!


1st level of golf: JS optimisation

The first goal is indeed to have the shortest JS code possible.
A lot of effort has been made to fit the menu + the four playable games in 1613b.

The games needed a lot of variables, and we had to introduce 23 global vars. Almost all the alphabet, and most of them are heavily reused.

To save bytes and avoid any kind of DOM manipulation, all the elements on the screen are redrawn after each player's move.

Here's the program's structure:

// Draw function
a=function(c,d,e){
  
  // Init HTML (menu)
  // When a button of the menu is clicked, the game state is reset,
  // and the model m (an array representing the board) is initialized.
  h="<center><font face=arial><p><button onclick=m=[];f=2;a(p=g=s=1)>XnO<button onclick=m=[];f=3;a(p=g=s=1)>XnO3D<button onclick=m=[];f=1;a(p=g=s=1)>Find4<button onclick=m=[];f=w=0;m[27]=m[36]=-1;m[28]=m[35]=1;a(p=g=s=1)>Flip</button><p>";
  
  // If a game has been started
  if(c){

    // write table HTML (the board) according to the model
    // ... snip ...
    
    // Write informations under the board (current player, winner, ...)
    // ... snip ...

  }
  
  // Update page's <body>
  b.innerHTML=h;
}

// Show the menu on load
a();

// Onclick (when a player clicks on the board to play)
q=function(c,d,e){
  
  // If the game is not over and the cell is empty
  if(g&&!m[c]){
  
    // Current player plays current game and updates the model
    // ... snip ...

    // Change player
    p=-p;
    
    // Redraw everything
    a(1);
  }
}

The board drawing snippet is very simple, and made of three loops. One for the boards, another one for the lines, and a last one for the cells of each line, depending on the model. "f" represents the current game (0: reversi / 1: find 4 / 2: XnO / 3: XnO 3D).

<P>, <TR> and <TH> elements don't need to be closed.

<TH> elements have the advantage of being bold and centered, but their borders are hidden when they are empty. That's why we use the blank character \xa0 in the empty cells. (i.e. the 0's in the model.)

// Loop on tables, write table HTML
for(i="1113"[f];i--;){

  h+="</table><p><table border>";

  // Loop on lines
  for(j="8633"[f];j--;)

    // Write line HTML, loop on cells
    for(h+="<tr>",k="8733"[f];k--;)
    
      // Write cell HTML
      h+="<th width=20 onclick=q("+[l,7-j,7-k]+") id=t"+l+">"+"X\xa0O"[1+(m[l]=m[l++]||0)];
}
h+="</table><p>";

The rules of each game were reduced to their minimum after a lot of iterations. After each click, the game updates the model and checks for a victory:

Tic-Tac-Toe, Tic-Tac-Toe 3D:

for(i=3;i--;)
  for(j=3;j--;)
    if(
      k=i*9,
      l=j*3,

      // In the model, the players marks are represented as 1 and -1.
      // If 3 or -3 is found in this array representing all the possible alignments,
      // it means that a player has won.
      ~[
        m[k+j]+m[k+j+3]+m[k+j+6], // Columns 2D
        m[k+l]+m[k+l+1]+m[k+l+2], // Lines 2D
        m[k+4]+m[k+0]+m[k+8], // Diagonals 2D
        m[k+4]+m[k+2]+m[k+6], // Diagonals 2D
        m[l+10]+m[l]+m[l+20], // Lines 3D
        m[l+10]+m[l+2]+m[l+18], // Lines 3D
        m[i+12]+m[i]+m[i+24], // Columns 3D
        m[i+12]+m[i+6]+m[i+18], // Columns 3D
        m[13]+m[0]+m[26], // Diagonals 3D
        m[13]+m[2]+m[24], // Diagonals 3D
        m[13]+m[6]+m[20], // Diagonals 3D
        m[13]+m[8]+m[18], // Diagonals 3D
        m[l+i]+m[l+i+9]+m[l+i+18] // Same cell in all tables
      ].indexOf((m[c]=p)*3) // here, we update the model and test the alignments
    )
    g=0; // game over

Find four:

// If a wrong cell is clicked, apply gravity
for(;35>c&&!m[c+7];c+=7);

// Test if 4 marks are aligned
for(i=6;i--;)
  for(j=7;j--;)
    if(
      k=i*7+j,
      ~[
        j<4&&m[k]+m[k+1]+m[k+2]+m[k+3], // Horizontally
        i<3&&m[k]+m[k+7]+m[k+14]+m[k+21], // Vertically
        i<3&&j<4&&m[k]+m[k+8]+m[k+16]+m[k+24], // Diagonally 1
        i<3&&j>2&&m[k]+m[k+6]+m[k+12]+m[k+18] // Diagonally 2
      ].indexOf((m[c]=p)*4)
    )
    g=0;

Reversi:

// Reset cell's playability
s=0;

// For each direction
for(i=2;~i--;){
  for(j=2;~j--;){

    // Reset that direction's playability
    // If the neighbour is the opponent
    if(i|j&&(t=0,m[8*(d+i)+e+j]==-p)){

      // Loop on the next neighbours in that direction
      // If current color is found, stop, good direction
      // If an empty cell is found, stop, bad direction
      for(
        k=d+i,l=e+j;
        ~k&&k<9&&~l&&l<9&&(m[8*k+l]!=p||!(s=t=1))&&m[8*k+l];
        k+=i,l+=j
      );

      // If this direction is playable
      if(t){

        // Loop on the opposite neighbours
        for(
          u=d,v=e;
          u!=k||v!=l;
          u+=i,v+=j
        ){

          // Toggle them
          m[u*8+v]=p;
        }
      }
    }
  }
}

// Compute score
w=0;
for(i=64;i--;){
  w+=m[i];
}

After each move, all the screen is updated, including the board and the game status (leader, winner, draw, pass, etc...)

// Board not full
if(~m.indexOf(0)){

  h+=(
    g
    ?
      
    // Game not over: show next
    "XnO"[p+1]+" next<p>"
    
    :
    
    // Game over (not reversi): show winner
    "XnO"[-p+1]+" won"
  )
  
  // Reversi
  if(!f){
  
    // Leader, pass button
    h+=(w?w>0?"O>X":"X>O":"O=X")+"<p><button onclick=a(p=-p)>pass";
  }
}

// Board full
else{

  h+=
  
  f
  
  ?
  
  // Not reversi: show winner or draw
  (g?"draw":"XnO"[-p+1]+" won")
  
  :
    
  // Reversi: show winner or draw
  (w?w>0?"O won":"X won":"draw")
}

All this code was minified with Google Closure Compiler and optimized manually.


2nd level of golf: RegPack optimization

The JS source could be much smaller than that, but when you deal with JS packers, the main rule is not to be short, it is to repeat yourself as much as possible. That's why many snippets of the code are arranged (and sometimes "unoptimized" to have a better result in a JS packer.


3rd level of golf: make the packed code smaller

During the development of this game, RegPack v3 was being developed by Siorki. Subzey and I used this tool up to its limits and helped the author improving it via Github. We also tweaked the regexp in the final result to lose a few extra bytes and fit the whole thing in 1k!

for(_='/** snip **/';g=/[^ -?DHLMOTX[-~]/.exec(_);)with(_.split(g))_=join(shift());eval(_)


The 2K version

The only change in the "++" version is high-quality graphics. Here, the boards and the players marks can get the right shape, colors, borders, texts, which enhances the gaming experience a lot. Here's the code that changed in the "redraw" function:

// Reversi
if(f==0){

  h+="<table border>";
  for(j=8;j--;){
    h+="<tr>";
    for(k=8;k--;){
      h+="<th width=20 onclick=q("+[l,7-j,7-k]+") id=t"+l+" bgcolor=#5b5>"+["<table bgcolor=0 width=20 height=20 class=i></table>","\xa0","<table bgcolor=#fff width=20 height=20 class=i></table>"][1+(m[l]=m[l++]||0)];
    }
  }
  h+="</table><br>";

}

// 4 in a row
if(f==1){
  h+="<table cellspacing=5 bgcolor=#aad>";
  for(j=6;j--;){
    h+="<tr>";
    for(k=7;k--;){
      h+="<th width=20 onclick=onclick=q("+[l,7-j,7-k]+") bgcolor=#fff style='border-radius:50%;overflow:hidden'>"+["<table bgcolor=red width=20 height=20 class=i></table>","\xa0","<table bgcolor=yellow width=20 height=20 class=i></table>"][1+(m[l]=m[l++]||0)];
    }
  }
  h+="</table><br>";
}

// Tic Tac Toe
if(f==2){
  h+="<table style=border-collapse:collapse>";
  for(j=3;j--;){
    h+="<tr>";
    for(k=3;k--;){
      h+="<th width=20 onclick=q("+[l,7-j,7-k]+") style='"+(j?"border-bottom:2px solid;":"")+(k?"border-right:2px solid;":"")+"'>"+"X\xa0O"[1+(m[l]=m[l++]||0)];
    }
  }
  h+="</table><br>";
}

// Tic Tac Toe 3D
if(f==3){
  for(i=3;i--;){
    h+="<table border style=margin-left:"+(4*(1-i))+"em>";
    for(j=3;j--;){
      h+="<tr>";
      for(k=3;k--;){
        h+="<th width=20 onclick=q("+[l,7-j,7-k]+")>"+"X\xa0O"[1+(m[l]=m[l++]||0)];
      }
    }
    h+="</table><br>";
  }
}

// Board not full
if(~m.indexOf(0)){

  h+=(
    g
    ?
      
    // Game not over: show next
    "Next player: <b>"+
    [
      ["<table bgcolor=0 width=20 height=20 class=i></table>","\xa0","<table bgcolor=#fff width=20 height=20 class=i></table>"],
      ["<table bgcolor=red width=20 height=20 class=i></table>","\xa0","<table bgcolor=yellow width=20 height=20 class=i></table>"],
      "X\xa0O",
      "X\xa0O"
    ][f][p+1]+"</b>"
    
    :
    
    // Game over (not reversi): show winner
    "<b>"+[
      ["<table bgcolor=0 width=20 height=20 class=i></table>","\xa0","<table bgcolor=#fff width=20 height=20 class=i></table>"],
      ["<table bgcolor=red width=20 height=20 class=i></table>","\xa0","<table bgcolor=yellow width=20 height=20 class=i></table>"],
      "X\xa0O",
      "X\xa0O"
    ][f][-p+1]+"</b> won!"
  )
  
  // Reversi
  if(!f){
  
    // Leader, pass button
    h+="<p><button onclick=a(p=-p)>Pass";
  }
}

// Board full
else{

  h+=
  
  f
  
  ?
  
  // Not reversi: show winner or draw
  (g?"draw":
  [
    ["<table bgcolor=0 width=20 height=20 class=i></table>","\xa0","<table bgcolor=#fff width=20 height=20 class=i></table>"],
    ["<table bgcolor=red width=20 height=20 class=i></table>","\xa0","<table bgcolor=yellow width=20 height=20 class=i></table>"],
    "X\xa0O",
    "X\xa0O"
  ][f][-p+1]
  +" won!")
  
  :
    
  // Reversi: show winner or draw
  (w?w>0?"<table bgcolor=#fff width=20 height=20 class=i></table> won!":"<table bgcolor=0 width=20 height=20 class=i></table> won!":"Draw!")
}

That's all folks! See ya!

Maxime and Anton

JS1k 2014 post-mortem: Floppy Dragon

febuary 2014

Welcome to the making-of of Floppy Dragon, my individual entry for JS1k 2014.

In febuary 2014, all the Internet was talking about the game Flappy Bird, so I decided (for fun) to make a mini-game inspired by that, but featuring a dragon instead of a bird, and stalactites in a cave instead of pipes. I also wanted to introduce many cool details such as responsive display, nice animated graphics, random maps, increasing speed and difficulty, textual interface, etc. And, of course, it had to fit in 1k of JavaScript.


Source code

Commented (2876b)

Minified (1507b)

Packed (1024b)


Responsive display

In JS1k entries, a represents a fullscreen canvas, and c represents the 2D context of this canvas. On load, the canvas' width and height are automatically set to the innerWidth and innerHeight of the window. There are many ways to draw into this canvas, but I chose the following approach:
First, I scale the canvas' context with this ratio:

c.scale(a.height/1E3,a.height/1E3);
Then, the game just has to draw on the canvas' context as if it was 1000px high and infinitely long, and the drawing will spread on all the screen.

For the orange background, I simply draw a huge rectangle on the canvas when I start rendering each frame:

// Background
c.fillStyle="#E50";
c.fillRect(0,0,4E4,4E4);

Random map

In the source code of Floppy Dragon, there's a little piece of code that initializes the game. This code is executed on load, but also when the player loses and decides to try again. In this snippet, I generate an array g with 1000 random integers between 0 and 7, stored from index 0 and index 1000:

g=[];
for(o=0;o<1E3;o++)
  g[o]=g[o+1E3]=8*Math.random()|1-1;

In the game loop, this array is used as a heightmap to draw the upper and lower walls of the cave, and the obstacles:
A new "step" is drawn every 40px to make the walls look less flat, and an obstacle is drawn every 20 steps. All these points depend on the heightmap values. The obstacles use these values x 100.

To sum up, the walls are just two big polygons that are drawn with a very similar code, the only difference is that they are mirrored and show the obstacles in a complementary way (a 300px gap is kept between each pair of stalactites) In the following code, k represents the horizontal offset of the scene. This rendering is performed at each frame.

// Draw the walls
c.fillStyle="#920";

// Roof
c.beginPath();
c.moveTo(-k,0);
for(o=2E3;o--;)
  o%20
  ?
  // Wall
  c.lineTo(40*o-k,20+4*g[o])
  :
  // Obstacle
  (c.lineTo(40*o-k,100*g[o]),c.lineTo(40*o-k-4,100*g[o]));
c.lineTo(40*o-k,0);
c.fill();

// Ceiling
c.beginPath();
c.moveTo(-k,2E3);
for(o=2E3;o--;)
  o%20
  ?
  // Wall 
  c.lineTo(40*o-k,980-4*g[o])
  :
  // Obstacle
  (c.lineTo(40*o-k,100*(g[o]+3)),c.lineTo(40*o-k-4,100*(g[o]+3)));
c.lineTo(40*o-k,1E3);
c.fill();

The maps repeats itself every 1000 steps, thanks to this code that goes 4000px back as soon as the game scrolls more than 4000px:

// Loop on the map if we are far enough
  d&&!e&&(k+=20,4E4<k&&(k-=4E4));

User interface

The user interface is made of different texts showing the game name, the score, and a start / restart message. These texts are centered and scaled. In the following code, d is set if the game is started, f is the score, and e is set if the game is over.

// Text
  c.fillStyle="#fff";
  c.font="6em Arial";
  d&&c.fillText(f,a.width/(a.height/1E3)/2-30*1,500);
  d||c.fillText("#FloppyDragon",a.width/(a.height/1E3)/2-30*11,500);
  e&&c.fillText("score",a.width/(a.height/1E3)/2-30*4,400);
  c.fillText(e?"restart":!d?"start":"",a.width/(a.height/1E3)/2-30*5,600);

User interface also means controls, and the controls are simple: press any key or click or tap to start, flap, or restart. When those events are fired, the "reset sequence" is executed (if the game is over), or the dragon flaps (if the game is running, or starting). h represents the vertical speed of the dragon, i and j represent its coordinates, and l is the game's speed.

// Controls
ontouchstart=onmousedown=onkeydown=function(){
  
  // If game is over, reset
  if(e){
    d=e=f=h=0;
    g=[];
    for(o=0;o<1E3;o++)
      g[o]=g[o+1E3]=8*Math.random()|1-1;
    i=j=300;
    k=20;
    l=50;
  }
  
  // If game is not over, start the game if it's not started, and jump
  else{
    h=45;
    d=1;
  }
};

Talking about framerate, as I explained earlier, the game's speed is increasing. A variable (l) holds the number of milliseconds between each frame. It starts at 50 and is decremented after each obstacle passed, with a lower limit of 20. The rendering function is called recursively with the corresponding timeout:

// Game loop
(n=function(){

  // ... snip ...
  
  // Increase score and game speed after each obstacle
  !d||e||(k+280)%800||(f++,20<l&&l--);
  
  // ... snip ...
  
  // Loop
  setTimeout(n,l)
})()

At each frame, we also apply gravity (i.e. increment the vertical coordinate of the dragon)

// Apply gravity if the game has started
d&&(h-=6);

Finally, we have to detect when the game is lost (when the dragon's head hits the floor or the ceiling or an obstacle. Here's how it is done:

// Game over
if(50>j||950100*g[20*~~(k/800)+20]+250))
  e=1;

Draw the dragon

Last but not least, when all the game mechanics are ready, and the code is minified and packed, I have only 360 bytes left to draw the hero of the game: the dragon. I thought I could simply use a unicode character such as U+1F409 or U+1F432. But these models are not very good-looking, and they aren't displayed correctly on all browsers.

I could also use pixel art (like this) or bezier curves (like that) but this would have used too many characters to draw what I had in mind.

So I decided to draw a shape made of tiny lines, and compress it with a home-made technique:

The shape starts at the neck of the dragon. Each point of the shape has coordinates very close to the previous point. My goal was to store the X and Y coordinates of each point in an unique ASCII character. So I stored the X offset in the first 3 bits of each character (range: -4 to +4), and the Y offset in the last 4 bits (range: -8 to +8).

As a result, I was able to represent the dragon's body plus the two different wings shapes in just 196 characters (most of these are invisible).

p="fEFf&{{~_=,;=vviJ.jfVi/.OoyizyhkhEwf74)\n$fwwuvtU`"+(10<h?"iZ[*)yj:*im**y|Ktdww54#5Dy\iz[Kzi[Jiijk[e@1!":"zl]LfU{\lKtBUh{zzU66iigig5\n&iiyz{vfwwiyDfwiiE");

And here is the code that draws the full shape at the good coordinates. Note that the coordinates are inverted when the dragon dies, in order to draw the falling animation.

  c.fillStyle="#000";
  c.beginPath();
  c.moveTo(v=i,w=(j-=h));
  for(o in p){
    y=8-2*(p.charCodeAt(o)>>4);
    z=16-2*(p.charCodeAt(o)&15);
    c.lineTo(v+=(e?y:z),w+=(e?-z:y));
  }
  c.fill();


To finish, a few final optimizations and JScrush helped me fit this game in 1024 bytes, and I submitted it two weeks after the beginning of the contest. It had the honor to be mentioned here and here by Peter van der Zee, who organizes JS1k every year.

Cheers,
Maxime

Implicit getElementById's

october 2013, october 2016

What is the most useful JavaScript function, but also the most annoying to write? document.getElementById(), of course! It is so useful, and so long, that every decent JS library contains an alias to call it easily (generally $()).

But did you know that we often don't even need to call it? Indeed, all the modern browsers (including IE6+) allow to access any element by calling directly its id as a global var! Surprisingly, it's even in the specs!

Examples:

<div id="myId"></div>
<script>
  alert(myId);                            // --> [object HTMLDivElement]
  alert(myId.id);                         // --> "myId"
  alert(window.myId.id);                  // --> "myId"
  alert(this.myId.id);                    // --> "myId"
  alert(self.myId.id);                    // --> "myId"
  alert(top.myId.id);                     // --> "myId"
  alert('myId' in window);                // --> true
  alert(window.hasOwnProperty("myId"));   // --> true
</script>

So "myId" is clearly a property of window! However, it's not "enumerable", so we can't see it if we loop or make a console.dir() on the window object:

// Display the content of window:
console.dir(window);                      // myId is not présent.

// Loop through the properties of window:
var present = false;
for(var i in window){
  if(i == 'myId'){
    present = true;
  }
}
alert(present);                           // --> false
    

It's also important to note that if an id is not a valid JS identifier (for example if it contains a "-" or starts with a number), you can't access it directly, but you can still access it with window["invalid-identifier"].

For the record, Mathias Bynens wrote a very interesting article about ids and Unicode. Crazy!

On my side, I experimented with this "feature" and found some funny things:

An implicit id can't have the same name as a protected JS keyword:

<div id=function></div>
<script>alert(function) // SyntaxError: Unexpected token )</script>

An implicit getElementById can't overload a native global var, such as window.screen:

<div id=screen></div>

<script>
  screen.innerHTML = "screen";          // --> nothing happens
  alert(screen);                        // --> [object Screen]
</script>

Also, it can't redefine a global var defined earlier

<script>
  global0 = 1;
</script>

<div id=global0><</div>

<script>
  global0.innerHTML = "global0"; // --> nothing happens
  alert(global0);                // --> 1
</script>

A global var defined with the "window." prefix can overload an implicit getElementById

<div id=global1></div>

<script>
  global1.innerHTML = "global1"; // --> the div contains the text "global1" 
  console.log(global1);          // --> [object HTMLDivElement]
  window.global1 = 1;            // overload global1
  console.log(global1);          // --> 1
</script>

If we try to overload an implicit getElementById with a global var defined without any prefix, this is what happens (the results differ according to the browser used):

<div id=global2></div>

<script>
  global2.innerHTML = "global2"; // --> the div contains the text "global2" 
  try{global2 = 1}               // Try to overload global2
  catch(e){alert(e)}             // --> TypeError on IE < 9 / no error on other browsers
  console.log(global2);          // --> [object HTMLDivElement] on IE < 9 / 1 on other browsers
</script>

Finally, if we try to overload an implicit getElementById with a global var defined with a "var", this is what happens:

<div id=global3></div>

<script>
  global3.innerHTML = "global3"; // --> TypeError: Can't set property 'innerHTML' of undefined
  console.log(global3);          // --> undefined
  var global3 = 1;               // --> Try to overload global3
  console.log(global3);          // --> 1
</script>

As you can see, even if the var global3 is defined later in the code, browsers hoist variable definitions at the beginning of their scopes, and that's why it still breaks the lines 1 and 2.


To conclude, this technique is useful in some particular cases where we don't want to use a heavy library nor call the long function document.getElementById(), especially in code golfing contests. But in real life, it's safer to use the good old native methods because the implicit document.getElementById comes with several traps, and can lead to hours of debugging...


Cheers!
@MaximeEuziere

The joys of text-shadow... on IE.

septembre 2013

We don't present text-shadow anymore, this CSS property allowing to set a shadow to a text.
(see: the specs, the MDN page)

Many browsers have some small implementation problems, but the worst are, of course, the old IE. More precisely, IE < 10.

However, those browsers have access to proprietary "filters", created by Microsoft, and allowing to achieve a similar effect.
Sometimes, we have no other choice than using them. This article explains how.

Two filters allow to simulate shadows: Shadow and Glow.

Here is the "Shadow" version:

CSS:

h1 {

  text-shadow: 2px 2px 3px #ff0000; /* IE10+, Firefox 3.5+, Opera 9.5+, Chrome, Safari, and mobile browsers */
  filter:progid:DXImageTransform.Microsoft.Shadow(color='#ff0000', Direction=135, Strength=3); zoom: 1; /* IE 6-9 */
}

HTML:

<h1>Shadowed title</h1>

Rendu:

Shadowed title

If you look at this wonderful demo on an old IE (or on IE10 in legacy mode) you will see a shadow. But you will also notice that the text became all pixellized, and unfortunately, there is no solution for that.

Please note that "Direction" is set in degrees (for example, 0 = up, 90 = right, 180 = bottom, 270 = left) and "Strength" vaguely corresponds to the shadow's spread. (in reality, it looks more like a perspective effect...)

And here is the "Glow" version

CSS:

h1 {

  text-shadow: 0px 0px 1px #ff0000; /* Good browsers */
  filter: glow(color=#ff0000,strength=3); zoom: 1; /* IE 6-9 */
}

HTML:

<h1>Glowing title</h1>

Rendu:

Glowing title

The Glow filter mainly creates a halo around the text. But it's still quite ugly on IE. (you can have something less ugly if you don't exaggerate this much on the colours)


Note that the filters can be combined, like the CSS3 text-shadows. It's useful, especially when we want a "text-stroke" effect.

h1 {

  text-shadow: 1px 1px 1px #ff0000, 1px -1px 1px #00ff00, -1px -1px 1px #0000ff, 1px 1px 1px #f0000f; /* Good browsers */
  filter: progid:DXImageTransform.Microsoft.Shadow(color='#ff0000', Direction=0, Strength=1), progid:DXImageTransform.Microsoft.Shadow(color='#00ff00', Direction=90, Strength=1), progid:DXImageTransform.Microsoft.Shadow(color='#0000ff', Direction=180, Strength=1), progid:DXImageTransform.Microsoft.Shadow(color='#f0000f', Direction=270, Strength=1); zoom: 1; /* IE 6-9 */
}

HTML:

<h1>Titre with many shadows</h1>

Rendu:

Titre with many shadows

Keep in mind that it's very verbose, and very ugly on IE, when it's not used correctly.


Besides, I have an exclusive bug to show you:

These browsers only work on block elements, not on inline elements.

So if you need shadows on a <span>, you'll have to write some nonsense like... span { display: block }.



Cheers!
@MaximeEuziere

Ordered and unordered lists

august 2013

HTML lists... we all know them by heart: they exist since the first versions of HTML and XHTML, and they almost didn't change since then. They also work on all browsers.

Let's study their HTML.
<ul> means "unordered list", <ol> means "ordered list", et <li> means "list item".
Here is their simplest form (note that the </li> are optionals)

Demo:

<ul>
  <li>Element 1
  <li>Element 2
  <li>Element 3
</ul>

<ol>
  <li>Element 1
  <li>Element 2
  <li>Element 3
</ol>
Result:
  • Element 1
  • Element 2
  • Element 3

  1. Element 1
  2. Element 2
  3. Element 3

The li can also contain ul or ol sub-lists. (but we'll talk about that later)

The lists also have specific attributes:

  • <ul> (and its <li>s) can have a "type" attribute, with the value "circle", "square" or "disc". This allows to change their ticks ("circle" by default).
  • <ol> (and its <li>s) can also have a "type" attribute, with values like "A", "a", "I", "i", "1". This allows to set their number notation ("1" by default).
  • You should avoid the type attribute, and use CSS instead (we'll come to this later)
  • <ol> can have a "start" attribute telling where the numbering should begin, and its <li>s can have a "value" attribute to change their default number, and the following ones. (This works on IE6+)

Démo:

<ol start="10">
  <li>Element 10
  <li>Element 11
  <li>Element 12
  <li value="20">Element 20
  <li>Element 21
  <li>Element 22
  <li value="5">Element 5
  <li>Element 6
  <li>Element 7
</ol>
Ce qui donne:
  1. Element 10
  2. Element 11
  3. Element 12
  4. Element 20
  5. Element 21
  6. Element 22
  7. Element 5
  8. Element 6
  9. Element 7
  • And finally, <ol> can have a "reversed" attribute (appeared with HTML5) allowing to count the elements from bottom up. But since it doesn't work on IE < 11, you should rather set a decreasing "value" to each <li> to get the same effect.

Now let's see their default CSS code, and their dedicated CSS properties:

<ul> and <ol> are normal blocks (display: block). They have a default padding-left that depends on the browser (around 40px). The ticks are included in this padding and they can't be selected as normal text with the mouse.

<ul> has disc-shaped bullets (list-style: disc outside none).

<ol> has numbers in front of each element (list-style: decimal outside none).

<li> have a special display (display: list-item), almost identical to display: block. They also have a list-style (inherited), but they can overload it individually.

The list-style property is a shorthand notation allowing to set three parameters:

  • list-style-type (that can have many many values),
  • list-style-position (that can have the value "inside", "outside" or "inherit". Inside allows them to contain their own ticks, instead of letting them stay in their parent's padding.)
  • list-style-image, which is "none" by default, but it can also be the url of an image. If the URL is invalid (or if the image isn't fount), there is a fallback on the default list-style-type (disc, decimal, ...).


Now let's see the different problems that lists can cause us:

 • If you apply a CSS reset to your page, it will delete the paddings on ul and ol elements, and their ticks will be hidden.
Solution 1: put the ticks inside the li with this rule:

ul, ol { list-style-position: inside }
The ticks reappear, but we still lose the indentation of nested lists.
<ul>
  <li>list
  <li>list
    <ol>
      <li>sub-list
      <li>sub-list
    </ol>
</ul>
  • list
  • list
    1. sub-list
    2. sub-list
Solution 2 (the good one): don't change the ticks position, but re-set a padding-left to the lists, like this:
ul, ol { padding-left: 2em }
Result:
  • list
  • list
    1. sub-list
    2. sub-list
Indeed, the padding-left are cumulative, and so the nested lists are correctly indented.

 • If we nest ordered lists, their numbers will stay the same (decimal notation). A good typographic practice is to do this:

ol { list-style-type: decimal }
ol ol { list-style: upper-alpha }
ol ol ol { list-style:  lower-roman }
ol ol ol ol { list-style: lower-alpha }

Which allows nested lists as pretty as this, with up to four levels:
  1. level 1
  2. item
  3. item
    1. level 2
    2. item
    3. item
      1. level 3
      2. item
      3. item
        1. level 4
        2. item
        3. item

 • The CSS resets generally set a vertical-align: baseline and a line-height: 1.5em to all the elements of the page, and this causes a problem on IE < 8 when we have nested lists.
Indeed, if you watch the following list on IE < 8, you will see the numbers "fall" at the bottom of the sub-lists, and the ticks will be misaligned with the text ahead.

  1. level 1
  2. item
  3. item
    1. level 2
    2. item
    3. item
      1. level 3
      2. item
      3. item
        1. level 4
        2. item
        3. item

To fix the "falling" ticks, use this CSS dode:

ol, ul, li { vertical-align: top }

To fix the alignment between the ticks and the text, there is no miracle solution, but five possible approaches:

  • Consider that an offset of a few pixels is not a big deal on IE < 8, and do nothing.
  • Put a "1em" line-height on all the page.
  • Let the default line-height to "1.5em", but set the <li>'s line-height to "1em" and their padding to "0.25em 0", which will do the job, unless the text of the <,li> spreads on two lines or more, because these lines would be very close.
  • Replace the default tick with an image that looks like a tick, but with a few pixels of vertical offset.
  • Or... (and this is the solution I recommend) put an element (like a <span>) in each <li>, and apply this style:
    li span { *position: relative; *top: -0.5em }
    Result:
    • Item 1
    • Item 2
    • Item 3

However, there is a bug on all the NEWER versions of IE (except IE6 and 7!), which you can see here:

  1. level 1
  2. item
  3. item
    1. level 2
    2. item
    3. item
      1. level 3
      2. item
      3. item
        1. level 4
        2. item
        3. item

Each sub-list has a huge gap on top. It's not a margin or a padding, it's an IE bug. To get rid of it, you can set float: left to the lists (but it could lead to flow issues), or put them in display: inline-block (that's what I recommend):

li ul, li ol { display: inline-block }

So, everything is correct now.

Other list-related bugs and their solutions are described here, but I won't talk about them because they are too much specific.


Instead, let's talk about the original things that we can do with HTML lists:

  • A <li> can live without a <ol> or a <ul> around it. In this case, it will have a circle-shaped tick (on all the browsers).
  • A <ul> / <ol> can contain elements that are not <li>. (or even some text). These elements won't have a tick or a number.
  • There are several easy ways to display a list on many columns.
    This article is exhaustive on this subject.
  • A list can look like a file tree with a simple 1px image and some ingenious CSS code: take a look at this.
  • The <li> elements can use one (or many) unicode character(s) as their ticks, thanks to CSS3 (and it works on IE8+):
    li { list-style: none }
    li:before { content: ">>"; margin:0 1em 0 -3em;  }
    Result:
    • Item 1
    • Item 2
    • Item 3

I'll stop here, and I invite you to look at my last project, Basis: a basic "boilerplate" for HTML/CSS/JS pages, with a custom CSS reset that contains all these best practices, and many others.



Cheers!
@MaximeEuziere

The HTML5 contenteditable attribute

july 2013

The contenteditable attribute makes any HTML element editable with the mouse and/or the keyboard.

This attribute is one of the HTML5 global attributes, so it can be applied to all HTML5 elements.
It's also one of those features that existed in browsers for a long time before W3C standardization.
Indeed, we can use this attribute on browser as old as IE 5 and Firefox 3! Microsoft created it, by the way.

It's easy to use: like id or class, you can use contenteditable="true" on any element, or simply contenteditable.
Note that this attribute is inherited by default. So the value contenteditable="inherit" is completely useless (but it exists).
However, if you want to make an element editable, but not its children, these children must have contenteditable="false".

Example: click on this paragraph to edit the text inside.


Here's the list of crazy facts about contenteditable:

  • Like we saw earlier, we can make an element non-editable if its parent is editable. The non-editable element can still be selected, moved and deleted.

    Example: In this editable paragraph, this text in bold can't be edited, but you can move and delete it with the mouse.

    On Firefox, we can't move text from an editable container to another editable container... that's a bug.
  • Firefox allows to resize images that have or inherit the contenteditable behavior.

    Example:

    Click on this image to resize it.

  • Any HTML code can be inserted in an editable element
    Example: You can copy-paste here

    - The content of another Webpage:



    - The content of a Word / Excel document:



  • Thanks to this attribute, we can make complete WYSIWYG editors, like CKEditor.
  • You can also access to a quick notepad by typing data:text/html, <html contenteditable> in the address bar of your browser (or by clicking here). This trick comes from Jose Jesus Perez Aguinaga. You can find many variations of this notepad here.
  • If you make a style element visible and editable, this can make a crazy auto-recursive editor, in which you can edit in real time the style of the block you're writing into.
    Try it:
  • script elements can also be visible and editable (here's an exemple). However, the JavaScript code isn't executed when you edit it. It's a simple text block.

    (open your F12 console to see this message)
    But JavaScript itself allows to detect changes happening in a script element and execute its content.
    Warning: don't do this at home (XSS attacks can occur). This example is only a proof of concept.

    This works fine on Chrome and IE 10. Sadly, Firefox and IE9 can't listen to keystrokes events on script elements... But you can replace them with textareas, which are natively editable.


Cheers!
@MaximeEuziere

Le doctype HTML5

juin 2013

Le doctype n'est pas un élément HTML comme les autres, c'est un préambule qui sert à dire au navigateur comment il doit afficher le reste de la page.

Ce préambule existe depuis HTML2 et XHTML1. Toutefois, jusqu'à l'apparition d'HTML5, les doctypes étaient de très longues chaînes impossibles à mémoriser par coeur, et pour peu qu'on fasse une faute de frappe, les navigateurs pouvaient mal le prendre, en particulier IE qui applique des règles d'affichage ancestrales (qu'on appelle aussi "mode quirks") en cas de doctype absent ou invalide.

Bref: ce qu'il faut retenir, c'est que maintenant, le doctype s'écrit comme ceci: <!doctype html>, et qu'il doit être placé tout au début du document HTML. Il est suivi de l'élément racine, à savoir <html>.

Oui mais comment on fait du XHTML5 alors? La réponse est simple: ça n'existe pas. Il n'y a qu'un HTML5, et il n'est ni "X" ni "strict" ni "transitional". et ça ne l'empêche pas de bien marcher, même sur les IE préhistoriques.


Voici maintenant la liste tant attendue des trucs de ouf concernant le doctype HTML5:

  • Le doctype est insensible à la casse et peut contenir plusieurs espaces avant et après le mot "html". Vous pouvez donc l'écrire <!DoCtYpE     HtMl   > si ça vous chante.
  • Le doctype HTML5 peut contenir d'autres informations, mais aucune n'est utile quand on écrit son HTML à la main ou avec un IDE moderne.
  • Le doctype et l'élément title sont les deux seuls éléments obligatoires dans un document HTML5 valide.
    Voici le plus petit valide document HTML5:
    <!doctype html>
    <title></title>
  • Le doctype ne peut pas être lu, ni modifié en JavaScript, mais on peut tout de même savoir si le navigateur est en mode quirks ou en mode compatibilité comme ceci:
    if(document.compatMode === 'CSS1Compat'){
    	// On est pas en mode quirks!
    }
    if(document.compatMode === 'BackCompat'){
    	// On est en mode compatibilité!
    }
    (si les deux sont "false", tout va bien)
  • A propos du "mode compatibilité", qui pousse IE7, IE8, IE9 et IE10 à afficher les pages Web avec le moteur de rendu de leur prédécésseur (E6, IE7, IE8 et IE9, respectivement), de nombreuses raisons font que ce mode s'active sur IE, mais le plus souvent, c'est dû au fait qu'on visionne une page HTML qui se trouve sur son PC, ou sur un réseau local. Pour éviter ce désagrément, ouvrez IE, faites "alt" pour que le menu du haut apparaisse, allez sur "outils > paramètres d'affichage de compatibilité" et décochez "Afficher les sites intranet dans Affichage de compatibilité". Faites "OK" et redémarrez IE. C'est réglé!
  • Sur IE < 9, le doctype, du fait qu'il commence par un !, est considéré comme un commentaire HTML. En voici la preuve:
    // Récupérons le nom du premier élément HTML de cette page dans le DOM
    var premier_element = document.getElementsByTagName("*")[0].tagName;
    ("!" sur IE < 9, "HTML" ailleurs)

    (Les IE < 9 sont d'ailleurs les seuls navigateurs à inclure les commentaires (sauf les commentaires conditionnels) dans document.getElementsByTagName("*"), comme décrit ici)
  • Le doctype HTML5 fait apparaitre de vilaines scrollbars autour des iframes sur IE6, pour d'obscures raisons, alors que les doctypes d'avant HTML4 ne le faisaient pas. Le seul moyen de les faire disparaître est d'utiliser l'attribut (hélas non standard) scrolling=no sur vos iframes... si vous voulez supporter IE6, bien sûr, ce que je ne vous conseille pas de faire.
  • Démo: iframe normale


    iframe sans scrollbars sur IE6
  • Il y a un autre bug sur IE, y compris IE10 (et peut-être IE11, à confirmer): si votre page est en mode quirks, et qu'elle contient des iframe qui ont un doctype valide, ces iframe s'afficheront tout de même en mode quirks!


A bientôt!
@MaximeEuziere

Wait! There's more!

Use the summary to see the other articles!