Tyler Robertson

Tetris in a Bookmark

Sunday, February 23, 2025

Running code directly in the address bar is something I've played around with before, but Adam Le Doux's new collection of bookmarklets inspired me to dig back into making the tiniest little apps I can muster. Which, after a long day of fighting against huge corporate monorepos, is a delightfully relaxing craft. My contribution to the genre is a game of Tetris (my go-to commute game right now), which runs entirely from the browser address bar. To play it, you can copy/paste the code below into your browser of choice (I tried to get it to work in a link but the encoding is weird 🤷🏻‍♂️). When it runs, use the WASD keys on your keyboard to play!

data:text/html,<html><head><title>bookmarktris</title><style>body{text-align:center;background:black;}</style></head><body><canvas width='150'height='300'/><script>let can=document.querySelector('canvas'),q=can.getContext('2d'),col=['white','blue','black','darkgrey'],j=(x)=>JSON.parse(JSON.stringify(x)),dM=new Array(15).fill(new Array(30).fill(0)),m=j(dM),s=0,sh=[[[[1,1],[1,1]]],[[[1,1,1,1]],[[0,1],[0,1],[0,1],[0,1]]],[[[1,0],[1,1],[1,0]],[[0,1,0],[1,1,1]],[[0,0,1],[0,1,1],[0,0,1]],[[1,1,1],[0,1,0]]],[[[0,0,1],[1,1,1]],[[1,1],[0,1],[0,1]],[[1,1,1],[1,0,0]],[[1,0],[1,0],[1,1]]],[[[1,1,1],[0,0,1]],[[1,1],[1,0],[1,0]],[[1,0,0],[1,1,1]],[[0,1],[0,1],[1,1]]],[[[1,0],[1,1],[0,1]],[[0,1,1],[1,1,0]]],[[[0,1],[1,1],[1,0]],[[1,1,0],[0,1,1]]]],nu=[[[1,1,1,1],[1,0,0,1],[1,1,1,1]],[[1,0,0,1],[1,1,1,1],[0,0,0,1]],[[1,0,1,1],[1,0,1,1],[1,1,0,1]],[[1,0,0,1],[1,1,0,1],[1,1,1,1]],[[1,1,1,0],[0,0,1,0],[1,1,1,1]],[[1,1,0,1],[1,1,0,1],[1,0,1,1]],[[1,1,1,1],[1,1,0,1],[1,0,1,1]],[[1,0,0,1],[1,0,1,0],[1,1,0,0]],[[1,1,1,1],[1,1,0,1],[1,1,1,1]],[[1,1,1,0],[1,0,1,0],[1,1,1,1]]],p={x:6,y:8,sh:j(sh[Math.floor(Math.random()*sh.length)]),r:0},N={x:11,y:2,sh:j(sh[Math.floor(Math.random()*sh.length)])},tM=()=>{let tA=j(m);for(let [oX,c] of p.sh[p.r].entries())for(let [oY,n] of c.entries())tA[p.x+oX][p.y+oY]+=n;return tA;},d=()=>{let tA=tM();let tS=s>999?999:s>=100?`${s}`:s>=10?'0'+s:'00'+s;for(let [oX,c] of nu[tS[0]].entries()){for(let [oY,n] of c.entries()){tA[0+oX][2+oY]=n*3;}}for(let [oX,c] of nu[tS[1]].entries()){for(let [oY,n] of c.entries()){tA[4+oX][2+oY]=n*3;}}for(let [oX,c] of nu[tS[2]].entries()){for(let [oY,n] of c.entries()){tA[8+oX][2+oY]=n*3;}}for(let [oX,c] of N.sh[0].entries()){for(let [oY,n] of c.entries()){tA[N.x+oX+Math.floor(N.sh[0][0].length/2)][N.y+oY+Math.floor(N.sh[0].length/2)]=n*1;}}for(let [oX,column] of tA.entries()){for(let [oY,n] of column.entries()){q.fillStyle=n==0&&oY<8?'lightgrey':col[n];q.fillRect(oX*10,oY*10,10,10);}}},vM=()=>{try{let tA=tM();let flatArray=tA.flat();if(p.x+p.sh[p.r].length>15)return false;if(flatArray.some((n)=>n>2))return false;if(flatArray.filter((n)=>n==1).length<4)return false;return true;}catch{return false;}},M=(r)=>{let t={x:p.x,y:p.y};if(r==0)p.y+=1;if(r==1)p.x-=1;if(r==2)p.x+=1;if(!vM()){p.x=t.x;p.y=t.y;}d()},k=(e)=>{if(!'wasd'.includes(e.key))return;e.preventDefault();if(e.key=='w'){t=p.r;p.r=p.r>=p.sh.length-1?0:p.r+1;if(!vM())p.r=t;d()}if(e.key=='s')M(0);if(e.key=='a')M(1);if(e.key=='d')M(2);},S,pP,T=(t)=>{if(t-S<1000/(1+Math.floor(s/50))){requestAnimationFrame(T);return;}S=t;pP={...p};M(0);if(pP.x==p.x&&pP.y==p.y&&pP.r==p.r){for(let [oX,c] of p.sh[p.r].entries())for(let [oY,n] of c.entries())m[p.x+oX][p.y+oY]+=n*2;let U=0;for(let i=0;i<30;i++){if(m.every((c)=>c[i]==2)){U+=10;for(let j=0;j<15;j++){m[j].splice(i,1);m[j].unshift(0);}}}s+=1*Math.max(1,U);p.x=6;p.y=8;p.r=0;p.sh=j(N.sh);N.sh=j(sh[Math.floor(Math.random()*sh.length)]);if(m.some((c)=>c[8]>1)){s=0;m=j(dM);}d()}requestAnimationFrame(T)},f=(t)=>{S=t;T(t)};document.addEventListener('keypress',k);d();requestAnimationFrame(f);</script>

I dabbled in making Tetris games back in my spreadsheet days, but to date I think this is my most feature-complete version. There's a couple things I could add (like music, or the ability to pause the game), and a bit of bloat that could be removed (like the big score numbers), but maybe those can be future additions if this becomes more than just a game to pick at between work tasks. At just north of 3kb it's a smidge bigger than Adam's snakelet, but still smaller than an Atari 2600 cartridge, so I'm quite pleased.

Like this post?