Friday, February 10, 2012
Retabbed
Login | Register
아티클
  Search
자바스크립트로 만들어 본 라이프게임
자바스크립트로 만들어 본 라이프게임

라이프게임은 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

javascriptLifeGame.gif

 


게시일자 Thursday, December 01, 2005 (Archive on Thursday, December 08, 2005)
게시자: 사이트관리자  저자: 불나방
돌아가기

평점:
평가:
평가 올리기

현재평점평균: 4.33
평점: 5
평점: 5
평점: 3
Terms | Privacy | host
Copyright 2005 Code99.NET
 | 아티클 | 자료실 | 블로그 | Q&A | 관리자에게