#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

  • You can execute JS code with <iframe onload=...>
  • You can use an undefined iframe id as a loop var (also works with svg).
  • The default value of an undefined id is '' (empty string). When you do 'id++', it's set to 1.
  • changing the iframe's (or svg's) outerHTML re-triggers its onload event, which leads to another loop.




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

  • Bookmarklets' code need to be embedded in an IIFE "(a=>{...})()", vor various reasons. The most obvious one is to avoid global scope pollution, but more generally, if you don't do it, your code will often be buggy.
  • The input element is included in the link, and updates this link's href when text is typed inside.
  • When the link is dragged in the bookmarks, the text content "@" is used for the bookmark's name, the link's href is used for the bookmark's url, and the input is ignored.




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:

  • cheap loop: iterate on a function's source code ("for(i in{}+o)" or "for(i in o+o)", if the function is called "o");
  • creating a var 'y' containing a cell's name, write an input element with id=${y}, then access this element with top[y] (or self[y] if you want your app to be iframe-able);
  • '/.../.test(z)' is shorter than 'z.match(/.../)' but is also handier because it works even when y is undefined;
  • 'x-~x' is better than "+x" to detect if a string contains a nimber (integer or float), as it supports the value '0';
  • '+x' acts as 'parseFloat(x)';
  • given 5 expressions A,B,C,D,E, 'document.write(y?(A,B):(C,D,E))' will execute (A and B), or (C, D and E), but will only write the value returned by B or E, according to the value of y;
  • Math or document can be used to store values like any other object.




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

  • Contrary to innerHTML, innerText allows to make visible line breaks using '\n', or even shorter: a template literal (``) containing a line break char.
  • Iterate on all the events of an element to pick only the ones you need can be shorter than naming them.
  • "let" can be useful in a for loop that generates different functions, to have access to the name of the current element of the loop inside the function (like a closure, but shorter).
  • Never rely on onkeypress. Its keyCode (a.k.a. which) doesn't match the keyCodes of onkeydown and onkeyup, and it is inconsistent across browsers.




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

  • You can use <canvas onclick=with(this.getContext`2d`)...> to execute your code on click instead of using an extra script, body or svg element to launch it automatically on load. It saves around 6 bytes.




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

  • When X returns 'undefined', usng canvas fillText(X(), 1, 1) can be used to draw a dotted line measuring about 50px on top of a canvas.




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

  • To display any Unicode glyph on a webpage, String.fromCodePoint(i) can be replaced with "&#"+i.
  • HTML entities usually require a trailing semicolon (&#1234;) but it works fine without it.




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

  • Use '...' to copy an Array to a new Array: A = [...B]
  • The return value of setTimeout() can be used as a quasi random number
  • 'A?B:0' is shorter than 'A&&(B)' if B is an assignment. Otherwise, 'A&&B' is shorter

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

  • In <svg onload=...>, the svg itself and 'document' are implicit
  • You can sometimes replace the number after % (modulo) with a multiple of this number, and it can save you some bytes




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

  • Instead of doing 'prompt()' four times, do it just once, and split the result with a separator character like a comma or a space
  • In <svg onload=...>, the variables x and y are not modifiable
  • Subtlety of 'with' - this works: 'with(Math)setInterval(z=>alert(sin(n)))' - this doesn't work, because 'sin' will become undefined: 'with(Math)setInterval("alert(sin(n))")




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

  • The transform and untransform functions need to agree on which char is used as the end-of-string delimiter. This is boring to do for the user, so they both automatically use the char "ÿ" for that. It's appended during the transform, and removed at the end of the untransform.
  • The second parameter of a function can contain an expression that includes the first parameter: '(d,t=d.indexOf`ÿ`)=>...'
  • ' "...".slice(0,-1)' is the shortest way (as far as we know) to remove the last char of a string.




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

  • Changing the frequency of the oscillator at regular intervals is shorter, and much less "clicky" than stopping each note before starting the next one. Actually, it's not clicky at all!




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

  • Body's background color and text color can be set through its bgColor and text attributes.
  • Everything in a trailing <style> tag can stay unclosed (parenthesis, curly brace, and aven the closing tag </style>).




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

  • A mp3 file can be called "3" with no extension, and played with <audio src=3>.
  • To execute JS when the page is loaded, you can use the <audio>'s onplay event, if it also has an autoplay attribute.




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!