前言
RoboCode的混戰模式中,如何更好的掌握多個對手的情況,從而采取更好的 策略,成為每一個玩家迫切需要解決的問題。而世界級的機器人大都采用了向量 (vector)數組的方式來保存多個對手的信息。
而且vector的作用不止於此,上屆世界冠軍Yngwie還使用vector來保存子彈 的命中率信息,為更好的決策提供依據。當然這超出了我們今天的話題,有興趣 的朋友可以看一下Yngwie中的Enemy類和Strategy類。
好了,讓我們正式開始今天的vector之旅吧,如果您對JAVA中的vector還不 是特別了解,沒關系,我在最後介紹了一些關於vector的知識。
給我們的敵人排個隊
熟悉JAVA的朋友都知道,vector是用來保存一系列對象的集合。今天我們用 他來保存我們的敵人的一些信息,把這些四處亂跑的家伙都抓進我們的集合還真 不是個輕松的活。孔子雲:“萬物皆類”。所以,我們首先要聲明一個類: Track類。將我們能知道的敵人的屬性全都作為這裡類中的一個屬性:名稱、絕 對角度、敵人坦克相對於你車頭方向的相對角度、距離、能量、速率和敵人坦克 所面對的方向等。這些都是通過ScannedRobotEvent對象得到的,具體的API函數 請參考Robocode的API幫助。代碼如下:
/**
* Track類,保存目標的信息
*/
package mytest;
import robocode.*;
public class Track
{
public String Name; //敵人坦克的名稱
//敵人的絕對角度,通過計算得出
public double Heading;
//敵人坦克相對於你車頭方向的相對角度
public double Bearing;
public double Distance; //敵人坦克的距離
public double Energy; //能量
public double Velocity; //速率
public double FaceHeading; //敵人坦克面向的方向
public double trackX,trackY; //敵人坦克的坐標
//下一個標准時間中敵人坦克所在的位置
public double nextTrackX,nextTrackY;
public void update(ScannedRobotEvent e)
{
Name=e.getName(); //敵人坦克的名稱
Bearing=e.getBearing(); //敵人坦克相對於你車頭方向的 相對角度
Distance=e.getDistance(); //敵人坦克的距離
Energy=e.getEnergy(); //能量
Velocity=e.getVelocity(); //速率
FaceHeading=e.getHeading(); //敵人坦克面向的方向
}
}
在戰場上,一個優秀的指揮官會很好的利用他手頭有限的信息,而我們的信 息都來自於雷達找到敵人後產生的ScannedRobotEvent事件,至於我們能得到哪 些信息,看上邊的注釋好了,不多解釋了。
下一步就是如何把已經現身在雷達中的敵人抓到一個vector裡去了,讓我們 回到我們的機器人主體中去:派生自AdvancedRobot 類的MyVector類中。
首先,聲明一個vector類型,並在run中進行初始化。
public class MyVector extends AdvancedRobot
{
final double version=0.1; //版本號
private Vector trackVector; //聲明我們的向量數組
/**
* run: MyVector's default behavior
*/
public void run() {
out.println("myVector Version is "+version);
trackVector=new Vector(); //初始化我們的向量數組
while(true) {
// Replace the next 4 lines with any behavior you would like
showTrack();
setTurnRadarRight(360); //讓雷達不停轉
execute();
}
}
好了,vector建好了,那下一步就……
請君入隊
在Robocode中90%以上的外界信息來自於雷達的掃描,在這個例子裡,我沒有 對雷達的動作進行更細致地處理,一直讓他在不停旋轉,從而能更多的收集不同 敵人的信息。如果是在單挑模式中,可能采取雷達鎖定目標會更加有效。
只要雷達工作正常,我們就能獲取每一個敵人的信息了。當敵人的信息源源 不斷地湧入我們的onScannedRobot中,我們的機器人要像一個優秀的指揮官一樣 去鑒別情報,那些是已經有的,那些是沒有的。如果已經存在我們則更新該對象 的屬性;如果沒有的話,就在向量數組中添加一個新的成員。讓我們去 onScannedRobot事件裡看一下吧。
/**
* onScannedRobot: What to do when you see another robot
*/
public void onScannedRobot(ScannedRobotEvent e) {
if(!isInVector(e))
{
Track myTrack=new Track();
myTrack.update(e);
trackVector.add(myTrack);
}
}
我的myVector機器人是靠自定義方法 isInVector來判斷該機器人是否存在於 向量數組中的,我們等下去看isInVector的裡邊。如果isInVector返回值為 false,則初始化一個Track對象,調用它的update方法來初始化敵人的信息,然 後調用Vector類型的add方法,將該對象加入到向量數組中。
在這裡請大家注意的一點是:同一個Vector對象中可以存儲不同類型的對象 ,這是JAVA優於C++的一點,但是切忌濫用,我們在trackVector對象中存貯的對 象都是Track類型。 好了,讓我們去isInVector裡邊看看吧。
/**
* isInVector:自定義方法,判斷該機器人是否已存在於隊列中
*/
public boolean isInVector(ScannedRobotEvent e)
{
int i=0;
while(i<trackVector.size())
{
Track myTrack=(Track)trackVector.get(i);
if(myTrack.Name==e.getName())
{
myTrack.update(e);
return true;
}
i++;
}
return false;
}
isInVector 方法的基本思路是,通過傳進來的ScannedRobotEvent中的 getName來和vector中已經存在的對象的Name來進行比較,如果有相同的Name存 在,則說明該敵人的對象已經儲存在vector中了,我們只需要簡單的調用Track 類的update方法,更新信息,並返回true 就可以了。如果沒有在vector中找到 同名的機器人,則返回false,交給onScannedRobot事件來將這個機器人添加到 vector中來。
這裡我使用了Vector類型的size方法來得到向量數組中存在的對象的數量, 在後邊我們還會用到這個方法。同時使用一個int變量來控制操作哪個對象,更 好的辦法是使用迭代器,有興趣的朋友可以參考一下《JAVA編程指南》。要得到 vector中的Track對象,則需要使用Vector 類型的get方法,它指定返回第幾個 對象。注意,這裡需要進行強制類型的轉換。得到對象後我們就可以比較Track 的 Name和ScannedRobotEvent的getName()是否相同了。
敵人不見了
在 Robocode的戰場上,殺戮與被殺的幾率是相同的。不知道大家想過沒有, 如果一個敵人被干掉了,他的對象還保存在我們的vector中!如果我們的火控系 統偏巧選中了他來作為下一個攻擊目標的話……不用擔心,如果我的機器人真那 麼傻,他恐怕等不到別人被殺的情況。很簡單,我們只需要在 onRobotDeath事 件中調用Vector類型的remove方法。Remove方法是用來刪除指定位置上的對象的 。下面代碼的基本思路和 isInVector是一樣的。顯示如下:
/**
* onScannedRobot:有機器人被消滅時產生該事件
*/
public void onRobotDeath(RobotDeathEvent event)
{
int i=0;
while(i<trackVector.size())
{
Track myTrack=(Track)trackVector.get(i);
if(myTrack.Name==event.getName())
{
trackVector.remove(i);
}
i++;
}
}
顯示敵人的距離
我們這麼辛苦地保存了戰場上所有敵人的信息後,由myVector在每個基本時 間裡報告每個機器人距我們的距離。但這裡應該注意的是,myVector報告的距離 是我們的雷達最後一次看到敵人時的距離,敵人很可能已經移動了。正如一位物 理學家所說:“我們無法預測未來是因為我們無法看到真實的現在。”
我在run的while中調用了下面的函數,用來顯示當前的時間、敵人的數量及 每個敵人與我們的距離。對數量的計算用到了Vector 的size方法。
/**
* 自定義函數:顯示當前敵人的距離
*/
public void showTrack()
{
int i=0;
out.println("This Time is "+getTime());
out.println("Track's count is "+trackVector.size ());
while(i<trackVector.size())
{
Track myTrack=(Track)trackVector.get(i);
out.println(myTrack.Name+"'s Distance is "+myTrack.Distance);
i++;
}
}
好了,一個簡單的使用vector來保存敵人信息的機器人完成了,你可以在這 裡下載他的代碼。在這裡我們的機器人僅僅是將敵人的距離顯示了出來,但是, 實戰中我們可以通過對這些信息的分析,來確定下一個攻擊目標,比如最近的一 個。這就要看你的發揮了。我在這裡提供的機器人可以說是很幼稚的,甚至公然 違反了一些面向對象編程的原則,比如把類中的元素直接聲明成public。這些問 題請大家在編碼的過程中避免。我在這裡想說明的是,在Robocode中你可以使用 任何的JAVA技術,讓你的機器人更強大。
下面是我寫的機器人myVector輸出測量結果時的情況,大家可以看到Time2和 Time3時的情況是不同的,在Time2時,雷達只掃描到了Crazy和Fire兩個敵人; Time3的時候雷達又發現了Corner。當有機器人被消滅的時候,Vector中的對象 會馬上被刪除。大家如果有興趣可以從下面找到myVector的源代碼(resource), 大家可以親自實驗一下。
Vector基本概念
最後,我來為不十分熟悉JAVA的朋友來簡單講解一些Vector的基礎知識,熟 悉這些內容的朋友可以跳過。
Vector 類型定義了Object 類型的一個元素集合,它最大的特點是能夠根據 你的需要動態增長。它實現了List接口,因此你可以把它看作一個列表。Vector 中只儲存對象的引用,而不是實際的對象。這裡引用的概念和C++中的很類似, 熟悉C++的朋友可以對照理解一下。
Vector的長度可以通過size()來獲得,而它的容量則用capacity()來得到。 容量(capacity)指的是為這個Vector分配的空間,而長度(size)則是Vector 中已經使用了的空間,size和儲存的對象個數相同。而長度永遠小於容量,這點 請大家注意。
Vector的容量可以通過setSize(i)來更改,如果Vector對象占用的元素個數 小於i,則其余元素將被null填充;如果包含的數量超過i,則所有i後的對象引 用將被丟棄。
Vector 中使用add為向量數組添加新的元素。Add(yourObject)是在vector的 最後添加一個元素。而Add(i,yourObject)則是在 i指定的位置添加一個元素, 使i以後的元素向後移動,總長度加1。與此類似,set(i,yourObject)則是由 yourObject替換i位置上的元素。這裡大家需要注意的是,Vector的記數是從0開 始的,而不是從1開始的。
Vector中使用get(i)來得到對象的引用,使用remove(i)來刪除i位置上的元 素。另外你還可以使用firstElement()來得到Vector中的第一個元素。形式如下 :
YourObject you=(YourObject)vector.firstElement();
我們可以通過把一個對象作為參數傳遞給indexOf()方法來獲得存儲在一個 Vector中的對象的索引位置。
至於,迭代器等一些高級屬性,如果有興趣,大家可以參考一下《Java編程指南》。裡面進行了很詳細的解說.
最後,我還要感謝天翼.李(Skyala.Li)耐心地看完了這篇文章的初稿,並提 出了寶貴的意見。