Show HN: URL Snake (github.com)

114 points by lbatchelor ↗ HN
Just a little demo I made of a fully functioning game of snake encoded entirely within a valid url (around 1033 bytes of code encoded into a 1464 byte url).

35 comments

[ 4.5 ms ] story [ 78.1 ms ] thread
HN doesn't linkify data: URLs

data:text/html;charset=utf-8,%3Cbody%3E%3Cscript%3Eeval(atob(%22bGV0IGU9ZG9jdW1lbnQsdD1lLmJvZHkscj1NYXRoLGw9J1ByZXNzIHNwYWNlIHRvIHN0YXJ0L3BhdXNlL3Jlc2V0LCBhcnJvdyBrZXlzIHRvIG1vdmU8YnI+PGJyPjxjYW52YXMgaWQ9ImEiPjxzdHlsZT5jYW52YXN7Ym9yZGVyOiAxcHggc29saWQgYmxhY2s7fWJvZHl7YmFja2dyb3VuZDojY2NjO308L3N0eWxlPicsaT00MCxvPTQwMCxuPW8scz0wLGY9MSxkPTAsYz0xLGg9MCxwPVtbMywzXSxbNCwzXSxbNSwzXV0sdT1wLHk9WzUsNV0sYj0iIzAwMCIsZz0oZSx0LHI9YixsPWksbz1pKT0+e1MuZmlsbFN0eWxlPXIsUy5maWxsUmVjdChlLHQsbCxvKX0sdj0oZSx0LHIsbD0iY2VudGVyIik9PntTLmZpbGxTdHlsZT1iLFMudGV4dEFsaWduPWwsUy5maWxsVGV4dChlLHQscil9LHg9ZT0+ci5mbG9vcihyLnJhbmRvbSgpKmUpLGs9KCk9PntpZighZCYmIWMpe2xldFtlLHRdPXAucG9wKCkscj1wWzBdWzBdK3MsbD1wWzBdWzFdK2Y7aWYocjwwfHw5PHJ8fGw8MHx8OTxsfHxwLnNvbWUoKFtlLHRdKT0+cj09ZSYmbD09dCkpcmV0dXJuIG0oKTtyPT15WzBdJiZsPT15WzFdJiYoaCsrLHAucHVzaChbLi4ucFtwLmxlbmd0aC0xXV0pLHk9W3goOSkseCg5KV0pLHAudW5zaGlmdChbcixsXSksZygwLDAsIiNmZmYiLG4sbyk7Zm9yKFtlLHRdb2YgcClnKGUqaSx0KmkpO2coeVswXSppLHlbMV0qaSwiIzBmMCIpLHYoIlNjb3JlOiAiK2gsbi05LDMwLCJyaWdodCIpfX0sbT0oKT0+e2M9MSxnKDAsMCwiI2YwMCIsbixvKSx2KCJZb3UgZGllZCA6KCIsbi8yLG8vMil9LHc9KCk9PntjPTAscD1bLi4udV0scz0wLGg9MCxmPTF9LFM9KHQuaW5uZXJIVE1MPWwsYS5nZXRDb250ZXh0KCIyZCIpKTthLmhlaWdodD1vLGEud2lkdGg9bixTLmZvbnQ9IjMwcHggQXJpYWwiLGUuYWRkRXZlbnRMaXN0ZW5lcigia2V5dXAiLGU9PntlPWUud2hpY2g7Mzc9PWUmJihzPS0xLGY9MCksMzg9PWUmJihzPTAsZj0tMSksMzk9PWUmJihzPTEsZj0wKSw0MD09ZSYmKHM9MCxmPTEpLDMyPT1lJiYoYz93KCk6ZD1kPzA6MSl9KSxzZXRJbnRlcnZhbChrLG4pOw==%22))%3C%2Fscript%3E

https://news.ycombinator.com/formatdoc says that between angles it should work, but it does not. Or i don't know how to use it, it doesn't seem to do anything.

<data:text/html;charset=utf-8,%3Cbody%3E%3Cscript%3Eeval(atob("bGV0IGU9ZG9jdW1lbnQuYm9keSx0PSJTcGFjZTogc3RhcnQvcmVzZXQuIEFycm93czogTW92ZTxkaXY+PGNhbnZhcyBpZD1hPjxzdHlsZT4jYXtib3JkZXI6c29saWR9KntiYWNrZ3JvdW5kOnRhbn08L3N0eWxlPiIscj00MCxuPTQwMCxvLGQ9W1s0LDNdXSxzPWRbMF0sYz0iIzAwMCIscD0oZT0icmVkIix0PW4sYT0wLGw9YSk9PntDW2Y9ImZpbGxTdHlsZSJdPWUsQy5maWxsUmVjdChhKnIsbCpyLHQsdCl9LGg9KGUsdD1uLzIsYT10LGw9ImNlbnRlciIpPT57Q1tmXT1jLEMudGV4dEFsaWduPWwsQy5maWxsVGV4dChlLHQsYSl9O2UuaW5uZXJIVE1MPXQsQz1hLmdldENvbnRleHRgMmRgLGEuaGVpZ2h0PWEud2lkdGg9bixDLmZvbnQ9IjMwcHggZiIsZS5vbmtleXVwPSh7d2hpY2g6ZX0pPT57MzY8ZSYmZTw0MT9IPWUtMzg6MzIhPWV8fG98fChvPTEsZD1bWzQsM11dLEg9MixTPTApfSxzZXRJbnRlcnZhbChpPT57aWYobyl7bGV0W2UsdF09ZFswXSxhPWUrSCUyLGw9dCsoSC0xKSUyLG49KFtlLHRdKT0+ZT09YSYmdD09bDtpZihkLnBvcCgpLGE8MHx8OTxhfHxsPDB8fDk8bHx8ZC5zb21lKG4pKXJldHVybiBvPTAscCgpLGhgOihgO2Q9W1thLGxdLC4uLmRdLG4ocykmJihkWysrU109ZFtTLTFdLHM9WyhEPW5ldyBEYXRlKSUxMCxEKlMlMTBdKSxwYHRhbmAsZC5tYXAoZT0+cChjLHIsLi4uZSkpLHAoaSxyLC4uLnMpLGgoUyw5LDMwLCJsZWZ0Iil9fSxuKTs="))%3C%2Fscript%3E>

And ChatGPT can tell that it's a game of Snake from the URL. https://www.tiktok.com/@y.i.t.z.i/video/7186055378977803521

"When decoded, the script appears to be a Snake game implemented using JavaScript and the HTML5 canvas element. The script creates an HTML page with a canvas element and adds event listeners for arrow keys and space bar to control the game."

Wow, that's actually impressive. Any idea how chat gpt would know that? Surely it's not making the request for it and surely it's not actually decoding any base64 right?
You'd be right a good chunk of the time if you said it was a snake game just by seeing that it was a data URL and had the word 'canvas' in.
ChatGPT has probably seen plenty of half-baked excercise/homework snake implementations on Github to tell that this is another one of those.

(Yes, this is moving the goalposts and implying that true AI is just over the horizon...)

I recall seeing that it's actually quite fluent in base64
Wow that's impressive. Is ChatGPT updating its model fast enough that this repo could have been included or is it really figuring out what this is from the code itself?
ChatGPT had it's last update on 15th Dec 2022, but the main scrape of the web for training data was ~2020.

Try asking it about someone famous from 2021 and it usually doesn't know.

(comment deleted)
I love these types of projects. A suggestion to take this a step further is to encode it inside a QR code.
That was actually the original plan! But unfortunately it seems like my phone's qr code reader won't open it in a browser automatically. I then tried using a url shortener to link to it but absolutely no shortener that I could find would take a data: url.

I could host my own shortener to do it, but it feels like that is cheating the spirit of the challenge.

I'll keep playing with it though, add touch device controls, maybe a site to allow generating different versions of the url (different colours, sizes, game over messages, etc).

Can't you just put the whole thing in a JavaScript URL without using base64? Eg

  JavaScript:alert(1)
It would be shorter than 1464. I'm not at my computer right now to try it...

Edit: It seems to work. Also, the original can be golfed some more.

(comment deleted)
Edit: I've actually managed to get the javascript down to a measly 859 bytes now. Pretty happy with some of the code golfing techniques I came up with
You should be able to change

  e.addEventListener("keyup"...
to

  e.onkeyup=...
but I haven't tested it.
Ooh! Don't know how I missed that, I'll give it a go! Cheers!
Here is one I did a while ago, this is the colored version (so not the smallest) Still under 1024bits which was my goal

<pre style="letter-spacing:8"id="A"><script>Y=200;P='o';J=' ';N=15;C='#';E='+';m=[];X=C.repeat(N+2);for(T in X)m[T]=C+J.repeat(N)+C;m[0]=m[0].slice(0,-2)+E+C;m.unshift(X);m.push(X);function s(a,b,c){return a.substring(0,b)+c+a.substring(b+1)}function f(){e=setInterval(function w(){if(!z){R=p.length-1;if(h=p[R][0],j=p[R][1],h=1==d?h-1:h,h=-1==d?h+1:h,j=2==d?j-1:j,j=-2==d?j+1:j,p.push([h,j]),g=p.length-1,l=m[h][j],r){if(J!=l)if(E==l){x=y=0;for(t++,k=1;J!=m[x][y];)with(Math)x=~~(random()N+2),y=~~(random()N+2);m[x]=s(m[x],y,E);Y-=5}else z=1,P='x';!k&&t<g&&(b=p[g-t],m[b[0]]=s(m[b[0]],b[1],J)),k=0}for(a='',i=0;i<t;i++)i<g+1&&(b=p[g-i],m[b[0]]=s(m[b[0]],b[1],P));for(b in m){for(K in m[b])H=m[b][K],a+=H.fontcolor(/\#/.test(H)&&"#03B"||/\w/.test(H)&&"#2B2");a+="\n"}A.innerHTML=a}return w}(),Y)}t=3,p=[[1,1]];var i,z,r,k,d;f(),window.addEventListener("keydown",function(k){if(c=k.keyCode,-1!=[37,38,39,40].indexOf(c))with(Math)D=1==abs(39-c)?39-c:2*(38-c),z||abs(D)!=abs(d)&&(d=D,clearInterval(e),f(),r=1)});</script>

If you want to make clickable links with this code, you can use URL Pages.

https://github.com/jstrieb/urlpages

It's a small JavaScript application to store entire web pages in URLs. I made it after learning that links with data URLs are no longer clickable.

(comment deleted)
My initial interpretation of the title was something that _displays_ the game inside the URL, using Unicode block characters.
I think it was changed by mods unfortunately :/
Can't update the post now, but I've actually got it down to 713 bytes, which I'm pretty happy with! I could remove features/colors/styles at this point, but I think I'll leave it there for now.

If anyone's interested, I've tried to annotate a fair amount of the tricks I've used so far: https://github.com/lukebatchelor/url-snake/blob/main/golf/de...

let e=document.body,t="Space: start/reset. Arrows: Move<div><canvas id=a><style>#a{border:solid}{background:tan}</style>",r=40,n=400,o,d=[[4,3]],s=d[0],c="#000",p=(e="red",t=n,a=0,l=a)=>{C[f="fillStyle"]=e,C.fillRect(ar,lr,t,t)},h=(e,t=n/2,a=t,l="center")=>{C[f]=c,C.textAlign=l,C.fillText(e,t,a)};e.innerHTML=t,C=a.getContext`2d`,a.height=a.width=n,C.font="30px f",e.onkeyup=({which:e})=>{36<e&&e<41?H=e-38:32!=e||o||(o=1,d=[[4,3]],H=2,S=0)},setInterval(i=>{if(o){let[e,t]=d[0],a=e+H%2,l=t+(H-1)%2,n=([e,t])=>e==a&&t==l;if(d.pop(),a<0||9<a||l<0||9<l||d.some(n))return o=0,p(),h`:(`;d=[[a,l],...d],n(s)&&(d[++S]=d[S-1],s=[(D=new Date)%10,DS%10]),p`tan`,d.map(e=>p(c,r,...e)),p(i,r,...s),h(S,9,30,"left")}},n);

Golfed to 697 bytes using a self-extracting webp/html polyglot:

    data:text/html;base64,UklGRhgCAABXRUJQVlA4TAwCAAAvygIAAIU019bbNspSFKWR9hdFZaqtb3rvM84/Q4AZ8bMlhCEF6nODpdXLmTVE9J9A0qbBtj+FIQ4ItSvXD2Q5unN6D4zhrFElTYKWlecbTy1xFLz13m3bSfDTbSjX9eZVXiq7UW1Qa1Sv8pb3hl6dqcOd85r8pHWm1v3ocKfK+5V3a6snrGyf3xwDhuDxSQwWn8QxONA4nz+BrCigRT2PCygxPIvjOIQGBWHoSYfAaEFhDAaVxFeH9/MlhsvamNlgEhZI8D4aOP5RyUKNPJiRBwaWPVQoaOC+SUEhg8GwJMvkw0cCBZbwPmLa8VtTryyaRwq3tOOBGJTspxTV1pL/cvvzBzK8RxWtiN87O+AuUr0AFVVUrypGFW1rzRXaAd9ZxjCLm12wDIEiZ+9pv25QHLZVXVYT6gf37FlOl5eUP0lef0G6zl5MsvQUqetc1wmHyX+X+YIpzDCWPbTEX4d6G2VEja8O9VI4eTDEcwIujgdUSOMvFykY5LH4cp3IixQsiiMyia8IUV1eMqKZ1kuho8Y1QoLK4657mauuM8cEput01LoHElZKT7z2NnAYwwBZLSZiMR26KDAFRFGkC7CilZeXQs/H49lQYHadDJ+aiw9oaRt8UEzyIonhw2h2kcSFhGbByi5ARw+qEYSvGlGCHyRISmhEfQzRSqjEDF5CFkNoaMmh7HuwcnpyAjxjYW52YXMgaWQ9Yz48aW1nIG9ubG9hZD13aXRoKGMuZ2V0Q29udGV4dGAyZGApZm9yKHA9ZT0nJztkcmF3SW1hZ2UodGhpcyxwLS0sMCksdD1nZXRJbWFnZURhdGEoMCwwLDEsMSkuZGF0YVswXTspZSs9U3RyaW5nLmZyb21DaGFyQ29kZSh0KTtldmFsKGUpIHNyYz0jPg==
(954 bytes of encoded URL)

Based on the technique used here: https://gist.github.com/gasman/2560551

Edit: hm, at some point in my golfing I appear to have broken the apple placement randomisation logic... I'm too lazy to debug it right now (probably)

But sometimes the new red element to eat is being placed on the snake. Easier to spot after level 25.
Nice! I made a 250-byte Snake game (265 as a URL) a few years ago after spending far too long code-golfing it:

https://danielgjackson.github.io/tinyjs#snake

...and even a 224 byte version (240 as a URL), but with a few too many sacrifices.

Some interesting features of this snake game:

1. You can change directions multiple times within a tick, making the last direction to be effective.

2. You can change to any directions, including the opposite direction of your current movement, which usually results in :( as the snakes head bumps into its body...

3. Except when the snake has length 1 or 2, then it's OK, as the snake either does not have a body yet, or its body and head switch places at the next tick.

As a combination of 1 and 2, quickly turning around is a risky move.