라이프게임은 John Conway라는 사람이 만든 셀 오토마타 입니다. 매우 간단한 룰에 의거하여 생명의 탄생과 죽음을 시뮬레이션 하는 게임 아닌 게임입니다. 게임에서 사용하는 보드는 모눈종이와 같은 개념입니다. 즉 사각형으로 만들어진 셀의 집합입니다. 이러한 경우에 하나의 셀은 8개의 셀에 의하여 둘러싸이게 되는데 이것을 “이웃”이라고 합니다. 게임의 법칙은 다음과 같습니다.
1. 탄생: “이웃”이 3개일 경우 중심에 생명이 없다면 중심에 새로운 생명이 만들어집니다.
2. 죽음: “이웃”이 2개보다 작거나 3개보다 큰 경우 즉, 2 또는 3이 아닌 경우 중심의 생명은 죽습니다. 2보다 작은 경우를 “과소”, 3보다 큰 경우를 “과밀”이라고 합니다.
3. 생존: “이웃”이 2 또는 3이고 중심에 생명이 있을 경우 다음 세대까지 생존합니다.
프로그램의 핵심은 화면에 모눈종이를 그리고 그 모눈종이의 각 셀을 마우스를 사용하여 채울 수 있도록 하여 최초의 셀 모양을 그릴 수 있도록 합니다. 그 다음은 시작 버튼을 눌러 게임을 시작하고 각 세대에 따라 변하는 모습을 지켜보면 됩니다.
전체 소스코드입니다.
var
IE = document.all?true:false;
var
boardX;
var
boardY;
var
cellSize = 10;
var
boardSizeX = 50;
var
boardSizeY = 50;
var
GBData = new Array2D(boardSizeY, boardSizeX);
var
GBDataTemp = new Array2D(boardSizeY, boardSizeX);
var
GameStoped = true;
var
GBTable;
function
Array2D(dim1, dim2) {
for (var i=0; i
this[i] = new Array(dim2);
}
this.length = new Array(dim1, dim2);
}
function
findPosX(obj) {
var curleft = 0;
if (obj.offsetParent) {
while (obj.offsetParent) {
curleft += obj.offsetLeft;
obj = obj.offsetParent;
}
curleft += obj.offsetTop;
}
else
if (obj.x)
curleft += obj.x;
return curleft;
}
function
findPosY(obj) {
var curtop = 0;
if (obj.offsetParent) {
while (obj.offsetParent) {
curtop += obj.offsetTop;
obj = obj.offsetParent;
}
curtop += obj.offsetTop;
}
else
if (obj.y)
curtop += obj.y;
return curtop;
}
function
BoardTableClick(table) {
if (IE) { // grab the x-y pos.s if browser is IE
x = event.clientX + document.body.scrollLeft
y = event.clientY + document.body.scrollTop
} else { // grab the x-y pos.s if browser is NS
x = e.pageX
y = e.pageY
}
x = x - boardX;
y = y - boardY;
if(x<0)
x = 0;
if(y<0)
y = 0;
x = Math.floor(x/cellSize);
y = Math.floor(y/cellSize);
if(table.cells[x+y*boardSizeX].style.backgroundColor == 'red') {
SetPixel(x, y, 'aqua');
GBData[y][x] = 0;
} else {
SetPixel(x, y, 'red');
GBData[y][x] = 1;
}
}
function
SetPixel(x, y, color) {
GBTable.cells[x+y*boardSizeX].style.backgroundColor = color;
}
function
NewGame() {
GameBoard.innerHTML = MakeBoardTable();
GBTable = document.getElementById('BoardTable');
boardX = findPosX(GBTable);
boardY = findPosY(GBTable);
for(y=0; y
for(x=0; x
GBData[y][x] = 0;
}
}
}
function
StartGame() {
GameStoped = false;
RunGame();
}
function
StopGame() {
GameStoped = true;
}
function
CopyBoardToTemp() {
for(y=0; y
for(x=0; x
GBDataTemp[y][x] = GBData[y][x];
}
}
}
function
RecoverBoardFromTemp() {
for(y=0; y
for(x=0; x
GBData[y][x] = GBDataTemp[y][x];
}
}
}
function
RunGame() {
CopyBoardToTemp();
for(y=1; y
for(x=1; x
var n = GBData[y-1][x-1] + GBData[y-1][x] + GBData[y-1][x+1] + GBData[y][x-1] + GBData[y][x+1] + GBData[y+1][x-1] + GBData[y+1][x] + GBData[y+1][x+1];
if(GBData[y][x] == 0) {
if(n == 3) {
SetPixel(x, y, 'red');
GBDataTemp[y][x] = 1;
}
} else {
if(n < 2 || n > 3) {
SetPixel(x, y, 'aqua');
GBDataTemp[y][x] = 0;
}
}
}
}
RecoverBoardFromTemp();
if(!GameStoped)
setTimeout(RunGame, 10);
}
function
MakeBoardTable() {
var t = "<table id='BoardTable' style='background-color:Aqua' cellspacing='0' cellpadding='0' onclick='BoardTableClick(this)'>"
var row = "<tr>";
var cell = "<td width=" + cellSize + " height=" + cellSize + "></td>";
for(i=0; i<boardSizeX; i++)
row += cell;
row += "</tr>";
for(i=0; i<boardSizeY; i++)
t += row;
t += "</table>";
return t;
}
우선 모눈종이를 나타내는 화면을 어떻게 그릴지를 고민해야 합니다. 여러 가지 방법이 있겠으나 여기서는 테이블을 이용합니다. 모눈종이의 가로, 세로 크기를 정해주면 그 크기 만큼의 테이블 HTML을 생성하는 함수가 MakeBoardTable 입니다. 이 함수에서는 보드크기 만큼의 태그를 완성합니다. 한 행을 나타내는 문자열을 먼저 만든 다음 이 행을 열 개수만큼 만드는 방법을 사용합니다. 이중 루프를 통해서 셀 단위로 만들 수도 있지만 이것보다는 여기서 사용한 방법이 훨씬 빠릅니다. 이유는 자바스크립트의 문자열 결합 함수가 문자가 크질 경우 매우 느려지기 때문입니다. 아주 길고 복잡한 문자열을 만들어야 할 경우 리모트스크립팅 같은 기법을 사용하여 서버에서 만드는 것도 좋은 방법입니다.
모눈종이는 바탕색을 aqua로 하고 생명을 나타내는 방법은 바탕색을 red로 바꾸는 방법을 택합니다. SetPixel 함수를 참고하면 금방 알 수 있을 것입니다.
화면에 나타나는 모양과 별도로 프로그램에서 “이웃”을 세고 각 세대를 표현하기 위해서는 내부적인 자료구조가 필요합니다. 모눈종이 이니 당연히 2차원 배열이 필요한데 자바스크립트에는 2차원 배열을 직접적으로 선언하는 방법이 없습니다. 그러므로 이에 필요한 클래스를 하나 만들어서 사용합니다. Array2D 가 그것입니다. 1차원 배열을 여러 개 붙여서 만듭니다. 물론 그냥 1차원 배열을 사용해서도 얼마든지 2차원 배열처럼 사용할 수 있지만 2차원 배열형식을 사용하는 것이 프로그램의 가독성을 높여 줍니다.
2차원 배열은 두 개가 필요합니다. 전체 모눈종이의 각 점에 대하여 오토마타 룰을 적용한 다음에 다음세대로 옮겨가야 하기 때문입니다. 각 셀을 처리할 때 마다 그 값을 바꾸는 것이 아니라 모든 셀에 대해서 동시적으로 탄생, 죽음, 생존을 파악해야 하는데 그렇게 할 방법은 없으므로 버퍼를 하나 두는 것입니다. 라이프게임의 핵심 오토마타는 RunGame속에 있습니다.
var
n = GBData[y-1][x-1] + GBData[y-1][x] + GBData[y-1][x+1] + GBData[y][x-1] + GBData[y][x+1] + GBData[y+1][x-1] + GBData[y+1][x] + GBData[y+1][x+1];
이 부분이 “이웃”의 숫자를 구하는 식입니다.
if
(GBData[y][x] == 0) {
if(n == 3) {
SetPixel(x, y, 'red');
GBDataTemp[y][x] = 1;
}
} else {
if(n < 2 || n > 3) {
SetPixel(x, y, 'aqua');
GBDataTemp[y][x] = 0;
}
}
이 부분이 게임의 핵심 “규칙”을 적용하는 부분입니다.
모눈종이 위의 특정 셀을 마우스로 클릭했을 경우 그 셀의 배경색을 바꾸기 위해서는 마우스의 위치를 파악해야 합니다. 마우스의 위치를 파악하는 함수가 BoardTableClick 입니다. 모든 테이블의 셀에 OnClick함수를 붙여주는 것도 방법이지만 이 방법을 사용할 경우 테이블 생성에 너무 많은 시간이 걸리므로 적합한 방법이 아닙니다. 셀의 숫자가 많지 않다면 이 방법이 훨씬 편한 방법입니다. 예제와 같이 복잡한 계산을 하지 않고도 바로 그 셀을 알 수 있기 때문입니다.
아래 그림은 실행 예제의 하나입니다. 아래 예제의 전체소스는 아래 링크에서 받을 수 있습니다.
http://www.code99.net/tabid/842/ItemID/11/Default.aspx