script.js
author Eugen Sawin <sawine@me73.com>
Fri, 06 Jan 2012 17:20:14 +0100
changeset 87 c19fea1cd73f
parent 85 e78c4a33e958
child 88 9c66e0a69a07
permissions -rw-r--r--
Switched index page to game of life.
     1 var QUOTES_NUMBER = 39;
     2 var SERVER = "http://" + window.location.hostname + ":" + window.location.port;
     3 var simulation;
     4 var gameoflife;
     5 
     6 $(document).ready(function() {   
     7     simulation = new Simulation(document.getElementById("sim"), 
     8                                 670, 50, 6, green, background);  
     9     var image = new Image();
    10     image.onload = init_logo;
    11     image.src = "images/logobase.png";
    12     $("#logo").click(handle_click);   
    13     $("#logo").hover(function() {
    14         $(this).css("cursor", "pointer");        
    15     }, function() {
    16         $(this).css("cursor", "auto");
    17     });
    18     $("#sim").mousemove(function(event) { simulation.mouse_moved(mouse_pos(event, "#sim")); });
    19     var onEachFrame;
    20     if (window.webkitRequestAnimationFrame) {
    21         onEachFrame = function(cb) {
    22             var _cb = function() { cb(); webkitRequestAnimationFrame(_cb); }
    23             _cb();
    24         };
    25     } else if (window.mozRequestAnimationFrame) {
    26         onEachFrame = function(cb) {
    27             var _cb = function() { cb(); mozRequestAnimationFrame(_cb); }
    28             _cb();
    29         };
    30     } else {
    31         onEachFrame = function(cb) {
    32             setInterval(cb, 1000 / 30);
    33         }
    34     }    
    35     window.onEachFrame = onEachFrame;   
    36     window.onEachFrame(update);  
    37     var pos = document.location.href.lastIndexOf("/") + 1; 
    38     var page = document.location.href.substr(pos);
    39     if (page == "mandelbrot.html") {
    40         draw(0, 0, 0, 0, 0);
    41     } else if (page == "" || page == "index.html") {
    42         init_gameoflife("ME73");
    43     }    
    44 });
    45 
    46 $(document).keypress(function(event) {
    47     if (event.which == 13) {      
    48 	event.preventDefault();
    49         update_logo();	    
    50     }
    51 });
    52 
    53 var menu_width = 670;
    54 var menu_height = 100;
    55 var menu_splits = new Array(180, 350, 505);
    56 
    57 var click_events = new Array();
    58 click_events["logo"] = new Array();
    59 click_events["logo"][0] = new Object();
    60 click_events["logo"][0]["min_x"] = 0;
    61 click_events["logo"][0]["max_x"] = menu_splits[0];
    62 click_events["logo"][0]["min_y"] = 0;
    63 click_events["logo"][0]["max_y"] = menu_height;
    64 click_events["logo"][0]["func"] = switch_page;
    65 click_events["logo"][0]["args"] = new Array("personalwork.html");
    66 click_events["logo"][1] = new Object();
    67 click_events["logo"][1]["min_x"] = menu_splits[0] + 1;
    68 click_events["logo"][1]["max_x"] = menu_splits[1];
    69 click_events["logo"][1]["min_y"] = 0;
    70 click_events["logo"][1]["max_y"] = menu_height;
    71 click_events["logo"][1]["func"] = switch_page;
    72 click_events["logo"][1]["args"] = new Array("howiwork.html");
    73 click_events["logo"][2] = new Object();
    74 click_events["logo"][2]["min_x"] = menu_splits[1] + 1;
    75 click_events["logo"][2]["max_x"] = menu_splits[2];
    76 click_events["logo"][2]["min_y"] = 0;
    77 click_events["logo"][2]["max_y"] = menu_height;
    78 click_events["logo"][2]["func"] = switch_page;
    79 click_events["logo"][2]["args"] = new Array("books.html");
    80 click_events["logo"][3] = new Object();
    81 click_events["logo"][3]["min_x"] = menu_splits[2] + 1;
    82 click_events["logo"][3]["max_x"] = menu_width - 1;
    83 click_events["logo"][3]["min_y"] = 0;
    84 click_events["logo"][3]["max_y"] = menu_height;
    85 click_events["logo"][3]["func"] = switch_page;
    86 click_events["logo"][3]["args"] = new Array("links.html", "linksend.html");
    87 
    88 function mouse_pos(event, id) { 
    89     var offset = $(id).offset();   
    90     return new Array(event.pageX - offset.left, 
    91                      event.pageY - offset.top);
    92 }
    93 
    94 function handle_click(event) {
    95     var xy = mouse_pos(event, "#logo");
    96     var x = xy[0];
    97     var y = xy[1];
    98     for (var i in click_events[event.target.id]) {
    99         var ces = click_events[event.target.id][i];
   100         if (ces && x >= ces["min_x"] && x <= ces["max_x"]
   101             && y >= ces["min_y"] && y <= ces["max_y"]) {    
   102             ces["func"](event.target.id, ces);
   103             break;
   104         }
   105     }
   106 }
   107 
   108 function switch_page(id, params) {
   109     var page = params["args"][0];
   110     $("body").load(page);
   111     document.location.href = page;    
   112 }
   113 
   114 function colour_area(image, min_x, max_x, min_y, max_y, colour) {   
   115     var pixels = image.data;    
   116     for (var y = min_y; y <= max_y; y += 1) {
   117         for (var x = min_x; x <= max_x; x += 1) {
   118             var pix_colour = getPixel(image, x, y);
   119             if (pix_colour.a > 0) {
   120                 colour.a = pix_colour.a;
   121                 setPixel(image, x, y, colour);
   122             }
   123         }
   124     }    
   125 }
   126 
   127 function init_logo(event) {
   128     var canvas = document.getElementById("logo");
   129     var context = canvas.getContext("2d");    
   130     canvas.width = menu_width;
   131     canvas.height = menu_height;
   132     context.drawImage(event.target, 0, 0);
   133     var image = context.getImageData(0, 0, menu_width, menu_height);    
   134     for (var i in click_events["logo"]) {
   135         var p = click_events["logo"][i]; 
   136         var min_x = p["min_x"];
   137         var max_x = p["max_x"];
   138         var min_y = p["min_y"];
   139         var max_y = p["max_y"];
   140         var pos = document.location.href.lastIndexOf("/") + 1; 
   141         var page = document.location.href.substr(pos);        
   142         for (var j in p["args"]) {
   143             if (page == p["args"][j]) {         
   144                 colour_area(image, min_x, max_x, 0, 60, black);
   145                 colour_area(image, min_x, max_x, 60, max_y, white);
   146                 break;
   147             }
   148             else {
   149                 colour_area(image, min_x, max_x, 0, 60, white);
   150                 colour_area(image, min_x, max_x, 60, max_y,  orange);  
   151             }
   152         }       
   153     } 
   154     context.putImageData(image, 0, 0);      
   155     // context.fillStyle = green.str();
   156     // console.log((found["max_x"] - found["min_x"]) / 2 + found["min_x"] - 2);
   157     // context.fillRect((found["max_x"]-found["min_x"])/2+found["min_x"]-1, 95, 2, 5);  
   158     draw_menu_lines();   
   159 }
   160 
   161 function draw_menu_lines() {
   162     var canvas = document.getElementById("logo");
   163     var context = canvas.getContext("2d");  
   164     var image = context.getImageData(0, 0, menu_width, menu_height);
   165     var pixels = image.data;
   166     context.lineCap = "round";
   167     context.lineWidth = 1;
   168     var colour1a = new Colour(0, 0, 0, 0.1);
   169     context.beginPath();   
   170     context.moveTo(0, menu_height - 1);
   171     context.lineTo(menu_width - 1, menu_height - 1);   
   172     context.closePath();  
   173     context.strokeStyle = colour1a.str();
   174     context.stroke(); 
   175     var colour1e = new Colour(0, 0, 0, 0.05);  
   176     context.beginPath();   
   177     context.moveTo(70, menu_height - 1);
   178     context.lineTo(menu_width - 71, menu_height - 1);   
   179     context.closePath();  
   180     context.strokeStyle = colour1e.str();
   181     context.stroke();  
   182     var colour1b = new Colour(0, 0, 0, 0.05);  
   183     context.beginPath();   
   184     context.moveTo(50, menu_height - 1);
   185     context.lineTo(menu_width - 51, menu_height - 1);   
   186     context.closePath();  
   187     context.strokeStyle = colour1b.str();
   188     context.stroke();
   189     var colour1c = new Colour(0, 0, 0, 0.05);  
   190     context.beginPath();   
   191     context.moveTo(25, menu_height - 1);
   192     context.lineTo(menu_width - 26, menu_height - 1);   
   193     context.closePath();  
   194     context.strokeStyle = colour1c.str();
   195     context.stroke();
   196     var colour1d = new Colour(0, 0, 0, 0.05);  
   197     context.beginPath();   
   198     context.moveTo(10, menu_height - 1);
   199     context.lineTo(menu_width - 11, menu_height - 1);   
   200     context.closePath();  
   201     context.strokeStyle = colour1d.str();
   202     context.stroke();  
   203     
   204     var colour2 = new Colour(0, 0, 0, 0.2);
   205     context.beginPath();  
   206     context.moveTo(menu_splits[0], 0); 
   207     context.lineTo(menu_splits[0], menu_height - 1);
   208     context.moveTo(menu_splits[1], 0);
   209     context.lineTo(menu_splits[1], menu_height - 1);
   210     context.moveTo(menu_splits[2], 0);
   211     context.lineTo(menu_splits[2], menu_height - 1);
   212     context.closePath();
   213     context.strokeStyle = colour2.str();
   214     context.stroke();
   215 
   216     context.clearRect(menu_splits[0] - 1, menu_height - 1, 2, 2);
   217     context.clearRect(menu_splits[1] - 1, menu_height - 1, 2, 2);
   218     context.clearRect(menu_splits[2] - 1, menu_height - 1, 2, 2);
   219 
   220     context.clearRect(menu_splits[0] - 1, menu_height - 2, 1, 1);
   221     context.clearRect(menu_splits[1] - 1, menu_height - 2, 1, 1);
   222     context.clearRect(menu_splits[2] - 1, menu_height - 2, 1, 1);
   223 }
   224 
   225 function clearMenuLines() {
   226     var canvas = document.getElementById("logo");
   227     var context = canvas.getContext("2d");  
   228     var image = context.getImageData(0, 0, menu_width, menu_height);
   229     var pixels = image.data;
   230     context.clearRect(0, menu_height - 2, menu_width - 1, 2);
   231     context.clearRect(menu_splits[0] - 1, 0, 2, menu_height - 1);
   232     context.clearRect(menu_splits[1] - 1, 0, 2, menu_height - 1);
   233     context.clearRect(menu_splits[2] - 1, 0, 2, menu_height - 1);
   234 }
   235 
   236 function draw_sim_lines() {
   237     var w = 670;
   238     var h = 50;
   239     var canvas = document.getElementById("sim");
   240     var context = canvas.getContext("2d");  
   241     var image = context.getImageData(0, 0, w, h);
   242     var pixels = image.data;
   243     context.lineCap = "round";
   244     context.lineWidth = 1;
   245     var colour1a = new Colour(0, 0, 0, 0.1);
   246     context.beginPath();   
   247     context.moveTo(0, h - 1);
   248     context.lineTo(w - 1, h - 1);   
   249     context.closePath();  
   250     context.strokeStyle = colour1a.str();
   251     context.stroke(); 
   252     var colour1e = new Colour(0, 0, 0, 0.05);  
   253     context.beginPath();   
   254     context.moveTo(70, h - 1);
   255     context.lineTo(w - 71, h - 1);   
   256     context.closePath();  
   257     context.strokeStyle = colour1e.str();
   258     context.stroke();  
   259     var colour1b = new Colour(0, 0, 0, 0.05);  
   260     context.beginPath();   
   261     context.moveTo(50, h - 1);
   262     context.lineTo(w - 51, h - 1);   
   263     context.closePath();  
   264     context.strokeStyle = colour1b.str();
   265     context.stroke();
   266     var colour1c = new Colour(0, 0, 0, 0.05);  
   267     context.beginPath();   
   268     context.moveTo(25, h - 1);
   269     context.lineTo(w - 26, h - 1);   
   270     context.closePath();  
   271     context.strokeStyle = colour1c.str();
   272     context.stroke();
   273     var colour1d = new Colour(0, 0, 0, 0.05);  
   274     context.beginPath();   
   275     context.moveTo(10, h - 1);
   276     context.lineTo(w - 11, h - 1);   
   277     context.closePath();  
   278     context.strokeStyle = colour1d.str();
   279     context.stroke();   
   280 }
   281 
   282 function update_logo() {
   283     var canvas = document.getElementById("logo");
   284     var context = canvas.getContext("2d");  
   285     var image = context.getImageData(0, 0, menu_width, menu_height);
   286     var pixels = image.data;
   287     var white = new Colour(255, 255, 255);
   288     for (var y = 0; y < menu_height; y += 1) {
   289         for (var x = 0; x < menu_width; x += 1) {
   290             var pix_colour = getPixel(image, x, y);
   291             if (pix_colour.a > 0) {
   292                 setPixel(image, x, y, new Colour(90, 215, 21, pix_colour.a));
   293             }
   294         }
   295     }   
   296     context.putImageData(image, 0, 0);
   297 }
   298 
   299 var char_map = new Array();
   300 char_map["A"] = new Array(new Array(0, 4), new Array(1, 3),
   301                           new Array(2, 2), new Array(3, 1),
   302                           new Array(4, 0), new Array(5, 1),
   303                           new Array(6, 2), new Array(7, 3),
   304                           new Array(8, 4), new Array(2, 3),
   305                           new Array(3, 3), new Array(4, 3),
   306                           new Array(5, 3), new Array(6, 3));
   307 char_map["M"] = new Array(new Array(0, 4), new Array(0, 3),
   308                           new Array(0, 2), new Array(0, 1),
   309                           new Array(0, 0), new Array(0, 5),
   310                           new Array(5, 4), new Array(5, 3),
   311                           new Array(5, 2), new Array(5, 1),
   312                           new Array(5, 0), new Array(5, 5),
   313                           new Array(1, 1), new Array(2, 2), 
   314                           new Array(4, 2), new Array(5, 1),
   315                           new Array(3, 3));
   316 char_map["E"] = new Array(new Array(0, 4), new Array(0, 3),
   317                           new Array(0, 2), new Array(0, 1),
   318                           new Array(0, 0), new Array(0, 5),
   319                           new Array(1, 0), new Array(2, 0),
   320                           new Array(3, 0),
   321                           new Array(1, 3), new Array(2, 3),                       
   322                           new Array(1, 5), new Array(2, 5),
   323                           new Array(3, 5))
   324 char_map["7"] = new Array(new Array(0, 0), new Array(1, 0),
   325                           new Array(2, 0),
   326                           new Array(0, 5), new Array(0, 4),
   327                           new Array(1, 3), new Array(2, 2));
   328 char_map["3"] = new Array(new Array(2, 4), new Array(2, 3),
   329                           new Array(2, 2), new Array(2, 1),
   330                           new Array(0, 0), new Array(0, 5),
   331                           new Array(1, 0), new Array(2, 0),                         
   332                           new Array(1, 3), new Array(2, 3),                          
   333                           new Array(1, 5), new Array(2, 5))
   334 
   335 function init_gameoflife(text) {
   336     var canvas = document.getElementById("gameoflife");
   337     var cell_size = 16;
   338     if (canvas) {
   339         gameoflife = new Simulation(canvas, 670, 670, cell_size,
   340                                     green, black);
   341         $("#gameoflife").mousemove(function(event) { 
   342             gameoflife.mouse_moved(mouse_pos(event, "#gameoflife")); 
   343         });       
   344         var offset = new Array(13, 16);
   345         for (var c in text) {            
   346             var w = 0;
   347             for (p in char_map[text[c]]) {
   348                 var x = char_map[text[c]][p][0];
   349                 var y = char_map[text[c]][p][1];
   350                 w = Math.max(w, x);
   351                 var pos = new Array((x + offset[0]) * cell_size, 
   352                                     (y + offset[1]) * cell_size);
   353                 gameoflife.activate_cell(pos);
   354             }            
   355             offset[0] += w + 2;
   356         }       
   357     }    
   358 }
   359 
   360 function Colour(r, g, b, a) {
   361     this.r = r;
   362     this.g = g;
   363     this.b = b;
   364     this.a = a == undefined ? 255 : a;
   365 }
   366 Colour.prototype.equals = function(rhs) {
   367     for (p in this) {
   368         if (typeof(rhs[p]) == undefined
   369            || this[p] != rhs[p]) {           
   370             return false;
   371         }
   372     }
   373     return true;
   374 }
   375 Colour.prototype.str = function() {
   376     return "rgba(" + this.r + "," + this.g + "," + this.b + "," + this.a + ")";
   377 }
   378 
   379 var white = new Colour(255, 255, 255);
   380 var black = new Colour(0, 0, 0);
   381 var orange = new Colour(201, 87, 35);
   382 var green = new Colour(90, 215, 21);
   383 var background = new Colour(34, 34, 34);
   384 
   385 function setPixel(image, x, y, colour) {
   386     var index = 4 * (x + y * image.width);
   387     image.data[index] = colour.r;
   388     image.data[index + 1] = colour.g;
   389     image.data[index + 2] = colour.b;
   390     image.data[index + 3] = colour.a;
   391 }
   392 
   393 function getPixel(image, x, y) {   
   394     var index = 4 * (x + y * image.width);
   395     var r = image.data[index];
   396     var g = image.data[index + 1];
   397     var b = image.data[index + 2];
   398     var a = image.data[index + 3];
   399     return new Colour(r, g, b, a);
   400 }
   401 
   402 function load_random_quote() {
   403     var file = "/quotes/quote" + Math.floor(Math.random() * QUOTES_NUMBER + 1) + ".html";
   404     $.ajax({url: SERVER + file,
   405             success: write_quote});
   406 }
   407 
   408 function write_quote(data, status, xhr) {   
   409     $("#random_quote").html(data);
   410 }
   411 
   412 function load_footer()
   413 {
   414 	var file = "/footer.html";
   415 	var currentFile = self.location.hostname + self.location.pathname;		
   416 	var request = http_request_object();
   417 	var url  = "http://" + self.location.hostname + file;	
   418 	request.open("GET", url, false);
   419 	request.setRequestHeader("User-Agent", navigator.userAgent);
   420 	request.send(null)
   421 	// if (oRequest.status == 200) alert(oRequest.responseText);
   422 	// else alert("Error executing XMLHttpRequest call!");	
   423 	//document.write(url);
   424 	//document.write(request.responseText);
   425     document.getElementById('footer').innerHTML = request.responseText;
   426 }
   427 
   428 
   429 
   430 // Mandelbrot functions
   431 function Complex(real, imag) 
   432 {
   433 	this.real = real;
   434 	this.imag = imag;
   435 }
   436 
   437 var MIN_C = new Complex(-2.2, -1.4);
   438 var MAX_C = new Complex(1.0, 1.4);
   439 var min_c = MIN_C;
   440 var max_c = MAX_C;
   441 var MIN_ITER = 100;
   442 var max_iter = MIN_ITER;
   443 var zoom = 1.0;
   444 var resolution = 3;
   445 var bailout = 4.0;
   446 
   447 function Result(z, iter) 
   448 {
   449 	this.z = z;
   450 	this.iter = iter;
   451 }
   452 
   453 function complex_quad(c)
   454 {
   455 	return new Complex(Math.pow(c.real, 2) - Math.pow(c.imag, 2), 
   456 		2.0 * c.real * c.imag);
   457 }
   458 
   459 function complex_quad_value(c)
   460 {
   461 	return Math.pow(c.real, 2) + Math.pow(c.imag, 2);
   462 }
   463 
   464 function complex_add(c1, c2)
   465 {
   466 	return new Complex(c1.real + c2.real, c1.imag + c2.imag);
   467 }
   468 
   469 function complex_equal(c1, c2)
   470 {	
   471 	return (c1.real == c2.real) && (c1.imag == c2.imag);
   472 }
   473 
   474 function iterate(z, c) 
   475 {
   476 	z_quad = complex_quad(z);
   477 	return new Complex(z_quad.real + c.real, z_quad.imag + c.imag);
   478 }
   479 
   480 function test(c, max_iter) 
   481 {
   482 	var iter = 0;
   483 	var z = new Complex(0.0, 0.0);
   484 	var last_z = new Complex(-1.0, 0.0);
   485 	var quad_z = complex_quad_value(z);
   486 	
   487 	while (iter < max_iter
   488 		&& !complex_equal(z, last_z)
   489 		&& quad_z <= bailout)
   490 	{
   491 		last_z = z;
   492 		z = iterate(z, c);
   493 		quad_z = complex_quad_value(z);
   494 		iter++;
   495 	}
   496 	return new Result(quad_z, iter);
   497 }
   498 
   499 function draw(diter, dx, dy, dz, dres)
   500 {  
   501 	var canvas = document.getElementById('mandelbrot'); 
   502 	
   503 	if (canvas.getContext)
   504 	{  
   505 		zoom += dz;
   506 		var ctx = canvas.getContext('2d');
   507 
   508 		if (dres != 0)
   509 		{		
   510 			resolution = Math.max(1, resolution + dres);
   511 		}
   512 
   513 		if (diter != 0)
   514 		{
   515 			max_iter = Math.max(MIN_ITER, max_iter + diter);
   516 		}
   517 		
   518 		var red = "rgb(255, 0, 0)";
   519 		var white = "rgb(255, 255, 255)";
   520 		var width = canvas.width;
   521 		var height = canvas.height;
   522 		var dim = Math.max(width, height);
   523 		var dim_ratio = Math.round(width / height);	
   524 		var diff_c = new Complex(max_c.real - min_c.real,
   525 			max_c.imag - min_c.imag);
   526 		dx_min = diff_c.real / 100 * (dx + dz);
   527 		dx_max = diff_c.real / 100 * (dx - dz);
   528 
   529 		dy_min = diff_c.imag / 100 * (dy + dz);
   530 		dy_max = diff_c.imag / 100 * (dy - dz);
   531 
   532 		var min_inc = new Complex(dx_min * dim_ratio / 2.0, dy_min);
   533 		var max_inc = new Complex(dx_max * dim_ratio / 2.0, dy_max);
   534 		min_c = complex_add(min_c, min_inc);
   535 		max_c = complex_add(max_c, max_inc);
   536 		diff_c = new Complex(max_c.real - min_c.real,
   537 			max_c.imag - min_c.imag);
   538 		
   539 		for (var y = 0; y < height; y += resolution) 
   540 		{
   541 			for (var x = 0; x < width; x += resolution) 
   542 			{    			
   543 				var c = new Complex(min_c.real + diff_c.real / dim * x, 
   544 					min_c.imag + diff_c.imag / dim * y);  	
   545 			 	var result = test(c, max_iter);	
   546 		 		var r = Math.min(255, Math.pow(Math.max(0, 
   547 		 			(result.iter - max_iter / 20.0)), 2));
   548 				var g = Math.min(255, Math.pow(Math.max(0, 
   549 					(result.iter - max_iter / 25.0)), 2));			
   550 		 		var b = Math.min(255, Math.pow(Math.max(0, 
   551 					(result.iter - max_iter / 20.0)), 2));
   552 		 		var colour = "rgb(" + r + "," + g + "," + b + ")";
   553 		 		ctx.fillStyle = colour; 
   554 				ctx.fillRect(x, y, resolution, resolution);			
   555 			}
   556   		}
   557 	}  
   558 } 
   559 
   560 function getEventOffsetX(evt)
   561 {
   562 	if (evt.offsetX != null)
   563 		return evt.offsetX;
   564  
   565     var obj = evt.target || evt.srcElement;
   566    	setPageTopLeft(obj);
   567     return (evt.clientX - obj.pageLeft);
   568 }
   569 
   570 function getEventOffsetY(evt)
   571 {
   572 	if (evt.offsetY != null)
   573 		return evt.offsetY;
   574  
   575     var obj = evt.target || evt.srcElement;
   576    	setPageTopLeft(obj);
   577     return (evt.clientY - obj.pageTop);
   578 }
   579  
   580 function setPageTopLeft( o )
   581 {
   582     var top = 0,
   583     left = 0,
   584     obj = o;
   585  
   586     while (o.offsetParent)
   587      {
   588          left += o.offsetLeft ;
   589          top += o.offsetTop ;
   590          o = o.offsetParent ;
   591     };
   592  
   593     obj.pageTop = top;
   594     obj.pageLeft = left; 
   595 }
   596  
   597 function draw2(evt) {
   598     var iter = 0;
   599     var res = 0;
   600     var x = (getEventOffsetX(evt) - 335) / 167.5;
   601     var y = (getEventOffsetY(evt) - 140) / 70;
   602     var z = 0;
   603     draw(iter, x, y, z, res);
   604 }
   605 
   606 function update() {
   607     simulation.update();
   608     simulation.draw();
   609     if (gameoflife) {
   610         gameoflife.update();
   611         gameoflife.draw();
   612     }
   613 }
   614 
   615 function Grid(width, height, cell_size) {
   616     this.width = parseInt(width / cell_size + 0.5);
   617     this.height = parseInt(height / cell_size + 0.5);
   618     this.canvas_width = width;
   619     this.canvas_height = height;
   620     this.cell_size = cell_size;  
   621     this.cells = new Array();
   622     for (var y = 0; y < this.height; ++y) { 
   623         this.cells[y] = new Array();
   624         for (var x = 0; x < this.width; ++x) {
   625             this.cells[y][x] = new Cell(x, y, 0, this);
   626         }
   627     }
   628 }
   629 Grid.prototype.cell = function(x, y) {   
   630     return this.cells[y][x];
   631 }
   632 Grid.prototype.pick_cell = function(x, y) {
   633     var lx = parseInt(x / this.cell_size - 0.5);
   634     var ly = parseInt(y / this.cell_size - 0.5);
   635     return this.cells[ly][lx];
   636 }
   637 
   638 function Cell(x, y, value, grid) {
   639     this.x = x;
   640     this.y = y;  
   641     this.canvas_x = x * grid.cell_size;
   642     this.canvas_y = y * grid.cell_size;
   643     this.value = value;
   644     this.grid = grid;
   645     this._neighbours = new Array();
   646     this.last_mod = 0;
   647 }
   648 Cell.prototype.hash = function() {
   649     return this.x + ", " + this.y;
   650 }
   651 Cell.prototype.set_value = function(value) {
   652     this.value = value;
   653     this.last_mod = $.now();
   654 }
   655 Cell.prototype.neighbours = function() {
   656     if (this._neighbours.length) {
   657         return this._neighbours;
   658     }
   659     if (this.x > 0) {
   660         this._neighbours.push(this.grid.cell(this.x-1, this.y));
   661         if (this.y > 0) {
   662             this._neighbours.push(this.grid.cell(this.x-1, this.y-1));
   663             this._neighbours.push(this.grid.cell(this.x, this.y-1));
   664         }
   665         if (this.y + 1 < this.grid.height) {
   666             this._neighbours.push(this.grid.cell(this.x-1, this.y+1));
   667         }
   668     }   
   669     if (this.x + 1 < this.grid.width) {
   670         this._neighbours.push(this.grid.cell(this.x+1, this.y));
   671         if (this.y + 1 < this.grid.height) {
   672             this._neighbours.push(this.grid.cell(this.x+1, this.y+1));
   673             this._neighbours.push(this.grid.cell(this.x, this.y+1));
   674         }
   675         if (this.y > 0) {
   676             this._neighbours.push(this.grid.cell(this.x+1, this.y-1));
   677         }
   678     }
   679     return this._neighbours;
   680 }
   681 Cell.prototype.density = function() {
   682     var d = 0;
   683     var neighbours = this.neighbours();
   684     for (var n in neighbours) {
   685         d += neighbours[n].value;
   686     }
   687     return d;
   688 }
   689 
   690 function Simulation(canvas, w, h, cell_size, life_colour, dead_colour) {
   691     this.last_update = $.now() + 3000;
   692     this.last_draw = 0;
   693     this.last_mouse_moved = 0;
   694     this.redraw = new Array();   
   695     this.canvas = canvas;
   696     this.canvas.width = w;
   697     this.canvas.height = h;
   698     this.context = canvas.getContext("2d");    
   699     this.pos_queue = new Array();
   700     this.cell_queue = new Array();  
   701     this.cell_size = cell_size;
   702     this.life_colour = life_colour;
   703     this.dead_colour = dead_colour;
   704     this.grid = new Grid(this.canvas.width, this.canvas.height, cell_size);
   705     draw_sim_lines();
   706 }
   707 Simulation.prototype.draw = function() {
   708     var now = $.now();
   709     if (this.redraw.length) {
   710         var cell = this.redraw[0];
   711         this.redraw.shift();
   712         this.context.fillStyle = cell.value == 1 ? 
   713             this.life_colour.str() : this.dead_colour.str();
   714         this.context.fillRect(cell.canvas_x, cell.canvas_y, 
   715                               this.grid.cell_size, this.grid.cell_size);
   716         this.last_draw = $.now();
   717     }
   718 }
   719 Simulation.prototype.update = function() {
   720     var now = $.now();
   721     while (this.pos_queue.length) {
   722         var pos = this.pos_queue.pop();       
   723         if (pos[0] < this.canvas.width
   724             && pos[1] < this.canvas.height) {
   725             var cell = this.grid.pick_cell(pos[0], pos[1]);
   726             cell.set_value(1);
   727             this.cell_queue.push(cell);
   728             this.cell_queue.concat(cell.neighbours()); 
   729             this.redraw.unshift(cell);          
   730         }
   731     }
   732     if (this.last_update + 1000 > now) {
   733         return;
   734     }
   735     var changed = new Array();
   736     var next_cell_queue = new Array();
   737     while (this.cell_queue.length) {
   738         var cell = this.cell_queue.pop();       
   739         if (changed[cell.hash()]) {           
   740             continue;
   741         }
   742         var d = cell.density();
   743         if (d == 3 && cell.value == 0) {           
   744             changed[cell.hash()] = cell;            
   745             next_cell_queue = next_cell_queue.concat(cell.neighbours());               
   746         } else if (cell.value == 1) {
   747             if (d < 2 || d > 3) {              
   748                 changed[cell.hash()] = cell;                
   749                 next_cell_queue = next_cell_queue.concat(cell.neighbours());               
   750             }
   751         }       
   752     }
   753     for (var pos in changed) {
   754         var cell = changed[pos];      
   755         cell.set_value(1 - cell.value);
   756         this.redraw.push(cell);
   757     }
   758     this.cell_queue = next_cell_queue;
   759     this.last_update = now;  
   760 }
   761 Simulation.prototype.activate_cell = function(pos) {
   762     this.pos_queue.push(pos);   
   763 }
   764 Simulation.prototype.mouse_moved = function(pos) {
   765     var now = $.now();    
   766     if (this.last_mouse_moved + 50 < now) {       
   767         this.pos_queue.push(pos);      
   768         this.last_mouse_moved = now;       
   769     }
   770 }
   771 
   772