#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.
Regolfing mini apps with ES6 & the great help and discoveries from the codegolf team and its epic newcomers @mmastrac and @justecorruptio 🙋
— xem 🔵 (@MaximeEuziere) 2 octobre 2017
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
#golfctober episode 1:
— xem 🔵 (@MaximeEuziere) 2 octobre 2017
minicodeEditor is now 3b shorter thanks to a complete rewrite and a "<iframe onload>" trickhttps://t.co/VAzvwH66aO pic.twitter.com/mBHvjDBBiE
#golfctober day 5
— xem 🔵 (@MaximeEuziere) 5 octobre 2017
MiniCodeEditor is back! (78th iteration)
Full: 149b =>142b (thx @justecorruptio)
Mini: 64b => 62bhttps://t.co/YgnawETfLr pic.twitter.com/F8PPO0OlGP
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
#golfctober day 2:
— xem 🔵 (@MaximeEuziere) 2 octobre 2017
miniBookmarklet (80b => 64b!)https://t.co/FI4qOaPZy9
<a id=a>@<input oninput='a.href=`javascript:(a=>{${value}})()`'> pic.twitter.com/8RCQDjhOkL
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
#golfctober day 3:
— xem 🔵 (@MaximeEuziere) 3 octobre 2017
The famous https://t.co/uay7diKK4U lost 15b over the weekend!
thanks to @mmastrac @justecorruptio
cc @subzey @p01 @aemkei pic.twitter.com/YcYyWZWgPC
#golfctober day 3 (bonus):
— xem 🔵 (@MaximeEuziere) 3 octobre 2017
The commented source code of our 188b spreadsheet.
A lot of stuff is happening in there!https://t.co/yxRATIymO7 pic.twitter.com/2aXyXkpd0g
#golfctober day 14:
— xem 🔵 (@MaximeEuziere) 14 octobre 2017
(Sprite) sheet just got real!https://t.co/yxRATIymO7
2 versions (mini: 188b, full: 253b)
& super-detailed source code pic.twitter.com/YCX7DXP1Lx
3) @mmastrac made a new version of sheet with ABC... / 123... headers, in 277b (plus a bit of optional CSS code)https://t.co/2SEj9lfBVw pic.twitter.com/pfFsy7mxyI
— xem 🔵 (@MaximeEuziere) 15 octobre 2017
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
#golfctober day 4:
— xem 🔵 (@MaximeEuziere) 4 octobre 2017
miniKeyCode (128b=>88b) logs all keyboard events!
by @mmastrac, @corruptio, @p01, @nderscore, @benjamin_js, @aemkeihttps://t.co/55xsmF6rSw
Source code (line break included):
<body onload="for(let a in b)b[a[4]>'x'&&a]=e=>b.innerText+=`
`+[a,e.which]"id=b> pic.twitter.com/wbU38uyKuh
1) @subzey removed 1 byte to MiniKeyCode (79 => 78b)https://t.co/ccSl2r0nmM
— xem 🔵 (@MaximeEuziere) 15 octobre 2017
No more line breaks, but you can still use the zoom ;) pic.twitter.com/lb6OEkT2Z4
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
#golfctober day 6: miniURI
— xem 🔵 (@MaximeEuziere) 6 octobre 2017
99b=>98b / 140b => 133b
drop a file, and it's transformed in dataURI (mime-type + base64)https://t.co/pv24AzHdGN pic.twitter.com/DFg8FczjGD
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
#golfctober day 7: miniDragonCurvehttps://t.co/yp83mWS7GB
— xem 🔵 (@MaximeEuziere) 7 octobre 2017
112b/118b optimizations by @xen_the, based on @p01's famous 121b DRAGON PUNCH pic.twitter.com/KPILpgpqRN
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
#golfctober day 8: miniSerpinskiTrianglehttps://t.co/Y9X42rh11Q
— xem 🔵 (@MaximeEuziere) 8 octobre 2017
119b/123b golf by @xen_the pic.twitter.com/hKLCy33ItW
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
#golfctober day 9
— xem 🔵 (@MaximeEuziere) 9 octobre 2017
Mini Lorenz attractor (112b=>111b)
Based on @aemkei's: https://t.co/1YswnrkaOq
Golfed by @xen_the: https://t.co/coKvGYDL9k pic.twitter.com/v26uEOkcZi
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
#golfctober day 10
— xem 🔵 (@MaximeEuziere) 10 octobre 2017
Fill an array with all numbers from 0 to N, in 20b:
[...Array(n).keys()]
by @subzey, @adabeezus, @nderscore, @atesgoral pic.twitter.com/93cbTrO0dy
Current record:
[...Array(n).keys()]
Day 11:
#golfctober day 11: miniUnicode
— xem 🔵 (@MaximeEuziere) 11 octobre 2017
Our 7 Unicode slideshows just lost 8+17+22+43+42+2x20b!
Tx @mmastrac @p01 @atesgoralhttps://t.co/aG1LkbOtTN pic.twitter.com/gTCErjFvjz
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 (Ӓ) 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!
#golfctober day 12: Mini Tetris
— xem 🔵 (@MaximeEuziere) 12 octobre 2017
made by @veubeke,
helped by @aemkei, @benjamin_js and @Fusselwurm
(great readme too)https://t.co/Drs7sIaesW pic.twitter.com/cjb6WmXWVK
This is by far the best entry of @MaximeEuziere's #golfctober combo: A full TETRIS game by @veubeke 🐌 in less than 512 bytes of HTML and JavaScript!
— Martin Kleppe (@aemkei) 12 octobre 2017
▶ Play: https://t.co/O1cT6PszSz
ℹ️ Source: https://t.co/SmpSv7g3kj
With help from @benjamin_js, @Fusselwurm and @aemkei (me). pic.twitter.com/01cl8vSHyR
4) @veubeke (& all the team) improved the 512b tetris!
— xem 🔵 (@MaximeEuziere) 15 octobre 2017
There's a game over screen, all 7 shapes are playable, etc. !https://t.co/Drs7sIaesW pic.twitter.com/1ZQDPaL7sf
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
#golfctober day 13:
— xem 🔵 (@MaximeEuziere) 13 octobre 2017
mini ASCII Serpinski triangle
golfed from 96b to 64/65b!!
thanks @benjamin_js & @justecorruptiohttps://t.co/ceBpd4MM6D pic.twitter.com/CYcy4O3tb4
2) @xen_the improved big MiniASCIISerpinski:
— xem 🔵 (@MaximeEuziere) 15 octobre 2017
- Autoplay (61b) https://t.co/jT9Q2xUzwX
- Click quotes to run (57b) https://t.co/urV9mGjrEN pic.twitter.com/0l3NnY5Jot
#golfctober bonus 4
— xem 🔵 (@MaximeEuziere) 1 novembre 2017
MiniASCIISerpinski+@subzey = 63bhttps://t.co/mNf4WIrQkP
<svg onload='for(i=1024;i--;)write(i%63<32?`<p>`:i&i>>5&&`_`)'> pic.twitter.com/HzwTrM5ygw
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
#golfctober day 16:
— xem 🔵 (@MaximeEuziere) 16 octobre 2017
Mini Clifford Attractor (226=>220b, canvas=>svg)
w/ @p01 & @subzey
Pick 4 numbers & wait for it!https://t.co/Cq4ZfX30He pic.twitter.com/P9Olk15aba
#golfctober day 17:
— xem 🔵 (@MaximeEuziere) 17 octobre 2017
Mini Peter de Jong Attractor (226b=>220b) & The One Ring (229b=>223b)
golfed with @p01 & @subzeyhttps://t.co/RGAnRsjAjh pic.twitter.com/3Y4pvYZ5vs
#golfctober day 22:
— xem 🔵 (@MaximeEuziere) 22 octobre 2017
HD clifford attractors with custom parameters, in 279bhttps://t.co/FJxvzu0DrO pic.twitter.com/FTEfdBWEsw
#golfctober day 23:
— xem 🔵 (@MaximeEuziere) 23 octobre 2017
HD Peter De Jong Attractor (279b) + "the one ring" (281b)
(both allow custom parameters)https://t.co/h6x1YeQIHZ pic.twitter.com/ELnYiAMpRD
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
#golfctober day 18:
— xem 🔵 (@MaximeEuziere) 18 octobre 2017
Mini (<128b) Burrows-Wheeler Transform & Untransform
Golfed w/ @mmastrac, @benjamin_js, @veubekehttps://t.co/wRFVG7lsyu pic.twitter.com/6v90ufShDL
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
#golfctober day 19:@Xen_the golfed atree from 137b to 134b: https://t.co/bUsec0wOfm
— xem 🔵 (@MaximeEuziere) 19 octobre 2017
and even 121b (click to start): https://t.co/0XewVzc9DN pic.twitter.com/wzFJJo9Jh5
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
#golfctober day 20:
— xem 🔵 (@MaximeEuziere) 20 octobre 2017
Tetris theme, with loop, golfed in 182b by @mmastrac, @p01, @justecorruptio & me.https://t.co/DjLwQWlCFv#noscreenshot
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
#golfctober day 21:
— xem 🔵 (@MaximeEuziere) 21 octobre 2017
An UTF-8 bytes counter function golfed in 39b with @ETHProductions:
s=>encodeURI(s).split(/%..|./).length-1 pic.twitter.com/MQL5SdPk2I
#golfctober bonus 2:
— xem 🔵 (@MaximeEuziere) 1 novembre 2017
Our UTF-8 bytes counter is down to 32b thanks to @subzey:
s=>unescape(encodeURI(s)).lengthhttps://t.co/4oy51Jh0lJ
Progress
//v1
s=>encodeURI(s).split(/%..|./).length-1
//v2
s=>unescape(encodeURI(s)).length
Day 24: attractorandom
#golfctober day 24:
— xem 🔵 (@MaximeEuziere) 24 octobre 2017
Random attractors in 356b!
w/ @Xen_the @veubeke @p01 @nderscore @justecorruptio @ETHprod senokai https://t.co/bZg9WprT6S pic.twitter.com/DauMhoL9mY
Progress
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
#golfctober 25:
— xem 🔵 (@MaximeEuziere) 25 octobre 2017
miniPlot, an interactive curve plotter, with axis and legends,
golfed in 243b with @p01https://t.co/L0U4CxIza9 pic.twitter.com/OPLQkXdOMf
#golfctober bonus 3:
— xem 🔵 (@MaximeEuziere) 1 novembre 2017
We didn't realize miniPlot was upside-down...😣
It's fixed now! And still 243b.
Thanks @subzey!https://t.co/L0U4CxIza9 pic.twitter.com/wWMpPmfS1h
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
#golfctober day 26:
— xem 🔵 (@MaximeEuziere) 26 octobre 2017
miniTicTacToe
golfed by @ethprod, @nderscore, @atesgoral and me.
Full: 225b. Mini: 167b.https://t.co/zrycFSyWbU pic.twitter.com/YpPzBzL1AO
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
#golfctober day 27:
— xem 🔵 (@MaximeEuziere) 27 octobre 2017
"paste my email here" bookmarklet in 66b
javascript:(a=>{with(document.activeElement)value=innerHTML="me@domain.com"})() pic.twitter.com/GhDdx42k1n
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
#golfctober day 28:
— xem 🔵 (@MaximeEuziere) 28 octobre 2017
an online IDE (handy for code golfing) in 491b (+ CodeMirror)https://t.co/F2mLGHAhhw pic.twitter.com/Y1rddB2Vqe
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
#golfctober day 29
— xem 🔵 (@MaximeEuziere) 29 octobre 2017
MiniAudioViz, golfed in 274b with @p01
Works with any mp3 file provided you rename it as "3".https://t.co/bR4gAYr7ei pic.twitter.com/5UruLMaQHC
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
#golfctober day 30:
— xem 🔵 (@MaximeEuziere) 30 octobre 2017
MiniGameOfLife 1 & 2:https://t.co/GDPcu68nGZ (238>200b)https://t.co/f5uvCVVUtj (182>176b)
with @mmastrac @p01 @xen_the pic.twitter.com/NosjDLwRoU
Bonus: MiniGameOfLife2 "autoplay" is also shorter (182 => 179b)
— xem 🔵 (@MaximeEuziere) 30 octobre 2017
thanks to @justecorruptio https://t.co/Xi9J4ytodA pic.twitter.com/km4pRPGPAs
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
#golfctober 31:
— xem 🔵 (@MaximeEuziere) 31 octobre 2017
A new tiny tetris game in 370b by @justecorruptio & all the team
play: https://t.co/rWfzq4R3HP
read: https://t.co/JefHcRG24d pic.twitter.com/mryVJso2RR
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
#golfctober bonus 1:
— xem 🔵 (@MaximeEuziere) 1 novembre 2017
print all unicode characters in 57b!https://t.co/DodHniPmkS
setInterval(`document.body.innerHTML+="<wbr>&#"+i++`,i=0) pic.twitter.com/MqRw1USxZJ
#golfctober bonus 5:
— xem 🔵 (@MaximeEuziere) 1 novembre 2017
nanoid golfed by @subzey in 106b
(+ some gzip optimizations explained on his twitter timeline)https://t.co/AhLycp0m1x
#golfctober bonus 6:
— xem 🔵 (@MaximeEuziere) 1 novembre 2017
A 389b maze-shaped maze generator: https://t.co/ZQk45vxLo2
and a 319b maze solver: https://t.co/w5UjoDBybE
by @aemkei pic.twitter.com/T61ogWxRwE
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!