script.js
author Eugen Sawin <sawine@me73.com>
Thu, 05 Jan 2012 23:29:27 +0100
changeset 82 cb8897d7f508
parent 81 a76b2fb0f21f
child 83 228adcfd8f23
permissions -rw-r--r--
Fixed minor bug.
     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, 4, 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 == "experiments.html") {
    40         draw(0, 0, 0, 0, 0);
    41     } else if (page == "gameoflife.html") {
    42         init_gameoflife();
    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 function init_gameoflife() {
   300     var canvas = document.getElementById("gameoflife");
   301     if (canvas) {
   302         gameoflife = new Simulation(canvas, 670, 670, 8,
   303                                     green, black);
   304         $("#gameoflife").mousemove(function(event) { 
   305             gameoflife.mouse_moved(mouse_pos(event, "#gameoflife")); 
   306         });       
   307     }
   308 }
   309 
   310 function Colour(r, g, b, a) {
   311     this.r = r;
   312     this.g = g;
   313     this.b = b;
   314     this.a = a == undefined ? 255 : a;
   315 }
   316 Colour.prototype.equals = function(rhs) {
   317     for (p in this) {
   318         if (typeof(rhs[p]) == undefined
   319            || this[p] != rhs[p]) {           
   320             return false;
   321         }
   322     }
   323     return true;
   324 }
   325 Colour.prototype.str = function() {
   326     return "rgba(" + this.r + "," + this.g + "," + this.b + "," + this.a + ")";
   327 }
   328 
   329 var white = new Colour(255, 255, 255);
   330 var black = new Colour(0, 0, 0);
   331 var orange = new Colour(201, 87, 35);
   332 var green = new Colour(90, 215, 21);
   333 var background = new Colour(34, 34, 34);
   334 
   335 function setPixel(image, x, y, colour) {
   336     var index = 4 * (x + y * image.width);
   337     image.data[index] = colour.r;
   338     image.data[index + 1] = colour.g;
   339     image.data[index + 2] = colour.b;
   340     image.data[index + 3] = colour.a;
   341 }
   342 
   343 function getPixel(image, x, y) {   
   344     var index = 4 * (x + y * image.width);
   345     var r = image.data[index];
   346     var g = image.data[index + 1];
   347     var b = image.data[index + 2];
   348     var a = image.data[index + 3];
   349     return new Colour(r, g, b, a);
   350 }
   351 
   352 function load_random_quote() {
   353     var file = "/quotes/quote" + Math.floor(Math.random() * QUOTES_NUMBER + 1) + ".html";
   354     $.ajax({url: SERVER + file,
   355             success: write_quote});
   356 }
   357 
   358 function write_quote(data, status, xhr) {   
   359     $("#random_quote").html(data);
   360 }
   361 
   362 function load_footer()
   363 {
   364 	var file = "/footer.html";
   365 	var currentFile = self.location.hostname + self.location.pathname;		
   366 	var request = http_request_object();
   367 	var url  = "http://" + self.location.hostname + file;	
   368 	request.open("GET", url, false);
   369 	request.setRequestHeader("User-Agent", navigator.userAgent);
   370 	request.send(null)
   371 	// if (oRequest.status == 200) alert(oRequest.responseText);
   372 	// else alert("Error executing XMLHttpRequest call!");	
   373 	//document.write(url);
   374 	//document.write(request.responseText);
   375     document.getElementById('footer').innerHTML = request.responseText;
   376 }
   377 
   378 
   379 
   380 // Mandelbrot functions
   381 function Complex(real, imag) 
   382 {
   383 	this.real = real;
   384 	this.imag = imag;
   385 }
   386 
   387 var MIN_C = new Complex(-2.2, -1.4);
   388 var MAX_C = new Complex(1.0, 1.4);
   389 var min_c = MIN_C;
   390 var max_c = MAX_C;
   391 var MIN_ITER = 100;
   392 var max_iter = MIN_ITER;
   393 var zoom = 1.0;
   394 var resolution = 3;
   395 var bailout = 4.0;
   396 
   397 function Result(z, iter) 
   398 {
   399 	this.z = z;
   400 	this.iter = iter;
   401 }
   402 
   403 function complex_quad(c)
   404 {
   405 	return new Complex(Math.pow(c.real, 2) - Math.pow(c.imag, 2), 
   406 		2.0 * c.real * c.imag);
   407 }
   408 
   409 function complex_quad_value(c)
   410 {
   411 	return Math.pow(c.real, 2) + Math.pow(c.imag, 2);
   412 }
   413 
   414 function complex_add(c1, c2)
   415 {
   416 	return new Complex(c1.real + c2.real, c1.imag + c2.imag);
   417 }
   418 
   419 function complex_equal(c1, c2)
   420 {	
   421 	return (c1.real == c2.real) && (c1.imag == c2.imag);
   422 }
   423 
   424 function iterate(z, c) 
   425 {
   426 	z_quad = complex_quad(z);
   427 	return new Complex(z_quad.real + c.real, z_quad.imag + c.imag);
   428 }
   429 
   430 function test(c, max_iter) 
   431 {
   432 	var iter = 0;
   433 	var z = new Complex(0.0, 0.0);
   434 	var last_z = new Complex(-1.0, 0.0);
   435 	var quad_z = complex_quad_value(z);
   436 	
   437 	while (iter < max_iter
   438 		&& !complex_equal(z, last_z)
   439 		&& quad_z <= bailout)
   440 	{
   441 		last_z = z;
   442 		z = iterate(z, c);
   443 		quad_z = complex_quad_value(z);
   444 		iter++;
   445 	}
   446 	return new Result(quad_z, iter);
   447 }
   448 
   449 function draw(diter, dx, dy, dz, dres)
   450 {  
   451 	var canvas = document.getElementById('mandelbrot'); 
   452 	
   453 	if (canvas.getContext)
   454 	{  
   455 		zoom += dz;
   456 		var ctx = canvas.getContext('2d');
   457 
   458 		if (dres != 0)
   459 		{		
   460 			resolution = Math.max(1, resolution + dres);
   461 		}
   462 
   463 		if (diter != 0)
   464 		{
   465 			max_iter = Math.max(MIN_ITER, max_iter + diter);
   466 		}
   467 		
   468 		var red = "rgb(255, 0, 0)";
   469 		var white = "rgb(255, 255, 255)";
   470 		var width = canvas.width;
   471 		var height = canvas.height;
   472 		var dim = Math.max(width, height);
   473 		var dim_ratio = Math.round(width / height);	
   474 		var diff_c = new Complex(max_c.real - min_c.real,
   475 			max_c.imag - min_c.imag);
   476 		dx_min = diff_c.real / 100 * (dx + dz);
   477 		dx_max = diff_c.real / 100 * (dx - dz);
   478 
   479 		dy_min = diff_c.imag / 100 * (dy + dz);
   480 		dy_max = diff_c.imag / 100 * (dy - dz);
   481 
   482 		var min_inc = new Complex(dx_min * dim_ratio / 2.0, dy_min);
   483 		var max_inc = new Complex(dx_max * dim_ratio / 2.0, dy_max);
   484 		min_c = complex_add(min_c, min_inc);
   485 		max_c = complex_add(max_c, max_inc);
   486 		diff_c = new Complex(max_c.real - min_c.real,
   487 			max_c.imag - min_c.imag);
   488 		
   489 		for (var y = 0; y < height; y += resolution) 
   490 		{
   491 			for (var x = 0; x < width; x += resolution) 
   492 			{    			
   493 				var c = new Complex(min_c.real + diff_c.real / dim * x, 
   494 					min_c.imag + diff_c.imag / dim * y);  	
   495 			 	var result = test(c, max_iter);	
   496 		 		var r = Math.min(255, Math.pow(Math.max(0, 
   497 		 			(result.iter - max_iter / 20.0)), 2));
   498 				var g = Math.min(255, Math.pow(Math.max(0, 
   499 					(result.iter - max_iter / 25.0)), 2));			
   500 		 		var b = Math.min(255, Math.pow(Math.max(0, 
   501 					(result.iter - max_iter / 20.0)), 2));
   502 		 		var colour = "rgb(" + r + "," + g + "," + b + ")";
   503 		 		ctx.fillStyle = colour; 
   504 				ctx.fillRect(x, y, resolution, resolution);			
   505 			}
   506   		}
   507 	}  
   508 } 
   509 
   510 function getEventOffsetX(evt)
   511 {
   512 	if (evt.offsetX != null)
   513 		return evt.offsetX;
   514  
   515     var obj = evt.target || evt.srcElement;
   516    	setPageTopLeft(obj);
   517     return (evt.clientX - obj.pageLeft);
   518 }
   519 
   520 function getEventOffsetY(evt)
   521 {
   522 	if (evt.offsetY != null)
   523 		return evt.offsetY;
   524  
   525     var obj = evt.target || evt.srcElement;
   526    	setPageTopLeft(obj);
   527     return (evt.clientY - obj.pageTop);
   528 }
   529  
   530 function setPageTopLeft( o )
   531 {
   532     var top = 0,
   533     left = 0,
   534     obj = o;
   535  
   536     while (o.offsetParent)
   537      {
   538          left += o.offsetLeft ;
   539          top += o.offsetTop ;
   540          o = o.offsetParent ;
   541     };
   542  
   543     obj.pageTop = top;
   544     obj.pageLeft = left; 
   545 }
   546  
   547 function draw2(evt) {
   548     var iter = 0;
   549     var res = 0;
   550     var x = (getEventOffsetX(evt) - 335) / 167.5;
   551     var y = (getEventOffsetY(evt) - 140) / 70;
   552     var z = 0;
   553     draw(iter, x, y, z, res);
   554 }
   555 
   556 function update() {
   557     simulation.update();
   558     simulation.draw();
   559     if (gameoflife) {
   560         gameoflife.update();
   561         gameoflife.draw();
   562     }
   563 }
   564 
   565 function Grid(width, height, cell_size) {
   566     this.width = parseInt(width / cell_size + 0.5);
   567     this.height = parseInt(height / cell_size + 0.5);
   568     this.canvas_width = width;
   569     this.canvas_height = height;
   570     this.cell_size = cell_size;  
   571     this.cells = new Array();
   572     for (var y = 0; y < this.height; ++y) { 
   573         this.cells[y] = new Array();
   574         for (var x = 0; x < this.width; ++x) {
   575             this.cells[y][x] = new Cell(x, y, 0, this);
   576         }
   577     }
   578 }
   579 Grid.prototype.cell = function(x, y) {   
   580     return this.cells[y][x];
   581 }
   582 Grid.prototype.pick_cell = function(x, y) {
   583     var lx = parseInt(x / this.cell_size - 0.5);
   584     var ly = parseInt(y / this.cell_size - 0.5);
   585     return this.cells[ly][lx];
   586 }
   587 
   588 function Cell(x, y, value, grid) {
   589     this.x = x;
   590     this.y = y;  
   591     this.canvas_x = x * grid.cell_size;
   592     this.canvas_y = y * grid.cell_size;
   593     this.value = value;
   594     this.grid = grid;
   595     this._neighbours = new Array();
   596     this.last_mod = 0;
   597 }
   598 Cell.prototype.hash = function() {
   599     return this.x + ", " + this.y;
   600 }
   601 Cell.prototype.set_value = function(value) {
   602     this.value = value;
   603     this.last_mod = $.now();
   604 }
   605 Cell.prototype.neighbours = function() {
   606     if (this._neighbours.length) {
   607         return this._neighbours;
   608     }
   609     if (this.x > 0) {
   610         this._neighbours.push(this.grid.cell(this.x-1, this.y));
   611         if (this.y > 0) {
   612             this._neighbours.push(this.grid.cell(this.x-1, this.y-1));
   613             this._neighbours.push(this.grid.cell(this.x, this.y-1));
   614         }
   615         if (this.y + 1 < this.grid.height) {
   616             this._neighbours.push(this.grid.cell(this.x-1, this.y+1));
   617         }
   618     } else {
   619         this._neighbours.push(this.grid.cell(this.grid.width-1, this.y));
   620     }
   621     if (this.x + 1 < this.grid.width) {
   622         this._neighbours.push(this.grid.cell(this.x+1, this.y));
   623         if (this.y + 1 < this.grid.height) {
   624             this._neighbours.push(this.grid.cell(this.x+1, this.y+1));
   625             this._neighbours.push(this.grid.cell(this.x, this.y+1));
   626         }
   627         if (this.y > 0) {
   628             this._neighbours.push(this.grid.cell(this.x+1, this.y-1));
   629         }
   630     } else {
   631         this._neighbours.push(this.grid.cell(0, this.y));
   632     }
   633     return this._neighbours;
   634 }
   635 Cell.prototype.density = function() {
   636     var d = 0;
   637     var neighbours = this.neighbours();
   638     for (var n in neighbours) {
   639         d += neighbours[n].value;
   640     }
   641     return d;
   642 }
   643 
   644 function Simulation(canvas, w, h, cell_size, life_colour, dead_colour) {
   645     this.last_update = 0;
   646     this.last_draw = 0;
   647     this.last_mouse_moved = 0;
   648     this.redraw = new Array();   
   649     this.canvas = canvas;
   650     this.canvas.width = w;
   651     this.canvas.height = h;
   652     this.context = canvas.getContext("2d");    
   653     this.pos_queue = new Array();
   654     this.cell_queue = new Array();  
   655     this.cell_size = cell_size;
   656     this.life_colour = life_colour;
   657     this.dead_colour = dead_colour;
   658     this.grid = new Grid(this.canvas.width, this.canvas.height, cell_size);
   659     draw_sim_lines();
   660 }
   661 Simulation.prototype.draw = function() {
   662     var now = $.now();
   663     if (this.redraw.length) {
   664         var cell = this.redraw.pop();
   665         this.context.fillStyle = cell.value == 1 ? 
   666             this.life_colour.str() : this.dead_colour.str();
   667         this.context.fillRect(cell.canvas_x, cell.canvas_y, 
   668                               this.grid.cell_size, this.grid.cell_size);
   669         this.last_draw = $.now();
   670     }
   671 }
   672 Simulation.prototype.update = function() {
   673     var now = $.now();
   674     if (this.pos_queue.length) {
   675         var pos = this.pos_queue.pop();
   676         if (pos[0] < this.canvas.width
   677             && pos[1] < this.canvas.height) {
   678             var cell = this.grid.pick_cell(pos[0], pos[1]);
   679             cell.set_value(1);
   680             this.cell_queue.push(cell);
   681             this.cell_queue.concat(cell.neighbours()); 
   682             this.redraw.push(cell);          
   683         }
   684     }
   685     if (this.last_update + 1000 > now) {
   686         return;
   687     }
   688     var changed = new Array();
   689     var next_cell_queue = new Array();
   690     while (this.cell_queue.length) {
   691         var cell = this.cell_queue.pop();
   692         if (changed[cell.hash()]) {           
   693             continue;
   694         }
   695         var d = cell.density();
   696         if (d == 3 && cell.value == 0) {           
   697             changed[cell.hash()] = cell;            
   698             next_cell_queue = next_cell_queue.concat(cell.neighbours());               
   699         } else if (cell.value == 1) {
   700             if (d < 2 || d > 3) {              
   701                 changed[cell.hash()] = cell;                
   702                 next_cell_queue = next_cell_queue.concat(cell.neighbours());               
   703             }
   704         }       
   705     }
   706     for (var pos in changed) {
   707         var cell = changed[pos];      
   708         cell.set_value(1 - cell.value);
   709         this.redraw.push(cell);
   710     }
   711     this.cell_queue = this.cell_queue.concat(next_cell_queue);
   712     this.last_update = now;  
   713 }
   714 Simulation.prototype.mouse_moved = function(pos) {
   715     var now = $.now();    
   716     if (this.last_mouse_moved + 50 < now) {
   717         this.pos_queue.push(pos);
   718         this.last_mouse_moved = now;       
   719     }
   720 }
   721 
   722