Hacker News new | past | comments | ask | show | jobs | submit login
Snake game in a data: URI (bytex64.net)
257 points by twodayslate on Dec 9, 2011 | hide | past | favorite | 73 comments



I'm surprised that the first nine comments didn't mention the easter egg in the lower right hand corner.


What easter egg exactly are you talking about? I wasn't able to find any, even with your hint.


I wonder if it's the link in "All content printed on 100% recycled internet memes"?


Ah, the easter egg is in the announcement page, not in the game. Thanks for clarifying!


It would have been awesome seeing this being created.. Did he start out with a large version? Did he refactor the whole thing a lot? What functional compromises did he have to make and which were the most creative ways of drastically shortening it? No matter how useful or not it is for others, its an amazing feat, I'm sure his brain got working.., well done!


If you're curious, the "readable" version is here: http://bytex64.net/code/datasnake/snake.html

I started out writing it as straightforward as possible, then applied functional tricks to slim it down. That was a lot of fun. I'm not sure how to post a code snippet here, but I'll try to illustrate one of my favorite portions.

In the original, I did key input with a switch statement. That got replaced by:

    { 37: P(-1,0), 38: P(0,-1), 39: P(1,0), 40: P(0,1) }[e.keyCode]
"But wait!" you're thinking, "What happens when e.keyCode is less than 37 or more than 40?" What happens is it evaluates to undefined, and it errors out in the M() function, effectively short-circuiting it. If you look in your browser's error log, it'll spew an error every time you hit another key. It's lazy, but it works. :]


I think you have an extra two bytes (or a bug) here:

function blocks(l) { s.map(function(p) { c.fillRect(p.x << 3, p.y << 3, 8, 8); }); }

l is ignored, so you can remove it (and the part where you pass it in from the engine). This code still works because s has global scope, but that looks like an accident.

I guess the "right" thing to do would be to rename s to l here, but when you're trying to fit stuff into 4K that's not the main concern :)

Cool program by the way, I'm impressed with how clear the code remained.


Yep, that was an accident. I've removed the parameter entirely. Nice catch!


Little bug fix, apples can appear under the snake..

   --- snake.html	2011-12-09 08:03:47.000000000 +0000
   +++ snake.html.2	2011-12-09 11:40:54.414774460 +0000
   @@ -59,7 +59,7 @@
   	var n = AP(s[0],d);
   	(OB(n) || TC(n)) && fail();
   	s.unshift(n);
   -	EQ(s[0],a) && (a = RP()) || s.pop();
   +	EQ(s[0],a) && (function() {do {a = RP()} while (TC(a))}()) || s.pop();

   	c.clearRect(0,0,128,128);
   	blocks(s);


Whoops, I see someone below beat me to it.


You should queue inputs. If you press left-down the snake should turn left and then go down on the next frame. That is, if you are attempting to recreate the classic Nokia experience, anyway.


>That is, if you are attempting to recreate the classic Nokia experience, anyway.

Wow, it is interesting that "the reference" for the snake game is the one in a Nokia mobile phone. The reference for me is the BASIC implementation that (I think) came with a version of Microsoft DOS some time ago.


There was a version called "Nibbles" that came with QBasic (Which in turn came with DOS 5). It also had another artillery style game called Gorillaz.

I guess the nokia implementation is considered the reference because it had the install base and must have wasted hundreds of thousands of hours worldwide.

IIRC the DOS version was pretty much unknown unless you happened to scour your disk for .BAS files.


Heh, yeah the Qbasic one was good, I remember extending it to have powerups, 23 levels and 4 player ...


The Wikipedia page for this is interesting: http://en.wikipedia.org/wiki/Snake_%28video_game%29 Wikipedia traces it back to "Blockade" in 1976.


And the reference for me is the version that was bundled with 4.0 BSD in 1980 (also the distribution that introduced Zork)


Aah, the time I wasted on that one and the Gorilla game. It's a bit odd given how advanced our current games are.


It's kind of like how the reference for tetris for many people is the gameboy version.


I thought about it. It's been so long since I've played Nokia snake that I didn't remember whether it queued inputs or not. I decided not to bother since it would make input handling more complicated.


Please do it! This is how (I'm showing my age now) it worked in DOS all those years ago.


Off-topic: what happened to your brisy.info/colors ??? I enjoyed that quite a lot, had it bookmarked and used it as my homepage. It's gone as of a few days ago and now the site just redirects to Github.


Nice code! There are 4 functions that can be collapsed to save some space, since they're only called once. Also assigned document.body & setTimeout to vars. Brings the size from 1090 to 966...nice to be less than 1kb!

  data:text/html,<script>S=setTimeout;function f(){do z={x:Math.floor(16*Math.random()),y:Math.floor(16*Math.random())};while(g(z));return z}function h(b,e){return b.x==e.x&&b.y==e.y}function g(b){return s.filter(h.bind(this,b)).length}function i(){s.map(function(b){c.fillRect(b.x<<3,b.y<<3,8,8)})}function j(){d=m;var b={x:s[0].x+d.x,y:s[0].y+d.y},e;if(e=0>b.x||15<b.x||0>b.y||15<b.y||g(b))c.fillStyle="red",i(),S(k,5E3),e=1;e||S(j,350-5*s.length);s.unshift(b);h(s[0],a)&&(a=f())||s.pop();c.clearRect(0,0,128,128);i();c.beginPath();c.arc((a.x<<3)+4,(a.y<<3)+4,4,0,7);c.fill()}window.addEventListener("keydown",function(b){b={37:{x:-1,y:0},38:{x:0,y:-1},39:{x:1,y:0},40:{x:0,y:1}}[b.keyCode];b.x!=-d.x&&b.y!=-d.y&&(m=b)},!1);function k(){d=document.body;d.innerHTML='<canvas width=128 height=128 style="width:256px;height:256px;border:8px solid black"></canvas>';c=d.firstChild.getContext("2d");s=[{x:8,y:8},{x:7,y:8},{x:6,y:8}];a=f();m={x:1,y:0};j()}S(k,0);</script>


You can save 6 bytes by switching up the bounds checking. From "0>b.x||15<b.x||0>b.y||15<b.y" to "(b.x^16)<1||(b.y^16)<1".

  data:text/html,<script>S=setTimeout;function f(){do z={x:Math.floor(16*Math.random()),y:Math.floor(16*Math.random())};while(g(z));return z}function h(b,e){return b.x==e.x&&b.y==e.y}function g(b){return s.filter(h.bind(this,b)).length}function i(){s.map(function(b){c.fillRect(b.x<<3,b.y<<3,8,8)})}function j(){d=m;var b={x:s[0].x+d.x,y:s[0].y+d.y},e;if(e=(b.x^16)<1||(b.y^16)<1||g(b))c.fillStyle="red",i(),S(k,5E3),e=1;e||S(j,350-5*s.length);s.unshift(b);h(s[0],a)&&(a=f())||s.pop();c.clearRect(0,0,128,128);i();c.beginPath();c.arc((a.x<<3)+4,(a.y<<3)+4,4,0,7);c.fill()}window.addEventListener("keydown",function(b){b={37:{x:-1,y:0},38:{x:0,y:-1},39:{x:1,y:0},40:{x:0,y:1}}[b.keyCode];b.x!=-d.x&&b.y!=-d.y&&(m=b)},!1);function k(){d=document.body;d.innerHTML='<canvas width=128 height=128 style="width:256px;height:256px;border:8px solid black"></canvas>';c=d.firstChild.getContext("2d");s=[{x:8,y:8},{x:7,y:8},{x:6,y:8}];a=f();m={x:1,y:0};j()}S(k,0);</script>


Aw, doesn't work in Opera. But a great idea. This reminds me of the time when I was young and worked in a small IT department in a local company. Most of the time it was boring, but installing games on the machines was forbidden. So I coded a set of Excel Macros to play Snake on an Excel cheat. Good times, we even had an internal high score list right in there; half the department joined :)


That's weird. I developed it in Opera.


I'm using 11.52. It says: "Uncaught exception: TypeError: 'Q.bind' is not a function Error thrown at line 1, column 516"


Ah, I used 11.60. It just came out a few days ago, and it may have added bind(). I seem to recall they added some ES5 features in this release. Go upgrade! :]


Thanks! Too many update cycles these days ;)


The most interesting thing about this is you can use plain text in data URIs. For some reason I assumed it had to be base64 encoded, but apparently only for binary data.


I thought the same thing until I watched a talk a few weeks ago about HTML5, and the guy demonstrated the world's smallest text editor:

  data:text/html,<pre contenteditable>


Doesn't work for me, but data:text/html,<pre contenteditable>Hello</pre> works for me.


If the Hello version works, the smaller version should work too. The <pre> will be 1-line high on HTML5 browsers, not window high, so you have to click the very top of the page body for it to focus.

(If that still doesn't work, out of curiosity, what browser are you using?)


    data:text/html,<pre contenteditable/>
Works in Chrome 15 without content but Firefox requires content.


Oh! Looks like we found a most unexciting difference in Fx's and Chrome's HTML5 rendering engines. Someone alert the authorities!

What's happening is in fact a little bit more complex than that. Both browsers create the pre element (you can verify that by typing that URI and hitting the tab key to switch focus from the address bar into it).

Now, where Chrome renders a line-sized pre element even when it's empty, Firefox renders it as a 0px element until you feed text into it (that's probably its standard behaviour for pre elements without a child text node, but I can't be bothered verifying that).

Boy, I love getting a rush out of figuring out something completely irrelevant! :-)


pshaw, try data:text/html,<textarea>

that's 11 bytes shorter


I tried putting this in a QR code and reading it with my phone. Neadless to say, an XL QR code can't be scanned by my old Android 2 MP phone camera.


That's interesting.

My phone kept (mis)reading UPC-E values from the XL QR code!


Yeah looks like the "Barcode Scanner" app doesn't like a QR code with multiple square nodes in it, but needs three good corners or so.


Just tested it on an iPhone 3 (using an app merely called "Scan") — no reaction whatsoever.


Weird you guys are having problems because it scans perfectly using the standard barcode scanner app on my Desire S (running MIUI): http://www.armyofcrabs.com/qrsnake.png


RL Classic on iPhone 4S correctly scanned the code, but did not launch a browser. Tried on iPad 2 last night and safari didn't render it properly, anyway.


This is awesome. Please HN, give us more hacks like this instead of yet another "X in Pure CSS3/WebGL".



Based on this, I made Shit Snake[1] in a data: URI: http://philh.net/datashitsnake/

[1] http://www.draknek.org/games/shitsnake/


Shouldn't the snake speed up as it gets longer? I played for a while but didn't notice.


Here you go. Starts out at a slightly quicker pace and gets gradually more challenging as the length increases.

  data:text/html,<script>function P(x,y){return{x:x,y:y}}function J(){return Math.floor(Math.random()*16)}function R(){z=s[0];while(T(z)){z={x:J(),y:J()};}return z;}function A(a,b){return{x:a.x+b.x,y:a.y+b.y}}function Q(a,b){return a.x==b.x&&a.y==b.y}function O(p){return p.x<0||p.x>15||p.y<0||p.y>15}function T(p){return s.filter(Q.bind(this,p)).length}function M(p){if(p.x!=-d.x&&p.y!=-d.y)m=p}function B(l){s.map(function(p){c.fillRect(p.x<<3,p.y<<3,8,8)})}function C(p){c.beginPath();c.arc((p.x<<3)+4,(p.y<<3)+4,4,0,7);c.fill()}function F(){c.fillStyle='rgb(200,0,0)';B(s);clearInterval(t);setTimeout(I,5000);}window.addEventListener('keydown',function(e){M({37:P(-1,0),38:P(0,-1),39:P(1,0),40:P(0,1)}[e.keyCode])},false);function I(){document.body.innerHTML='<canvas width=128 height=128 style="width:256px;height:256px;border:8px solid black"></canvas>';ww=0;c=document.body.firstChild.getContext('2d');c.fillStyle='rgb(0,0,0)';s=[P(8,8),P(7,8),P(6,8)];a=R();m=P(1,0);v=function(){clearInterval(t);t=setInterval(v,350-s.length);d=m;n=A(s[0],d);(O(n)||T(n))&&F();s.unshift(n);Q(s[0],a)&&(a=R())||s.pop();c.clearRect(0,0,128,128);B(s);C(a);};t=setInterval(v,250);}setTimeout(I,0)</script>
I also removed the annoying alert if you hit something.

Edit: also updated to prevent new dot spawning under tail and turn the snake red when you die

Clickable: http://preview.tinyurl.com/biotsnake


Very nice. I also see you got a few other tricks that I missed, like factoring out the random number generator. I am impressed.


Thanks. I really like your use of map and filter to do the drawing and hit testing!

Edit: oh, and please consider it a pull request. :)


I've merged in your changes and made a few improvements, too. I streamlined the timeout handling so that it uses setTimeout instead of clearing and resetting setInterval. I also used color literals instead of CSS rgb(). I removed the reset to black in I() since recreating the canvas context does that automatically.

Thanks, biot, you are officially awesome!


Of course! It's been a while since I've done much javascript and I had forgotten about setTimeout. And your do/while loop in the R() function is much cleaner than my hack. :)

One thing I noticed is that you're setting the timeout value to 350-s.length*5. As the grid is 16x16, the maximum snake length will be 256. Multiplying the length by 5 will soon make the game humanly unplayable somewhere around a length of 60.


Re-reading my comment, it's ambiguous what the "of course" refers to. What I meant: of course I should have used setTimeout rather than the lamer setInterval/clearInterval method.


Sure thing. What was ww=0; trying to do? It seems to be the only place ww is referenced.


Prior to implementing the s.length-based speed increase, I tried implementing a funkier frame-based gradual increase using a log scale. Ultimately, that proved to be not worth the effort as speeding it up in proportion to how many pellets were eaten was a more natural fit. So that variable is a leftover and should have been deleted.


it grew for me.


Fantastic work, very impressed!

Far more interesting start to the day than when I woke up the other day dreaming of how to get culture codes into my .net application for a foreign website we are building...


Nice job. One note: dots can spawn underneath the snake.


I was so confused thinking to myself "Where's the next dot?!?!"


> "CHIP. CHIP, WAKE UP. YOU KNOW WHAT WOULD BE TOTALLY BITCHIN'? A VERSION OF SNAKE THAT COULD FIT IN A URI!"

Sorry to Melvin all over this comment, but technically, anything can fit in a data: URL. On its own, it’s not much of an accomplishment.

OTOH, a fully functional snake game that fits in ~1KB is pretty impressive. Kudos to you, sir!


I read somewhere that most browsers have a practical limit to what you can paste in. What I should have written was that it would fit in an address bar. shrug


I agree, RFC2397 shows a bunch of things that can limit the length of a data URL.

(http://www.ietf.org/rfc/rfc2397)


    The "data:" URL scheme is only useful for short values. Note that
    some applications that use URLs may impose a length limit; for
    example, URLs embedded within <A> anchors in HTML have a length limit
    determined by the SGML declaration for HTML [RFC1866]. The LITLEN
    (1024) limits the number of characters which can appear in a single
    attribute value literal, the ATTSPLEN (2100) limits the sum of all
    lengths of all attribute value specifications which appear in a tag,
    and the TAGLEN (2100) limits the overall length of a tag.
These are references to (theoretical) limitations in HTML/SGML. They are not limitations of the data URL scheme.

Also note that bytex64’s page doesn’t use a link; it just shows the data URL so you can copy and paste it. Either way, none of the above limitations apply here.

</nitpick>


afaik it's usually about 4k but more in modern browsers.


Where does the "CHIP. CHIP, WAKE UP" quote come from. I recognise it, but I can't recall why.


It's a reference to the beginning of The Matrix.


TinyURL allows one to create http://tinyurl.com/7q3en34 -- but, alas, Chrome refuses to follow the redirect. Safari and Firefox don't seem to mind, though.


it works in Chrome after refresh.


Hack of the day!


Nice! Just wish there was some kind of score counter...


Snake length is a score counter. :).


sleep() in javascript. just feel it's absence.


Nice work!


I don't know if I should have just been more patient waiting for levels, but this is way too slow.

If you guys want a playable experience, replace the literal 500 with 100 in the URL (careful using search and replace, there's also a 5000 literal, that's not the one I'm talking about).


This is insane




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: