Robocode
什么是Robocode?
其實我對機(jī)器人一直很感興趣。我記得在我還是初中的時候,就知道 AplleⅡ上有一個程序,用它來編寫簡單的機(jī)器人程序,然后相互作戰(zhàn)。當(dāng)時自己還完全不懂編程,總是向往著,那神秘的編程高手玩的游戲是怎樣的?
Robocode 就是這樣一個東西,但是更好一些。它是一個基于Java語言的機(jī)器人作戰(zhàn)游戲。 其代碼的編寫和建模都不錯,玩起來也很有趣。Robocode是很多”編程游戲”軟件中的一個,他們共同的特征是在沒有用戶輸入的狀態(tài)下許多機(jī)器人在一個 及競技場中比賽,用戶必須編制一個高效的機(jī)器人來取勝。Robocode特別的像一場機(jī)器人坦克的大混戰(zhàn),它們互相開火直到只剩一個勝利者。程序完全是由 JAVA編寫,并且玩家必須要創(chuàng)造一個繼承自Robot類的類。
你希望在玩游戲的過程中、在閃躲炮彈、執(zhí)行精確攻擊的演練中學(xué)會Java編 程的 繼承、多態(tài)性、事件處理以及內(nèi)部類這些內(nèi)容嗎?Robocode 這個游戲為全世界的 Java 開發(fā)者實現(xiàn)這個愿望,它把游戲風(fēng)潮變成了教學(xué)工具,人們對它的上癮程度令人吃驚。下面,我參考網(wǎng)友 Sing Li 以前寫的文章,讓我們一起來拆解 Robocode,同時著手建造屬于自己的、定制的、小而精悍的戰(zhàn)斗機(jī)器吧!
Robocode 是一個很容易使用的機(jī)器人戰(zhàn)斗仿真器,可以在所有支持 Java 2 的平臺上運(yùn)行。您創(chuàng)建一個機(jī)器人,把它放到戰(zhàn)場上,然后讓它同其他開發(fā)者們創(chuàng)建的機(jī)器人對手拼死戰(zhàn)斗到底。Robocode 里有一些預(yù)先做好的機(jī)器人對手讓你入門,但一旦您不再需要它們,就可以把您自己創(chuàng)建的機(jī)器人加入到正在世界范圍內(nèi)形成的某個聯(lián)盟里去和世界最強(qiáng)手對陣。
每 個 Robocode 參加者都要利用 Java 語言元素創(chuàng)建他或她的機(jī)器人,這樣就使從初學(xué)者到高級黑客的廣大開發(fā)者都可以參與這一娛樂活動。初級的 Java 的開發(fā)者們可以學(xué)習(xí)一些基礎(chǔ)知識:調(diào)用 API 代碼、閱讀 Javadoc、繼承、內(nèi)部類、事件處理等等。高級開發(fā)者們可以在構(gòu)建“最優(yōu)品種”的軟件機(jī)器人全球競賽中提高他們的編程技巧。在本文中,我們將介紹 Robocode,并指導(dǎo)您從構(gòu)建您平生第一個 Robocode 機(jī)器人開始征服世界。我們還將看一下迷人的“后臺”機(jī)制,正是它使得 Robocode 起作用。
首先當(dāng)然是下載和安裝 Robocode 啦
Robocode 是 Mathew Nelson 的智慧之作,他是 IBM Internet 部門 Advanced Technology 的軟件工程師。現(xiàn)在Robocode的主頁已經(jīng)搬遷到sourceforge這個開源網(wǎng)站上了,大家可以在這里下載RobotCode的最新版http://robocode.sourceforge.net/ 到3月21日為止最新版本是1.0.7,大小為3.2M。
好了,下載回來后當(dāng)然還要在你的電腦上安裝JAVA運(yùn)行庫才行的哦~地址是http://java.sun.com/getjava
1.先安裝好JAVA運(yùn)行庫,好像需要重啟的?忘記了……
2.把下載回來的robocode-setup.jar復(fù)制到c盤根目錄
3.打開 開始菜單 的“運(yùn)行”,輸入 java -jar “c:robocode-setup.jar” 進(jìn)行安裝
4.安裝完后就可以在開始菜單中找到Robocode的菜單了,來~我們進(jìn)入戰(zhàn)場咯!
安裝完成后,您也可以通過 shell 腳本(robocode.sh)、批處理文件(robocode.bat)或桌面上的圖標(biāo)來啟動 Robocode 系統(tǒng)。此時,戰(zhàn)場將會出現(xiàn)。在此,您可以通過菜單調(diào)用 Robot Editor 和 compiler。
Robocode 系統(tǒng)組件
當(dāng)您啟動 Robocode 時,將看到兩個GUI窗口,這兩個窗口構(gòu)成了 Robocode 的 IDE:
圖 1. Robocode IDE
戰(zhàn) 場是機(jī)器人之間進(jìn)行戰(zhàn)斗直至分出勝負(fù)的場地。主要的仿真引擎被置于其中,并且允許您在這里創(chuàng)建戰(zhàn)斗、保存戰(zhàn)斗以及打開新建的或現(xiàn)有的戰(zhàn)斗。通過界面區(qū)域內(nèi) 的控件,您可以暫停或繼續(xù)戰(zhàn)斗、終止戰(zhàn)斗、消滅任何機(jī)器人個體或獲取任何機(jī)器人的統(tǒng)計數(shù)據(jù)。此外,您可以在此屏幕上激活 Robot Editor。
Robot Editor 是一個定制的文本編輯器,它可以用于編輯生成機(jī)器人的 Java 源文件。在它的菜單里集成了 Java 編譯器(用于編譯機(jī)器人代碼)以及定制的 Robot 打包器。由 Robot Editor 創(chuàng)建并成功編譯的所有機(jī)器人都會處于戰(zhàn)場上一個部署就緒的位置。
Robocode 里的每個機(jī)器人都由一個或多個 Java 類構(gòu)成。這些類可以被壓縮成一個 JAR 包。為此,Robocode 的最新版本提供了一個可以在戰(zhàn)場 GUI 窗口中激活的“Robot Packager”。
對 Robocode 機(jī)器人的詳細(xì)分析
在寫這篇文章時,Robocode 機(jī)器人是一個圖形化的坦克。圖 2 是一個典型的 Robocode 機(jī)器人的圖解。
圖 2. 對 Robocode 機(jī)器人的詳細(xì)分析
請 注意,機(jī)器人有一門可以旋轉(zhuǎn)的炮,炮上面的雷達(dá)也是可以旋轉(zhuǎn)的。機(jī)器人坦克車(Vehicle)、炮(Gun)以及雷達(dá)(Radar)都可以單獨旋轉(zhuǎn),也 就是說,在任何時刻,機(jī)器人坦克車、炮以及雷達(dá)都可以轉(zhuǎn)向不同的方向。缺省情況下,這些方向是一致的,都指向坦克車運(yùn)動的方向。
我們先不考慮怎么編程來實現(xiàn)機(jī)器人戰(zhàn)斗,我們先用自帶的例子機(jī)器人來一場戰(zhàn)斗吧
單擊菜單上的Battle,然后選New,出現(xiàn)了New Battle對話框
圖 3. New Battle 對話框
左邊的框是Packages,相當(dāng)于一個文件夾,里面包含多個Robots(機(jī)器人)
我們選擇sample這個包,里面有Corners、Crazy、Fire等等很多例子的機(jī)器人了
隨便選擇幾個你喜歡的,然后按Add添加到Selected Robots框,進(jìn)了這個框就是準(zhǔn)備要上戰(zhàn)場的機(jī)器人了~選擇好后,按 StartBattle 開戰(zhàn)吧!
現(xiàn)在你已經(jīng)知道怎樣可以使用機(jī)器人去戰(zhàn)斗并且也構(gòu)建好你的戰(zhàn)場了,好,下面我們學(xué)習(xí)怎樣來編寫屬于自己的戰(zhàn)斗機(jī)器人!!
戰(zhàn) 場是機(jī)器人之間進(jìn)行戰(zhàn)斗直至分出勝負(fù)的場地。主要的仿真引擎被置于其中,并且允許在這里創(chuàng)建戰(zhàn)斗、保存戰(zhàn)斗以及打開新建的或現(xiàn)有的戰(zhàn)斗。通過界面區(qū)域內(nèi)的 控件,可以暫停或繼續(xù)戰(zhàn)斗、終止戰(zhàn)斗、消滅任何機(jī)器人個體或獲取任何機(jī)器人的統(tǒng)計數(shù)據(jù)。此外,我們可以在此屏幕上的Robot菜單打開 Editor,就是我們機(jī)器人的代碼編輯器了!Robot Editor 是一個定制的文本編輯器,它可以用于編輯生成機(jī)器人的 Java 源文件。在它的菜單里集成了 Java 編譯器(用于編譯機(jī)器人代碼)以及定制的 Robot 打包器。由 Robot Editor 創(chuàng)建并成功編譯的所有機(jī)器人都會處于戰(zhàn)場上一個部署就緒的位置。我們就是要在這里編寫機(jī)器人了。
選擇“File”》“New”》“Robot”來 新建一個機(jī)器人。它會首先要你輸入這個機(jī)器人的名字(注意名字首字母要大寫哦),然后要你輸入包的名字(就是保存這個機(jī)器人的文件夾名稱),這樣就生成了 一個蠢蠢的機(jī)器人XForce的代碼了~因為我們還沒替它加上人工智能,呵呵!
現(xiàn)在單擊菜單的Complie下的Complie進(jìn)行編譯,保存好,我們的機(jī)器人已經(jīng)生產(chǎn)出來咯~
現(xiàn)在關(guān)閉Editor,在進(jìn)入New Battle,Pakeage下選擇你剛才的包的名字,Robot下就有了我們新建的XForce機(jī)器人了~添加進(jìn)去吧,然后選擇多幾個其他的機(jī)器人,開始戰(zhàn)斗!
看~我們的XForce在戰(zhàn)斗了!
是否覺得它太蠢了點呢?來,繼續(xù)來學(xué)習(xí)~~
Robocode 機(jī)器人是一個圖形化的坦克,請注意,機(jī)器人有一門可以旋轉(zhuǎn)的炮,炮上面的雷達(dá)也是可以旋轉(zhuǎn)的。機(jī)器人坦克車(Vehicle)、炮(Gun)以及雷達(dá) (Radar)都可以單獨旋轉(zhuǎn),也就是說,在任何時刻,機(jī)器人坦克車、炮以及雷達(dá)都可以轉(zhuǎn)向不同的方向。缺省情況下,這些方向是一致的,都指向坦克車運(yùn)動 的方向。
附:Robot 命令
Robocode 機(jī)器人的命令集都收錄在 Robocode API Javadoc 中。這些命令都是 robocode.Robot 類的公共方法。
(1)移動機(jī)器人、炮和雷達(dá)
移動機(jī)器人及其裝備的基本命令:
- turnRight(double degree) 和 turnLeft(double degree) 使機(jī)器人轉(zhuǎn)過一個指定的角度。
- ahead(double distance) 和 back(double distance) 使機(jī)器人移動指定的像素點距離;這兩個方法在機(jī)器人碰到墻或另外一個機(jī)器人時即告完成。
- turnGunRight(double degree) 和 turnGunLeft(double degree) 使炮可以獨立于坦克車的方向轉(zhuǎn)動。
- turnRadarRight(double degree) 和 turnRadarLeft(double degree) 使炮上面的雷達(dá)轉(zhuǎn)動,轉(zhuǎn)動的方向也獨立于炮的方向(以及坦克車的方向)。
這些命令都是在執(zhí)行完畢后才把控制權(quán)交還給程序。此外,轉(zhuǎn)動坦克車的時候,除非通過調(diào)用下列方法分別指明炮(和雷達(dá))的方向,否則炮(和雷達(dá))的指向也將移動。
- setAdjustGunForRobotTurn(boolean flag):如果 flag 被設(shè)置成 true,那么坦克車轉(zhuǎn)動時,炮保持原來的方向。
- setAdjustRadarForRobotTurn(boolean flag):如果 flag 被設(shè)置成 true,那么坦克車(和炮)轉(zhuǎn)動時,雷達(dá)會保持原來的方向。
- setAdjustRadarForGunTurn(boolean flag):如果 flag 被設(shè)置成 true,那么炮轉(zhuǎn)動時,雷達(dá)會保持原來的方向。而且,它執(zhí)行的動作如同調(diào)用了 setAdjustRadarForRobotTurn(true)。
(2)獲取關(guān)于機(jī)器人的信息
- getX() 和 getY() 可以捕捉到機(jī)器人當(dāng)前的坐標(biāo)。
- getHeading()、getGunHeading() 和 getRadarHeading() 可以得出坦克車、炮或雷達(dá)當(dāng)前的方向,該方向是以角度表示的。
- getBattleFieldWidth() 和 getBattleFieldHeight() 可以得到當(dāng)前這一回合的戰(zhàn)場尺寸。
(3)射擊命令
一旦掌握了移動機(jī)器人以及相關(guān)的武器裝備的方法,我們就該考慮射擊和控制損害的任務(wù)了。每個機(jī)器人在開始時都有一個缺省的“能量級別”,當(dāng)它的能量級別 減小到零的時候,我們就認(rèn)為這個機(jī)器人已經(jīng)被消滅了。射擊的時候,機(jī)器人最多可以用掉三個能量單位。提供給炮彈的能量越多,對目標(biāo)機(jī)器人所造成的損害也就 越大。 fire(double power) 和 fireBullet(double power) 用來發(fā)射指定能量(火力)的炮彈。調(diào)用的 fireBullet() 版本返回 robocode.Bullet 對象的一個引用,該引用可以用于高級機(jī)器人。(也就是說,當(dāng)你確定能擊中對方,火力越大越好咯^_^)
(4)事件
每當(dāng)機(jī) 器人在移動或轉(zhuǎn)動時,雷達(dá)一直處于激活狀態(tài),如果雷達(dá)檢測到有機(jī)器人在它的范圍內(nèi),就會觸發(fā)一個事件。作為機(jī)器人創(chuàng)建者,我們有權(quán)選擇處理可能在戰(zhàn)斗中發(fā) 生的各類事件。基本的 Robot 類中包括了所有這些事件的缺省處理程序。但是,們可以覆蓋其中任何一個“什么也不做的”缺省處理程序,然后實現(xiàn)自己的定制行為。下面是一些較為常用的事 件:
- ScannedRobotEvent。通過覆蓋 onScannedRobot() 方法來處理 ScannedRobotEvent;當(dāng)雷達(dá)檢測到機(jī)器人時,就調(diào)用該方法。
- HitByBulletEvent。通過覆蓋 onHitByBullet() 方法來處理 HitByBulletEvent;當(dāng)機(jī)器人被炮彈擊中時,就調(diào)用該方法。
- HitRobotEvent。通過覆蓋 onHitRobot() 方法來處理 HitRobotEvent;當(dāng)您的機(jī)器人擊中另外一個機(jī)器人時,就調(diào)用該方法。
- HitWallEvent。通過覆蓋 onHitWall() 方法來處理 HitWallEvent;當(dāng)您的機(jī)器人撞到墻時,就調(diào)用該方法。
很多研究Robocode的 玩家都被其中的方向及坐標(biāo)弄糊涂了。整個屏幕哪個是0度角,整個是坐標(biāo)原點呢? 順時針與逆時針的方向如何區(qū)分?
一段英文的翻譯及說明:
- heading – absolute angle in degrees with 0 facing up the screen, positive clockwise. 0 <= heading < 360.
- bearing – relative angle to some object from your robots heading, positive clockwise. -180 < bearing <= 180
- heading:是機(jī)器人方向與屏幕正上方的角度差,方向在0到360之間.
- bearing:是機(jī)器人的某個部件如雷達(dá)發(fā)現(xiàn)的目標(biāo)與方向的角度差,順時針為正角度在-180到180之間
幾個在Robocode中很重要的概念:
- 坐標(biāo)系:Robocode整個坐標(biāo)系都是戰(zhàn)場屏幕以左下角為原點
- 絕對方向系:Robocode中不管機(jī)器人在哪個方向都是以靜態(tài)戰(zhàn)場屏幕為參照的絕對角度(也即大家說的Heading),正上方為0度角。也即不管是Robot,Gun,Radar向北為0,向東為90,向南為180,向西為270。
- 相對方向系:相對方向是Robot,Gun,Radar以機(jī)器人的動態(tài)heading角度為參照的角度差不再以整個靜態(tài)屏幕為參照了,叫它相對因為機(jī)器人的heading是隨著機(jī)器人移動而不停的在改變,heaing只是個相對物體。
- 順時針和逆時針是看另一機(jī)器人是在你的Heading角度的(0,180)還是(-180,0)之間。
再次提醒:Heading是個靜態(tài)角度,正上方總為0.不管是取Heading,還是取方向。Bearing是個角度差值,是由參照的Heading和發(fā)現(xiàn)時的Heading的差值。方向的問題就說到這,歡迎大家討論。
我看了Robocode的基礎(chǔ)知識,自己寫了個bot,放到BattleField上卻是屢戰(zhàn)屢敗……傷心ing。
Bot對于周圍環(huán)境的了解非常有限。它可以知道其它機(jī)器人的距離、方位、方向、速度和能量等級。但是,它看不到子彈。怎么才可以有效的躲避對方的子彈呢?
Bot雖然看不到子彈,但是對方的能量等級還是可以scan到了。對方只要發(fā)射子彈就會耗損能量,并且耗損的能量介于0和3之間。根據(jù)這些線索,如何發(fā)現(xiàn)其它機(jī)器人正向它開炮對于“笨笨”的Bot不就易如反掌了? ^_^
當(dāng)Bot檢測到對方發(fā)射子彈的信息時,向左或向右移動一小步,嘿嘿,子彈就打不到咯~并且大多數(shù)Bot的瞄準(zhǔn)方法是要么直接向目標(biāo)開炮,要么試著根據(jù) Bot的速度和方向來推算位置。如果我的Bot不移動,兩種算法都會正好沖著這個Bot的當(dāng)前位置開炮。哈哈哈,這時我的Bot再移動,不就全部都打不到 啦。(是不是頗有武俠小說里以靜制動的高手味道?^_^)
下面是部分代碼和注釋:
double previousEnergy = 100; //初始狀態(tài)對方能量為100
int movementDirection = 1; //移動方向
int gunDirection = 1; //炮管方向
public void onScannedRobot(ScannedRobotEvent e) {
//調(diào)整自己和對方之間的角度
setTurnRight(e.getBearing()+90-30*movementDirection);
//如果對方的能量損耗一定值,進(jìn)行躲避動作
double changeInEnergy = previousEnergy – e.getEnergy();
if (changeInEnergy>0 && changeInEnergy<=3) {
//躲避!
movementDirection = -movementDirection; //和上次的躲避方向相反
setAhead((e.getDistance()/4+25)*movementDirection);
}
//將炮管指向?qū)Ψ疆?dāng)前位置
gunDirection = -gunDirection;
setTurnGunRight(99999*gunDirection);
//射擊
fire(1);
//重新設(shè)置對方能量
previousEnergy = e.getEnergy();
}
是不是很簡單?這個技巧還存在問題。子彈一發(fā)射,我的Bot就移動,所以它最終可能會移回炮彈軌跡之內(nèi)。最好是在估計子彈要到達(dá)時再移動。
我有個更大膽的假設(shè):因為現(xiàn)在我的Bot命中率還不高,那么如果我的Bot一直不開火,只是躲避對方的子彈的話,能不能拖到對方的能量為0呢?確實存在 一點問題。對方子彈一發(fā)射,我的Bot就移動,并且這個移動是規(guī)律的來回移動。如果移動距離短了,就可能在回來的時候撞到對方的子彈;如果移動距離長了, 就等于做一個直線運(yùn)動,對方很容易計算得到Bot的運(yùn)動軌跡。還有一個問題,躲避的時候很有可能撞到墻上……(撞墻是要減energy的:~()
針對以上的問題,我另寫了一個Bot。代碼如下:
import robocode.*;
public class HanicBot extends AdvancedRobot{
private double eDist; //對方的距離
private double move; //移動的距離
private double radarMove = 45; //雷達(dá)移動的角度
private double dFirePower; //火力
public void run() {
eDist = 300;
while(true){
//每過一個周期,運(yùn)動隨機(jī)的距離
double period = 4*((int)(eDist/80)); //周期;敵人越接近,周期越短,移動越頻繁
//周期開始,則移動
if(getTime()%period == 0){
move = (Math.random()*2-1)*(period*8 – 25);
setAhead(move + ((move >= 0) ? 25: -25));
}
//避免撞墻
double heading = getHeadingRadians(); //取得bot方向的弧度數(shù)
double x = getX() + move*Math.sin(heading); //移動move后將要達(dá)到的x坐標(biāo)
double y = getY() + move*Math.cos(heading); //移動move后將要達(dá)到的y坐標(biāo)
double dWidth = getBattleFieldWidth(); //戰(zhàn)場的寬度
double dHeight = getBattleFieldHeight(); //戰(zhàn)場的長度
//當(dāng)(x,y)超過指定的范圍,則反向移動move
if(x < 30 || x > dWidth-30 || y < 30 || y > dHeight-30){
setBack(move);
}
turnRadarLeft(radarMove); //轉(zhuǎn)動雷達(dá)
}
}//end run()
public void onScannedRobot(ScannedRobotEvent e) {
eDist = e.getDistance(); //取得對方距離
radarMove = -radarMove; //設(shè)置雷達(dá)
double eBearing = e.getBearingRadians(); //取得和對方相對角度的弧度數(shù)
//將bot轉(zhuǎn)動相對的角度,以后bot的運(yùn)動將是以對方為圓心的圓周運(yùn)動
setTurnLeftRadians(Math.PI/2 – eBearing);
//轉(zhuǎn)動炮管指向?qū)Ψ?br /> setTurnGunRightRadians(robocode.util.Utils.normalRelativeAngle(
getHeadingRadians() + eBearing – getGunHeadingRadians()));
//根據(jù)對方距離射擊
dFirePower = 400/eDist;
if (dFirePower > 3){
dFirePower = 3;
}
fire(dFirePower);
}
}
首先,為了迷惑對方,不讓對方容易的得到Bot的移動規(guī)律,Bot就要在一定的時間內(nèi)做出隨機(jī)的運(yùn)動,這個很容易辦到。并且,我給Bot的運(yùn)動改變時間規(guī)定了周期。這個周期隨離對方的距離改變,敵人越接近,周期越短,移動越頻繁。
double period = 4*((int)(eDist/80));
if(getTime()%period == 0){
move = (Math.random()*2-1)*(period*8 – 25);
setAhead(move + ((move >= 0) ? 25: -25));
}
其次,Bot的運(yùn)動不是呈直線的。而是以對方為圓心的圓周運(yùn)動。
setTurnGunRightRadians(robocode.util.Utils.normalRelativeAngle(
getHeadingRadians() + eBearing – getGunHeadingRadians()));
最后是如何避免撞墻。這里要用到點三角函數(shù)-_-!! 原理就是,計算Bot一次運(yùn)動后將要達(dá)到的坐標(biāo)是不是位于規(guī)定的危險區(qū)域。如果是,則立即反方向運(yùn)動。
double heading = getHeadingRadians();
double x = getX() + move*Math.sin(heading);
double y = getY() + move*Math.cos(heading);
double dWidth = getBattleFieldWidth();
double dHeight = getBattleFieldHeight();
if(x < 30 || x > dWidth-30 || y < 30 || y > dHeight-30){
setBack(move);
}
這個Bot的威力如何?呵呵,我去測試一下先~
好了,就說到這里了,歡迎各大高手來踩……
關(guān)于其它的一些”編程游戲”
有許多軟件是基于這種思想的,Robocode它自己就是來源于機(jī)器人大戰(zhàn)Robot Battle(http://www.robotbattle.com/)這款軟件。其它的編程游戲還包括:
· AI Fleet Commander
· AI Wars
· AT-Robots
· Bolo
· BotWarz
· C-Robots
· Cadaver
· CodedWombat
· Colobot
· Corewars
· CybWar
· GRobots
· DroidBattles
· Karel the Robot
· Mindrover
· IntelliBots
· Omega
· RealTimeBattle
· Robot Wars
· RoboWar
· SRobots
· VBRobots
就我所看過的”編程游戲”,Robocode是最簡單上手的。
· 它非常容易上手,是特別為教學(xué)而設(shè)計的
· 它具有平滑且吸引人的圖形
· 它完全地將編輯器,編譯器和運(yùn)行環(huán)境集成在了一起。
· 它是由JAVA編寫的,且JAVA非常適合當(dāng)作初學(xué)語言
總結(jié)