关于复杂自适应系统的一切曾经是学生时代的最爱,其中一个很有代表性的原型就是这种叫做“生命游戏”的元胞自动机。后来由于种种原因,并没有把这个兴趣深入延续下去。最近在看一本关于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上运行的截图,细心的读者会发现,在暂停状态下还可以用鼠标来种植新细胞,您可以动手创造一些有着有趣演化规律的细胞群。