Billard, Windmühlen, Memory und Siedlungen

Die Klasse Kugel

Eine Kugel auf dem Bildschirm zu bewegen ist relativ einfach.

import basis.*;

public class billard1
{
Fenster f;
Stift s;
Maus m;

public billard1()
{
f = new Fenster(800,600);
s = new Stift();
m = new Maus();
}

public void fuehreAus()
{
s.bewegeBis(f.breite() / 2,f.hoehe() / 2);
do
{
warte(10);
s.radiere();
s.zeichneKreis(10);
s.hoch();
s.bewegeUm(1);
s.runter();
s.normal();
s.zeichneKreis(10);
if (s.hPosition() >=f.breite()-10)
s.dreheBis(180);
if (s.hPosition() <= 10)
s.dreheBis(0);
}
while (!m.doppelKlick());
}
public void warte(long msec){
long startzeit=System.currentTimeMillis();
while(System.currentTimeMillis()-startzeit<msec);
}
} //Ende class

Die Methode warte ist nicht unbeding nötig (das hängt von der Graphikleistung des Computers ab.) Möchte man mehrere Kugeln auf dem Bildschirm bewegen, so wäre der Programmieraufwand ziemlich groß. Inhaltlich unterschieden sich die einzelnen Methoden nur gering. Es wäre also einfacher, eine Art Methode zum Zeichnen einer Kugel zu entwickeln. Wir wollen deshalb eine Klasse Kugel erstellen. Alle Objekte dieser Klasse solen sich selbst verwalten, d.h. sie sollen sich selber bewegen und deshlab auch selber zeichnen. Das Hauptprogramm regelt nur das Nötigste.

Die hat-Beziehung

Die Klasse Kugel hat einen eigenen Stift, mit dem sich das Obekt selber zeichnet. Bei einer hat-Beziehung ist das besitzende Objekt verantwortlich für die Erzeugung und Freigabe des besessenen Objektes. Was muss die Kugel können? Sie muss sich initialisieren, bewegen, ihre Position mitteilen, sich drehen und den Rand des Fensters erkennen, und sie ist zuständig für die Erzeugung und Verwaltung ihres Zeichenstiftes. Eine mögliche Realisierung könnte so aussehen. Um eine neue Klasse in BlueJ zu erzeugen, wählt man im Projektfenster den Button Neue Klasse, benennt einen Klassennamen und wählt die Art der Klasse, nämlich Klasse. Nach Bestätigung der Auswahl findet sich die neue Klasse im Projektfenster.

import basis.*;

public class Kugel extends Object
{
private Stift hatStift;
private int r;

public Kugel()
{
// Instanzvariable initialisieren
hatStift = new Stift();
}

public void bewege(double v)
{
hatStift.radiere();
hatStift.zeichneKreis(r);
hatStift.hoch();
hatStift.bewegeUm(v);
hatStift.runter();
hatStift.normal();
hatStift.zeichneKreis(r);
}

public void bewegeBis(double px, double py)
{
hatStift.radiere();
hatStift.zeichneKreis(r);
hatStift.hoch();
hatStift.bewegeBis(px,py);
hatStift.runter();
hatStift.normal();
hatStift.zeichneKreis(r);
}

public void dreheBis(double pWinkel)
{
hatStift.dreheBis(pWinkel);
}

public void setzeRadius(int zahl)
{
r=zahl;
}

public void gibFrei()
{
hatStift.radiere();
hatStift.zeichneKreis(10);
hatStift.gibFrei();
}

public double hPosition()
{
return hatStift.hPosition();
}

public double vPosition()
{
return hatStift.vPosition();
}

public double rWinkel()
{
return hatStift.winkel();
}
}

Der Zusatz extends Object kann entfallen, wenn die Klasse automatisch von der obersten Klasse Object abgeleitet wird. Ansonsten wird hier die Klasse genannt, von der die neu Klasse abgeleitet werden soll. Das Hauptprogramm hat dann die Aufgabe, die Kugeln zu erzeugen und zu steuern. Das folgende Beispiel generiert zwei Kugeln. Eine Kugel bewegt sich nur von rechts nach links, die andere wird an den Rändern des Felders zurückgestoßen. Entgegen den Gesetzen der Physik können sich die Kugeln durchdringen, sie stoßen sich nicht ab. Dieses Problem wird später gelöst.

import basis.*;

public class billard2
{
Fenster f;
Kugel k1,k2;
Maus m;

public billard2()
{
f = new Fenster(800,600);
k1 = new Kugel();
k2 = new Kugel();
m = new Maus();
}

public void fuehreAus()
{
k1.setzeRadius(10);
k2.setzeRadius(20);
k1.bewegeBis(f.breite() / 2,f.hoehe() / 2);
k1.dreheBis(135);
k2.bewegeBis(f.breite() / 2,f.hoehe() / 2+40);
k2.dreheBis(0);
do
{
k1.bewege(1);
k2.bewege(2);
warte(10);
if (k1.hPosition() >=f.breite()-10)
k1.dreheBis(180-k1.rWinkel());
if (k1.hPosition() <= 10)
k1.dreheBis(180-k1.rWinkel());
if (k1.vPosition() <= 10)
k1.dreheBis(360-k1.rWinkel());
if (k1.vPosition() >=f.hoehe()-10)
k1.dreheBis(360-k1.rWinkel());
if (k2.hPosition() >=f.breite()-10)
k2.dreheBis(180-k2.rWinkel());
if (k2.hPosition() <= 10)
k2.dreheBis(180-k2.rWinkel());
}
while (!m.doppelKlick());
k1.gibFrei();
k2.gibFrei();
}

public void warte(long msec){
long startzeit=System.currentTimeMillis();
while(System.currentTimeMillis()-startzeit<msec);
}
}

Die Klasse Windrad

Für die Darstellung des Windrades benötigt man einige Angaben über dessen Aufbau: Bezugspunkt für die Zeichnung ist vereinbarungsgemäß die linke untere Ecke des Bildschirms (X,Y). Die "Größe" legt man über die Höhe des Mastes fest (Hoehe); der Typ der Mühle wird ducrh die Werte 2 (zweiflüglig), 3 (dreiflüglig) und 4 (vierflüglig) festgelegt. Alle weiteren Werte und Maße werden aus diesen Angaben berechnet. Die Breite des Mastes beträgt 1/10 und die Länges des Rotorblattes 8/10 der Masthöhe. Das Lager des Rotors befindet sich in der Mitte der oberen Mastkante.

Wir können jetzt also eine Methode WindradZeichnen definieren. Das zugehörige Hauptprogramm und die Klasse Windrad erkären sich selbst.

import basis.*;

public class windrad1
{
Fenster f;
WindRad w1,w2,w3;
Maus m;

public windrad1()
{
f = new Fenster(800,600);
w1 = new WindRad();
w2=new WindRad();
w3=new WindRad();
m = new Maus();
}

public void fuehreAus()
{
w1.zeichne(400,500,200,3);
w2.zeichne(180,100,50,2);
w3.zeichne(600,300,150,4);
do
{
w1.bewege(-1);
w2.bewege(-1);
w3.bewege(-1);
warte(10);
}
while (!m.doppelKlick());
w1.gibFrei();
}

public void warte(long msec)
{
long startzeit=System.currentTimeMillis();
while(System.currentTimeMillis()-startzeit<msec);
}
}

import basis.*;

public class WindRad extends Object
{
private Stift hatStift;
private int px,py,ph,xm,ym,typ;
private double fl,winkel;

public WindRad()
{
hatStift = new Stift();
}

public void zeichne(int x, int y, int h, int t)
{
px=x;
py=y;
ph=h;
typ=t;
xm=px+ph/20;
ym=py-ph;
fl=ph*0.8;
winkel=0;
zeichneTurm();
if (typ==2) {zeichneFluegel();}
else {if (typ==3) {zeichneFluegel3();}
else {zeichneFluegel4();}}
}

private void zeichneTurm()
{
hatStift.hoch();
hatStift.setzeLinienBreite(1);
hatStift.bewegeBis(px,py);
hatStift.runter();
hatStift.bewegeBis(px+ph/10,py);
hatStift.bewegeBis(px+ph/10,py-ph);
hatStift.bewegeBis(px,py-ph);
hatStift.bewegeBis(px,py);
hatStift.hoch();
hatStift.bewegeBis(xm,ym);
hatStift.runter();
hatStift.zeichneKreis(ph/20);
}

private void zeichneFluegel()
{
hatStift.dreheBis(winkel);
hatStift.setzeLinienBreite(5);
hatStift.bewegeUm(fl);
hatStift.dreheBis(winkel+180);
hatStift.bewegeUm(2*fl);
hatStift.dreheBis(winkel);
hatStift.bewegeUm(fl);
}

private void zeichneFluegel3()
{
hatStift.dreheBis(winkel);
hatStift.setzeLinienBreite(5);
hatStift.bewegeUm(fl);
hatStift.dreheBis(winkel+180);
hatStift.bewegeUm(fl);
hatStift.dreheBis(winkel+120);
hatStift.bewegeUm(fl);
hatStift.dreheBis(winkel+300);
hatStift.bewegeUm(fl);
hatStift.dreheBis(winkel+240);
hatStift.bewegeUm(fl);
hatStift.dreheBis(winkel+60);
hatStift.bewegeUm(fl);
}

private void zeichneFluegel4()
{
hatStift.dreheBis(winkel);
hatStift.setzeLinienBreite(5);
hatStift.bewegeUm(fl);
hatStift.dreheBis(winkel+180);
hatStift.bewegeUm(fl);
hatStift.dreheBis(winkel+90);
hatStift.bewegeUm(fl);
hatStift.dreheBis(winkel+270);
hatStift.bewegeUm(2*fl);
hatStift.dreheBis(winkel+90);
hatStift.bewegeUm(fl);
hatStift.dreheBis(winkel+180);
hatStift.bewegeUm(fl);
hatStift.dreheBis(winkel);
hatStift.bewegeUm(fl);
}

public void bewege(double v)
{
hatStift.radiere();
hatStift.zeichneKreis(ph/20);
if (typ==2) {zeichneFluegel();}
else {if (typ==3) {zeichneFluegel3();}
else {zeichneFluegel4();}}
hatStift.hoch();
winkel=winkel+v;
hatStift.runter();
hatStift.normal();
zeichneTurm();
hatStift.zeichneKreis(ph/20);
if (typ==2) {zeichneFluegel();}
else {if (typ==3) {zeichneFluegel3();}
else {zeichneFluegel4();}}
}

public void dreheBis(double pWinkel)
{
hatStift.dreheBis(pWinkel);
}

public void gibFrei()
{
hatStift.radiere();
zeichneTurm();
if (typ==2) {zeichneFluegel();}
else {if (typ==3) {zeichneFluegel3();}
else {zeichneFluegel4();}}
hatStift.gibFrei();
}
}

Die kennt-Beziehung

Die Kugeln aus em Anfangsbeispiel bewegen sich auf einem gemeinsamen Bildschirm. Wenn aber eine Kugel selber erkennen soll, wann sie sich dem Rand nähert, muss sie Kenntnisse über den Bildschirm haben. Bewegen sich mehrere Kugeln auf dem Bildschirm, kann dieser nicht wie der Stift von jeder Kugel selbst erzeugt werden. Die Kugeln sind also nicht für die Initialisierung des Bildschirms zuständig, aber sie müssen ihn kennen.

In unserem Beispiel kontrollierte das Hauptprogramm den Ort der beiden Kugeln. In der folgenden Version sind die Kugeln dafür selber verantwortlich. Das Hauptporgramm initialisiert den Bildschirm und übergibt die nötigen Angaben an die einzelne Kugel. Die zu erzeugende Kugel erhält die Koordianten des Startpunktes, den Radius, die Startrichtung und den Hinweis auf den zu benutzenden Bildschirm:

import basis.*;

public class billard5
{
Fenster f;
Kugel k1,k2,k3,k4;
Maus m;

public billard5()
{
f = new Fenster(800,600);
k1 = new Kugel(300,200,20,30,f);
k2 = new Kugel(200,200,10,-40,f);
k3 = new Kugel(100,300,20,20,f);
k4=new Kugel(400,400,15,-50,f);
k1.setV(0.4);
k2.setV(0.4);
k3.setV(0.4);
k4.setV(0.4);
m = new Maus();
}

public void fuehreAus()
{
do
{
k1.bewege();
k2.bewege();
k3.bewege();
k4.bewege();
warte(1);
}
while (!m.doppelKlick());
k1.gibFrei();
k2.gibFrei();
k3.gibFrei();
k4.gibFrei();
}

public void warte(long msec){
long startzeit=System.currentTimeMillis();
while(System.currentTimeMillis()-startzeit<msec);
}
}

Die Klasse Kugel stellt sich wie folgt dar:

import basis.*;

public class Kugel extends Object
{
private Stift hatStift;
private Fenster kenntF;
private Kugel kenntKugel;
private int r;
private double zWinkel, zv; //zv=Geschwindigkeit

public Kugel(double px, double py, int pRadius, double pWinkel, Fenster pF)
{
hatStift = new Stift();
kenntF = pF;
r=pRadius;
bewegeBis(px,py);
zWinkel=pWinkel;
hatStift.dreheBis(zWinkel);
setV(0.1);
}

public void bewege()
{
hatStift.radiere();
hatStift.zeichneKreis(r);
hatStift.bewegeUm(zv);
hatStift.normal();
hatStift.zeichneKreis(r);
if (this.hPosition() >=kenntF.breite()-r)
dreheBis(180-zWinkel);
if (this.hPosition() <= r)
dreheBis(180-zWinkel);
if (this.vPosition() <= r)
dreheBis(360-zWinkel);
if (this.vPosition() >=kenntF.hoehe()-r)
dreheBis(360-zWinkel);
}

private void bewegeBis(double px, double py)
{
hatStift.radiere();
hatStift.zeichneKreis(r);
hatStift.bewegeBis(px,py);
hatStift.normal();
hatStift.zeichneKreis(r);
}

private void dreheBis(double pWinkel)
{
hatStift.dreheBis(pWinkel);
zWinkel=pWinkel;
}

public void gibFrei()
{
hatStift.radiere();
hatStift.zeichneKreis(10);
hatStift.gibFrei();
}

public double hPosition()
{
return hatStift.hPosition();
}
public double vPosition()
{
return hatStift.vPosition();
}

public void setV(double pv)
{
zv=pv;
}
public double getV()
{
return zv;
}

public void setWinkel(double pWinkel)
{
zWinkel=pWinkel;
hatStift.dreheBis(pWinkel);
}

public double getWinkel()
{
return zWinkel;
}
}

In der nächsten Version sollen beliebig viele Bälle produziert werden können. Dazu benutzen wir ein Array. Außerdem sollen die Kugeln sich auch gegenseitig abstossen. Die Kollisionskontrolle darüber übernimmt das Hauptporgramm. Es überprüft, ob zwei Kugeln sich berüheren. Sie tauschen dann Geschwindigkeit und Richtung. Der nötige Programmcode des Hauptprgramms könnte dann wie folgt aussehen:

import basis.*;

public class billard5
{
int MAX=10;
Fenster f;
Kugel[] k=new Kugel[MAX];
Maus m;

public billard5()
{
f = new Fenster(800,600);
for (int i=0;i<MAX;i++){
k[i] = new Kugel(Hilfe.zufall(0,f.breite()-50)+25,Hilfe.zufall(0,f.hoehe()-50)+25,
Hilfe.zufall(10,20),Hilfe.zufall(0,360),f);
k[i].setV(Hilfe.zufall()+0.1);
}
m = new Maus();
}

public void stoss()
{
double h;
for (int i=0;i<MAX-1;i++)
for (int j=i+1;j<MAX;j++)
if (Hilfe.wurzel(Hilfe.quadrat(
k[i].hPosition() - k[j].hPosition()) +
Hilfe.quadrat(k[i].vPosition() -
k[j].vPosition())) <= k[i].getRadius()+k[j].getRadius())
{
h = k[i].getWinkel();
k[i].setWinkel(k[j].getWinkel());
k[j].setWinkel(h);
h = k[i].getV();
k[i].setV(k[j].getV());
k[j].setV(h);
}
}

public void fuehreAus()
{
do
{
for (int i=0;i<MAX;i++)
k[i].bewege();
warte(1);
stoss();
}
while (!m.doppelKlick());
for (int i=0;i<MAX;i++)
k[i].gibFrei();
}

public void warte(long msec){
long startzeit=System.currentTimeMillis();
while(System.currentTimeMillis()-startzeit<msec);
}
}

Die Klassendefinition wird ebenfalls leicht verändert.

import basis.*;

public class Kugel extends Object
{
private Stift hatStift;
private Fenster kenntF;
private Kugel kenntKugel;
private int r;
private double zWinkel, zv; //zv=Geschwindigkeit

public Kugel(double px, double py, int pRadius, double pWinkel, Fenster pF)
{
hatStift = new Stift();
hatStift.setzeFuellMuster(Muster.GEFUELLT);
hatStift.setzeFarbe(Farbe.ROT);
kenntF = pF;
r=pRadius;
bewegeBis(px,py);
zWinkel=pWinkel;
hatStift.dreheBis(zWinkel);
setV(0.1);
}

public void bewege()
{
hatStift.radiere();
hatStift.zeichneKreis(r);
hatStift.bewegeUm(zv);
hatStift.normal();
hatStift.zeichneKreis(r);
if (this.hPosition() >=kenntF.breite()-r)
dreheBis(180-zWinkel);
if (this.hPosition() <= r)
dreheBis(180-zWinkel);
if (this.vPosition() <= r)
dreheBis(360-zWinkel);
if (this.vPosition() >=kenntF.hoehe()-r)
dreheBis(360-zWinkel);
}

private void bewegeBis(double px, double py)
{
hatStift.radiere();
hatStift.zeichneKreis(r);
hatStift.bewegeBis(px,py);
hatStift.normal();
hatStift.zeichneKreis(r);
}

private void dreheBis(double pWinkel)
{
hatStift.dreheBis(pWinkel);
zWinkel=pWinkel;
}

public void gibFrei()
{
hatStift.radiere();
hatStift.zeichneKreis(r);
hatStift.gibFrei();
}

public double hPosition()
{
return hatStift.hPosition();
}

public double vPosition()
{
return hatStift.vPosition();
}

public void setV(double pv)
{
zv=pv;
}

public double getV()
{
return zv;
}

public void setWinkel(double pWinkel)
{
zWinkel=pWinkel;
hatStift.dreheBis(pWinkel);
}

public double getWinkel()
{
return zWinkel;
}

public double getRadius()
{
return r;
}
}

Memory

Es soll das Spiel Memory mit 16 Karten entwickelt werden. Zu Beginn des Spiels sind die Karten verdeckt. Sie müssen die gleichen Abmessungen haben, damit man sie nicht erkennen kann ohne sie aufzudecken. Jede Karte hat ein blaues Quadrat mit schwarzem Rand und der Kantenlänge 100 Pixel. Der Abstand zwischen den Karten beträgt 50 Pixel sowohl nach unten als auch nach oben. Für das Programm werden acht Kartenpaare benötigt. Diese unterscheiden sich nur in der Gestaltung der Kartenvorderseite. Die Methoden zum Aufdecken, Zudecken, Trefferangabe, Positionierung und Weiteres sind identisch. Es ist also sinnvoll, eine Klasse Karte zu bilden, von der alle anderen karten abgeleitet werden. Zusätzlich wird natürlich das Hauptprogramm benötigt, dass die benötigten Karten generiert und verwaltet. Beginnen wir mit dem Hauptprogramm:

import basis.*;
import java.util.*;
public class MemoryProgramm{
private Stift st;
private Fenster f;
private Karte[] k;
private Maus mau;
private Random zufall;
private int zaehler;
private int index1, index2;
private boolean gefunden;
private long startzeit, endzeit, zeit;

public void starte(){
f= new Fenster();
f.setzeGroesse(800,800);
st= new Stift();
mau= new Maus();
k = new Karte [16];
zufall = new Random();
k[0] = new Kreuzkarte(st,mau);
k[1] = new Kreuzkarte(st,mau);
k[2] = new Rautekarte(st,mau);
k[3] = new Rautekarte(st,mau);
k[4] = new Dreieckkarte(st,mau);
k[5] = new Dreieckkarte(st,mau);
k[6] = new Kreiskarte(st,mau);
k[7] = new Kreiskarte(st,mau);
k[8] = new Balkenkarte(st,mau);
k[9] = new Balkenkarte(st,mau);
k[10]= new Querbalkenkarte(st,mau);
k[11]= new Querbalkenkarte(st,mau);
k[12]= new Rechteckkarte(st,mau);
k[13]= new Rechteckkarte(st,mau);
k[14]= new Quadratkarte(st,mau);
k[15]= new Quadratkarte(st,mau);
mische();
startzeit=System.currentTimeMillis();
for(int j=0;j<4;j=j+1){
for(int i=0;i<4;i=i+1){
k[i+4*j].setXY(50+150*j,10+150*i);
warte(100);
k[i+4*j].deckeZu();
}
}
zaehler=0;
gefunden=false;
do {
while(!gefunden){
for(int i=0;i<16;i=i+1){
if(k[i].getroffen()){
k[i].deckeAuf();
index1=i;
gefunden=true;
}
}
}
gefunden=false;
while(!gefunden){
for(int i=0;i<16;i=i+1){
if(k[i].getroffen()){
k[i].deckeAuf();
index2=i;
gefunden=true;
}
}
}
gefunden=false;
if(k[index1].getClass().getName().equals(k[index2].getClass().getName())){
zaehler=zaehler+2;
}
else{
warte(1000);
k[index1].deckeZu();
k[index2].deckeZu();
}
} while(zaehler<16);
endzeit=System.currentTimeMillis();
zeit=endzeit-startzeit;
st.hoch();
st.bewegeBis(10,700);
st.schreibeText("Du hast "+ zeit+" Millisekunden benötigt.");
}

private void mische(){
for (int i=0;i<100;i=i+1){
this.vertauscheKarten(zufall.nextInt(16),zufall.nextInt(16));
}
}

private void vertauscheKarten(int i,int j) {
Karte tauschkarte;
tauschkarte=k[i];
k[i]=k[j];
k[j]=tauschkarte;
}
private void warte(long msec){
long startzeit=System.currentTimeMillis();
while(System.currentTimeMillis()-startzeit<msec);
}
}

Interessant ist der Ausdruck k[index1].getClass().getName().equals(k[index2].getClass().getName()). Was passiert hier? Ein Kartenpaar hat dann identische Bilder, wenn beide Karten derselben Klasse angehören und denselben Namen haben. Mit dem Befehl equals werden also Objekte verglichen. Die Oberklasse Karte sieht wie folgt aus:

import basis.*;

public abstract class Karte{
double xPos, yPos, breite, hoehe;
Stift sti;
Maus m;
boolean aufgedeckt;

public Karte(Stift s, Maus ma){
sti=s;
m=ma;
}

protected void setXY (double x,double y){
breite=100;
hoehe=100;
xPos=x;
yPos=y;
}

protected abstract void zeichneDich();

protected void deckeAuf(){
sti.setzeFarbe(Farbe.WEISS);
sti.setzeFuellMuster(Muster.GEFUELLT);
sti.rechteck(xPos,yPos,breite,hoehe);
sti.setzeFuellMuster(Muster.DURCHSICHTIG);
sti.setzeFarbe(Farbe.SCHWARZ);
sti.rechteck(xPos,yPos,breite,hoehe);
this.zeichneDich();
aufgedeckt=true;
}

protected void deckeZu(){
sti.setzeFarbe(Farbe.BLAU);
sti.setzeFuellMuster(Muster.GEFUELLT);
sti.rechteck(xPos,yPos,breite,hoehe);
sti.setzeFuellMuster(Muster.DURCHSICHTIG);
sti.setzeFarbe(Farbe.SCHWARZ);
sti.rechteck(xPos,yPos,breite,hoehe);
aufgedeckt=false;
}

protected boolean getroffen(){
if ((!aufgedeckt)&&(m.istGedrueckt())
&(m.hPosition()<xPos+breite)&(m.hPosition()>xPos)
&(m.vPosition()<yPos+breite)&(m.vPosition()>yPos))
return true;
else
return false;
}
}

Eine abstrakte Klasse ist eine Klasse, von der keine Objekte erzeugt werden können. Die Klasse Karte ist eine solche abstrakte Klasse, da eine dort definierte Methoden ( zeichneDich()) inhaltsleer ist. Erst in den abgeleiteten Klassen, z. B. der Klasse Rautekarte, wird diese Methoden zur konkreten, ausführbaren Methode, indem sie dort mit Inhalt gefüllt wird. Eine Methode wird durch das Schlüsselwort abstract als abstrakte Methode gekennzeichnet. Nur virtuelle Methdoen können dann als "abstract" deklariert werden. Das Überschreiben einer abstrakten Methode ist identisch mit dem Überschreiben einer beliebigen virtuellen Methode mit dem Unterschied, dass in der Implementierung der überschriebenen Methode keine super-Methode zum Aufrufen verfügbar ist.

Für die benötigten Unterklassen steht die folgende Klasse beispielhaft. Die anderen Kartenklassen unterscheiden sich in der Zeichnung.

import basis.*;

public class Rautekarte extends Karte{
private double[] xKo,yKo;

public Rautekarte(Stift s, Maus m){
super(s,m);
}

protected void zeichneDich(){
xKo=new double[4];
xKo[0]=xPos+50;
xKo[1]=xPos+25;
xKo[2]=xPos+50;
xKo[3]=xPos+75;
yKo=new double[4];
yKo[0]=yPos+25;
yKo[1]=yPos+50;
yKo[2]=yPos+75;
yKo[3]=yPos+50;
sti.setzeFuellMuster(Muster.GEFUELLT);
sti.setzeFarbe(Farbe.BLAU);
sti.polygon(xKo,yKo);
sti.setzeFarbe(Farbe.SCHWARZ);
}
}

 

Brücke, Landschaft und Siedlungen

Mit diesem Beispiel wird die Unterrichtsreihe abgeschlossen. Wir greifen hier auf Elemente füherer Beispiele zurück, ergänzen diese etwas und verändern sie zum Teil. Die wichtigste Änderung ist, dass wir nicht mehr auf die Basisklasse zurückgreifen, sondern nur noch mit den von Java mitgelieferten Bibliotheken arbeiten. Am Ende wird dann noch gezeigt, wie man das entwickelte Beispiel auch ohne BlueJ laufen lassen kann.

Alle bisherigen Beispiele wurden in einem Fenster gezeichnet. Die Klasse Fenster wurde von der Bibliothek Basis zur Verfügung gestellt. Java ermöglicht etwas ähnliches mit der Klasse Frame. Alle Objekte, in denen gezeichnet werden kann, haben eine einheitliche Struktur: sie besitzen ein Objekt der Klasse Graphics, welche die Zeichenmethoden zur Verfügung stellt und die notwendigen Einstellungen (Farbe, Zeichensatz...) verwaltet. Der Ursprung des zugrunde liegenden Koordinatensystems ist die linke obere Ecke des Fensters. Gemessen wird in Pixeln. Man muss beachten, dass der Rand des Fensteres einschließlich der Titelzeile mit berücksichtigt werden muss, weil nur innerhalb des Fensters gezeichnet wird. Objekte, die die Klasse Graphics besitzen, stellen zusätzlich die Methode getGraphcs() bereit, mit der der Zugriff auf die Zeichenfläche möglich ist. Folgende Methoden der Klasse Graphics können verwendet werden:

  • drawLine(x1,y1,x2,y2): zeichnet eine Strecke von (x1,y1) bis (x2,y2)
  • drawRect(x1,y1,width, height): zeichnet ein Rechteck als Umriss
  • drawOval(x1,y1,width,height): zeichnet ein Oval als Umriss
  • fillRect(x1,y1,width, height): zeichnet ein Rechteck in der Füllfarbe
  • fillOval(x1,y1,width,height): zeichner ein Oval in der Füllfarbe
  • drawPolygon(p): zeichnet einen geschlossenen Polygonzug; die benötigten Eckpunkte werden in der Klasse Polygon erzeugt
  • fillPolygon(p): füllt den Polygonzug
  • fillPoygon(arx,ary,cnt): weitere Methode zum Füllen eines Polygonzuges, die Eckpunkte werden vorher in Arrays Point[] arx bzw. Point[] ary festgelegt
  • drawString(s,x,y): schreibt eine Zeichenkette als Graphik
  • drawArc(x1,y,width,height,starAngle,arcAngle): zeichnet einen Kreisbogen als Umriss
  • setColor(c): setzt eine neue Umriss- oder Füllfarbe

Eventuell werden weitere Objekte benötigt wie

  • Klasse Color: Zeichenfarbe für Linien- und Füllfarben
  • Klasse Font: Schriftart für Texte
  • Klasse Polygon: eine Abfolge von Eckpunkten eines Polgonzuges
  • Klasse Array: Feldobjekte zum Verwalten von Polygon-Ecken (Point[])

Einzelheiten werden bei den Beispielen gezeigt.

 In einer ersten Version sieht unsere Klasse Fenster wie folgt aus (java.awt.* ist die benötigte Bibliothek):

import java.awt.*;

public class Fenster extends Frame {

public Fenster (int breite, int hoehe, int xAbstand, int yAbstand) {
setSize (breite, hoehe);
setLocation (xAbstand, yAbstand);
setTitle ("Zeichenfenster");
setResizable (true);

}
}

In unserer Basis-Bibliothek gibt es die Klasse Rechteck. Wie sieht diese eigene Klasse aus? Die Klasse muss das Fenster und die Zeichenfläche kennen. Wir benötigen den Startpunkt und die Angaben von Höhe, Breite und Farbe. Wie oben schon erwähnt, müssen die Rahmenbreiten der Zeichnfläche berücksichtigt werden. Die übergebenen Koordinaten müssen entsprechend korrigiert werden. Dies geschieht mit Hilfe der getInsets-Methode.

import java.awt.*;

public class Rechteck {

Fenster kZf;   // kennt Fenster
Graphics kLw;  //kennt Leinwand
int zX, zY, zB, zH;
Color zFarbe;
boolean zSichtbar;

public Rechteck (Fenster Zf, int xPos, int yPos, int breite, int hoehe, Color farbe)
{
kZf = Zf;
kLw = kZf.getGraphics(); // Leinwand = Zeichenflaeche
zX = xPos + kZf.getInsets().left;
zY = yPos + kZf.getInsets().top;
zB = breite;
zH = hoehe;
zFarbe = farbe;
zSichtbar = false;
}

public void zeichnen() {
Color farbeAlt;
farbeAlt = kLw.getColor();
kLw.setColor (zFarbe);
kLw.fillRect (zX, zY-zH, zB, zH);
kLw.setColor (Color.black);
kLw.drawRect (zX, zY-zH, zB, zH);
kLw.setColor (farbeAlt);
zSichtbar = true;
}

public void loeschen() {
Color farbeAlt;
farbeAlt = kLw.getColor();
kLw.setColor (Color.white);
kLw.fillRect (zX, zY-zH, zB, zH);
kLw.drawRect (zX, zY-zH, zB, zH);
kLw.setColor (farbeAlt);
zSichtbar = false;
}

public void setzeFarbe (Color farbe) {
zFarbe = farbe;
if (zSichtbar)
zeichnen();
}

public void setzePosition (int xPos, int yPos) {
if (zSichtbar) {
loeschen();
zX = xPos; zY = yPos;
zeichnen();
}
else {
zX = xPos; zY = yPos;
}
}

public int liesXPosition() {
return zX;
}

public int liesYPosition() {
return zY;
}

} // class

Kreis, Quadrat und Dreieck lassen sich ähnlich konstruieren.

import java.awt.*; // Color

public class Quadrat
{
// Attribute

private FrmZeichnen kZf;
private Graphics kLw;
private int zX, zY, zB;
private Color zFarbe;
private boolean zSichtbar;


// Konstrutor(en)

/** Konstruktor: erzeugt ein Objekt der Klasse Quadrat */
public Quadrat (FrmZeichnen zf, int xPos, int yPos, int breite, Color farbe)
{
// Instanzvariable initialisieren
kZf = zf;
kLw = kZf.getGraphics(); // Leinwand = Zeichenflaeche
zX = xPos + kZf.getInsets().left;
zY = yPos + kZf.getInsets().top;
zB = breite;
zFarbe = farbe;
}

// Methoden

/** zeichnet die Figur */
public void zeichnen() {
Color farbeAlt;
farbeAlt = kLw.getColor();
kLw.setColor (zFarbe);
kLw.fillRect (zX, zY-zB, zB, zB);
kLw.setColor (Color.black);
kLw.drawRect (zX, zY-zB, zB, zB);
kLw.setColor (farbeAlt);
zSichtbar = true;
}

/** übermalt die Figur mit weißer Farbe */
public void loeschen() {
Color farbeAlt;
farbeAlt = kLw.getColor();
kLw.setColor (Color.white);
kLw.fillRect (zX, zY-zB, zB, zB);
kLw.drawRect (zX, zY-zB, zB, zB);
kLw.setColor (farbeAlt);
zSichtbar = false;
}

/** zeichnet die Figur in der neuen Farbe */
public void setzeFarbe (Color farbe) {
zFarbe = farbe;
if (zSichtbar)
zeichnen();
}

/** Bezugspunkt: Ecke unten links */
public void setzePosition (int xPos, int yPos) {
if (zSichtbar) {
loeschen();
zX = xPos; zY = yPos;
zeichnen();
}
else {
zX = xPos; zY = yPos;
}
}

/** liefert die x-Koordinate der Figur */
public int liesXPosition() {
return zX;
}

/** liefert die y-Koordinate der Figur */
public int liesYPosition() {
return zY;
}


} // Klasse Quadrat

import java.awt.*; // Color

public class Kreis {

// Attribute

FrmZeichnen kZf;
Graphics kLw;
int zX, zY, zR;
Color zFarbe;
boolean zSichtbar;

// Konstruktoren

/** Konstruktor */
public Kreis (FrmZeichnen zf, int xPos, int yPos, int radius, Color farbe) {

// Instanzvariable initialisieren
kZf = zf;
kLw= kZf.getGraphics(); // Leinwand = Zeichenflaeche
zX = xPos + kZf.getInsets().left;
zY = yPos + kZf.getInsets().top;
zR = radius;
zFarbe = farbe;
zSichtbar = false;
}

// Methoden

/** zeichnet die Figur */
public void zeichnen() {
Color farbeAlt;
farbeAlt = kLw.getColor();
kLw.setColor (zFarbe);
kLw.fillOval (zX-zR, zY-zR, 2*zR, 2*zR);
kLw.setColor (Color.black);
kLw.drawOval (zX-zR, zY-zR, 2*zR, 2*zR);
kLw.setColor (farbeAlt);
zSichtbar = true;
}

/** übermalt die Figur mit weißer Farbe */
public void loeschen() {
Color farbeAlt;
farbeAlt = kLw.getColor();
kLw.setColor (Color.white);
kLw.fillOval (zX-zR, zY-zR, 2*zR, 2*zR);
kLw.drawOval (zX-zR, zY-zR, 2*zR, 2*zR);
kLw.setColor (farbeAlt);
zSichtbar = false;
}

/** Farbe ... */
public void setzeFarbe (Color farbe) {
zFarbe = farbe;
if (zSichtbar)
zeichnen();
}

/** Bezugspunkt: Ecke unten links */
public void setzePosition (int xPos, int yPos) {
if (zSichtbar) {
loeschen();
zX = xPos; zY = yPos;
zeichnen();
}
else {
zX = xPos; zY = yPos;
}

}

/** liefert die x-Koordinate der Figur */
public int liesXPosition() {
return zX;
}

/** liefert die y-Koordinate der Figur */
public int liesYPosition() {
return zY;
}

} // class

import java.awt.*; // Color

public class Dreieck {

// Attribute

private FrmZeichnen kZf;
private Graphics kLw;
private int zX, zY, zB, zH;
private Color zFarbe;
private boolean zSichtbar;

// Konstruktoren

/** Konstruktor */
public Dreieck (FrmZeichnen Zf, int xPos, int yPos, int breite, Color farbe)
{
// Instanzvariable initialisieren
kZf = Zf;
kLw= kZf.getGraphics(); // Leinwand = Zeichenflaeche
zX = xPos + kZf.getInsets().left;
zY = yPos + kZf.getInsets().top;
zB = breite;
zH = breite / 2;
zFarbe = farbe;
}

// Methoden

/** zeichnet die Figur */
public void zeichnen() {
// kLw= kZf.getGraphics();
Polygon eckpunkte;

eckpunkte = new Polygon();
eckpunkte.addPoint (zX, zY);
eckpunkte.addPoint (zX + zB, zY);
eckpunkte.addPoint (zX + zB/2, zY - zH);

kLw.setColor (zFarbe);
kLw.fillPolygon (eckpunkte);
kLw.setColor (Color.black);
kLw.drawPolygon (eckpunkte);
zSichtbar = true;
}

/** übermalt die Figur mit weißer Farbe */
public void loeschen() {
// ...
zSichtbar = false;
}

/** Farbe ... */
public void setzeFarbe (Color farbe) {
zFarbe = farbe;
zeichnen();
}

/** Bezugspunkt: Ecke unten links */
public void setzePosition (int xPos, int yPos) {
zX = xPos;
zY = yPos;
if (zSichtbar)
zeichnen();
}

/** liefert die x-Koordinate der Figur */
public int liesXPosition() {
return zX;
}

/** liefert die y-Koordinate der Figur */
public int liesYPosition() {
return zY;
}

} // class

Für unser Projekt benötigen wir noch weitere Klassen; diese werden hier aber nicht aufgelistet. Sie finden sich im Download. Auf eine Besonderheit in der Klasse Linie soll aber hingewiesen werden. Ein Problem beim Zeichnen mit Hilfe der Bibliothek ist, dass die Linienstärken der Objekte nicht einfach veränderbar sind. Dies geschieht durch ein Typcasting. Was dieses genau ist, wird später erklärt. Im Quellcode ist ersichtlich, wie dies programmiertechnisch gemacht werden muss.

public void zeichnen() {
Graphics2D g2D=(Graphics2D) kLw;
Color farbeAlt;
farbeAlt = kLw.getColor();
g2D.setColor (zFarbe);
g2D.setStroke(new BasicStroke(d));
g2D.drawLine (zX,zY,zX2,zY2);

g2D.setColor (farbeAlt);
zSichtbar = true;
}

Stadt

Wie auf dem Bild zu erkennen ist, haben unsere Haustypen Zuwachs bekommen. Es gibt jetzt das Luxushaus und ein Haus mit Garage. Desweiteren haben wir eine einfache Landschaft und einen Viadukt. HAben wir in den vorherigen Verisonen unserer Stadt für jeden Haustyp eine eigene Objektvariable gehabt, so benutzen wir jetzt ein Array mit einer einzigen Objektvariable Haus für alle Gebäude.

public class Gebaeude
{ // Objektvariable
Fenster f;
Haus[] hhaus;
Viadukt[] hBruecke;

Die einzelnen Haustypen werden wie folgt erzeugt:

hhaus[8]= new LuxusHaus(f,600,550,85);
hhaus[8].zeichnen();
hhaus[9]= new Haus(f,300,250,60);
hhaus[9].zeichnen();
hhaus[10]= new Halle(f,950,250,60);
hhaus[10].zeichnen();

Objekte einer Unterklasse können in einer Objektvariable der Oberklasse verwaltet werden; denn alle Operation für die Objektvariable der Oberklasse sind auch für das Objekt der Unterklasse vorhanden. Der Quelltext für die einzelnen Haustypen ist im Downloadbereich verfügbar. Eine Besonderheit hat die Klasse HausMitGarage. Zwei ihrer Methoden (GarageAuf, GarageZu) kommen in keiner Oberklasse vor. Ein Aufruf aus unserem Hauptprogramm nach dem Beispiel LichtAn oder TuerAuf würde einen Fehler produzieren.

public void garageZu()
{for (int i=0;i<19;i=i+1)
if (hhaus[i] instanceof HausMitGarage)
((HausMitGarage)hhaus[i]).garageZu();
}

Da hHaus[x] eine Objektvariable der Klasse Haus ist, wird die Methode garageAuf( ) auch in der Klasse Haus gesucht. Das Voranstellen des Klassennamens in Klammern veranlasst das System, die Methode garageAuf( ) bei den Methoden der Unterklasse HausMitGarage zu suchen, also die Methode in der richtigen Klassendefinition. Diese Konstruktion wird Typumwandlung (Type-Casting) genannt. Durch das Voranstellen des Klassennamens wird festgelegt, dass nicht irgendein Objekt bearbeitet wird, sondern ein Objekt eben dieser Klasse - mit allen Möglichkeiten, die diese Klasse zur Verfügung stellt.
Ein Methodenaufruf erfordert eine weitere Klammer, um den Vorrang der Typ-Umwandlung zu gewährleisten: (( <Klassenname>) <Objekt> ). <Methodenaufruf>
· Typumwandlung ist immer erforderlich, wenn Methoden der Unterklasse aufgerufen werden, die in der Oberklasse noch nicht existieren.
· Type-Casting kann auch den Aufruf der gleichnamigen Methode der Unterklasse - oder auch der Oberklasse - erzwingen.
· Typumwandlung ist auch bei Methoden, die aus der (Ober-)Klasse der Objektvariable geerbt werden, möglich, aber unnötig.
Type-Casting in eine falsche Unterklasse führt zu einem Fehler. Der Typumwandlung geht daher häufig ein Test auf die Klassenzugehörigkeit des Objektes mithilfe des instanceof-Operators voraus.
Allgemeine Form des instanceof-Operators:
<Objektvariable> instanceof <Objektklasse>
Der instanceof-Operator liefert true oder false, also einen Wahrheitswert (Typ boolean). Das Ergebnis ist true, wenn Objektvariable ein Exemplar der Klasse Objektklasse ist oder ein Exemplar einer Klasse, die von der Klasse Objektklasse abgeleitet ist. Wenn die Objektvariable = null ist, also auf kein Objekt verweist, ist das Ergebnis immer false

Der Rest des Programms ist eigentlich verständlich, wenn man sich mit dem Quellcode etwas auseinandersetzt.