关于复杂自适应系统的一切曾经是学生时代的最爱,其中一个很有代表性的原型就是这种叫做“生命游戏”的元胞自动机。后来由于种种原因,并没有把这个兴趣深入延续下去。最近在看一本关于HTML5的书,读到关于canvas的一章,突然有点手痒,花了一个下午的时间,用Javascript写了一个简单的生命游戏。
记得在学校的时候,也用ASP和Javascript来写过一个:ASP用来动态生成table,Javascript用来控制其中的td的背景色,在IE4上运行。相比现在的HTML5,那实在是一种繁琐和简陋的尝试。当时,由于对这些简单的数学程序背后隐藏的复杂世界的着迷,还用VB6开发过一些可以在网页上运行的ActiveX控件,可以通过调整方程的参数生成一些分形图画。其中一部分控件用的是当时微软大力鼓吹的ActiveDocument技术(即OLE的Web翻版),现在已经没有多少人记得了。如今,HTML5终于横空出世,并迅速普及,那么再过10年,是否还会有人记得现在的Flash、Sliverlight和Ajax呢?
在近期的展会上,基于HTML5的PACS/RIS已经横空出世。在iPad上达到医用质量的图像显示,至少软件层面的障碍已经清除。也许某一天,HTML5 API真的可以成为一个新的,更有生命力的行业标准,以及一个一统Web前端应用领域的开发平台。而在此过程中蕴藏的种种机会,正等待着大家去挖掘。
下面是生命游戏的完整源码,感兴趣的朋友可以复制粘贴到本地一个html文件中,用IE9以上版本打开。我在自己的机器(Intel i3 M350)上发现用IE9运行时确实比较慢,每一代之间都有明显的短暂停顿,CPU占用率在25%以上。但完全相同的代码,在Opera10上运行就快得多,几乎感觉不到停顿,CPU占用率在25%以下。我在网上看到国外某大学学生的作品,在IE9上跑速度比我的快很多,估计做了一些算法上的优化。据说IE10对Javascript引擎做了很大的性能提升,我原本不是个热衷于升级软件的人,但现在却莫名地冲动。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Conway's Game of Life</title>
<style type="text/css" mce_bogus="1">
table {margin:auto;}
</style>
</head>
<body>
<table>
<tr align="center"><td colspan="2"><h3>Conway's Game of Life</h3><hr height="1px"/></td></tr>
<tr>
<td align="left">Generation: <span id="spanGen">0</span></td>
<td align="right">
<button id="btnStart" onclick="startGame();return true;">Start</button>
<button id="btnPause" onclick="pauseGame();return true;" disabled="disabled">Pause</button>
<button id="btnReset" onclick="resetGame();return true;" disabled="disabled">Reset</button>
<button id="btnRandom" onclick="randomGame();return true;">Random</button>
<button id="btnHelp" onclick="help();return true;">What's this?</button>
</td>
</tr>
<tr align="center">
<td colspan="2"><canvas id="space" width="500" height="500" style="border: 1px solid; background-color: #eeeeee;" /></td>
</tr>
<tr align="center">
<td colspan="2">© May, 2011, lifegame@263.net</td>
</tr>
</table>
</body>
<script type="text/javascript">
var canvas = document.getElementById('space');
var context = canvas.getContext('2d');
var timerInterval = 10; // ms
var cellWidth = 10; // please ensure: cellWidth > 2
var cellXLen = 50; // please ensure: cellWidth * cellXLen = 500
var cellYLen = 50; // please ensure: cellWidth * cellYLen = 500
var cells = [];
var running = 0;
var generation = 0;
function drawCell(x,y,state){
var cx = x * cellWidth;
var cy = y * cellWidth;
if(state && state==1){
context.fillStyle = "Gold";
context.fillRect(cx, cy, cellWidth, cellWidth);
context.strokeStyle = "DarkGoldenRod";
context.strokeRect(cx+1, cy+1, cellWidth-2, cellWidth-2);
}
else{
context.clearRect(cx, cy, cellWidth, cellWidth);
}
}
function drawPatterns(){
function setCell(x,y){
cells[[x,y]] = 1;
drawCell(x, y, 1);
}
function drawGliderPattern(){
setCell(1,0);
setCell(2,1);
setCell(2,2);
setCell(1,2);
setCell(0,2);
}
drawGliderPattern();
}
function applyRule(x,y){
var neighbours = [];
var neighbourCount = 0;
var currentState = cells[[x,y]];
var nextState = 0;
neighbours[0] = cells[[(x-1+cellXLen)%cellXLen, (y-1+cellYLen)%cellYLen]];
neighbours[1] = cells[[(x-1+cellXLen)%cellXLen, (y+1+cellYLen)%cellYLen]];
neighbours[2] = cells[[(x+cellXLen)%cellXLen, (y+1+cellYLen)%cellYLen]];
neighbours[3] = cells[[(x+cellXLen)%cellXLen, (y-1+cellYLen)%cellYLen]];
neighbours[4] = cells[[(x+1+cellXLen)%cellXLen, (y+1+cellYLen)%cellYLen]];
neighbours[5] = cells[[(x+1+cellXLen)%cellXLen, (y-1+cellYLen)%cellYLen]];
neighbours[6] = cells[[(x+1+cellXLen)%cellXLen, (y+cellYLen)%cellYLen]];
neighbours[7] = cells[[(x-1+cellXLen)%cellXLen, (y+cellYLen)%cellYLen]];
for(i=0;i<8;i++){
state = neighbours[i];
if(state && state==1) neighbourCount++;
}
if(currentState && currentState==1){
if(neighbourCount<2 || neighbourCount>3) return 0;
else return 1;
}
else{
if(neighbourCount==3) return 1;
else return 0;
}
}
function loadGame(){
canvas.onmousedown = function(e){
if(running==1) return;
if(e.offsetX) {
x = e.offsetX;
y = e.offsetY;
}
else if(e.layerX) {
x = e.layerX;
y = e.layerY;
}
x = Math.floor(x / cellWidth);
y = Math.floor(y / cellWidth);
state = cells[[x,y]];
if(state && state==1)
{
cells[[x,y]] = 0;
drawCell(x, y, 0);
}
else
{
cells[[x,y]] = 1;
drawCell(x, y, 1);
}
}
drawPatterns();
}
function startGame(){
function runGame(){
var nextgen = [];
for(x=0;x<cellXLen;x++){
for(y=0;y<cellYLen;y++){
nextgen[[x,y]] = applyRule(x,y);
}
}
for(x=0;x<cellXLen;x++){
for(y=0;y<cellYLen;y++){
cells[[x,y]] = nextgen[[x,y]];
}
}
for(x=0;x<cellXLen;x++){
for(y=0;y<cellYLen;y++){
drawCell(x, y, cells[[x,y]]);
}
}
generation++;
spanGen.innerHTML = generation;
if(running==1) setTimeout(runGame, timerInterval);
}
btnStart.disabled = true;
btnPause.disabled = false;
btnReset.disabled = true;
btnRandom.disabled = true;
running = 1;
runGame();
}
function pauseGame(){
running = 0;
btnStart.disabled = false;
btnPause.disabled = true;
btnReset.disabled = false;
btnRandom.disabled = false;
}
function resetGame(){
for(x=0;x<cellXLen;x++){
for(y=0;y<cellYLen;y++){
cells[[x,y]] = 0;
drawCell(x,y,0);
}
}
drawPatterns();
generation = 0;
spanGen.innerHTML = generation;
}
function randomGame(){
for(x=0;x<cellXLen;x++){
for(y=0;y<cellYLen;y++){
s = (Math.random()>=0.8) ? 1 : 0;
cells[[x,y]] = s;
drawCell(x,y,s);
}
}
generation = 0;
spanGen.innerHTML = generation;
}
function help(){
window.open("http://en.wikipedia.org/wiki/Conway's_Game_of_Life");
}
window.addEventListener("load", loadGame, true);
</script>
<html>
下面是程序在Opera10上运行的截图,细心的读者会发现,在暂停状态下还可以用鼠标来种植新细胞,您可以动手创造一些有着有趣演化规律的细胞群。