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;
}
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.