티스토리 뷰

반응형
출처 : http://www.ibm.com/developerworks/kr/library/j-robocode2/index.html


단순한 로봇을 넘어..: 자바 클래스 상속

첫 번째 글에서 만들었던 로봇은 Robot 클래스에서 모두 상속받았다:



public class DWStraight extends Robot {

DWStraight extends Robot 클래스는 turnRight()과 turnLeft() 등의 이 클래스에서 제공되는 모든 메소드를 사용할 수 있다는 것을 의미한다. 이 메소드에 대한 유일한 제한은 작동을 끝낼 때 까지 코드에 제어를 리턴하지 않는다는 것이다. 사실 이러한 작동은 완료하기 까지 많은 회전을 할 수 있다. 이 메소드를 호출함으로서 매 회전에 대해 결정할 수 있는 기능을 잃어버리게된다. 다행인것은 AdvancedRobot 이라는 클래스는 제어를 우리에게 다시 돌려준다. AdvancedRobot가 제공한 모든 메소드들을 사용하기 위해서 우리 로봇을 이것의 서브클래스(subclass)로 만들어야한다. 예를 들어 AdvancedRobot에서 상속받은 MultiMoveBot이라는 로봇을 만들려면 다음의 코드를 사용한다:



public class MultiMoveBot extends AdvancedRobot {

AdvancedRobot이 실제로 Robot의 서브클래스이고 MultiMoveBot은 AdvancedRobot의 서브클래스라는 것에 주목하라.



그림 1. 상속 관계
Inheritance relationships

클래스에서 상속을 받는것 또는 이것의 서브클래스가 되는것은 이들의 모든 public 메소드를 사용할 수 있음을 의미한다. 결과적으로 AdvancedRobot 클래스와 Robot 클래스에서 메소드를 사용할 수 있다.

다음에는 AdvancedRobot 클래스에서 상속받은 새로운 기능을 검토하겠다.

Note: 이 글에는 관련 코드 파일이 많이 있다. 이 코드를 다운로드할 것을 권한다. 이 글 자체로는 많은 것을 다루고 있기 때문에 최대 효과를 거두기 위해서는 반드시 코드를 분석해야한다.




위로


여러 개의 동시 액션 수행하기: 비 블로킹 메소드 호출

MultiMoveBot은 AdvancedRobot의 서브클래스이기 때문에 매 회전마다 로봇의 액션을 변경할 수 있다. Robocode에서 회전(turn)tick 이라고 하고 전투장에 디스플레이된 그래픽 프레임과 관련이 있다. Robocode 전투를 볼 때 시뮬레이션 엔진은 실제로 많은 정적 그래픽 프레임을 연속적으로 디스플레이한다. 영화나 텔레비젼 원리와 비슷하다. 이들 각 프레임 동안에 코드는 제어를 돌려받을 수 있고 로봇을 이용해서 무엇을 해야할지를 결정한다. 대부분의 PC들이 빠르기 때문에 하나의 프레임이나 tick 동안에 많은 코드를 실행할 수 있다.

tick 동안 로봇을 가지고 어떻게 할 것인가? 첫째, Robot 클래스와 만나는 거의 모든 메소드가 메소드 호출을 막고 있다는 것을 알게된다: 작동을 완료할 때까지 우리에게 제어를 리턴하지 않는다. 이제 MultiMoveBot는 AdvancedRobot의 서브클래스이기 때문에 비 블로킹인 새로운 메소드를 사용할 수 있다: 그들은 제어를 우리에게 동시에 리턴한다. 표 1은 우리가 사용했던 블로킹 호출이고 그에 상응하는 비 블로킹 이다:

표 1. 블로킹 vs 비 블로킹 메소드

Robot에서 상속받은 블로킹 메소드 AdvancedRobot에서 상속받은 비 블로킹 메소드
turnRight() setTurnRight()
turnLeft() setTurnLeft()
turnGunRight() setTurnGunRight()
turnGunLeft() setTurnGunLeft()
turnRadarRight() setTurnRadarRight()
turnRadarLeft() setTurnRadarLeft()
ahead() setAhead()
back() setback()

모든 비 블로킹 호출은 set 으로 시작한다. 이 호출을 사용하여 우리 로봇에게 한번에 여러 가지 일을 하도록 명령할 수 있다. 예를 들어 소스 코드의 MultiMoveBot.java 파일을 보자. (Listing 1):



Listing 1. 비 블로킹 메소드 호출로 작동하기
public class MultiMoveBot extends AdvancedRobot
{

		 ...
		 public void run() {
                ...
                setTurnRight(fullTurn);
                setAhead(veryFar);
                setTurnGunLeft(fullTurn);

이 코드는 MultiMoveBot에게 오른쪽으로 돌아 앞으로 나가서 총을 왼쪽으로 돌리가고 지시한다. 다음 순서에도 항상 같다.

위의 모든 메소드들이 로봇과 총을 회전하거나 움직이지 않고 여러분의 프로그램이 즉시 제어를 리턴한다는 것에 주목하라. 제어를 다시 Robocode로 돌려주기 전까지는 어떤 일도 일어나지 않을 것이다. 블로킹 호출을 만들어서 제어를 다시 돌려줄 수 있다. 또는 특별한 execute() 메소드를 사용할 수 있다.

execute() 메소드는 정확한 한 tick 동안 Robocode에게 제어를 준다. 체스 게임처럼 이 순서에 움직임을 결정했다는 것을 알리면 Robocode는 움직일것이다.

MultiMoveBot 로봇은 제어를 Robocode로 돌려줄 때 실제로 다른 기술을 사용한다. (Listing 2)



Listing 2. 블로킹 메소드 호출로 Robocode에 제어 돌려주기
while(true) {
			waitFor(new TurnCompleteCondition(this));
                        toggleDirection();
         	}

waitFor() 메소드는 블로킹 호출이다. 지정된 조건이 만족될 때까지 호출을 막는다. 이 경우 Robocode에게 전차 회전을 끝낸 후 제어를 리턴하라고 명령하고 있다. 물론 로봇이 앞으로 움직이는 동시에 회전하도록 설정했기 때문에 곡선으로 움직일것이다.

clock tick 동안의 움직임

tick 동안 로봇, 레이다, 총은 얼마만큼 회전할까? 이 질문은 대답하기가 쉽지 않다. 총은 20도 회전하고 레이다는 45도 회전한다. 로봇의 회전 비율은 속도에 달려있다. 하지만 총이 로봇에 탑재되어 있고 레이더는 총에 탑재되어 있기 때문에 그들의 회전 비율은 서로서로 영향을 끼친다. 정확한 수학적 계산을 원하면 메뉴 옵션의 Help->Robocode FAQ 에서 상세한 설명을 참조하기 바란다.

toggleDirection() 메소드는 호출될 때 마다 로봇의 방향(총의 방향)을 매번 바꾼다. (Listing 3)



Listing 3. 곡선 움직임 방향과 총 회전 방향 바꾸기
private void toggleDirection() {
                       if (clockwise) {
                          setTurnLeft(fullTurn);
                          setBack(veryFar);
                          setTurnGunRight(fullTurn);
                        } else {
                          setTurnRight(fullTurn);
                          setAhead(veryFar);
                          setTurnGunLeft(fullTurn);
                        }
                        clockwise = ! clockwise;
        }

우리가 그동안 다루지 않았던 메소드들도 있다. 나중에 검토하기 바란다. MultiMoveBot을 해보려면 Robocode 전투장 윈도우에서 Battle->Open 을 선택하고 showMultiMove.battle 이라고 하는 전투를 로딩한다. 로봇이 만들어내는 재미있는 움직임 패턴을 감상할 수 있다.




위로


커스텀 이벤트: 메소드 오버라이드와 anonymous 클래스

DWStraight 같은 간단한 로봇으로 이벤트를 핸들하는 코드를 보았다. 예를 들어 총알이 로봇을 맞추면 Listing 4의 코드가 실행된다.



Listing 4. 이벤트 핸들링 코드
public void onHitByBullet(HitByBulletEvent e) {
		 		 turnLeft(90 - e.getBearing());
		 }

로봇 제작자로서 핸들 할 수 있는 모든 이벤트를 보려면 Help->Robocode API를 선택한다. Robot 클래스 API만 가지고도 다음의 이벤트를 핸들 할 수 있다:

  • onBulletHit()
  • onBulletHitBullet()
  • onBulletMissed()
  • onDeath()
  • onHitByBullet()
  • onHitRobot()
  • onHitWall()
  • onRobotDeath()
  • onScannedRobot()
  • onWin()

이벤트 핸들러 메소드로 이벤트를 다루었다. 사실 상속받은 클래스에는 이러한 메소드들이 이미 포함되어 있다. 하지만 고유의 메소드를 만든다면 그것은 서브클래스에서 제공받은 것 대신하여 사용될 것이다. 이를 가상 메소드 오버라이드(virtual method override) 또는 오버라이드(override) 라고 한다.

AdvancedRobot 클래스에 유연성이 더해지면서 자신만의 커스텀(custom) 이벤트를 만들 수 있다. 커스텀 이벤트는 Robocode가 우리를 호출 할 때의 상황을 정의할 수 있다. CustomEventBot의 예를 보자:



Listing 5. CustomEventBot의 커스텀 이벤트 만들기
public class CustomEventBot extends AdvancedRobot
{
      ...
	public void run() {

     	   ...


          addCustomEvent(
			new Condition("LeftLimit") {
			  public boolean test() {
				  return (getHeading() <= quarterTurn);
				};
			}
		);

          addCustomEvent(
			new Condition("RightLimit") {
			  public boolean test() {
				  return (getHeading() >= threeQuarterTurn);
				};
			}
		);

이 코드는 anonymous 클래스로 알려진 메커니즘을 사용하여 두 개의 커스텀 이벤트를 만드는 방법을 보여주고 있다. addCustomEvent() 메소드 안에서 Condition 클래스의 새로운 서브클래스를 정의하고 이것의 테스트 메소드를 오버라이딩한다. 이 새로운 서브클래스는 anonymous 이다. 다시말하면, 이름이 없다. "LeftLimit" 같은 인자는 Condition 클래스의 구조체 메소드에 대한 인자로서 전달된다.

그림 2는 Robocode에서 사용되는 좌표와 방향 규정이다. "LeftLimit" 커스텀 이벤트는 로봇이 90도 또는 그 미만의 방향을 가지고 있으면 발사되도록 정의했다. "RightLimit" 조건은 방향이 270도 또는 그 이상일 때 발사되는 것이다.



그림 2. Robocode 좌표 시스템
Robocode coordinates system

이러한 커스텀 이벤트를 핸들하기 위해서 AdvancedRobot 클래스는 우리가 오버라이드 할 수 있는 onCustomEvent() 메소드를 갖고있다. 이 경우 탱크와 총의 회전 방향을 전환한다. (Listing 6)



Listing 6. onCustomEventBot()로 커스텀 이벤트 핸들링하기
public void onCustomEvent(CustomEvent ev) {
         Condition cd = ev.getCondition();
         System.out.println("event with " + cd.getName());
         if (cd.getName().equals("RightLimit")) {
            setTurnLeft(fullTurn);
            setTurnGunRight(fullTurn);
            }
            else {
             setTurnRight(fullTurn);
             setTurnGunLeft(fullTurn);
            }
        } 

커스텀 이벤트 정의와 핸들러는 로봇의 회전을 90도에서 270도 사이에서 제한한다. 로봇을 비트는(twist) 움직임으로 보낸다. twistergalore.battle 파일을 로딩하여 시작해도 좋다. "twist" 에 대한 좀더 역동적인 움직임을 원한다면 Options->Preferences->Visible Scan Arcs 을 선택하라.




위로


목표 위치: 인터페이스와 내부 클래스(inner class)

AdvancedRobot이 제공하는 세 가지 새로운 핵심 기능은 다음의 일을 수행한다:

  • 여러 개의 움직임을 동시에 수행하기
  • 매 clock tick 마다 로봇의 액션과 전략을 결정하기
  • 커스텀 이벤트를 정의하고 핸들하기

이러한 특징들이 고급의 로봇 구현자들에게 많은 가능성을 주지만 목표물을 찾아서 어떻게 가야하는지와 접근 방법에 대해서는 알려주지 않는다. 이것은 매우 기본적인 질문이지만 효과적인 로봇을 만들기 위해서는 해결되어야 할 문제들인 것이다. 이러한 문제에 접근하는 두 가자 방법이 있다. 간단한 방법부터 보자.

Listing 7의 DuckSeekerBot 코드 부터 살펴보자. 이 간단한 로봇은 전투장에 앉아있는 로봇을 조준하고 다가가서 공격할 것이다.



Listing 7. DuckSeekerBot 클래스 정의
public class DuckSeekerBot extends AdvancedRobot implements DuckConstants
{
        boolean targetLocked = false;
        Target curTarget = null;

이 로봇은 여분의 유연성을 위해 AdvancedRobot을 하위클래스로 두고있다는 것은 놀라운 일이 아니다. 또한 DuckConstants 인터페이스를 구현하고 그 상수를 공유한다.

DuckConstants.java를 보면 인터페이스 정의를 찾을 수 있다. 이 경우 계속 사용해오던 모든 상수들을 포함하고 있다. (halfTurn, fullTurn 등). 따라서 DuckConstants 인터페이스를 구현하는 것은 모든 상수를 클래스에 포함할 수 있는 지름길이다. 위 코드에서 curTarget이라는 변수를 만들었다는 것을 주목하라. Target은 Robocode 라이브러리의 일부가 아니다. 그럼 무엇인가?

DuckSeekerBot.java의 나중 부분(Listing 8)을 유심히 살펴보면 단서가 나온다. DuckSeekerBot 클래스 내에 완전히 새로운 클래스 정의를 발견하게 된다. 이것은 내부 클래스 정의 또는 "멤버 클래스(member class)" 라고 한다.



Listing 8. Target 멤버 클래스
class Target {
  ...
  public Target(String inname, boolean inalive, boolean inlocked) {
  ...
   }
  public boolean isAlive() {
  ...
   }
  public boolean isLocked() {
   ...
   }
  ...
}  // of Target

DuckSeekerBot 안에 Target을 멤버클래스로 선언함으로서 DuckSeekerBot 안에서 사용할 수 있게 되었다. 코드를 보면 curTarget이 onScannedRobot() 메소드를 이용해서 탐지된 현재의 목표물을 갖는데 사용되는 것을 알 수 있다. 이 타겟을 유도하는데 간단한 방법을 사용한다. 목표물을 향해 회전하고 움직여서 발사한다. Listing 9는 onScannedRobot() 메소드 안의 코드이다.



Listing 9. 목표물 유도하기
stop();
         turnRight(evt.getBearing());
         if (evt.getDistance() > safeDistance)
           ahead(evt.getDistance() - safeDistance);

DuckSeekerBot은 새로운 duck을 찾는 것과 목표물을 감금하는 것을 번갈아가면서 수행한다. DuckSeekerBot의 작동을 보려면 Battle->Open을 선택하고 duckoAducko.battle 파일을 연다.




위로


전투장 조망하기: 벡터(Vector), 다형성(polymorphism), java.Math

DuckSeekerBot:

  1. 목표 duck 정탐
  2. 목표 줌인 & 공격
  3. 전체 무리가 사라질 때 까지 반복

또 다른 접근 방식은 다음과 같다:

  1. 전투장에서 보이는 모든 duck을 조사(scan)하고 "정보 지도(intelligence map)"를 만든다.
  2. 한번에 무리에 초점을 맞추고 제거한다.
  3. 스캔 정보에서 "지도"를 지속적으로 업데이트한다.

두 번째 접근방식에서는 첫 번째와 같은 결과를 달성하지만 좀더 지능적이다. 대부분의 고급 로봇들은 순간적인 전략 결정을 고안할 때 "큰 그림(big picture)"을 사용한다. 그와같은 지도를 관리하는 방법을 배우면 좀더 뛰어난 지능을 갖춘 로봇을 만들 수 있다.

FlockRoasterBot은 다음의 헬퍼 커스텀 클래스를 사용하여 큰 그림을 관리한다:

  • Duck (Duck.java에 포함): DuckSeekerBot의 간단한 Target 처럼 이 클래스는 목표 duck에 대해 모을 수 있는 모든 정보를 갖고 있다.

  • Flock (Flock.java에 포함): 이 클래스는 우리가 찾아낸 모든 duck을 나열한다; 이것이 우리의 "정보 지도(intelligence map)" 이다. 실제로 표준 자바 라이브러리 클래스인 java.util.Vector를 하위클래스로 하고 무리(flock)를 만든다. Vector 클래스는 모든 관리 기능들을 제공해준다.

FlockRoasterBot.java 소스 코드를 보면 새롭게 스캔 된 모든 로봇이 onScannedRobot() 이벤트 핸들러에 추가된것을 알 수 있다. (Listing 10)



Listing 10. 스캔 된 로봇을 무리(flock)에 추가하기
 public void onScannedRobot(ScannedRobotEvent evt) {
        Duck nuDuck = new Duck(evt, getTime(), getX(), getY(), getHeading(),
        getVelocity(), true, false);

        if (!curFlock.contains(nuDuck))  {
           curFlock.add(nuDuck);
            }
       }

Listing 10을 보면 Duck의 새로운 인스턴스를 만들고 duck을 조사해서 모은 모든 정보를 저장했다. 그런다음 이를 flock에 추가하기 전에 새롭게 스캔 된 duck이라는 것을 확인한다. Duck.java를 자세히 보면 하나의 Duck 인스턴스를 또다른 duck 인스턴스와 비교할 때 equals() 메소드 구현을 오버라이딩 한다는 것을 알 수 있다. 이 과정은 contains() 메소드가 올바르게 작동하는데에 있어서 필수적인 과정이다.

java.util.Vector의 contains() 메소드가 Java API 라이브러리 창시자에 의해 작성되었다는 사실은 흥미롭다. 그들 중 어떤 누구도 Duck 클래스를 알지 못한다. 하지만 이는 Duck 클래스와 함께 완벽하게 작동한다. 이것은 자바 언어의 다형성을 이용한 예제이다. contains() 메소드 내부의 프로그래밍 로직은 지금 또는 앞으로도, equals() 메소드를 정확히 구현한 클래스를 통해 작동한다.

마지막으로 수학(특히, 삼각법)도 불가피하게 사용해야한다. Duck.java에 대한 소스코드를 살펴보면 DuckRoasterBot의 기존 Target 멤버 클래스의 모든 메소드 뿐만 아니라 두 개의 메소드도 갖고 있다:

  • bearingToDuck: 주어진 현재 x,y 위치에서 목표 duck에 대한 행동을 결정한다.

  • distanceToDuck: 현재 t x,y 위치에서, 목표 duck에 대한 거리를 결정한다.

이 메소드들은 Duck 클래스를 다재다능하게 한다. (Unit circle trigonometry 참조). bearingToDuck()과 distanceToDuck() 메소드에 대한 세부적인 소스 코드를 검토하기 바란다. 이 메소드들은 java.Math 코드 라이브러리를 확장 사용한것이다. java.Math 라이브러리에는 고급 수학적 메소드들이 포함되어있다.

FlockRoasterBot은 distanceToDuck() 메소드를 사용하여 공격할 가장 가까운 duck을 결정한다. getNextRoaster()에서 Flock 클래스의 메소드는 가장 가까운 duck을 선택하는 로직이다. (Listing 11)



Listing 11. getNextRoaster() 메소드
public Duck getNextRoaster(double x,double y) {
   Iterator it = this.iterator();
   double curMin = 9999.0;
   while (it.hasNext())  {
     Duck tpDuck = (Duck) it.next();
     double tpDist = tpDuck.distanceToDuck(x,y);
     // check for non-alive ducks
     if ((tpDuck.isAlive ()  && (tpDist < curMin))) {
        curMin   = tpDist;
        curDuck = tpDuck;
     } // of if
   } // of while
}

공격할 다음 duck을 결정한 후에 bearingToDuck() 메소드는 이를 유인하는데 사용될 수 있다.

FlockRoasterBot이 작동하는 것을 보려면 RoastedDucks.battle 파일에 로딩한다. "from-the-hip" 스타일의 DuckRoasterBot과 유사하다는 것을 알게 될 것이다. 하지만, Visible Scan Arcs 옵션을 (Options->Preferences->Visible Scan Arcs 선택) 실행하면, FlockRoasterBot이 각 duck 사이를 다시 스킨하지 않는다. 전투장의 큰 그림을 통해 다음 공격대상이 누구인지를 알고있기 때문에다.




위로


FlockRoasterBot을 팀 플레이에 맞게 바꾸기

최근 발표된 Robocode는 오랜 숙원이였던 팀 플레이를 지원한다. 팀 플레이 모드에서 단지 한 개의 로봇을 디자인하는 것 대신 다른 로봇들로 구성된 전체 팀을 디자인한다. 한 팀에 원하는 만큼의 다양한 클래스를 사용하거나 같은 클래스에서 많은 로봇 인스턴스를 가질 수 있다.

다음은 팀 플레이 옵션이 제공하는 것들이다:

  • 팀 리더로서 한 로봇을 지정할 수 있다. 리더는 시작할 때 추가 에너지를 얻는다(총 200 유닛). 리더가 공격당하면 팀의 모든 멤버들은 30 유닛을 잃게된다.

  • 팀 로봇들은 AdvancedRobot의 서브클래스인 TeamRobot 클래스를 사용하여 메시지를 서로 서로 보낼 수 있는 추가 기능을 갖는다.

각자의 팀 로봇을 만들려면 TeamRobot이라는 하위 클래스를 두어야한다. 또한 로봇에 Droid 인터페이스를 구현해야 한다. droid는 레이더가 없고 스캐닝이 불가능하다. 하지만 모든 droid는 비 droid 로봇과 비교해 볼 때 가외의 20 유닛으로 시작한다.

팀을 만들려면 Robot->Team->Create Team을 선택한다. Create Team 다이얼로그는 다음과 같다.(그림 3)



그림 3. Create Team dialog
Create Team Dialog

FlockRoasterBot을 수정하여 새로운 팀 플레이 모드로 작동시킬 것이다. 우리 팀은 한 개의 지능형 리더와 두개의 droid로 구성되어있다. 팀 리더와 droid들 사이에는 FlockRoasterBot에 포함된 "정보"를 배분해야한다. 이를 위해 다음 두 개의 클래스를 사용한다:

  • FlockSweepLeader: 스캐닝을 수행하고, 전투장의 정보 지도를 관리하며 다른 팀 droid에게 duck을 없애라고 명령한다. 공격에도 사용된다.

  • DuckCookerDroid: Monitors for "command to roast" from the FlockSweepLeader로 부터 "공격 명령"을 모니터링하고, duck을 유인하여 목표 duck을 공격한다. 마지막으로 duck이 잡히면 FlockSweepLeader에게 리포트를 보낸다.

Duck.java와 Flock.java를 재사용할 수 있다면 이것은 이상적이지만 그럴 수 없다. 팀 플레이에서 flock의 duck이 살아있으나 FlockSweepLeader 에게 갇혀, 현재 목표물은 아니다. 다시말해 문제의 duck은 droid에게 할당되어 공격당한다. 이는 팀 멤버중 하나가 duck을 결정할 수 있어야함을 의미한다. TeamDuck 과 TeamFlock 클래스는 이 상태를 일반 duck에게 추가한다. 그들은 각각 Duck과 Flock을 하위 클래스로 두어 내장 기능을 재사용한다.




위로


팀 내부의 메시지교환: 통신 프로토콜 디자인

팀 멤버들간에 메시지를 보내는 것이 팀이 통신할 수 있는 유일한 방법이기 때문에 승리 전략에는 정의가 잘 되어있는 통신 프로토콜이 필요하다. 팀의 모든 멤버들은 혼돈을 피하기 위해 상호 통신에 대한 규약에 합의해야한다.

우리 팀에는 FlockSweepLeader 클래스의 형태로 한 개의 리더를 가지고 있고 꽤 많은 수의 DuckCookerDroid가 있다. 그들은 다음의 프로토콜을 사용하여 통신한다:

Cook-a-duck 프로토콜
Cook-a-duck 프로토콜(표 2)은 리더가 droid의 위치를 파악하고 droid가 공격해야 할 duck을 할당하기 위해 사용한다.

표 2. Cook-a-duck 프로토콜

From To Message 설명
Leader Droids Report Position ("reppos," TeamCommands.java 참조) 모든 droid에서 팀의 모든 멤버에게 x y 정보에 대한 요청 알림.
Droid Leader x-y position (DroidPosition.java 참조) x,y 좌표 리포트. 리더는 이 정보를 사용하여 droid와 가장 가까이 있는 flock 중에서 아직 살아있는 duck을 찾아줄 때 사용한다.
Leader Droid Serialized Duck (TeamDuck.java 참조) droid에게 특정 duck을 타겟팅 할 것을 명령한다.

Cook-a-duck 프로토콜을 시작하는 코드를 검토해보자. FlockSweepLeader.java의 assignMission() 메소드를 보자.(Listing 12)



Listing 12. assignMission() 메소드
 public void assignMission() {
   		      try {
                   broadcastMessage(new String(REPORT_POSITION));
		 		 } catch (IOException e) {
                  e.printStackTrace();
		 		 }

        }

broadcastMessage() 메소드는 TeamRobot 클래스가 제공한 새로운 기능중 일부이다. REPORT_POSITION은 TeamCommand.java 인스턴스의 일부인 상수이다. 이 인스턴스는 우리가 보내는 메시지에 대한 프로토콜 상수를 포함하고 있다.

droid 입장에서, onMessageReceived() 메소드를 보자. 이것은 팀 로봇이 메시지를 받을 장소이다. 메시지 세부내용은 전달된 MessageEvent 인스턴스의 일부로서 가능하다. 메시지 내용 자체는 직렬화가 가능한 자바 객체 인스턴스가 될 수 있고 REPORT_POSITION와 FLOCK_GONE 메시지는 메시지로서 String 클래스를 보내는 반면 FlockSweepLeader 로 부터 오는 duck 할당 메시지는 메시지 내에 직렬화된 TeamDuck 인스턴스를 가지고 있다. Listing 13은 메시지로 전달되는 객체의 클래스를 결정하는 instanceof 연산자를 설명하고 있다:



Listing 13. droid가 메시지를 받는 방법
public void onMessageReceived(MessageEvent e)
    {
    Object msg = e.getMessage();
     if (msg instanceof String) {
       if (((String) msg).equals(REPORT_POSITION))
         try {
           ourLeaderTheGreat = e.getSender();
           sendMessage(e.getSender(), new DroidPosition(getX(), getY()));
           }
           catch (IOException ex) {
             ex.printStackTrace();
             }
           if (((String) msg).equals(FLOCK_GONE))
             flockGone = true;
     } // if instanceof String

             if (msg instanceof TeamDuck)
             {
                onAssignment = true;
                curTarget = (TeamDuck) msg;
             }
    }

Duck-bagged 프로토콜
Duck-bagged 프로토콜(표 3)은 droid가 duck을 손에 넣을 때마다 리더에게 신호를 보낼 때 사용된다. 리더는 정보 지도를 업데이트하고 droid에게 줄 다른 duck이 있는지를 점검한다. 있다면 리더는 Cook-a-duck 프로토콜을 사용하여 droid에게 명령한다.

표 3. Duck-bagged 프로토콜

From To Message 설명
Droid Leader Serialized DuckBagged (DuckBagged.java 참조) 할당이 완료되고 duck이 수중에있다는 것을 리더에게 알림.

Flock-gone 프로토콜
Flock-gone 프로토콜(표 4)은 리더가 남아있는 모든 droid에게 flock이 죽었음(gone)을 알린다.

표 4. Flock-gone 프로토콜

From To Message 설명
Leader Droids Flock Gone ("flockgone," TeamCommands.java 참조) 메시지를 전체 팀에게 알림.




위로


충돌 회피: 임의적 후퇴 구현하기

전투장에서 빈번하게 발생하는 문제는 팀원들끼리의 불가피한 충돌이다. 우리 팀은 충돌 후 백업을 함으로서 당황스럽고 반복적이며 에너지를 소멸시키는 충돌을 피한다. 두 개의 팀 동료를 교착상태로 두는 반복적인 전략을 피하기 위해서 세 가지 다른 움직임 중 하나를 임의로 선택한다. pickRandAvoidance() 메소드(Listing 14)는 이러한 작동을 구현한다:



Listing 14. Backup 작동
  protected void pickRandAvoidance()  {
           double tpRnd = Math.random() * 10;
           int rndInt = (int) Math.ceil(tpRnd);
           tpRnd = tpRnd % 3;
           switch (rndInt) {
             case 0:  back(100);
                      break;
             case 1:  back(10);
                      turnRight(90);
                      ahead(50);
                      break;
             case 2: back(10);
                     turnLeft(90);
                     ahead(50);
           }
        }

pickRandAvoidance() 메소드는 갇힌 목표 로봇을 칠 때마다 호출된다. onHitRobot() 이벤트 핸들러(Listing 15)는 다음의 메소드를 호출한다:



Listing 15. onHitRobot() 이벤트 핸들러
public void onHitRobot(HitRobotEvent e) {
                       pickRandAvoidance();
                        if (curTarget != null)
                           curTarget.setLocked(false);
        }

FlockSweepLeader와 DuckCookerDroid가 duck 제거를 수행할 것이기 때문에 그 둘 모두는 임의적인 후퇴와 위의 onHitRobot() 구현이 필요하다. 이들 공통의 메소드들은 dwTeamRobot 이라고 하는 베이스 클래스로 추출된다. FlockSweepLeader와 DuckCookerDroid 는 dwTeamRobot의 서브클래스이다. 이러한 유형의 리팩토링은 자바 프로그래밍에서는 일반적이다.

"duck 청소 팀(duck sweeping team)" 작동은 Battle->Open을 선택하여 teamsweep.battle 파일을 로딩한다. 리더가 초기 스캔을 끝낸 후에 파견 명령을 하는 것을 볼 수 있다. 충돌 회피 작동도 볼 수 있다. (그림 4)



그림 4. duck 청소 팀(duck sweeping team)의 전투
Distributed duck sweep team



위로


Robocode 실전: 일등 로봇

앉아있는 duck을 공격하는 것은 확실히 재미있는 일이지만 가장 나약한 상대편이라도 패배당하기까지 가만히 있는것은 아니다. 움직이는 로봇을 다룰 때 전략을 약간 수정해야한다. 예를 들어 목표 로봇에 다가가기 전에 앞으로 어디에 위치할 것인지를 예견해야한다. 이를 예견 타겟팅(predictive targeting) 이라고 한다. 뒤로 발사하는 로봇의 경우는 총알을 탐지하고 도망가는 것에 대해 생각해야한다.

이는 위대한 로봇을 만들 때 고려되어야 하는 문제들이다. 일반적인 로봇 전략 디자인은 이 글의 범위를 벗어난다. Robocode Rumble Web site (참고자료)에서 뛰어난 Robocode 로봇을 만드는 것을 다룬 자료들을 찾아야한다.

Nicator : Alisdair Owens (영국)

Nicator는 강하게 공격하고 일찍 죽는 공격적인 로봇이라고 Alisdair Owens는 설명한다. 이 로봇은 난전(그룹 전투) 상황에 특화된 로봇이다. 이것의 승리 전략은 "반 중력(anti-gravity)" 운동이다. 로봇을 가장 유리한 코너에 배치시키고 상대를 적극적으로 공격하면서 홀딩(holding) 패턴을 유지한다. Nicator는 전투장의 큰 그림을 스캐닝하고 관리한다. AdvancedRobot 클래스의 비 블로킹 액션을 확대 사용하여 매 tick 마다 결정하고 커스텀 클래스도 사용한다. (참고자료)

Wolverine : Jae Marsh (미국)

Nicator와는 다르게 Wolverine은 일대일 로봇 전투에 맞춘 로봇이다. Wolverine은 Jae Marsh가 만들었다. Wolverine의 전략은 근본적으로는 공격이고 50점의 우위에 있을 때 공격을 강화한다. Wolverine은 전투장의 최신 큰 그림을 구현하지 않는다. 이것 역시 Nicator와는 다른점이다. 대신 일시적인 전투장의 스냅샷을 사용하여 패턴-매칭 알고리즘을 사용한다. Wolverine은 AdvancedRobot 클래스의 커스텀 이벤트 기능을 사용하여 이벤트 로깅 로직을 강화한다.

RayBot : Ray Vermette (캐나다)

RayBot은 예견 타겟팅을 사용하고 전투장의 큰 그림을 관리한다. Nicator와 Wolverine 과는 다르게, RayBot은 기본적으로 방어 로봇이다. 하지만 특성을 바꿔 일대일 시나리오에서 우위에 있다고 결정하면 공격적으로 바꾼다. 스캔 된 데이터와 전추장 정보(큰 그림)는 이 로봇의 차별화 요소라고 RayBot을 만든 Ray Vermette는 설명한다.

JollyNinja : Simon Parker (호주)

JollyNinja 로봇은 혼전과 일대일 플레이 모두에 맞춰 디자인되었다. Simon에 따르면 JollyNinja의 가장 뚜렷한 특징은 움직임 전략이라고 한다. 중급 정도의 공격형 로봇은 JollyNinja를 공격하기가 힘들다. JollyNinja의 핵심은 로봇의 다음 움직임을 결정하는 전략적 평가 함수이다: AdvancedRobot 클래스의 비 블로킹 호출 기능을 사용하여 매 clock tick 마다 결정한다. JollyNinja는 전투장에 단 하나의 상대편이 남아있다고 판단되면 공격레벨을 강화한다. JollyNinja는 큰 그림을 관리하고 전투장 정보를 모아 이 전략을 수행한다. (참고자료)

Tron & Shadow : ABC (포르투갈)

ABC는 두 개의 뛰어난 로봇을 만들었다. Tron은 뚜렷한 움직임 패턴이 있는 로봇이지만 전략을 보면 방어적이다. Shadow는 공격적인 반대편이며 충돌을 피하는 움직임을 사용한다. Shadow의 유일한 점은 혼전과 일대일 플레이에 같은 전략을 사용한다는 것이다. 두 로봇 모두 AdvancedRobot 클래스의 동시적인 움직임을 사용한다.




위로


결론

Robocode 기능에 대한 의문은 자바 프로그래밍, 알고리즘 디자인, 기초 삼각법, 분산 컴퓨팅 원리를 위한 훌륭한 교육톨이 된다. Robocode는 초보의 로봇 디자이너가 승리하는 고급 로봇을 만들 수 있도록 프로그래밍과 알고리즘 디자인 마스터링을 돕는다. "단순한 또 하나의 게임"을 너머 Robocode가 교육적 목표도 달성하기를 바란다.




위로


참고자료

반응형
댓글