IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  오픈 소스  >

JSEclipse로 자바스크립트 애플리케이션 작성하기 (한글)

유용한 도구를 배워가며 진화하는 창조물 만들기

developerWorks
Go to the previous page11 페이지 중 6 페이지Go to the next page

문서 옵션

샘플 코드


제안 및 의견
피드백

튜토리얼 평가

이 컨텐츠를 개선하기 위한 도움을 주십시오.


교배 다루기

생명체들은 이제 준비가 되었다. 그런데 새로운 것을 만들어내는 것은 어떻게 할까? 계속해서 교배 그리고 그와 관련된 이슈를 살펴보자.

생명체 추적하기

가장 먼저 해야 할 것은 우리가 만들어낸 생명체들을 추적하는 것이다. 따라서 그것들이 어디에 있는지 확인할 수 있어야 한다. 첫 번째 추적 타입은 매우 간단하다. 생명체들이 움직이는 턴마다, 각각의 생명체에 대한 레퍼런스를 갖고 있으면 된다. 간단하게 environment.js에 배열을 추가하여 이렇게 할 수 있다.


Listing 16. 생명체 추적하기
                    
...
var interval = 100;
var creatureList = new Array();

function rand(maxNum){
     return Math.round(maxNum*Math.random());
}

function createNewEnvironment(){
     
     size = rand(maxSize);
     creatureList[0] = new Creature();
     creatureList[0].createAt("thisId1", 0, rand(width-size),
                rand(height-size), size, 
                rand(255), rand(255), rand(255),
                -10 + rand(20), -10 + rand(20));

     size = rand(maxSize);
     creatureList[1] = new Creature();
     creatureList[1].createAt("thisId2", 1, rand(width-size),
                rand(height-size), size, 
                rand(255), rand(255), rand(255),
                -10 + rand(20), -10 + rand(20));

     setTimeout("nextTurn()", interval);
}

function nextTurn(){

     for(var thisCreatureIndex =0; 
         thisCreatureIndex < creatureList.length; 
         thisCreatureIndex++) {
          
         creatureList[thisCreatureIndex].move();
     }
     
     if (ison){
         setTimeout("nextTurn()", interval);
     }
}

새로운 배열 creatureList를 만드는 것부터 시작하자. 그리고 두 개의 변수를 만드는 대신에 두 개의 초기 생명체를 배열에 추가하자. 그러면 명시적으로 그것들의 변수 이름으로 객체를 움직이지 않고 배열을 순회하면서 nextTurn()으로 그 안에 있는 각각의 생명체를 움직일 수 있다. 이 방법으로 우리는 제어 스크립트를 사용해 다수의 생명체를 관리할 수 있다.

하지만 결국은 동적으로 생명체를 만들 수 있어야 하므로 createAt() 메서드 안에 있는 대입(또는 이 경우에는 복사본)을 제거하는 것이 좋겠다.


Listing 17. creatureList에 새로운 생명체 추가하기
                    
...        
       theDiv.style.backgroundColor= 
                      "rgb("+this.colorRed+", "+this.colorGreen+", 
                           "+this.colorBlue+")";
          
       creatureList[this.gridIndex] = this;
   }
   
   this.breedWith = function(otherCreature){ 
...

운 좋게도 gridIndex 또는 생명체가 creatureList에 자리를 잡고 있는 index를 객체의 속성으로 갖고 있으므로 이런 방법으로 쉽게 추가할 수 있다. 이 속성은 교배를 하고 있는 생명체를 알아 내고 싶을 때 유용하게 사용할 수 있다.




위로


코드 템플릿으로 쉽게 만들기: 다차원 배열

이제 하나의 생명체가 다른 생명체가 근처에 있는지 알려면, 누가 어디에 있다는 정보에 쉽게 접근할 수 있어야 한다. 그렇게 하기 위한 최선의 방법은 다차원 배열을 만드는 것이다. 그것을 사용하여 grid[233][412]의 값을 확인하는 것으로 "233, 412"에 있는 객체는 어떤 것이지?"라는 물음에 쉽게 답할 수 있다.

그렇게 하려면 다차원 배열이 필요한데 이것은 자바스크립트의 기능이 아니다. 다행히도 배열의 배열을 만들 수 있고 동적으로 값을 대입하여 그것들을 생성할 수 있다. 이 아이디어로 MultiDimensionalArray 클래스를 만들 수 있다. David Nishomoto가 실제 이것을 구현했으며(참고자료 참조) 우리는 그것을 여기에서 사용할 것이다.

그런데 이것을 사용하는 것은 매우 일반적인 코드 집합이므로 코드 템플릿 사용법을 시연하기에 좋은 기회다.

비어있는 MultiDimensionalArray.js 파일을 더블클릭하여 파일을 JSEclipse 편집기에서 연다. 그리고 "fun"("function"의 앞 부분)이라고 글자를 입력한 다음에 Ctrl+Space를 누른다. 그럼 그림 22에 보이는 것처럼 함수 템플릿으로 auto-insert function을 보여줄 것이다.


그림 22. 템플릿 삽입하기
템플릿 삽입하기

오른쪽에 있는 상자는 현재 선택된 템플릿이 무엇인지 보여준다. 그것이 바로 우리가 원한 것이기 때문에 그것을 선택하고 계속 진행한다. 그러면 그림 23에서 볼 수 있는 것처럼 자동으로 코드를 페이지에 삽입할 것이다.

계속해서 클래스를 작성해 보자. 템플릿 코드를 잠시 보면, JSEclipse가 for 루프를 위한 템플릿도 제공할 수 있음을 알 수 있을 것이다. 그림 23에 보이는 것처럼 응용할 수 있다.


그림 23. 루프 삽입하기
루프 삽입하기

이 경우에는 그냥 코드를 삽입하는 것 외에도 템플릿을 사용해 변수들을 커스터마이징하는 것과 같은 작업들을 쉽게 할 수 있다. 여기서는 인덱스 변수로 보통 사용하는 "I"를 "thisRow"로 변경하였다. 첫 번째 변수 상자에 입력하는 순간 다른 객체들도 변경된다. 따라서 한 번만 해주면 된다. 매우 편리하다!

MultiDimensionalArray 클래스의 완성된 코드를 보자.


Listing 18. MultiDimensionalArray 클래스 만들기
                    
function MultiDimensionalArray(numRows,numCols)
{
    var thisRow, thisColumn;
    var arrayRet = new Array(numRows);
    
    for(thisRow=0; thisRow < numRows; thisRow++) {
       arrayRet[thisRow] = new Array(numCols);
       for (thisColumn=0; thisColumn < numCols; thisColumn++)
       {
           arrayRet[thisRow][thisColumn] = -1;
       }	
    }	
    return(arrayRet);
}

배열은 좀 더 큰 인덱스를 추가할 때마다 자동으로 재배열되므로(커지기) 우리가 해야 할 일은 그리드에 임시 값을 갖고 있는 엔트리를 생성하는 것이다. 이 경우, "비어있는"을 -1 값으로 표현하겠다(운이 좋게도 자바스크립트는 느슨한 타입을 사용한다. 따라서 같은 배열에 숫자와 객체를 모두 넣을 수 있다).

이제 우리는 배열 인스턴스를 만들고 그 안에 객체를 넣을 수 있다.




위로


생명체 위치 추적하기

이번 아이디어는 각각의 생명체들 위치를 그리드 위에 놓고 그리드 위에서 다차원 배열로 표현하는 것이다. 즉 생명체를 만들 때 그 좌표를 그리드 배열에 추가하고 생명체가 사라질 때는 그 위치를 배열에서 제거해야 한다.

이것을 두 부분에서 다룰 필요가 있다. 먼저 그리드 배열을 environment.js에서 만들 필요가 있다.


Listing 19. Environment 그리드 만들기
                    
...
var grid = new MultiDimensionalArray(maxWidth, maxHeight);
var creatureList = new Array();
var interval = 100;

그리고 나서 Creature.js에서 생명체를 보여줄 때 그리드의 적당한 위치에 생명체를 대입해야 한다.


Listing 20. 각각의 생명체 위치 설정하기
                    
   this.move = function (){
   	
       grid[this.x][this.y] = -1;
   	
   	 this.x = this.x + this.xspeed;
   	 this.y = this.y + this.yspeed;
...

   this.createAt = function(idString, gridIdx, xpos, ypos, theSize, 
                            redLevel, greenLevel, blueLevel,
                            xspeedLevel, yspeedLevel){       
...          
       theDiv.style.backgroundColor= 
                      "rgb("+this.colorRed+", "+this.colorGreen+", 
                           "+this.colorBlue+")";
          
       grid[this.x][this.y] = this.gridIndex;
       creatureList[this.gridIndex] = this;          
   }
   
   this.renderAt = function(xpos, ypos){         
       theDiv = document.getElementById(this.id);
       theDiv.style.left = this.x;
       theDiv.style.top = this.y;
          
       grid[this.x][this.y] = this.gridIndex;
   };   
...

생명체가 만들어질 때 그것을 그리드 배열에 추가했다. 그리고 그것을 보여줄 때 새로운 위치에 배치했다. 즉 새로운 위치로 생명체를 옮길 때 예전 위치는 깨끗하게 비워야 한다. 따라서 배열을 처음 만들었을 때 우리가 했던 것처럼 -1로 설정해야 한다.

이제 교배를 할 파트너를 찾기 위해 그리드를 확인해야 할 차례다.




위로


파트너 찾기

잠재적인 파트너를 찾는 과정은 가능한 간단하게 하려고 한다. 그리드 좌표를 현재 생명체를 기준으로 다른 생명체가 있는지 확인할 것이다. 아래 변경 사항을 Creature.js에 반영하자.


Listing 21. 파트너 찾기
                    
...
   this.move = function (){
...             
           this.renderAt(this.x, this.y);
             
           this.checkForPartner();
   };
   
...

   this.checkForPartner = function(){
        
       xmax = this.x+size;
       if (this.x+size > maxWidth){
           xmax = maxWidth;
       } 
        
       ymax = this.y+size;
       if (this.y+size > maxHeight){
          ymax = maxHeight;
       } 
        
       for(var xtocheck = this.x; xtocheck < xmax; xtocheck++) {
          for(var ytocheck = this.y; ytocheck < ymax; ytocheck++) {
              if (grid[xtocheck][ytocheck] != -1 &&
                  grid[xtocheck][ytocheck] != this.gridIndex){

                  this.breedWith(
                        creatureList[grid[xtocheck][ytocheck]]);
                  break;
              }
          }                
      }
   }
}

생명체를 움직일 때마다 checkForPartner() 메서드를 호출한다. 이 메서드는 생명체의 몸체에 해당하는 좌표를 순회하면서 해당하는 좌표가 비어있지 않은 것이 있는지 확인한다. 그런 것을 발견하면 교배를 시킨다.




위로


교배

실제 두 개의 생명체에 대한 속성을 혼합하여 새로운 것을 만드는 과정은 Creature.js에 담겨있다.


Listing 22. 새로운 생명체 만들기
                    
...

   this.breedWith = function(otherCreature){
        
           babyxspeed = this.getNewValue(this.xspeed, 
                              otherCreature.xspeed, -20, 20);
           babyyspeed = this.getNewValue(this.yspeed, 
                              otherCreature.yspeed, -20, 20);
           babysize = this.getNewValue(this.size, 
                              otherCreature.size, 0, 100);
           babycolorred = this.getNewValue(this.colorRed, 
                              otherCreature.colorRed, 0, 255);
           babycolorgreen = this.getNewValue(this.colorGreen, 
                              otherCreature.colorGreen, 0, 255);
           babycolorblue = this.getNewValue(this.colorBlue, 
                              otherCreature.colorBlue, 0, 255);

           baby = new Creature();
           
           index = creatureList.length;

           baby.createAt("index"+index, index, 
                          maxWidth/2, maxHeight/2, babysize, 
                          babycolorred, babycolorgreen, 
                          babycolorblue, babyxspeed, babyyspeed);
   }

   this.checkForPartner = function(){
...
   }
   
   this.getNewValue = function(old1, old2, min, max){
       if (old1 < old2){
          low = old1;
          high = old2;
       } else {
          low = old2;
          high = old1;
       }
          
       low = low - Math.round(low * .15);
       high = high + Math.round(high * .15);
          
       if (low < min){
          low = min;
       }
       if (high > max){
          high = max;
       }
          
       spread = high - low;
       offset = Math.round(Math.random()*spread);
       return low + offset;
   }

}

상당히 많은 코드가 있지만 실제로는 매우 간단하다. 각각의 매개변수에 대해 객체에 새로운 값을 설정할 수 있다. 이때 우리는 getNewValue() 메서드를 사용해 그리드에서 가져온 두 부모의 현재 값을 혼합한다. 새로운 값을 얻어낸 후 그것을 사용하여 새로운 객체를 만들고 그 객체를 creatureList에 추가한다. 그것을 화면에 보여줄 때 그리드 배열에 추가되겠지만 각각의 객체는 그리드 중앙에 추가된다.

새로운 값은 예전 것들을 기반으로 계산한다. 범위를 15% 늘리고 최대값과 최소값을 벗어나지 않도록 한다. 거기에서 범위를 정하고 offset 값을 선택한다. 이 값은 이 범위 안에서 임의의 수로 가져오고 이 값을 최소값에 더해 새로운 값을 얻어낸다.

이런 과정을 별도의 함수로 분리하여 새로운 값을 정하는 과정을 여러분이 원하는 진화 형태에 따라 쉽게 변경할 수 있다. 예를 들어, 값을 임의로 변경하는 과정을 추가할 수도 있다.

변경 사항을 저장하고 브라우저를 다시 읽어 들이자. 그러면 깜짝 놀랄 것이다. 갑자기 베이비 붐이 일어나기 때문이다.


그림 24. 새로운 생명체 만들기
새로운 생명체 만들기

이 문제의 원인은 기존 생명체들이 서로를 순간적으로 지나가지 않기 때문이다. 즉 적어도 두 개 이상의 생명체들이 같은 장소에서 만들어졌고 그들이 또 그 순간 교배를 했기 때문이다. 이 과정은 우리가 멈추거나 컴퓨터가 다운될 때까지 계속될 것이다. 이 과정을 좀 느리게 진정시킬 필요가 있다.




위로


탄생 비율 지연시키기

베이비 붐 문제를 해결하기 위해 우리는 새로운 생명체가 적어도 50턴이 지난 후에만 만들어질 수 있게 하겠다. 이 턴을 environment.js에서 추적한다.


Listing 23. 턴 추적하기
                    
...
var elapsedTurns = 0;

function rand(maxNum){
     return Math.round(maxNum*Math.random());
}
...
function nextTurn(){
...
     if (ison){
         setTimeout("nextTurn()", interval);
     }
     elapsedTurns++;
}

물론 0부터 시작해서 elapsedTurnsnextTurn()을 호출할 때마다 증가시킨다. 그런 다음 이 값을 Creature.js에서 새로운 생명체를 만들 때마다 확인하도록 한다.


Listing 24. 경과된 턴 값 확인하기
                    
...
   this.breedWith = function(otherCreature){
        
       if (elapsedTurns < 50){
           // do nothing
       } else {
           elapsedTurns = 0;        
       
           babyxspeed = this.getNewValue(this.xspeed, 
                              otherCreature.xspeed, -20, 20);
...
           baby.createAt("index"+index, index, 
                          maxWidth/2, maxHeight/2, babysize, 
                          babycolorred, babycolorgreen, 
                          babycolorblue, babyxspeed, babyyspeed);
       }                           
   }
...

여기에서는 새로운 생명체를 만들기 전에 50턴이 경과했는지 확인하고 있다. 50턴이 경과했다면 그 값을 다시 설정하고 이전처럼 과정을 수행한다. 성능적인 의도 때문에 elapsedTurns 값을 파트너를 확인하기 전에 확인하고 싶을 수도 있다. 이제 파일을 저장하고 페이지를 다시 읽어 들이면 좀 더 생명체 집단 같은 것을 볼 수 있을 것이다.


그림 25. 더 그럴듯한 서식지
더 그럴듯한 서식지

이제 남아있는 문제는 너무 많은 집단이 될 수 있다는 것이다.




위로


늙으면 죽게 하기

마지막 변경사항은 각각의 생명체에 제한된 삶을 주는 것이다. 그들에게 "age" 속성을 추가하여 언제 목록에서 제거할지 설정할 것이다.


Listing 25. 생명체 소멸시키기
                    
...
   this.die = function(){
       this.status = 0;
       grid[this.x][this.y] = -1;
           
       this.xspeed = 0;
       this.yspeed = 0;
       this.size = 0;
       theDiv = document.getElementById(this.id);
       theDiv.style.backgroundColor = "white";
       theDiv.style.border = "0px";
   }
   
   this.move = function (){
        
       this.currentAge++;
       if (this.currentAge > this.maxage){
          this.die();
       } else {
        
           this.x = this.x + this.xspeed;
           this.y = this.y + this.yspeed;
             
...
           this.checkForPartner();
       }             
   };
   
...

아래부터 살펴보자. maxagecurrentAge를 클래스의 일부로 정의해 두었므로 생명체를 움직일 때마다 나이를 먹게 할 수 있다. 나이를 충분히 먹었다면, die() 메서드를 호출하여 그것을 제거할 것이다. 이 메서드는 실제 엘리먼트를 제거하지는 않지만 그것을 숨기고 배경 색과 동일하게 설정한다.

마지막으로 시뮬레이션을 실행할 수 있다. 몇몇 시뮬레이션은 성공적으로 수행되겠지만 몇몇은 서로 만나기 전에 죽을 것이다. 재미있게 즐기라!




위로



Go to the previous page11 페이지 중 6 페이지Go to the next page
    IBM 소개 개인정보 보호정책 문의