Left, right and down arrow keys to move. Z and X to rotate clockwise and anti-clockwise
for(h=' `+v)=>zk+$H]<<GforK=0;J})(Q.fillPcP__Style=`^^#002ZT(x,yY,k*l+X|(e&1W==A){=new q==_Rect(n,l-2*},99%,>>=1;k--0)(L/10|,m=g[k],0],[{^hsl(${0,,24=h=>);g.splice(D[k-yGxK(k=i*l+ &1)W))=(a,d,EztImageData(;_Text(`if(,76.reduce((e,n,bze nXnl=21,$=D=N=y=C=q=x=I=V=L=S=gArray(l)P(A=65520(F{I=V,VDate%7,D=N,N=[[1,63,62,3],[4+9,0]][V]Qonkeydownq=h.which-38(R{y<NJ20|=()ml,l*k,10*l,l$++}setTimeout(h=>{2$=0;)g[H]H,132784k)c.puc.ge0(H)*l),lS+=++$*10L++;}$>4)L=SJF(x=9,y=-3,$=},200*($>)QTg+(n&(E[b-dGa)),(f{!$){l;)K(i=16,E=,e=N[k-4G1;n=i--;m,E,e)240+k*312%)`;!(mn=i>0&i<11&k<20W!n|(E48*(i<11?I:V)${!n*50+50}%)`;Z4`; 4X4,16*n,16)}}Z`SCOREvS2LINESvL4LEVELv602)CJ1||!~q)x=T(a=x-q,y,D)?x:a;D=Y,e=O(D,50?3:52?1:)?D:e;n=perKmance.now(q=0n-C>500-*4Y+1,D)?R():C=n,y++}requestAnimationFrame(f)QOd>0?O(a.map((l,kza<<1|n>>k&1,),d-1):a)';G=/[-W-Z^_PQJKGHzv]/.exec(h);)with(h.split(G))h=join(shift());eval(h)
Zm9yKGg9JyBgK3YpPT56ayskSF08PEdmb3JLPTA7Sn0pKFEuZmlsbFBjUF9fU3R5bGU9YF5eIzAwMlpUKHgseVksaypsK1h8KGUmMVc9PUEpex89bmV3IB5xPT0dX1JlY3QoHG4sbC0yKht9LDk5JSwaPj49MRk7ay0tGDApFyhMLzEwfBcWLG09Z1trXRUsMF0sWxR7XmhzbCgkexMwLBIsMjQSET1oPT4QKTsPZy5zcGxpY2UoDkRbay15R3gMSyhrPQscaSpsKwkmMSlXKSkIPShhLGQsRXoHdEltYWdlRGF0YSgGO19UZXh0KGAFaWYoBCw3FBI2Ay5yZWR1Y2UoKGUsbixiemUCCW5YGxtuDwFsPTIxLCQ9RD1OPXk9Qz1xPXg9ST1WPUw9Uz0SZx5BcnJheShsKVAoQT02NTUyMA8oRhB7ST1WLFYeRGF0ZSU3LEQ9TixOPVtbMQMsNhQzLDYUMgMsM10sWzQDKzksEjBdXVtWXVFvbmtleWRvd24QcT1oLndoaWNoLTM4DyhSEHsEeTwXTkoLMjAYFXw9KAwPKQRtHxxsLGwqaywxMCpsLGwPJCsrfXNldFRpbWVvdXQoaD0+ewsyEiQ9MBg7KQRnW0hdHw5ILDEPDhISMzI3ODQPBGspYy5wdQZjLmdlBhIwEShIKSpsKSwSbA9TKz0rKyQqMTASTCsrO30EJD40KUw9U0pGKHg9OSx5PS0zLCQ9F30sMjAwKigkPhcpUVQHZwIrKG4mKEVbYi1kR2EpKSwXDyhmEHsEISQpewtsGDspSyhpPTE2FSxFPQwsZT1OW2stNEcxO249EmktLTttGSxFGSxlGSkTMjQwK2sqMxoxMiUpYDsEIShtCAFuPWk+MCZpPDExJms8MjBXDwQhbnwoRQgTNDgqKGk8MTE/STpWKRokeyFuKjUwKzUwfSUpYDsBWjRgOwk0WDQsMTYqbiwxNil9fVpgBVNDT1JFdlMRMhcFTElORVN2TBE0FwVMRVZFTHYWETYwDwQdMilDSgQdMXx8IX5xKXg9VChhPXgtcSx5LEQpP3g6YTtEPVksZT1PKEQsHTUwPzM6HTUyPzE6Fyk/RDplO249cGVyS21hbmNlLm5vdyhxPTAPBG4tQz41MDAtFio0F1krMSxEKT9SKCk6Qz1uLHkrK31yZXF1ZXN0QW5pbWF0aW9uRnJhbWUoZilRTwdkPjA/TyhhLm1hcCgobCxremECPDwxfG4+PmsmMSwXKSxkLTEpOmEpJztHPS9bAS0fVy1aXl9QUUpLR0h6dl0vLmV4ZWMoaCk7KXdpdGgoaC5zcGxpdChHKSloPWpvaW4oc2hpZnQoKSk7ZXZhbChoKQ==
// A full-colour, fully featured Tetris clone
// I wasn't aware of the other Tetris clones from previous years until just before submission
// It's been interesting to learn what we did the same and differently!
// INITIALISE VARIABLES
// x,y = co-ordinates of active piece
// P,N = current and next piece
// W,V = indices of current and next pieces
// L = line count
// S = score
// q = keyboard input
// M = last frame time
// g = game grid
// B = no of lines being cleared (used to pause game)
j=21,B=P=N=y=M=q=x=W=V=L=S=0,g=new Array(j).fill(H=65520);
// SET THE CURRENT PIECE TO THE NEXT PIECE, GENERATE A NEW PIECE
// Pieces are represented as square bit arrays, stored as decimal values
// The "6+9" saves a few bits during compression by creating a longer repeated string
(U=_=>{W=V,V=new Date%7,P=N,N=[[1,7,0],[0,6,6,0],[3,6,0],[2,7,0],[0,6,3],[4,7,0],[0,6+9,0,0]][V]})
// CHECK FOR KEY PRESSES
(onkeydown=_=>q=_.which-38);
// CURRENT PIECE HAS HIT THE BOTTOM
(R=_=>{
// TEST FOR GAME OVER
if(y<0)N=0;
// LOCK PIECE
// The game grid is combined with the current piece
// Note: the cleared lines animation is incredibly expensive (the game is about ~950 bytes without it)
// I prefer to include it to more clearly highlight the line clears though
for(k=20;k--,v=g[k]|=(P[k-y]<<x);)if(v==H){c.fillRect(j,j*k,10*j,j);B++}
// CLEAR COMPLETED LINES
// Two things are happening here:
// 1. The game grid is updated by removing completed lines and padding the top with new blank lines
// 2. The rows above each completed row are copied and pasted one row down
// This allows me to preserve the block colours, while storing each square as a single bit
setTimeout(_=>{
for(k=20,B=0;k--;)
if(g[k+B]==H){
g.splice(k+B,1);
g.splice(0,0,32784);
if(k)c.putImageData(c.getImageData(0,0,240,(k+B)*j),0,j);
S+=++B*100,L++;
}
// THIS RESETS THE STATS AFTER THE INITIAL LOAD
if(B>4)L=S=0;
// GENERATE THE NEXT PIECE
U(x=9,y=-3,B=0)
},200*(B>0))
})
// COLLISION DETECTION
// X,Y = Piece test co-ordinates
// Q = The piece to be tested
// This is a bit-wise check for overlaps between the piece and the game grid
(T=(X,Y,Q)=>g.reduce((e,z,b)=>e+(z&(Q[b-Y]<<X)),0));
// MAIN GAME LOOP
(Z=_=>{
// RENDER GAME GRID
// The current piece, next piece and game grid are all bit-shifted
if(!B){
for(k=j;k--;)
for(i=16,v=g[k],Q=P[k-y]<<x,e=N[k-4]<<1;z=0,i--;v>>=1,Q>>=1,e>>=1)
{
// Background gradient
c.fillStyle=`hsl(${240+k*3},99%,12%)`;
// Don't draw over locked blocks (as we don't store their colours)
if(!(v&1)|(e&1))c.fillRect(i*j+z,k*j+z,j-2*z,j-2*z);
// Test for squares that should be white
z=i>0&i<11&k<20|(e&1);
if(!z|(Q&1)|(e&1)){
// Colour the current and next pieces based on their index
c.fillStyle=`hsl(${48*(i<11?W:V)},99%,${!z*50+50}%)`;
c.fillRect(i*j+z,k*j+z,j-2*z,j-2*z);
// Shading
c.fillStyle=`#0024`;
c.fillRect(i*j+4,k*j+4,16*z,16)
}
}
// GAME STATS
c.fillStyle=`#002`;
c.fillText(`SCORE `+S,240,20);
c.fillText(`LINES `+L,240,40);
c.fillText(`LEVEL `+(L/10|0),240,60);
// CONTROLS
// Down
if(q==2)M=0;
// Left / Right
if(q==1||!~q)x=T(X=x-q,y,P)?x:X;
// Z / X (rotate CW/ACW)
P=T(x,y,e=O(P,q==50?3:q==52?1:0))?P:e;
// DROP PIECE
// The speed adjusts according to the level
z=performance.now(q=0);
if(z-M>500-(L/10|0)*40)T(x,y+1,P)?R():M=z,y++
}
requestAnimationFrame(Z)})
// ROTATE PIECE
// Recursive function that rotates a piece Y times (either 1 or 3 times)
(O=(X,Y,Q)=>Y>0?O(X.map((j,k)=>X.reduce((e,z,b)=>e<<1|z>>k&1,0)),Y-1):X)