V minulom článku o Robocode sme túto programátorskú hru stručne predstavili. Dnes sa pozrieme na niektoré programátorské koncepty, ktoré sa v nej používajú. Text bude ilustrovaný jednoduchými príkladmi, avšak kompletné návody a príklady robotov sú nad jeho rozsah. Tie nájdete v odkazoch pod článkom, prípadne medzi vzorovými robotmi z inštalácie Robocode. Tento článok by mal pomôct pri orientácii, poukázať na možnosti. A snáď inšpirovať.
Robocode a vyučovanie
Na úvod mi dovoľte malé odbočenie z praxe do akademického sveta. Robocode sa totiž začína používať ako nástroj pri výuke programovania na stredných a vysokých školách a tak by som ho rád predstavil aj z tohto pohľadu. Za zmienku stoja tieto jeho vlastnosti:
Jednoduchosť
Robocode je kompletné vývojové prostredie s arénou, editorom a kompilerom. Odpadá preto zbytočná réžia, ktorá vzniká, keď študenti musia okrem algoritmov zápasiť aj s nástrahami pre výuku zbytočne komplexného IDE. Je veľmi ľahké vytvoriť vlastného jednoduchého robota a hneď vydieť výsledky svojej práce v praxi – na bojovom poli.
Názornosť
Mnohé programátorské postupy sa tažko učia kvôli ich abstraktnosti (objekty, udalosti). Robocode ponúka velmi názorné a pochopiteľné príklady týchto konceptov. Triedy ako Robot
a Bullet
alebo udalosť HitByBulletEvent
môže študent vidiet okamžite v akcii.
Praktickosť
Roboti sa nevytvárajú pomocou nejakého „výukového“ alebo „zastaralého“ jazyka (každý doplní jazyk podľa vlastných skúseností:). Programuje sa v Jave, ktorá je v súčasnosti veľmi používaná.
Učenie pomocou problémov
Pri programovaní robotov vznikajú rôzne problémy – ako sa pohybovať, ako zameriavať nepriateľov, ako sa vyhýbať ich streľbe a podobne. Študent musí tieto problémy identifikovat a vyriešiť pomocou algoritmov.
Motivácia
Robocode si pomáha dvoma motivačnými zdrojmi: hrou a sútažou. Jeho prostredníctvom može byt výuka zábavná a navyše sa v ňom dá usporiadať turnaj alebo liga, kde študenti môžu porovnať svoje schopnosti s ostatnými.
Napriek všetkým prednostiam musím spomenút, že Robocode vyžaduje znalosť syntaxe jazyka Java a základných princípov algoritmizácie (premenné, cykly, vetvenia a podobne). Na výuku úplných zaciatočníkov sa veľmi nehodí.
Základná schéma
Vráťme sa ale k sľúbeným programátorským technikám. Aby som mohol o nich hovoriť, spomeniem najprv základnú schému programu robota. Kód je obvykle rozdelený do niekoľkých oblastí:
package mf;
import robocode.*;
import java.awt.Color;
public class MyFirstRobot extends Robot {
// oblast 1a
public void run() {
// oblast 2
setColors(Color.red, Color.blue, Color.green);
while(true) {
// oblast 3
ahead(100);
myTurnBack();
}
}
// oblast 1b
public void myTurnBack() {
turnRight(180);
}
public void onScannedRobot(ScannedRobotEvent e) {
fire(1);
}
}
Oblasť 1 (a,b)
Tu sa deklarujú pomocné premenné (obvykle 1a:), pomocné metódy používané v metóde run()
a tiež tu môžu byť prekryté metódy obsluhy udalostí, na ktoré by mal robot reagovať (obvykle v 1b, pre poriadok:).
Oblasť 2
Kód v tejto oblasti prebehne iba raz – na začiatku boja. Tu sa robot inicializuje (nastavenie farieb, určenie pozície v aréne, určenie smeru útoku/úteku).
Oblasť 3
Táto nekonečná slučka určuje chovanie robota počas boja.
Triedy a dedičnosť
Každý vytvorený robot musí byť odvodený od triedy Robot
alebo AdvancedRobot
z Robocode API. (Existuje ešte TeamRobot
, ale o tom niekedy inokedy.)
public class MyFirstRobot extends Robot
Trieda AdvancedRobot
je dedičom triedy Robot
a hierarchia týchto tried potom vyzerá nasledovne:
Naši roboti odvodení z týchto základných tried potom musia prekryť metódu run()
, aby robot niečo robil. Rovnako sa prekrývajú aj metódy obsluhy udalostí, na ktoré by mal robot reagovať.
Samozrejme, v záujme prehľadnosti kódu si programátor môže vytvoriť aj vlastné abstraktné triedy, v ktorých ukryje napríklad bežne používanú logiku a z týchto potom dediť svojich skutočných robotov:
public class MyAbstractRobot extends AdvancedRobot ... public class MyRealRobot extends MyAbstractRobot ...
Na dizajn kódu v Robocode sa však pozrieme neskôr.
Blokovacie a neblokovacie volania API
Na začiatku boja správca arény spustí robotovi jeho metódu run()
. V tej by mala byť nekonečná slučka, ktorá obsahuje správanie robota. Kód v tejto slučke pobeží dovtedy, kým nenastane jedna z nasledujúcich vecí (potom sa odovzdá kontrola ďalšiemu robotovi a tak dokola):
- Je prekročený časový limit – na vykonanie svojich príkazov má každý robot istý čas, ten je však dostatočne dlhý aj na vykonanie zložitých výpočtov. Toto sa teda zvyčajne nestáva, je to skôr ochrana pred nekonečnými cyklami.
- Vyskytne sa udalosť – udalosť preruší vykonávanie kódu v metóde
run()
a zavolá obslužnú metódu (viď nižšie). - Je zavolaná blokovacia metóda (blocking call) – blokovacie metódy nevracajú kontrolu dovtedy, kým nie je dokončená určitá činnosť, neobjaví sa nejaká udalosť alebo nie je splnená návratová podmienka.
Typicky sú blokovacie volania metódy na pohyb robota z triedy Robot
, napríklad void ahead(double distance)
. Ich volaním však nie je možné vykonávať viac činností naraz, príkazy sa vykonávajú jeden po druhom:
while(true) { ahead(100); turnLeft(90); }
Takto teda vyzerá pohyb do štvorca. To ale zvládol už Robot Karel™, doba pokročila a my tu máme preto triedu AdvancedRobot
, ktorá umožňuje volania neblokovacie. Tie na pohyb majú prefix set
a tak napríklad blokovaciemu void ahead(double distance)
zodpovedá neblokovacie void setAhead(double distance)
. Neblokovacie metódy nastavujú istý spôsob správania sa, ktorý sa začne vykonávať v momente volania blokovacej metódy. Pohyb po kruhu bude:
setMaxVelocity(5); while(true) { setAhead(1000); turnLeft(360); // blokovacie volanie }
alebo, s rovnakým výsledkom:
setMaxVelocity(5); while(true) { setAhead(1000); setTurnLeft(360); waitFor(new TurnCompleteCondition(this)); // blokovacie volanie s podmienkou }
Udalosti
Robot na okolie reaguje vďaka udalostiam, ktoré mu posiela správca arény. Je to ako pri programovaní skutočných aplikácií, ale namiesto poklepania myši udalosť vyvoláva dopad nepriatelského granátu na vežu nášho tanku. Ak robot prekrýva metódu public void onHitByBullet(HitByBulletEvent event)
, môžete sa z miesta nešťastia pokúsiť utiecť:
public void onHitByBullet(HitByBulletEvent e) { turnRight(180); ahead(100 * e.getPower()); }
Užívateľom definované udalosti a anonymné triedy
V API je celá sada preddefinovaných udalostí (viď Robocode Javadoc:), programátor si však môže vytvárať vlastné. Môžu sa pri tom využívať anonymné triedy:
public void run() { int limit = 50; addCustomEvent( new Condition("lowEnergy") { public boolean test() { return (getEnergy() < limit); }; } ); }
V tomto príklade vnútri volania addCustomEvent()
anonymne definujeme podtriedu triedy Condition
a prekrývame jej metódu test()
. Obsluha tejto užívateľskej udalosti vyzerá nasledovne:
public void onCustomEvent(CustomEvent e) {
if (e.getCondition().getName().equals("lowEnergy")) {
limit = 0;
out.println("Low energy!");
}
}
Záver
Tento článok bol trochu viac zameraný na princípy samotnej hry, ukázal však tiež, že sa pri nej dá naučiť niečo o objektoch, dedičnosti, udalostiach či o používaní anonymných tried. Nabudúce sa budeme venovať všeobecnejším konceptom ako sú dátové štruktúry, efektívny návrh znovupoužiteľného kódu, trigonometria alebo vstup/výstup. Samozrejme všetko v rámci Robocode.
Linky
Linky užitočné pri učení (sa) Robocode:
Rock 'em, sock 'em Robocode!
Rock 'em, sock 'em Robocode: Round 2
Secrets from the Robocode masters
Robocode Tutorial
Robocode FAQ