Programmieren leicht gemacht - adäquate Modelle für den Einsatz im Unterricht/ Die erste eigene Mikrowelt

Aus Wikibooks

Die erste eigene Mikrowelt[Bearbeiten]

Wir wollen nun eine eigene einfach Mikrowelt erstellen, der wir den Namen „IceWorld“ geben. In dieser Welt, die wir durch eine gerasterte Eislandschaft repräsentieren möchten, sollen Eisschollen, Wasser, Eisbären und Fische involviert sein.

Erstellen eines neuen Projekts[Bearbeiten]

Dazu öffnen wir ein neues Projekt, welches wir mit dem Namen „IceWorld“ versehen:







Erzeugen einer eigenen Klasse[Bearbeiten]

Die noch leere Mikrowelt wollen wir nun an unsere Vorstellungen anpassen; dazu erzeugen wir eine Unterklasse der World-Klasse, welcher wir den Namen „IceWorld“ geben. Darin sollen alle Anweisungen in Bezug auf das Erscheinungsbild unserer Welt untergebracht werden. Klicken Sie dazu einfach mit der rechten Maustaste auf die World-Klasse und wählen Sie aus dem sich öffnenden Menü den Eintrag „New subclass...“ aus:







Bearbeiten einer Klasse[Bearbeiten]

Um der anfangs noch leeren Klasse die gewünschten Anweisungen hinzuzufügen, führen Sie einen Doppelklick auf die Klasse „IceWorld“ aus. Daraufhin öffnet sich das entsprechende Editor-Fenster:




Um einen passenden Zellenhintergrund einzustellen, fügen Sie dafür die notwendigen Bilder in den images-Ordner (Pfad, z.B. C:\ Programme\Greenfoot\scenarios\IceWorld\images) Ihrer Mikrowelt ein. Alle darin enthaltenen Bilder können Sie im Folgenden einfach durch Angabe des Dateinamens direkt ansprechen.

Die notwendigen Informationen fügen wir nun in den Konstruktor der IceWorld-Klasse hinzu:

public IceWorld() 
{
   super(8, 8, 60);
   setBackground("schnee.jpg");
 }


Nach dem Kompilieren Ihrer Klasse sehen Sie die Auswirkungen der Änderungen sofort in Ihrem Hauptfenster:






Die Polarbaer-Klasse[Bearbeiten]

Nun soll die Mikrowelt auch von Lebewesen besiedelt werden. Dazu legen wir in analoger Weise eine Unterklasse „Polarbear“ der Actor-Klasse an, welche wir auch mit einem passenden Klassenbild versehen:





Wir öffnen nun diese Klasse und fügen dieser folgende Zeilen hinzu:

public class Polarbear extends Actor
{
   private static final int EAST = 0;
   private static final int WEST = 1;
   private static final int NORTH = 2;
   private static final int SOUTH = 3;
   private int direction;
   private int fishesEaten;
   private GreenfootImage polarbearRight;
   private GreenfootImage polarbearLeft;
   public Polarbear()
   {
       polarbearRight  = getImage();
       polarbearLeft = new GreenfootImage(getImage());
       polarbearLeft.mirrorHorizontally();
       
       setDirection(EAST);
       fishesEaten = 0;
   }

Um das Bild des Eisbären bei entsprechender Richtungsänderung in angemessener Weise zu ändern, wird das anfänglich festgesetzte Bild „polarbearRight“ einfach horizontal gespiegelt und dann als „polarbearLeft“ gespeichert. Nun müssen wir die verwendete Methode setDirection(...) implementieren, welche die Blickrichtung des Eisbären festlegen soll:

   public void setDirection(int direction)
   {
       this.direction = direction;
       switch(direction) {
           case SOUTH :
               setImage(polarbearRight);
               setRotation(90);
               break;
           case EAST :
               setImage(polarbearRight);
               setRotation(0);
               break;
           case NORTH :
               setImage(polarbearLeft);
               setRotation(90);
               break;
           case WEST :
               setImage(polarbearLeft);
               setRotation(0);
               break;
           default :
               break;
       }
   }

Die Klasse lässt sich nun zwar fehlerlos kompilieren. Fügen wir unserer Welt jedoch einen Eisbären hinzu und führen anschließend die Simulation aus, so passiert noch gar nichts. Dazu müssen wir die act-Methode der Polarbear-Klasse erst geeignet modifizieren. Um eine ansprechendere Simulation zu erzeugen, fügen wir zuerst noch die Klassen „Fish“, „Water“ und „IceFloe“ hinzu:




Jetzt wollen wir uns der act-Methode der Polarbear-Klasse widmen.


public void act()
{     
  if(foundFish()) 
  {
     eatFish();
  }     
  else if(canMove())
  {    
     move();   
  }
  else
  {
      turnRandom();   
  }
}

Wenn der Eisbär auf ein Feld mit einem Fisch stößt, so soll er diesen „fressen“. Die Methode canMove() soll dabei überprüfen, ob er einen Schritt in seine Blickrichtung machen kann:


public boolean canMove()
{
       World myWorld = getWorld();
       int x = getX();
       int y = getY();
       switch(direction) 
       {
           case SOUTH :
               y++;
               break;
           case EAST :
               x++;
               break;
           case NORTH :
               y--;
               break;
           case WEST :
               x--;
               break;
       } 
       if (x >= myWorld.getWidth() || y >= myWorld.getHeight()) 
       {
           return false;
       }
       else if (x < 0 || y < 0) 
       {
           return false;
       }
       List water = myWorld.getObjectsAt(x, y, Water.class);
       List ice = myWorld.getObjectsAt(x, y, IceFloe.class);
       if(water.isEmpty() && ice.isEmpty()) 
       {
           return true;
       }
       else 
       {
           return false;
       }
   }


Im letzten Teil dieser Methode wird überprüft, ob sich auf dem Feld, auf welches er sich bewegen möchte, weder ein Objekt der Water-Klasse, noch ein Objekt der IceFloe-Klasse befindet. Nur wenn dies erfüllt ist, soll er sich vorwärts bewegen können. Nun müssen wir folgende Pakete einbinden, damit diese Klasse sich fehlerfrei kompilieren lässt:

import java.util.List;
import java.util.ArrayList;

Folgende Methode soll überprüfen, ob sich auf einem Feld ein Objekt der Fish-Klasse befindet:

public boolean foundFish()
{
   Actor fish= getOneIntersectingObject(Fish.class);
   if (fish != null) 
  {
     return true;
   }
   else 
  {       
     return false;   
   }
}


Befindet sich auf dem Feld, auf dem der Eisbär platziert ist, ein Fisch, so soll dieser vom Eisbär “gefressen” werden. Weiters soll das Erscheinungsbild des Eisbären geändert werden. Diese Änderung soll bis zur nächsten Richtungsänderung bestehen bleiben. In diesem Fall wird das Erscheinungsbild des Eisbären, wenn er einen Fisch verzehrt hat, vom Bild „polarBearHappy.jpg“ bestimmt:

public void eatFish()
{
    Actor fish= getOneIntersectingObject(Fish.class);
    if (fish != null) 
    {
        getWorld().removeObject(fish);
        setImage("polarBearHappy.jpg");
        fishesEaten++;
     } 
 }


Die Methode, die für die eigentliche Bewegung des Eisbären verantwortlich ist, bezeichnen wir mit move():

public void move()
{
    if (!canMove()) 
    {
           return;
     }
     switch(direction) 
     {
           case SOUTH :
               setLocation(getX(), getY() + 1);
               break;
           case EAST :
               setLocation(getX() + 1, getY());
               break;
           case NORTH :
               setLocation(getX(), getY() - 1);
               break;
           case WEST :
               setLocation(getX() - 1, getY());
               break;
      }
}


Damit sich der Eisbär in zufälliger Weise innerhalb der Welt bewegt, müssen wir noch folgende beiden Methoden hinzufügen:

public void turnRandom()
{
   //liefert eine Zufallszahl zwischen 0 und 3        
   int turns = Greenfoot.getRandomNumber(4);
   //und dreht sich gemäß der entsprechenden Anzahl nach rechts        
   for(int i=0; i<turns; i++) 
   {
     turnLeft();
   }
}


public void turnLeft()
{
       switch(direction) 
       {
           case SOUTH :
               setDirection(EAST);
               break;
           case EAST :
               setDirection(NORTH);
               break;
           case NORTH :
               setDirection(WEST);
               break;
           case WEST :
               setDirection(SOUTH);
               break;
        }
 }

Schlussendlich fehlt uns noch eine Methode, die die Anzahl der vom Eisbär verzehrten Fische liefert:

public int getFishesEaten()
{
   return fishesEaten;
 }

Kompilieren Sie nun diese Klasse, fügen Sie anschließend Ihrer Welt einen Eisbären hinzu und starten Sie die Simulation. Ihr Eisbär sollte kreuz und quer in Ihrer Welt umher laufen.

Die IceWorld-Klasse[Bearbeiten]

Öffnen Sie nun die IceWorld-Klasse, um einige Anpassungen vorzunehmen. Wir fügen dieser Klasse einige Methoden hinzu, die eine zufällige Anzahl an Fischen, Polarbären, Wasserflächen und Eisschollen in der Welt platzieren sollen:

public void populate()
{ 
    int cells= getWidth() * getHeight();
    randomFishes(Greenfoot.getRandomNumber(cells/3));
    randomIceFloes(Greenfoot.getRandomNumber(cells/5));
    randomWater(Greenfoot.getRandomNumber(cells/2));
    randomPolarbears(Greenfoot.getRandomNumber(cells/5));     
}
   
public void randomFishes(int howMany)
{   
    for(int i=0; i<howMany; i++) 
   {
      Fish Fish = new Fish();
      int x = Greenfoot.getRandomNumber(getWidth());
      int y = Greenfoot.getRandomNumber(getHeight());
      addObject(Fish, x, y);  
      List intersectingObjects = getObjectsAt(x, y, Polarbear.class);
      if(intersectingObjects.isEmpty()==false) 
     {
           removeObject(Fish);
     }
   }
}
    
  
public void randomIceFloes(int howMany)
{
   for(int i=0; i<howMany; i++) 
  {
      IceFloe IceFloe = new IceFloe();
      int x = Greenfoot.getRandomNumber(getWidth());
      int y = Greenfoot.getRandomNumber(getHeight());
      addObject(IceFloe, x, y);
      List intersectingObjects = getObjectsAt(x, y, Polarbear.class);
      if(intersectingObjects.isEmpty()==false) 
     {
          removeObject(IceFloe);
      }
  }
}
   
public void randomWater(int howMany)
{
   for(int i=0; i<howMany; i++) 
  {
     Water Water = new Water();
     int x = Greenfoot.getRandomNumber(getWidth());
     int y = Greenfoot.getRandomNumber(getHeight());
     addObject(Water, x, y);
     List intersectingObjects = getObjectsAt(x, y, Polarbear.class);
     if(intersectingObjects.isEmpty()==false) 
    {
        removeObject(Water);
     }
  }
}
   
public void randomPolarbears(int howMany)
{
   for(int i=0; i<howMany; i++) 
  {
     Polarbear Polarbear = new Polarbear();
     IceFloe IceFloe = new IceFloe();
     int x = Greenfoot.getRandomNumber(getWidth());
     int y = Greenfoot.getRandomNumber(getHeight());
     addObject(Polarbear, x, y);
   }
}

Nun müssen noch folgende Pakete eingefügt warden:

import java.util.List;
import java.util.ArrayList;

Um die vorgenommenen Modifikationen zu testen, kompilieren Sie erst einmal alles und klicken Sie mit der rechten Maustaste in Ihrer Welt auf „IceWorld“:




In dem sich öffnenden Menü wählen Sie den Eintrag „void populate()“ aus, um Ihre Welt in zufälliger Weise zu besiedeln:




Die Fish-Klasse[Bearbeiten]

Abschließend wollen wir noch erreichen, dass sich auch die Fische jeweils von rechts nach links bewegen, jedoch nur auf entsprechenden Wasserflächen. Dazu nehmen wir in der Fish-Klasse folgende Modifikationen vor; die entsprechenden Methoden wurden in Anlehnung an die Methoden der Polarbear-Klasse erstellt:

public class Fish extends Actor
{
   private int direction;
   
   private static final int EAST = 0;
   private static final int WEST = 1;
   private static final int NORTH = 2;
   private static final int SOUTH = 3;
   
   private GreenfootImage fishRight;
   private GreenfootImage fishLeft;
   public Fish()
   {
       fishRight = getImage();
       fishLeft = new GreenfootImage(getImage());
       fishLeft.mirrorHorizontally();
       setDirection(EAST);
       
   }
   public void act()
   { 
       if (canMove())
       {
     	move();
       }
       else
       {
        	turnBack();   
       }
    }


   public void move()
   {
       if (!canMove()) 
      {
           return;
      }
      switch(direction) 
     {
           case EAST :
               setLocation(getX() + 1, getY());
               break;
           case WEST :
               setLocation(getX() - 1, getY());
               break;
     }
  }
   
   public boolean canMove()
   {
       World myWorld = getWorld();
       int x = getX();
       int y = getY();
       switch(direction) 
      {
           case EAST :
               x++;
               break;
           case WEST :
               x--;
               break;
       }
       if (x >= myWorld.getWidth() || y >= myWorld.getHeight()) 
      {
           return false;
       }
       else if (x < 0 || y < 0) 
      {
           return false;
       }
       List water = myWorld.getObjectsAt(x, y, Water.class);
       if (!water.isEmpty())
       {
           return true;
       }
       else 
      {
           return false;
      }
  }
   
   
  public void turnBack()
 {     
    switch(direction) 
   {
           case EAST:
           setDirection(WEST);
           break;
           
           case WEST:
           setDirection(EAST);
           break;
   }
 }
   
   
   public void setDirection(int direction)
   {
       this.direction = direction;
       switch(direction) 
      {
           case EAST :
               setImage(fishRight);
               setRotation(0);
               break;
           case WEST :
               setImage(fishLeft);
               setRotation(0);
               break;
           default :
               break;
      }
  }  
}

Wiederum müssen folgende Pakete eingebunden werden:

import java.util.List;
import java.util.ArrayList;


Nun besitzt unsere Mikrowelt die von uns gewünschte Grundfunktionalität. Natürlich kann diese noch beliebig erweitert werden.