2007.08_Robocode–walki robotów_[Programowanie].pdf

(1079 KB) Pobierz
439031305 UNPDF
programowanie
Walki robotów
– walki robotów
Marek Sawerwain
Jeśli ktoś zapyta, czym jest Robocode, to odpowiedź jest bardzo prosta. Robocode to zintegrowane
środowisko, którego zadaniem jest przeprowadzanie symulacji walk wirtualnych robotów bądź też botów,
a dokładniej czołgów. Wolno potraktować Robocode jako grę komputerową, choć bardzo specyiczną.
Bowiem naszym zadaniem jest stworzenie programu, który będzie sterował wirtualnym czołgiem
i walczył z wirtualnymi przeciwnikami sterowanymi przez inne programy.
cyjne, ponieważ poprzez zabawę uczy
programować. Język używany do pro-
gramowania robotów to Java. I jest to
pełna wersja tego języka – bez żadnych uproszczeń. Ma-
my dostępne API, za pomocą którego możemy sterować
naszym robotem. Występują zdarzenia, jak choćby traie-
nie naszego pojazdu, na które możemy reagować. Wydaje
się, że prosto jest napisać program do sterowania robotem.
To po części prawda – korzystanie z klas, metod i zdarzeń
sterujących robotem jest dość łatwe, ale opracowanie stra-
tegii na tyle dobrej, aby nasz czołg wygrywał pojedynki, to
już inny problem.
Jednak zaprojektowanie nawet najprostszych robo-
tów daje wielką frajdę, toteż w tym artykule postaram się
przedstawić wszystkie niezbędne wiadomości, jakie przy-
dadzą się na początku przygody z Robocode.
ostatnia nosi numer 1.3. Aplikacja została opracowana
w całości w Javie, co oznacza, że niestety musimy mieć za-
instalowaną Javę w systemie. Ramka pt. Instalacja Javy i Ro-
bocode podpowiada jak samodzielnie zainstalować Javę
i sam program Robocode. Niektóre dystrybucje, przykła-
dowo Debian, oferują gotowe pakiety, więc warto spraw-
dzić czy nie mamy gotowych pakietów np. z Javą.
Po uruchomieniu programu warto po obejrzeniu po-
czątkowej demonstracyjnej potyczki wybrać z menu opcję
File , a następnie New , aby przeprowadzić testową bitwę. Po
wybraniu opcji New zobaczymy okno dialogowe podobne
do okna z Rysunku 1. Na początku interesująca jest tylko
zakładka Robots . Z listy dostępnych robotów za pomocą
kliknięcia przyciskiem myszy bądź przyciskiem o nazwie
Add dodajemy wybrane roboty do listy Selected Robots .
Po wybraniu kilku robotów naciskamy przycisk Start
Battle i zobaczymy arenę, gdzie nasze czołgi będą to-
czyć bój na śmierć i życie, co widać również na Rysunku 3.
Warto także dopasować wielkość areny, jeśli korzystamy
z wysokiej rozdzielczości, to początkowa arena o rozmia-
rach 800x600 jest po prostu za mała i warto ją powiększyć,
co możemy zrobić w zakładce BattleField .
Wirtualna maszyna wojenna
Program Robocode to sprawdzony i dojrzały projekt roz-
wijany od roku 2001, czyli już niemal siedem lat. Jednak
mimo to nadal pojawiają się nowe wersje tego programu,
62
sierpień 2007
Robocode
W ielką zaletą Robocode są walory eduka-
439031305.045.png 439031305.046.png 439031305.047.png
 
programowanie
Walki robotów
Listing 1. Najmniej skomplikowany czołg – sam
się nie rusza, ale czasem strzela
Rysunek 1. Początek walki, dodawanie robotów, które będą walczyć na arenie
package myrobots;
import robocode.*;
import java.awt.Color;
public class MyRobot_V1 extends
Robot {
public void run () {
setColors ( Color . blue , Color . re
d , Color . yellow ) ;
setScanColor ( Color . WHITE ) ;
while ( true ) {
doNothing () ;
}
}
public void onScannedRobot ( Scan
nedRobotEvent e ) {
ire ( 1 ) ;
}
public void onHitByBullet ( HitBy
BulletEvent e ) {
turnRight ( e . getBearing ()) ;
ire ( 1 ) ;
}
}
Rysunek 2 pokazuje, w jaki sposób zbu-
dowany jest nasz czołg. A składa się on z kil-
ku głównych części: podwozia, wieżyczki, na
której znajduje się działo oraz radaru. Natu-
ralnie za poruszanie się pojazdu odpowiada
podwozie, natomiast sama wieżyczka z dzia-
łem, identycznie jak w prawdziwym czołgu,
jest niezależna i możemy ją obracać w dowol-
nym kierunku. Podobnie radar – jest on rów-
nież niezależny i możemy nim obracać nieza-
leżnie od podwozia i działka. Ponieważ po-
ruszamy się po dwuwymiarowej arenie, to
zmiana nachylenia działa nie jest nam po-
trzebna, ale i tak opracowanie bardzo dobrej
strategii jest dużym wyzwaniem. Dość łatwo
steruje się czołgiem – np. przesunięcie się do
przodu o 100 pikseli realizujemy, pisząc po
prostu ahead(100); . Obrócenie radaru o 90
stopni w prawo sprowadza się do wywołania
metody turnRadarRight(90); . Natomiast
obrócenie samego pojazdu to zadanie dla me-
tod turnRight oraz turnLeft .
Pierwszy program
Listing 1. przedstawia najprostszy program
sterujący czołgiem na arenie. Możliwości na-
szego programu nie są naturalnie zbyt duże.
Nasz czołg zawsze stoi w miejscu, ale gdy
w jego polu rażenia znajdzie się inny czołg,
to oddajemy strzał w kierunku przeciwnika.
Reagujemy także na traienie. Jeśli ktoś strzeli
do naszego pojazdu i trai, to obrócimy czołg
w kierunku przeciwnika, a następnie odda-
my strzał w jego kierunku. Od razu trzeba
powiedzieć, iż taka bierna taktyka, powodu-
je, że w większości przypadków przegramy
pojedynki z innymi robotami.
Mimo tej uwagi warto zobaczyć jak jest
skonstruowany program, który realizuje tę
prostą strategię. Pierwsze trzy linie kodu
z Listingu 1. są typowe dla programów w Ja-
vie. Deklarujemy nowy pakiet, bowiem nasz
program sterujący to w rzeczywistości klasa.
Następnie włączamy dodatkowe klasy sło-
wem kluczowym import . Pierwszy z pakie-
tów, a dokładniej cały zestaw robocode.* , to
zbiór wszystkich przydatnych klas niezbęd-
nych do sterowania naszym robotem. Na-
stępnie włączamy deinicje kolorów, ponie-
waż nasz czołg możemy „pomalować” we-
dług własnego uznania.
W programie musi się znajdować przy-
najmniej jedna klasa, która dziedziczy z kla-
sy Robot . Klasa ta w naszym programie no-
si nazwę MyRobot_V1 i jest konieczne, aby to
była klasa publiczna. Nie ma przeszkód, aby
tworzyć inne pomocnicze klasy do wykorzy-
stania w naszym programie sterującym. Cen-
tralnym elementem każdej klasy sterującej
Tabela 1. Najważniejsze wydarzenia z życia robota-czołgu
Zdarzenie Krótki opis
onBulletHit(BulletHitEvent event); pocisk wystrzelony przez nasz czołg traił w przeciwnika
onHitByBullet(HitByBulletEvent event); przeciwnik traił w naszą maszynę
onDeath(DeathEvent event); zdarzenie pojawia się w momencie zniszczenia naszego robota
onWin(WinEvent event); zdarzenia wywoływane w momencie zwycięstwa rundy
onRobotDeath(RobotDeathEvent event); zdarzenie oznaczające zniszczenie innego robota
onScannedRobot(ScannedRobotE-
vent event);
onPaint(Graphics2D g);
używając tego zdarzenia, możemy zmienić graikę naszego
pojazdu
Rysunek 2. Schemat budowy czołgu, którym
sterujemy
www.lpmagazine.org
63
pojawienie się tego zdarzenia oznacza, że radar „zauważył”
przeciwnika
onHitRobot(HitRobotEvent event); zdarzenie występuje w momencie, gdy zderzymy się z innym
robotem
onHitWall(HitWallEvent event); pojawienie się tego zdarzenia oznacza, iż nasz czołg zderzył
się ze ścianą okalającą arenę
439031305.001.png 439031305.002.png 439031305.003.png 439031305.004.png 439031305.005.png 439031305.006.png 439031305.007.png 439031305.008.png 439031305.009.png 439031305.010.png 439031305.011.png 439031305.012.png 439031305.013.png 439031305.014.png 439031305.015.png 439031305.016.png 439031305.017.png 439031305.018.png 439031305.019.png 439031305.020.png 439031305.021.png 439031305.022.png 439031305.023.png 439031305.024.png
 
programowanie
Walki robotów
robotem jest metoda publiczna run() . W tej
metodzie znajduje się pętla sterująca zacho-
waniem robota. W naszym przypadku w tre-
ści pętli while znajduje się tylko wywołanie
metody doNothing , która, zgodnie ze swo-
ją nazwą, nie wykonuje żadnych czynności.
Głównym zadaniem tej pętli jest sterowanie
naszym robotem, np. przesunięcie robota w kie-
runku przeciwnika lub wykonanie uniku.
Listing 1. zawiera także dwie inne meto-
dy. Pierwsza z nich o nazwie onScannedRobot
jest wywoływana w momencie, gdy nasz ro-
bot wykryje innego robota. Argumentem tej
metody jest obiekt o typie ScannedRobotE-
vent . Otrzymany obiekt dostarcza kilku cen-
nych informacji jak np. odległość od przeciw-
nika. My jednak w naszym pierwszym robocie
tylko strzelamy w kierunku przeciwnika. Od-
danie strzału jest wykonywane w momencie
wywołania metody ire . Jej argumentem jest
siła ognia. Jednak trzeba pamiętać, że odda-
nie strzału powoduje nagrzewanie się działa,
jakim dysponuje nasz czołg. Dlatego nie może-
my ciągle strzelać z maksymalną siłą.
Druga metoda o nazwie onHitByBullet
jest wywoływana w momencie, gdy nasz po-
jazd jest traiony przez przeciwnika, a jej treść
jest następująca:
turnRight(e.getBearing());
ire(1);
Metoda turnRight powoduje obrócenie na-
szej maszyny w kierunku, z którego otrzyma-
liśmy traienie. Informacja o tym, o ile stopni
należy się obrócić jest dostarczona w obiekcie
o typie HitByBulletEvent . Wartość kąta (w
przedziale od -180 do 180 stopni) jest zwra-
cana przez funkcję getBearing . Obracając się
w prawo (albo w lewo, jeśli kąt będzie miał
ujemną wartość), obrócimy się dokładnie w
kierunku, z którego otrzymaliśmy traienie.
Po wykonaniu obrotu oddamy strzał (meto-
da ire ) w kierunku przeciwnika.
Instalacja Javy oraz Robocode
Niewielkie innowacje
Zamiast bezczynnie stać w miejscu, lepiej,
aby nasz pojazd znajdował się w ciągłym ru-
chu. Listing 2. pokazuje, w jaki sposób zre-
alizować poruszanie się naszego robota na
ścieżce przypominającej ósemkę. Nie zwa-
żając na wymiary areny i położenie naszego
robota, możemy za pomocą ciągu wywołań
turnLeft , ahead i turnRight poruszać pojaz-
dem. Taka zmiana taktyki powoduje, że nasz
robot staje się trudniejszym przeciwnikiem.
Możemy zaproponować również taki sposób
poruszania się:
Jeśli system przez nas używany nie zawie-
ra Javy, to warto sprawdzić czy w dystry-
bucji której używamy, istnieją odpowiednie
pakiety. Interesuje nas Java w jak najnow-
szej wersji, np. wersja 1.5.09, może to być
zarówno pakiet uruchomieniowy JRE bądź
całe kompletne środowisko JDK. Jeśli na-
sza ulubiona dystrybucja nie zawiera od-
powiednich pakietów, to musimy zainstalo-
wać środowisko Javy, np. JDK, samodziel-
nie, co wbrew pozorom jest dość łatwe. Ze
strony java.sun.com ściągamy odpowied-
ni pakiet dla Linuksa. Dobrym wyborem jest
środowisko JDK wraz z programem Netbe-
ans. Odpowiedni plik może nosić następu-
jącą nazwę:
pu do Javy, jeśli w zmiennej PATH nie został
dopisany katalog z Javą):
/opt/jdk/bin/java -jar robocode.jar
Zostaniemy zapytani tylko o katalog docelo-
wy i po wyrażeniu zgody na jego utworzenie
po krótkiej chwili zostaną przegrane wszyst-
kie potrzebne pliki. Jeśli do zmiennej PATH
nie dopisaliśmy ścieżki do Javy, trzeba do-
datkowo poprawić skrypt startujący Robo-
code o nazwie robocode.sh . Zmieniamy
następującą linię:
java -Xmx512M -Dsun.io.useCanonCache
s=false -jar robocode.jar,
ahead(100);
turnGunRight(360);
back(100);
turnGunRight(360);
dopisując pełną ścieżkę do Javy:
jdk-1_5_0_09-nb-5_5-linux.bin
Jeśli mamy już ten plik, nadajemy mu atry-
but „wykonywalny”, np. z konsoli będzie to
przedstawiać się następująco:
/opt/jdk150_09/bin/java -Xmx512M
-Dsun.io.useCanonCaches=false -jar
robocode.jar.
Nasz pojazd jedzie sto pikseli do przodu, na-
stępnie obraca wieżyczkę o 360 stopni, cofa
się o 100 pikseli i ponownie obraca wieżycz-
kę. Taka zmiana również spowoduje, że nasz
czołg stanie się bardziej wymagającym prze-
ciwnikiem.
Jednak nie zawsze należy sterować ro-
botem w metodzie run() . Bardzo często le-
piej zmieniać położenie maszyny w zależno-
ści od wydarzeń, np. jeśli zostaliśmy traieni,
warto przesunąć naszego robota o kilka pik-
seli w prawo bądź w lewo, aby uniknąć na-
stępnego traienia. Jak to zrobić? Nie jest to
trudne, bo następujące trzy linie kodu reali-
zują to zadanie:
chmod +x jdk-1_5_0_09-nb-5_5-
linux.bin
Pozostaje jeszcze problem kompilatora jikes
stosowanego przez Robocode do kompila-
cji programów sterujących. Jednakże nie
musimy się martwić czy posiadamy ten
kompilator w systemie, ponieważ Roboco-
de zawiera kod źródłowy kompilatora jikes
i przy próbie kompilacji programu sterują-
cego następuje sprawdzenie czy posiada-
my wspomniany kompilator. Jeśli nie, to
Robocode samodzielnie dokona kompila-
cji, a to oznacza, że musimy mieć zainsta-
lowany kompilator C++, czyli pakiet GCC.
Jest on obecny we wszystkich dystrybu-
cjach, więc przy jego braku wystarczy tyl-
ko doinstalować potrzebny pakiet zwiera-
jący gcc , a dokładnie rzecz ujmując – pa-
kiet z programem g++ , czyli kompilator dla
języka C++.
Po uruchomieniu programu instalacyjnego
– koniecznie w środowisku X – zobaczymy
graiczny instalator, gdzie klikając przyciski
Next oraz wybierając nazwy katalogów, za-
instalujemy Javę oraz graiczne środowisko
pracy Netbeans.
Po instalacji Javy możemy przystąpić
do instalacji Robocode, choć warto spraw-
dzić czy nasza ulubiona dystrybucja nie ofe-
ruje takiego pakietu. Jeśli nie, to ściągamy
ze strony domowej wersję instalacyjną – jest
to plik o nazwie robocode.jar . Warto, by to
była jak najnowsza wersja stabilna. Sama in-
stalacja programu sprowadza się do urucho-
mienia procesu instalacji np. w następujący
sposób (musimy podać pełną ścieżkę dostę-
turnRight(relative_angle(90 -
(getHeading() - e.getHeading())));
ahead(dist);
dist *= -1;
Wywołanie metody turnRight powoduje ob-
rót o 90 stopni względem kierunku, z jakie-
go uzyskaliśmy traienie. A zadaniem funk-
64
sierpień 2007
439031305.025.png 439031305.026.png 439031305.027.png
 
programowanie
Walki robotów
Listing 2. Modyikacja metody Run . Nasz czołg
będzie się poruszał po trasie przypominającej
ósemkę
public void run () {
setColors ( Color . red , Color . blue , C
olor . green ) ;
turnLeft ( 45 ) ;
while ( true ) {
ahead ( 100 ) ; turnRight ( 90 ) ;
ahead ( 100 ) ; turnRight ( 90 ) ;
ahead ( 100 ) ; turnRight ( 90 ) ;
ahead ( 200 ) ; turnLeft ( 90 ) ;
ahead ( 100 ) ; turnLeft ( 90 ) ;
ahead ( 100 ) ; turnLeft ( 90 ) ;
ahead ( 100 ) ;
}
Rysunek 3. Arena, na której toczy się walka
pełną siłą ognia, ponieważ możemy nie tra-
ić i stracimy niepotrzebnie sporo energii.
Dlatego będziemy strzelać standardową si-
łą ognia ire(1) , jeśli przeciwnik jest dale-
ko, powiedzmy ponad 200 pikseli, lub nasz
poziom energii jest mniejszy niż 15 jedno-
stek. Następnie – jeśli przeciwnik jest bliżej,
to strzelamy dwukrotną siłą ognia oraz osta-
tecznie – jeśli przeciwnik znajduje się bliżej
niż 50 pikseli, strzelamy potrójną siłą ognia.
Ostatecznie zapiszemy to w następujący
sposób:
cji relative_angle (Listing 3) jest konwersja
wartości kąta do wartości z przedziału -180
do 180 stopni.
Inaczej mówiąc, zadaniem pierwszej linii
kodu jest wykonanie obrotu w prawo bądź
w lewo, pomimo nazwy metody turnRight ,
ponieważ wartość kąta może być dodatnia al-
bo ujemna. Następnie przesuwamy czołg do
przodu albo w tył, co też zależy od wartości
dist . Wartość ta na przemian jest dodatnia
i ujemna, co zapewnia trzecia linijka, gdzie
mnożymy dist przez -1 . W ten sposób, jeśli
przeciwnik strzela do nas serią, mamy dużą
pewność, że zostaniemy traieni tylko raz na
początku, a pozostałe traienia nas ominą.
Inną modyikacją jest zmiana siły ognia
w zależności od tego, jak daleko znajduje
się przeciwnik. Jeśli zostanie on wykryty
przez radar, to następuje wywołanie meto-
dy onScannedRobot , a jej argumentem jest
obiekt o typie ScannedRobotEvent . Odczy-
tanie odległości do przeciwnika jest dość
proste:
enemyDist = e.getDistance( );
Każdy strzał zabiera określoną ilość energii
(i jeszcze nagrzewa działo, więc nie możemy
strzelać zawsze pełną siłą ognia), toteż jeśli
przeciwnik jest daleko, to nie warto strzelać
if ( enemyDist > 200 || getEnergy( )
< 15 ) {
ire ( 1 );
} else if ( enemyDist > 50 ) {
ire ( 2 );
} else {
ire ( 3 );
}
Listing 3. Pomocnicza funkcja do korekcji warto-
ści kąta względnego
public double relative_
angle ( double angle ) {
double good_angle = angle ;
if ( angle > - 180 && angle <=
180 ) {
return angle ;
}
while ( good_angle <= - 180 ) {
good_angle += 360 ;
}
while ( good_angle > 180 ) {
good_angle -= 360 ;
}
return good_angle ;
}
Rysunek 4. Edycja programu sterującego naszym wirtualnym wojownikiem
www.lpmagazine.org
65
439031305.028.png 439031305.029.png 439031305.030.png 439031305.031.png 439031305.032.png 439031305.033.png 439031305.034.png 439031305.035.png 439031305.036.png 439031305.037.png 439031305.038.png 439031305.039.png 439031305.040.png
 
programowanie
Walki robotów
W powyższym kodzie milcząco zakładamy
też, że radar oraz działo obracają się razem
z całą wieżyczką. Możemy to zmienić, wy-
korzystując metodę setAdjustRadarForGun-
Turn . Jeśli podamy wartość true albo false ,
pozwalamy na niezależne sterowanie rada-
rem bądź blokujemy tę możliwość. Podobnie
jest w przypadku działa – sposób funkcjono-
wania ustala się za pomocą metody setAdju-
stGunForRobotTurn .
Łatwo też sprawdzić temperaturę działa
i, jeśli wynosi ona zero, możemy strzelać ca-
łą dostępną mocą, co realizujemy za pomocą
następującego kodu:
if (getGunHeat() == 0) {
ire(Rules.MAX_BULLET_POWER);
}
Śledzenie przeciwnika
Zwiększenie mobilności naszego robota to na-
turalnie krok w dobrą stronę, jednak oprócz
tego naszego robota należy też nauczyć zna-
cznie lepiej strzelać. Dobrą taktyką będzie śle-
dzenie przeciwnika i strzelanie w jego kie-
runku tak długo, aż zostanie zniszczony. Do-
datkowo z każdą turą będziemy się przybli-
żać do śledzonego przeciwnika, w ten sposób
polepszając skuteczność ognia.
Łatwo jest przedstawić taką taktykę, ale
znacznie trudniej napisać odpowiedni kod.
Listing 4. przedstawia wszystkie najważniej-
sze fragmenty implementacji przedstawio-
nej strategii. Implementacja, jak widać, zosta-
ła dokonana tylko w metodach run oraz on-
ScannedRobot .
Na początku musimy zadeklarować trzy
dodatkowe zmienne. Pierwsza z nich, GunAn-
gleToEnemy , to kąt, pod jakim należy obrócić
działo, aby strzelać do przeciwnika. Nasz pro-
gram sterujący w trakcie pracy będzie ciągle
uaktualniał tę wartość, aby zawsze celować
w śledzony czołg. W drugiej zmiennej Ene-
myTrackingName znajduje się nazwa śledzo-
nego robota. Ostatnia trzecia zmienna turns
służy nam do swoistego odliczania, np. po
piętnastu turach do zmiennej EnemyTracking
Name wpisujemy null , co oznacza, że będzie-
my szukać innego przeciwnika. Wymienione
piętnaście tur wystarczy do zniszczenia na-
mierzonego robota.
W metodzie run nie wykonujemy zbyt
wielu czynności, jednak każda z nich jest bar-
dzo ważna. Po pierwsze – przed wejściem do
pętli while wpisujemy do EnemyTrackingNa-
me wartość null . Zezwalamy na niezależ-
ne sterowanie działem od podwozia, ustala-
my początkową wartość kąta oraz zerujemy
zmienną turns .
Natomiast w samej pętli while zawsze
za pomocą metody turnGunRight staramy
się mieć działo obrócone w kierunku prze-
ciwnika. Zmieniamy także kąt o 10 stopni,
co się przydaje w momencie, gdy aktualnie
nasz czołg nie ma namierzonego przeciwni-
ka. Następnie zwiększamy wartość zmiennej
turns i jeśli przekroczyła ona wartość piętna-
stu, usuwamy nazwę śledzonego robota.
W metodzie onScannedRobot wykonuje-
my najważniejszą „robotę” związaną ze śle-
dzeniem. Na samym początku pierwsza in-
strukcja warunkowa sprawdza czy nazwa śle-
dzonego robota jest różna od null oraz czy ak-
tualnie przekazany obiekt typu ScannedRobo-
Listing 4. Śledzenie przeciwnika
private double GunAngleToEnemy = 0 ;
private String EnemyTrackingName ;
private int turns ;
public void run () {
EnemyTrackingName = null ;
setAdjustGunForRobotTurn ( true ) ;
GunAngleToEnemy = 15 ;
turns = 0 ;
while ( true ) {
turnGunRight ( GunAngleToEnemy ) ;
GunAngleToEnemy += 10 ;
turns ++;
if ( turns > 15 ) {
EnemyTrackingName = null ;
}
}
}
public void onScannedRobot ( ScannedRobotEvent e ) {
if ( EnemyTrackingName != null && ! e . getName () . equals ( EnemyTrackingName
)) {
return ;
}
if ( EnemyTrackingName == null ) {
EnemyTrackingName = e . getName () ;
turns = 0 ;
}
if ( e . getDistance () > 150 ) {
GunAngleToEnemy = relative_angle ( e . getBearing () + ( getHeading () -
getRadarHeading ())) ;
turnGunRight ( GunAngleToEnemy ) ;
turnRight ( e . getBearing ()) ;
ahead ( e . getDistance () - 140 ) ;
return ;
}
GunAngleToEnemy = relative_angle ( e . getBearing () + ( getHeading () -
getRadarHeading ())) ;
turnGunRight ( GunAngleToEnemy ) ;
ire ( 3 ) ;
if ( e . getDistance () < 100 ) {
if ( e . getBearing () > - 90 && e . getBearing () <= 90 ) {
back ( 40 ) ;
}
else {
ahead ( 40 ) ;
}
}
scan () ;
}
66
sierpień 2007
439031305.041.png 439031305.042.png 439031305.043.png 439031305.044.png
 
Zgłoś jeśli naruszono regulamin