Snake game in a data: URI
bytex64.net
bytex64.net
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. :] --- 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);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.
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.
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.
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> 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> data:text/html,<pre contenteditable>(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.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! :-)
that's 11 bytes shorter
My phone kept (mis)reading UPC-E values from the XL QR code!
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
Edit: oh, and please consider it a pull request. :)
Thanks, biot, you are officially awesome!
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.
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...
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!
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>
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).