Simple water simulation based on cellular automata. Move mouse to make ripples. Fast in latest Chrome and Opera, slow in Safari 5, almost inert in Firefox 3.6.
var c=document.body.children[0],d=c.getContext('2d'),w=100,p=5E3,i,x,y,u,v,t=[],m=[],n=[],F=Math.floor,R=Math.random;d.globalAlpha=0.8;for(i=-1;++i<p;){m[i]=n[i]=0;t[i]=9*(i%2E3<1E3?i%20<10:i%20>9)}setInterval(function(){for(i=-1;++i<p;){n[i]=(m[(p+i-1)%p]+m[(i+1)%p]+m[(p+i-w)%p]+m[(i+w)%p]>>1)-n[i];n[i]-=n[i]>>5}for(i=-1;++i<p;){x=m[(p+i-1)%p]-m[(i+1)%p]>>9;y=m[(p+i-w)%p]-m[(i+w)%p]>>9;u=(x<0?8:x>7?15:x+8).toString(16);v=t[((w*y)+x+i+p)%p];d.fillStyle='#'+u+v+u+'ff'+v;d.fillRect(i%w*3,F(i/w)*3,3,3)}x=m;m=n;n=x},20);function W(z){m[z+1]=m[z-1]=m[z+w]=m[z-w]=m[z]-=p}c.onmousemove=function(e){W(F(e.offsetX/3)+w*F(e.offsetY/3))};(function X(){W(F(R()*p));setTimeout(X,F(R()*p))})()
dmFyIGM9ZG9jdW1lbnQuYm9keS5jaGlsZHJlblswXSxkPWMuZ2V0Q29udGV4dCgnMmQnKSx3PTEwMCxwPTVFMyxpLHgseSx1LHYsdD1bXSxtPVtdLG49W10sRj1NYXRoLmZsb29yLFI9TWF0aC5yYW5kb207ZC5nbG9iYWxBbHBoYT0wLjg7Zm9yKGk9LTE7KytpPHA7KXttW2ldPW5baV09MDt0W2ldPTkqKGklMkUzPDFFMz9pJTIwPDEwOmklMjA+OSl9c2V0SW50ZXJ2YWwoZnVuY3Rpb24oKXtmb3IoaT0tMTsrK2k8cDspe25baV09KG1bKHAraS0xKSVwXSttWyhpKzEpJXBdK21bKHAraS13KSVwXSttWyhpK3cpJXBdPj4xKS1uW2ldO25baV0tPW5baV0+PjV9Zm9yKGk9LTE7KytpPHA7KXt4PW1bKHAraS0xKSVwXS1tWyhpKzEpJXBdPj45O3k9bVsocCtpLXcpJXBdLW1bKGkrdyklcF0+Pjk7dT0oeDwwPzg6eD43PzE1OngrOCkudG9TdHJpbmcoMTYpO3Y9dFsoKHcqeSkreCtpK3ApJXBdO2QuZmlsbFN0eWxlPScjJyt1K3YrdSsnZmYnK3Y7ZC5maWxsUmVjdChpJXcqMyxGKGkvdykqMywzLDMpfXg9bTttPW47bj14fSwyMCk7ZnVuY3Rpb24gVyh6KXttW3orMV09bVt6LTFdPW1beit3XT1tW3otd109bVt6XS09cH1jLm9ubW91c2Vtb3ZlPWZ1bmN0aW9uKGUpe1coRihlLm9mZnNldFgvMykrdypGKGUub2Zmc2V0WS8zKSl9OyhmdW5jdGlvbiBYKCl7VyhGKFIoKSpwKSk7c2V0VGltZW91dChYLEYoUigpKnApKX0pKCk=
// Simple cellular-automata based water simulation,
// http://iobound.com/2010/08/js1k/
// Based on Hugo Elias' algorithm at:
// http://freespace.virgin.net/hugo.elias/graphics/x_water.htm
// Variable lookup:
//
// c - Canvas
// d - 2D Context
// w - Pixels across (150)
// p - Total pixels (150*100)
// m - First water buffer
// n - Second water buffer
// t - Texture buffer
// x,y,u,v - Temp vars
// Function lookup:
//
// W - Disturb water
// F - Math.floor
// R - Math.random
// To compress (hat tip to Paul Hammond, http://phmmnd.me/1k):
// perl -p -0 -e 's!(//[^\n]*)?\n\s*!!g;s!;}!}!g;s!;+!;!g' <script.js >compressed.js
// Initialise variables
var c=document.body.children[0],
d=c.getContext('2d'),
w=100,
p=5E3,
i,x,y,u,v,t=[],m=[],n=[],
F=Math.floor,
R=Math.random;
// Use alpha to take edge off pixellyness
d.globalAlpha=0.8;
// Clear water buffers.
// Initialise background checkerboard texture.
for(i=-1;++i<p;){
m[i]=n[i]=0;
t[i]=9*(i%2E3<1E3?i%20<10:i%20>9)
}
// Tick the simulation and render
setInterval(function(){
// Update water height map - see algorithm for explanation
for(i=-1;++i<p;){
// Calculate new value based on previous 2 frames
n[i]=(m[(p+i-1)%p]+
m[(i+1)%p]+
m[(p+i-w)%p]+
m[(i+w)%p]>>1)-n[i];
// Apply damping
n[i]-=n[i]>>5
}
// Render pixels
for(i=-1;++i<p;){
// Get X and Y wave gradient by comparing height with neighbours
x=m[(p+i-1)%p]-m[(i+1)%p]>>9;
y=m[(p+i-w)%p]-m[(i+w)%p]>>9;
// Use X gradient to pick a reflection map value
// (Fake environment map)
u=(x<0?8:x>7?15:x+8).toString(16);
// Use X and Y gradients as offsets into texture array
// (Fake refraction)
v=t[((w*y)+x+i+p)%p];
// Use these components to make a bluish hex value
d.fillStyle='#'+u+v+u+'ff'+v;
// Draw a 3x3 pixel
d.fillRect(i%w*3,F(i/w)*3,3,3)
}
// Swap water buffers over
x=m;
m=n;
n=x
},20);
// Disturb water at position z
function W(z){
// Perturb a cross of pixels centered on z
m[z+1]=m[z-1]=m[z+w]=m[z-w]=m[z]-=p
}
// Register mouse handler to stir water
c.onmousemove=function(e){
W(F(e.offsetX/3)+w*F(e.offsetY/3))
};
// Make it rain
(function X(){
W(F(R()*p));
setTimeout(X,F(R()*p))
})()