Programmieranleitung: Spieler und Spielstrategien


Jeder Fußballspieler wird durch eine Java-Klasse definiert. Mehrere Spieler einer Mannschaft können Instanzen derselben Klasse sein. Es ist aber auch möglich und sinnvoll für verschiedene Spieler verschiedene Klassen vorzusehen (z.B. Abwehrspieler, Stürmer, etc.).
Das Framework zum Programmieren kann von der Informatik 1 Homepage heruntergeladen werden. Zum Testen des Frameworks kann der Dummy-Spieler TestPlayer.java verwendet werden.

Die Basisklasse zum Ausbauen

Das folgende kleine Java Programm genügt, um einen Spieler zu implementieren, der nur herumsteht und sonst nichts tut:
      
import org.RoboBall.*;

public class BasicPlayer extends Player
{
    public BasicPlayer() { super(); }

    public SoccerAction makeMove()
    {

        return SoccerAction.nothing();

    }

}

Die Bestandteile des Programms sind schnell aufgezält. Mit "import org.RoboBall.*;" werden alle zur Verfügung stehenden Aktionen, die die Spieler durchführen können zugänglich gemacht. Der Konstruktor "public BasicPlayer()" ruft lediglich den Konstruktor der Oberklasse Player auf. Es ist möglich - aber nicht nötig - den Konstruktor zu erweitern. Die eigentliche Aktivität des Spielers wird in der Methode "public SoccerAction makeMove()" definiert. Diese Methode wird vom Spielprogramm regelmäßig aufgerufen. Jeder Spieler kommt so reihum einmal dran, bevor die Runde mit dem nächsten Spielzug wieder von vorne beginnt. Bei jedem Aufruf kann die Methode dann eine Aktion (vom Typ SoccerAction) zurückgeben. Die zur Verfügung stehenden Aktionen werden weiter unten in diesem Tutorial aufgelistet und beinhalten im wesentlichen Bewegungen des Spielers. Bevor man sich für eine Bewegung entscheidet, ist es möglich, sich auf dem Feld umzusehen, festzustellen, wo man (der Spieler) selbst steht, wo die anderen Spieler und der Ball sich befinden. Auch dafür gibt es fertig implementierte Methoden, die von "public SoccerAction makeMove()" aufgerufen werden können.

Ein erster eigener Spieler


Um eigene Spieler zu schreiben, erzeugt man sich am besten eine Kopie der Datei "TestPlayer.java", nennt sie um in "MyFirstPlayer.java" oder "MyOffensePlayer.java", benennt den Namen der Klasse darin auch entsprechend um und editiert den Rumpf der Methode "makeMove()". Ein erster Spieler, der mehr als nur nichts tut, wäre zum Beispiel der folgende:
      
import org.RoboBall.*;

public class MyFirstPlayer extends Player
{
    public MyFirstPlayer() { super(); }

    public SoccerAction makeMove()
    {

        return SoccerAction.move(UP);

    }

}

Dieser Spieler macht bei jedem Spielzug einen Schritt nach vorne. Bewegungen können sein "move(UP)", "move(DOWN)", "move("RIGHT)" und "move(LEFT)". Hierbei bedeutet "UP" immer "in Richtung des gegnerischen Tores, "DOWN" immer in Richtung des eigenen Tores, "RIGHT" immer in Richtung der rechten Seite aus der Sicht des eigenen Tores, und "LEFT" immer in Richtung der linken Seite aus der Sicht des eigenen Tores. Die Bewungsrichtungen sind also immer unabhängig von der Aufteilung der Platzhälften, von der Position der Spieler und von der - sowieso nicht vorhandenen - Blickrichtung.

Ein paar "Beispieler"


Im folgenden sind einige Spieler aufgeführt, die ein paar einfache Strategien verfiolgen (der Einfachheit halber ist nur die Methode "makeMove()" angegeben:

// Spieler, der sich immer zum
// gegnerischen Tor hin bewegt.

public SoccerAction makeMove()

{

  return SoccerAction.move(UP);

}
// Ein Spieler, der wartet, bis der Ball
// bei ihm erscheint, und dann nach vorne dribbelt.

public SoccerAction makeMove()

{
  if (BallAround(getMyPos()) {
     return SoccerAction.dribble(UP);
  }
  return SoccerAction.nothing();

}
// Spieler, der als Torwart fungiert,
// und immer versucht, den Ball auf
// der Linie abzuweheren.

public SoccerAction makeMove()

{
  if (getBallXPos() < getMyXPos())
    return SoccerAction.move(LEFT);
  if (getBallXPos() > getMyXPos())
    return SoccerAction.move(RIGHT);
  return SoccerAction.move(DOWN);

}
// Ein Spieler, der, wenn er den Ball hat,
// versucht, diesen zum weiter vorne stehenden
// Mannschaftskameraden abzuspielen.

public SoccerAction makeMove()

{
  if (lookTo(UP) == BALL)
  return SoccerAction.pass(UP);

}

So lassen sich beliebige weitere Spielstrategien programmieren. Über die Funktionen sendMessageTo und sendMessageToAll kann man Informationen zwischen den Spielern austauschen. Die Nachricht ist dabei immer ein Object (z.B. ein String). Mit receiveMessages bekommt man ein Array aller Nachrichten, die an einen während der letzten Runde geschickt wurden. Wenn man sie nicht verwendet verfallen sie zur nächsten Runde.
Im folgenden werden die einzelnen durchführbaren Aktionen aufgelistet. Eine vollständige Dokumentation im JavaDoc-Format steht auch auf dem Server zum Download zur Verfügung.

Mögliche Spielaktionen


Es gibt 5 mögliche Aktionen: move, run, dribble, pass und nothing. Die ersten haben als Parameter eine Richtung. Richtungen können sein UP, DOWN, LEFT und RIGHT.

move(<Richtung>)

Bewegt den Spieler einen Schritt in die angegebene Richtung. Falls auf dem Zielfeld ein Spieler egal welcher Mannschaft steht, wird dieser in diese Richtung geschoben. Falls kein Platz zum Schieben vorhanden ist (anderer Spieler, Spiefeldrand, Ball), wird ein Foul erzeugt. Bei einem Foul tauscht der gefoulte Spieler seine Position mit dem Foulenden. Falls auf dem Zielfeld der Ball ist, wird dieser auch verschoben. Bälle koennen wie Spieler geschoben werden.

run(<Richtung>)

Bewegt den Spieler um zwei Schritte in die angegebene Richtung. Falls dabei nur ein Schritt gemacht werden kann, weil das um zwei Schritte entfernte Feld besetzt ist, dann verhält sich run(<RICHTUNG>) wie move(<Richtung>).

dribble(<Richtung>)

Bewegt den angrenzendem Ball auf das Feld, das einen Schritt vom Spieler die angegebene Richtung liegt. D.h. dribble(UP) positioniert den Ball vor den Spieler, egal wo er vorher war. Der Spieler bewegt sich nicht. Der Ball kann nicht auf ein Feld gedribbelt werden, auf dem schon ein Spieler steht.

pass(<Richtung>)

Bewegt den Ball in die angegebene Richtung so lange bis er auf einen Spieler stößt. Kurz davor bleibt er stehen. Wenn in der angegebenen Richtung kein Spieler mehr kommt, dann wird der Ball nur um eine Position verschoben.

nothing()

Tut nichts. Bleibt einfach stehen.

Zustandsabfragen

Für das Finden einer sinnvollen Spielaktion können verschiedene Informationen über den Spielzustand eingeholt werden. Einige Mehtoden erwarten als Parameter eine Richtungsabgabe (die gleichen Konstanten wie bei den Spielaktionen) oder eine Ortsangabe. Ortsangaben können in Form von zwei Koordinaten (int x, int y) angegeben werden. Es ist auch möglich, statt dessen eine Instanz der java.awt.Point Klasse zu verwenden. Die Zustandsabfragemöglichkeiten sind:

Wo ist der Ball?

Dazu können die Methoden ballAround(<ORT>), getBallPos(), getBallXPos(), und getBallYPos() verwendet werden. ballAround() liefert als Ergebnis eine Richtungskonstante (UP, DOWN, LEFT, RIGHT oder NOTHING). getBallPos() liefert als Ortsangabe des Balls ein java.awt.Point-Objekt, die anderen beiden Methdden liefern jeweils die X oder Y Koordinate des Balls als int-Wert. Mit selfBallAround() erhält man die Information ob der Ball auf einem dem Spieler angrenzenden Feld liegt.

Kann ich ein Tor schießen?

Um festzustellen, ob mit einem Spielzug ein Tor gemacht werden könnte bietet sich der Aufruf folgender Methoden an: canScoreGoal() liefert eine SoccerAction, die unmittelbar zu einem Tor füren würde oder null, falls dies nicht möglich ist.  canScoreGoal(<ORT>) liefert die gleiche Information unter der Annahme, daß ein Spieler der eigenen Mannschaft an dem angegebenen Ort stehen würde.

Wo sind die (anderen) Spieler?

getMyPos() liefert die eigene Position als java.awt.point-Objekt. Alternativ kann man auch getMyXPos() und getMyYPos() verwenden, um die Position als getrennte X und Y Koordinaten zu erhalten. findRobots()liefert alle Positionen der Roboter aus dem angegebenen Team (entweder MY_TEAM oder OTHER_TEAM) als Array von java.awt.Point-Objekten. D.h. zum Beispiel findRobots(OTHER_TEAM)[0].X  ist die X-Koordinate des ersten (naja nullten) Spielers der gegnerischen Mannschaft. Manchmall kann es interessant sein, zu erfahren, was für ein Spieler auf einem Bestimmten Feld steht. Dazu dient die Methode getRobotClassName(java.awt.Point pos). So läßt sich die Entscheidung für einen Spielzug in Abhängigkeit der Klasse der anderen Spieler machen. getRobotClassName()funktioniert bei eigenen Spielern.
Mit den Methoden nextOtherRobotAround(<RICHTUNG>) und nextOtherRobotAround(<ORT>)  erhält man die Richtungskonstante, in welcher der Spieler steht, der als erstes auf das angegebene Feld oder anf aus in die angegebene Richtung angrenzende Feld treten darf . Das gleiche gibt es auch für Spieler der eigenen Mannschaft, nämlich als nextOwnRobotAround(...). oder mannschaftsunabhängig als nextRobotAround(<ORT>).

Sich umschauen

int lookAt(<ORT>) und int lookTo(<RICHTUNG>) liefern Informationen über den Inhalt der durch den Ort angegeben Felder oder der durch die Richtung angegebenen an mich angrenzenden Felder. Die Rückgabewerte vom Typ int sind die (selbsterklärenden) Konstanten: BALL, MY_TEAM, OTHER_TEAM, FREE, oder WALL. Man kann sogar "um die Ecke" und "durch andere Spieler hindurch" blicken mit int lookToRel(<ORT>,<RICHTUNG>) oder mit int lookToRel(<ORT>, <RICHTUNG>, int distance).  Diese Methoden erlauben es von einem angegeben Ort aus in die angegeben Richtung um ein Feld oder um die angegebene Distanz Felder zu blicken.

Kann ich einen Pass spielen?

Dazu liefern die Methoden isPassPossible(<ORT>,<RICHTUNG>) und isSelfPassPossible(<RICHTUNG>) informationen. Die Antworten sind vom Typ boolean und sagen, ob ich oder der Roboter am angegebenen Ort in die angegebene Richtung passen kann.

Sonstige Infos

Die Methode int getPriority(<ORT>) gibt die Priorität des Roboters auf dem angegebenen Feld. Je kleiner die Priorität, desto früher kommt der Roboter an die Reihe.

Weitere weniger wichtige, aber trotzdem aufrufbare Methoden sind die folgenden:
int getFieldWidth(), int getFieldHeight(), java.awt.Dimension getFieldSize() liefern Größenangaben des Spielfeldes. Bei unserem Turnier wird dies aber immer 13x15 sein (wobei y=0 und y=14 die Torbereiche sind). int getRelativeDirection(java.awt.Point from, java.awt.Point to) liefert eine Richtungskonstante, nämlich die Richtung in der der Punk to aus Sicht des Punktes from liegt. Die Methode moveOrRun(<RICHTUNG>) liefert eine SoccerAction als Vorschlag, wie der Ball bestmöglich zu erreichen ist.