-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAsteroids.html
More file actions
1508 lines (1482 loc) · 85.6 KB
/
Asteroids.html
File metadata and controls
1508 lines (1482 loc) · 85.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html><html lang="de"><head>
<title>Variationen zum Thema: Java</title>
<meta name="title" content="Variationen zum Thema: Java">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta charset="UTF-8">
<meta name="description" content="Introduction to Java Programming">
<meta name="keywords" content="java,introduction">
<meta name="author" content="Ralph P. Lano">
<meta name="robots" content="index,follow">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="book.css">
</head>
<body><center>
<div id="wrap">
<ul class="sidenav">
<p><a href="index.html">Variationen zum Thema</a><a href="index.html">Java</a></p>
<li><a href="Karel.html">Karel</a></li>
<li><a href="Graphics.html">Graphics</a></li>
<li><a href="Console.html">Console</a></li>
<li><a href="Agrar.html">Agrar</a></li>
<li><a href="MindReader.html">MindReader</a></li>
<li><a href="Swing.html">Swing</a></li>
<li><a href="Asteroids.html">Asteroids</a></li>
<li><a href="Stocks.html">Stocks</a></li>
</ul>
<div class="content"><p>
<img alt="" src="images/e271affc-a756-4bfd-b50e-7429e71932e8.png" style="display: block; margin-left: auto; margin-right: auto;width: 285px; height: 294px;" /></p>
<h1>
Asteroids</h1>
<p>
In diesem Kapitel gibt es wieder viele Spiele. Aber vorher müssen wir uns mit Objekt-Orientierung beschäftigen. Wir lernen die Grundpfeiler der Objekt-Orientierung kennen, nämlich Vererbung und Komposition. Wir werden auch Arrays kennenlernen, und wie man mit mehrdimensionalen Arrays Bilder manipuliert. Außerdem lernen wir wie man mit Tastaturereignissen (KeyEvents) arbeitet und die Klasse GCompound wird kurz vorgestellt. Wir beginnen mit den Arrays.</p>
<p>
.</p>
<h2>
<img alt="" src="images/eierKarton.jpg" style="width: 316px; height: 256px; float: right;" />Arrays</h2>
<p>
Was sind Arrays? Ein Eierkarton ist ein Array. Und zwar ist es ein Array für zehn Eier. D.h. aber nicht dass da auch zehn Eier drin sind, manchmal sind nur drei Eier drin. </p>
<p>
Offensichtlich sind also Arrays ganz praktisch und deswegen betrachten wir Arrays in Java. Nehmen wir einmal an wir wollten ein Array für zwölf Ganzzahlen anlegen, dann würden wir schreiben:</p>
<pre>
int<span style="color:#0000ff;">[]</span> eggs;
eggs = new int[10];
</pre>
<p>
In der ersten Zeile deklarieren wir ein Array. Wir sagen dass es sich bei der Variablen <em>eggs</em> um ein Array vom Typ <em>int</em> handelt, indem wir hinter dem Datentyp einfach eckige Klammern schreiben. In der zweiten Zeile legen wir dann das Array an und sagen, dass es Platz für zehn <em>int</em>'s geben soll. </p>
<p>
Wir können Arrays von beliebigen Datentypen anlegen, z.B. könnten wir auch ein Array mit vier GOvals anlegen:</p>
<pre>
GOval[] circles = new GOval[4];</pre>
<p>
Alle Arrays haben zwei wichtige Eigenschaften:</p>
<ol>
<li>
sie sind immer vom selben Datentyp, man sagt auch sie sind homogen, und</li>
<li>
sie sind geordnet, d.h., sie sind durchnummeriert beginnend mit 0.</li>
</ol>
<p>
.</p>
<h2>
Mit Arrays arbeiten</h2>
<p>
Nachdem wir ein Array deklariert und angelegt haben, müssen wir es mit Werten füllen. Das können wir von Hand machen:</p>
<pre>
eggs[0] = 0;
eggs[1] = 2;
eggs[2] = 4;
...
eggs[9] = 18;</pre>
<p>
Wir weisen also dem ersten Element im Array (dem Element Nummer 0) den Wert 0 zu, dem zweiten Element den Wert 2 usw. Wir können die Zuweisung aber auch mit einer Schleife machen:</p>
<pre>
for (int i=0; i<10; i++) {
eggs[i] = readInt("?");
}
</pre>
<p>
oder mit dem folgende Trick (der allerdings nur beim Anlegen funktioniert):</p>
<pre>
int[] eggs = { 2, 4, 6, 8 };
</pre>
<p>
Wenn wir auf ein Element zugreifen wollen, müssen wir seine Hausnummer angeben. Also auf das dritte Element greifen wir mit :</p>
<pre>
println( eggs[2] );</pre>
<p>
zu. Wenn wir alle Elemente ausgeben wollen, geht das am besten mit einer Schleife:</p>
<pre>
for (int i=0; i<eggs.length; i++) {
println( eggs[i] );
}
</pre>
<p>
.</p>
<h2>
<img alt="" src="images/monthName.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 100px; float: right;" />Übung: MonthName</h2>
<p>
Ein nützliches Beispiel ist die Konvertierung vom Monat als Zahl, also z.B. 12, in den Monatsnamen, also z.B. Dezember. Man könnte das mit einer langen <em>if</em> oder <em>switch</em> Bedingung machen, aber man kann das auch sehr elegant mit Arrays lösen:</p>
<pre>
public class MonthName extends ConsoleProgram {
private String[] monthName = { "January", "February", "March", "April",
"May", "June", "July", "August", "September", "October",
"November", "December" };
public void run() {
int monthNr = readInt("Enter number of month (1=January): ");
println(monthName[monthNr - 1]);
}
}</pre>
<p>
.</p>
<h2>
<img alt="" src="images/fourGOvals.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 200px; float: right;" />Array von Objekten</h2>
<p>
Im Prinzip sind Arrays nicht weiter kompliziert. Allerdings gibt es manchmal eine kleine Verwirrung wenn man Arrays von Objekten hat. Schauen wir uns dazu zunächst die Deklaration eines Arrays von GOvals an:</p>
<pre>
GOval[] circles = new GOval[4];
</pre>
<p>
und vergleichen es mit dem Anlegen eines neuen Kreises:</p>
<pre>
GOval circle = new GOval(100,100,50,50);
</pre>
<p>
Was ist der Unterschied? Im ersten Fall legen wir Platz für vier GOvals an. Wir legen aber noch keine Kreise selbst an. Im zweiten Fall dagegen legen wir einen Kreis an. Wenn wir also ein Array mit vier Kreisen anlegen wollen (und nicht nur Platz für vier Kreise), dann müssen wir ein bischen mehr Code schreiben:</p>
<pre>
GOval[] circles = new GOval[4];
circles[0] = new GOval(100, 66, 50, 50);
circles[1] = new GOval(100, 116, 50, 50);
circles[2] = new GOval(150, 66, 50, 50);
circles[3] = new GOval(150, 116, 50, 50);
</pre>
<p>
.</p>
<h2>
<img alt="" src="images/chess2.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 200px; float: right;" />Mehrdimensionale Arrays</h2>
<p>
Eindimensionale Arrays sind ganz lustig und sparen uns viel Schreibarbeit. Aber wirklich cool sind zweidimensionale Arrays. Wir fangen ganz einfach mit einem Schachspiel an.</p>
<pre>
private char[][] chess = {
{ 'r', 'n', 'b', 'q', 'k', 'b', 'n', 'r' },
{ 'p', 'p', 'p', 'p', 'p', 'p', 'p', 'p' },
{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' },
{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' },
{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' },
{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' },
{ 'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P' },
{ 'R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R' } };
</pre>
<p>
Es handelt sich hier also um ein 8 mal 8 Array von chars. Kleinbuchstaben stehen für schwarz, und Großbuchstaben für weiß. Wenn wir das Spielfeld ausgeben wollen, dann könnten wir das mit zwei verschachtelten <em>for</em> Schleifen tun:</p>
<pre>
private void printChessBoard() {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
print(chess[i][j]);
}
println();
}
}</pre>
<p>
.</p>
<figure style="margin-left: 10px; margin-right: 10px; float:right;text-align: center;">
<img alt="" src="images/grayImage.png" style="width: 400px; height: 222px;" /> <figcaption>
<p>
Taj Mahal, Bildquelle Wikipedia [1]</p>
</figcaption></figure>
<h2>
Übung: GrayImage</h2>
<p>
Bilder sind auch zweidimensionale Arrays. Als kleine Übung wollen wir ein Farbbild in ein Graubild umwandeln. Zunächst laden wir das Bild mittels der GImage Klasse:</p>
<pre>
GImage image = new GImage("Taj_Mahal_(Edited).jpeg");</pre>
<p>
Als nächstes müssen wir an die Pixel rankommen. Das geht mit der Methode <em>getPixelArray()</em> der Klasse GImage:</p>
<pre>
int[][] array = image.getPixelArray();
int height = array.length;
int width = array[0].length;</pre>
<p>
Diese liefert uns ein zweidimensionales Array von <em>int</em>s. Jeder dieser <em>int</em> entspricht einem Pixel. Wenn wir also den Pixel an Position x=5 und y=22 möchten, dann geht das mittels</p>
<pre>
int pixel = array[5][22];</pre>
<p>
Jeder dieser Pixel enthält dessen rot, grün und blau Werte, und mittels</p>
<pre>
int r = GImage.getRed(pixel);
int g = GImage.getGreen(pixel);
int b = GImage.getBlue(pixel);</pre>
<p>
erhalten wir diese. Um daraus ein Graubild zu erstellen, verwenden wir die Formel die auch Gimp verwendet [2]:</p>
<pre>
lum = 0.21 * r + 0.72 * g + 0.07 * b;</pre>
<p>
das packen wir dann wieder in das zweidimensionales Array</p>
<pre>
array[5][22] = GImage.createRGBPixel(lum, lum, lum);</pre>
<p>
und am Ende machen wir daraus ein neues GImage</p>
<pre>
GImage grayImage = new GImage(array);</pre>
<p>
.</p>
<h2>
Objekt-Orientierung</h2>
<p>
Im zweiten Teil dieses Kapitels wollen wir unsere Kenntnisse bzgl der Objektorientierung vertiefen. Die zwei großen Themen die anstehen sind zum einen <em>Vererbung</em> ("is a" Beziehung) und zum anderen <em>Komposition</em> ("has a" Beziehung). Wir beginnen mit einem kleinen Spiel, dem <em>MarsLander</em>.</p>
<p>
.</p>
<h2>
<img alt="" src="images/marsLander.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 200px; float: right;" />Übung: MarsLander</h2>
<p>
Elon Musk will ja im kommenden Jahrzehnt die ersten Menschen auf den Mars schicken. Karel hat sich freiwillig gemeldet, und muss jetzt erst mal die Landung üben. Dazu gibt es einen Simulator den <em>MarsLander</em>. Es geht darum ein Raumschiff sicher auf dem Mars zu landen. Dazu können wir mit den Pfeiltasten (nach oben und nach unten) das Raumschiff abbremsen oder beschleunigen. Wenn unser Geschwindigkeit beim Touchdown zu hoch ist, sterben wir.</p>
<p>
Der Top-Down Ansatz bietet sich an: Betrachten wir die <em>run()</em> Methode:</p>
<pre>
public void run() {
setup();
waitForClick();
// game loop
while (<span style="color:#0000ff;">spaceShip != null</span>) {
moveSpaceShip();
checkForCollision();
pause(DELAY);
}
displayGameOver();
}</pre>
<p>
Wie üblich fangen wir mit dem <em>setup()</em> an. Nach dem Setup warten wir bis der Nutzer mit der Maus einmal auf den Bildschirm klickt um das Spiel zu starten. Danach beginnt der GameLoop und der sieht genauso aus wie bei unserer letzten Animation, dem Billiard. Interessant ist jetzt, dass wir keine Endlosschleife mehr haben, sondern eine Schleife mit Abbruchkriterium: nämlich wenn es kein SpaceShip mehr gibt, also <em>spaceShip == null</em>, dann soll das Spiel aufhören.</p>
<p>
Es gibt drei Instanzvariablen,</p>
<pre>
private GPolygon spaceShip;
private int vy = 0;
private int vx = 0;</pre>
<p>
also das spaceShip, sowie dessen Geschwindigkeiten vx und vy.</p>
<p>
In der setup() Methode wird das spaceShip initialisiert, der Code ist identisch mit dem der Übung aus Kapitel zwei,</p>
<pre>
private void setup() {
spaceShip = new GPolygon();
spaceShip.addVertex(0, -SPACE_SHIP_SIZE);
spaceShip.addVertex(-2 * SPACE_SHIP_SIZE / 3, SPACE_SHIP_SIZE);
spaceShip.addVertex(0, SPACE_SHIP_SIZE / 2);
spaceShip.addVertex(2 * SPACE_SHIP_SIZE / 3, SPACE_SHIP_SIZE);
add(spaceShip, (getWidth() - SPACE_SHIP_SIZE) / 2, SPACE_SHIP_SIZE);
<span style="color:#0000ff;">addKeyListeners()</span>;
}</pre>
<p>
und wir fügen einen <em>KeyListener</em> hinzu. Wir wollen das spaceShip ja mittels der Tastatur (keyboard) steuern, und deswegen müssen wir auf Tastenereignisse (KeyEvents) hören. Das ist also vollkommen analog zu den MouseEvents und dem MouseListener.</p>
<p>
Die <em>moveSpaceShip()</em> Methode ist absolut trivial:</p>
<pre>
private void moveSpaceShip() {
vy += GRAVITY;
spaceShip.move(vx, vy);
}</pre>
<p>
Da wir uns im Schwerefeld (GRAVITY) des Mars befinden, erhöht sich unsere Geschwindigkeit in jedem Schritt. Und in jedem Schritt bewegen wir das spaceShip um den Betrag der Geschwindigkeit.</p>
<p>
In der <em>checkForCollision()</em> Methode checken wir, ob wir schon auf der Marsoberfläche (also unten) angekommen sind:</p>
<pre>
private void checkForCollision() {
double y = spaceShip.getY();
if (y > (getHeight() - SPACE_SHIP_SIZE)) {
spaceShip = null;
}
}</pre>
<p>
Falls ja, dann setzen wir das spaceShip einfach auf <em>null</em>. "null" heißt soviel wie "nicht initialisiert" oder "existiert nicht" oder "gibt es nicht". Das ist ein vordefinierter Wert, den alle Objekte haben bevor sie mittels <em>new</em> erzeugt werden. Wir können aber auch Objekte explizit auf <em>null</em> setzen, und das bedeutet das wir das Objekt löschen. In unserem Beispiel verwenden wir das um die Endlosschleife zu beenden.</p>
<p>
Was noch bleibt sind die Tastenereignisse. Ähnlich wie es bei der Maus die <em>mousePressed()</em> Methode gibt, gibt es auch eine <em>keyPressed()</em> Methode:</p>
<pre>
public void keyPressed(KeyEvent e) {
switch (e.<span style="color:#0000ff;">getKeyCode()</span>) {
case 38: // up
vy--;
break;
case 40: // down
vy++;
break;
}
}</pre>
<p>
Wir wollen natürlich wissen welche Taste gedrückt wurde und das erfahren wir von der <em>getKeyCode()</em> Methode de KeyEvents. Jede Taste hat ihren eigenen KeyCode, und für die Pfeil-Oben Taste ist das die 38 und für die Pfeil-Unten Taste ist das die 40.</p>
<p>
So, jetzt können wir spielen, bzw. trainieren.</p>
<p>
.</p>
<h2>
<img alt="" src="images/marsLander2.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 200px; float: right;" />Vererbung</h2>
<p>
Was hat der <em>MarsLander</em> mit Vererbung zu tun? Noch nicht viel. Aber schauen wir uns den Code an. Was uns ein bischen stören sollte sind die drei Zeilen,</p>
<pre>
private GPolygon spaceShip;
private int vy = 0;
private int vx = 0;</pre>
<p>
denn bei <em>vx</em> und <em>vy</em> handelt es sich um die Geschwindigkeit des spaceShip, die gehören also eigentlich zum spaceShip. Nehmen wir an wir hätten mehrere spaceShips, oder wir hätten ganz viele Asteroiden die sich durch die Gegend bewegen, dann hätten wir ganz viele vx's und vy's. Und das wird total unübersichtlich und häßlich.</p>
<p>
Um das zu verhindern machen wir folgendes: wir deklarieren eine neue Klasse namens GSpaceShip und alles was mit spaceShip zu tun hat packen wir in diese Klasse:</p>
<pre>
public class GSpaceShip <span style="color:#0000ff;">extends GPolygon</span> {
// constants
private final int GRAVITY = 1;
// instance variables
public int vy = 0;
public int vx = 0;
public GSpaceShip(int SPACE_SHIP_SIZE) {
<span style="color:#0000ff;">super()</span>;
addVertex(0, -SPACE_SHIP_SIZE);
addVertex(-2 * SPACE_SHIP_SIZE / 3, SPACE_SHIP_SIZE);
addVertex(0, SPACE_SHIP_SIZE / 2);
addVertex(2 * SPACE_SHIP_SIZE / 3, SPACE_SHIP_SIZE);
}
public void move() {
vy += GRAVITY;
move(vx, vy);
}
}</pre>
<p>
Als erstes sehen wir, dass es sich bei <em>GSpaceShip</em> um ein <em>GPolygon</em> handelt, denn es sagt "GSpaceShip extends GPolygon", also GSpaceShip <em>ist ein</em> GPolygon. GSpaceShip erbt also alle Eigenschaften und Methoden von GPolygon. Deswegen sagt man auch <em>Vererbung</em> ist eine "is a" Beziehung.</p>
<p>
Als zweites sehen wir, dass die Instanzvariablen <em>vx</em> und <em>vy</em> jetzt Instanzvariablen des spaceShips sind, sie sind also da wo sie hingehören.</p>
<p>
Als drittes schauen wir uns den Konstruktor an: dort sehen wir in der ersten Zeile ein "<em>super()</em>". Die Methode super() tut nichts anderes als den Konstrukter der Superklasse aufzurufen, also der Elternklasse. In unserem Fall ist das GPolygon(). Danach sehen wir, wie wir uns selbst (wir sind ja jetzt ein GPolygon) Vertices hinzufügen. D.h. im Konstruktor bestiimmen wir unser Aussehen.</p>
<p>
Als letztes sehen wir, dass wir eine neue Methode namens <em>move()</em> hinzugefügt haben. Da GSpaceShip jetzt ja seine eigene Geschwindigkeit kennt, kann es sich ja auch selbst bewegen. </p>
<p>
Vererbung hat also viele Vorzüge: vor allem führt sie dazu, dass Klassen selbstständiger werden, und weniger Abhängigkeiten haben. Man sagt auch die Klasse übernimmt Verantwortung über ihre eigenen Attribute (Variablen) und Verhalten (Methoden). Diese geringeren Abhängigkeiten führen auch zu einer geringeren Kopplung, die dazu führt, dass unser Code weniger kompliziert wird. </p>
<p>
Schauen wir uns die Vereinfachungen im <em>MarsLander2</em> an. Zunächst brauchen wir nur noch eine Instanzvariable:</p>
<pre>
private GSpaceShip spaceShip;</pre>
<p>
und außerdem werden die <em>setup()</em> und <em>moveSpaceShip()</em> Methoden viel kürzer:</p>
<pre>
private void setup() {
spaceShip = new GSpaceShip(SPACE_SHIP_SIZE);
add(spaceShip, (getWidth() - SPACE_SHIP_SIZE) / 2, SPACE_SHIP_SIZE);
addKeyListeners();
}
private void moveSpaceShip() {
spaceShip.move();
}</pre>
<p>
Das ist schon ziemlich cool. Den wahren Wert dieser Vereinfachungen werden wir aber erst schätzen lernen wenn es daran geht <em>Asteroids</em> zu programmieren.</p>
<p>
.</p>
<h2>
<img alt="" src="images/marsLander3.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 200px; float: right;" />Komposition</h2>
<p>
Das zweite wichtige Konzept der Objektorientierung ist die Komposition. Wie wir gesehen haben, kann man neue Klassen (GSpaceShip) durch Vererbung von einer existierenden Klasse (GPolygon) erzeugen. Man kann aber auch neue Klassen erzeugen, indem man sie aus mehreren existierenden Klassen zusammensetzt, also komposiert.</p>
<p>
Als Beispiel schreiben wir eine Klasse GSmiley. </p>
<pre>
public class GSmiley {
public GSmiley(int SIZE) {
super();
GOval face = new GOval(SIZE, SIZE);
face.setFilled(true);
face.setFillColor(Color.YELLOW);
add(face);
GOval leftEye = new GOval(SIZE/10, SIZE/10);
leftEye.setColor(Color.GREEN);
add(leftEye, SIZE/4, SIZE/4);
GOval rightEye = new GOval(SIZE/10, SIZE/10);
rightEye.setColor(Color.RED);
add(rightEye, 3*SIZE/4, SIZE/4);
GArc mouth = new GArc(SIZE/2, SIZE/2, 225, 90);
add(mouth, 0.3*SIZE, 0.3*SIZE);
}
}</pre>
<p>
Unser GSmiley besteht aus verschiedenen Komponenten, es hat also ein <em>face</em>, ein <em>leftEye</em>, ein <em>rightEye</em> und ein <em>mouth</em>. Im Konstruktor basteln wir also ein neues Objekt aus mehreren alten. Das ist Komposition. Deswegen sagt man auch dass <em>Komposition</em> eine "has a" Beziehung ist, denn GSmiley hat ein <em>face</em>, ein <em>leftEye</em>, ein <em>rightEye</em> und ein <em>mouth</em>.</p>
<p>
.</p>
<h2>
GCompound</h2>
<p>
Man kann Vererbung und Komposition auch mischen. Wenn wir beim GSmiley Beispiel noch "extends GCompound" zur Klassendeklaration hinzufügen, dann können wir GSmiley auch in unserem MarsLander verwenden. Wir müssen dann in der ersten Version des MarsLanders einfach "GPolygon" durch "GSmiley" ersetzen.</p>
<p>
.</p>
<h2>
<img alt="" src="images/8aff1c4e-4764-43d9-9651-6f407d47f9fe.png" style="width: 150px; height: 121px; float: right;" />Vererbung vs Komposition</h2>
<p>
Wann verwendet man Vererbung und wann Komposition? Eine Daumenregel lautet, wenn möglich sollte man Komposition verwenden. Das hat damit zu tun, dass es in Java keine Mehrfachvererbung gibt. Also eine Klasse kann keine zwei Eltern haben. Diese Einschränkung gibt es bei Komposition nicht, im Prinzip kann eine Klasse aus beliebig vielen Komponenten bestehen.</p>
<p>
.</p>
<p>
<img alt="" src="images/9a6ee258-1e5b-4941-a675-2be6f1216447.png" style="width: 136px; height: 155px; float: right;" />Eine kleine Anmerkung noch: mit Mehrfachvererbung meinen wir immer eine Klasse hat mehrere Elternklassen. Das ist nicht erlaubt. Es ist aber durchaus möglich dass eine Klasse eine Elternklasse hat, und diese Elternklasse hat wieder eine Elternklasse, also sozusagen die Großelternklasse der ursprünglichen. Also z.B. GObject ist die Großelternklasse der Klasse GSpaceShip. Das ist erlaubt.</p>
<p>
.</p>
<p>
.</p>
<hr />
<h1>
Review</h1>
<p>
Mit den Prinzipien Vererbung und Komposition haben wir den Kern der Objektorientierung erreicht und geknackt. Wir haben gelernt wie man einer existierenden Klasse mittel Vererbung zusätzlich Eigenschaften geben kann. So kann sich ein GPolygon nicht selbstständig bewegen, da es keine Geschwindigkeit hat. Die Klasse GSpaceShip, die ja eigentlich auch ein GPolygon ist, kennt aber seine eigene Geschwindigkeit, und kann sich selbstständig bewegen. Weiter haben wir gesehen, dass man mittels Komposition aus mehreren Klassen eine neue Klasse zusammenbauen kann. Beides ist sehr nützlich, wie wir sehen werden.</p>
<p>
Zusätzlich haben wir noch ein paar andere nützliche Dinge, wie z.B.</p>
<ul>
<li>
Arrays</li>
<li>
mehrdimensionale Arrays</li>
<li>
Bildverarbeitung</li>
<li>
Tastaturereignisse</li>
<li>
und die Klasse GCompound</li>
</ul>
<p>
kennengelernt.</p>
<p>
.</p>
<hr />
<h1>
Projekte</h1>
<p>
Die Projekte in diesem Kapitel fangen an richtig Spass zu machen. Los geht's.</p>
<p>
.</p>
<h2>
<img alt="" src="images/pianoConsole.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 100px; float: right;" />PianoConsole</h2>
<p>
Als erste Anwendung für Arrays schreiben ein kleines Musikprogramm. In unserem Array speichern wir die Melodie:</p>
<pre>
private String[] tune = { "C", "D", "E", "F", "G", "G" };</pre>
<p>
Zum einfachen Abspielen von Audio-Dateien können wir die <em>AudioClip</em> Klasse verwenden:</p>
<pre>
AudioClip audioClip = getAudioClip(getCodeBase(), "sound.wav");
audioClip.play();
pause(500);
audioClip.stop();</pre>
<p>
Der <em>AudioClip</em> Klasse sagt man welche Datei sie abspielen soll, mit der Methode <em>play()</em> wird dann das Abspielen gestartet und mit <em>stop()</em> beendet. Wenn man jetzt für die verschiedenen Noten verschiedene wav-Dateien hat, also z.B. "C.wav", "D.wav", usw. dann kann man so Melodien abspielen, indem man mit einer Schleife die einzelnen Noten un dem <em>tune</em> Array durchiteriert.</p>
<p>
.</p>
<h2>
<img alt="" src="images/piano3.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 133px; float: right;" />Piano</h2>
<p>
Konsolenanwendungen sind immer etwas langweiliger, und ehrlich wer würde dafür schon Geld ausgeben? Wir haben aber ja schon im zweiten Semester eine UI für unser Piano geschrieben. Natürlich würde wir unser Klavier über die Maus steuern, also MouseListener im <em>setup()</em> hinzufügen. </p>
<p>
Die Frage die sich allerdings stellt, wie wissen wir welche Taste gedrückt wurde? Interessanterweise können wir dafür unsere <em>getElementAt()</em> Methode verwenden:</p>
<pre>
public void mouseClicked(MouseEvent e) {
int x = e.getX();
int y = e.getY();
GObject obj = getElementAt(x, y);
if (obj != null) {
...
}
}</pre>
<p>
Darüber können wir also herausfinden, auf welches GRect gedrückt wurde. Jetzt gibt es drei Alternativen weiter zu machen:</p>
<ol>
<li>
Wenn das GRect einen Namen hätte wäre die Welt ganz einfach. Wir können das mit Vererbung erreichen: wir definieren eine neue Klasse <em>GKey</em>, die ein GRect ist und noch zusätzlich ein Attribute für den Namen hat.</li>
<li>
Wir merken uns irgendwo die x-Koordinate der Tasten. Mit <em>obj.getX() </em>können wir diese ja erhalten, und viola wissen wir welche Taste gedrückt wurde.</li>
<li>
Oder wir halten uns Referenzen zu allen Tasten in einem Array.</li>
</ol>
<p>
Diese dritte Möglichkeit wollen wir kurz etwas näher betrachten. Dazu brauchen wir ein Array als Instanzvariable</p>
<pre>
private GRect[] keys = new GRect[12];</pre>
<p>
Wenn wir die Tasten erzeugen, dann speichern wir die einfach in unserem Instanzarray:</p>
<pre>
int keyCounter = 0;
// draw 8 white keys
for (int i = 0; i < 7; i++) {
<span style="color:#0000ff;">keys[keyCounter] = </span>new GRect(WIDTH / 7, HEIGHT - HEIGHT_OFFSET);
add(keys[keyCounter], i * WIDTH / 7, 0);
keyCounter++;
}</pre>
<p>
Und jetzt können wir in unserer <em>mouseClicked()</em> Methode einfach auf Gleichheit testen:</p>
<pre>
for (int i = 0; i < keys.length; i++) {
if (obj == keys[i]) {
AudioClip audioClip = getAudioClip(getCodeBase(), "music/"
+ tunes[i] + "4.wav");
println(tunes[i] + "4.wav");
audioClip.play();
}
}</pre>
<p>
Wenn wir jetzt die App auch noch auf dem Handy zum Laufen kriegen würden, dann wären wir reich! (Nächstes Jahr...)</p>
<p>
.</p>
<h2>
<img alt="" src="images/swap.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 100px; float: right;" />Swap</h2>
<p>
In diesem Projekt wollen wir zwei Elemente eines Arrays vertauschen. In dem Array</p>
<pre>
int[] arr = { 0, 2, 4, 6 };</pre>
<p>
möchten wir das Element an der zweiten Position (also die "2") mit dem Element an der dritten Position (also der "3") vertauschen. Das wollen wir mit einer Methode <em>swap(int[] arr)</em> machen, die ein Array als Übergabeparameter hat.</p>
<p>
Zwei Dinge wollen wir in dieser Übung lernen: Erstens in Arrays beginnen wir immer mit 0 zu zählen, und Arrays werden als Referenz übergeben, d.h., wenn wir ein Array als Übergabeparameter an eine Methode übergeben, dann wir dieses im Original übergeben. Alle Änderungen die wir in der Methode daran vornehmen sind permanent, also ändern das Array.</p>
<p>
.</p>
<h2>
<img alt="" src="images/examStatistics.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 200px; float: right;" />ExamStatistics</h2>
<p>
Als weiteres Beispiel für eine Anwendung von Arrays wollen wir ein paar statistische Daten zu den Punkten in einer Klausur ermitteln. Zusätzlich wollen wir die Punkte in einem Array speichern. Da wir noch nicht genau wissen wieviele Studierende an der Klausur teilnehmen, es aber sehr unwahrscheinlich ist, dass es mehr als 100 sind, legen wir eine Array für 100 Noten an:</p>
<pre>
int[] scores = new int[MAX_SIZE];</pre>
<p>
Wir bitten den Nutzer die Noten einzugeben. Dafür können wir wieder den Loop-and-a-Half verwenden. Damit wir wissen wann wir fertig sind, vereinbaren wir, dass die Eingabe der "-1" (dem Sentinel) bedeutet, dass alle Noten eingegeben wurden. Das ist also unser Abbruchkriterium. </p>
<p>
Die statistischen Daten die wir ermittlen wollen sind: Anzahl der Klausuren, der Durchschnitt, die niedrigste Punktzahl und die höchste Punktzahl.</p>
<p>
.</p>
<h2>
<img alt="" src="images/flippedImage.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 111px; float: right;" />FlippedImage</h2>
<p>
Arrays sind bestens geeignet um mit Bilder zu arbeiten. Wir haben ja oben schon gesehen wie wir auf die Pixel zugreifen können. In diesem Beispiel wollen wir ein gegebenes Bild spiegeln. Das kann horizontal oder vertikal sein. Dazu kreiiren wir ein neues Array</p>
<pre>
int[][] arrayFlipped = new int[height][width];</pre>
<p>
und verwenden zwei verschachtelte Schleifen</p>
<pre>
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int pixel = array[i][j];
arrayFlipped[height - i - 1][j] = pixel;
}
}</pre>
<p>
und die Pixel zu tauschen. Aus dem neuen Array machen wir dann ein neues Bild via</p>
<pre>
GImage flippedImage = new GImage(arrayFlipped);</pre>
<p>
.</p>
<h2>
<img alt="" src="images/grayImageXOR.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 211px; float: right;" />GrayImageXOR</h2>
<p>
Die Steganographie ist die Kunst der verborgenen Übermittlung von Informationen [3]. Interessant ist, dass man das ganz einfach mit der XOR Funktion, also dem Exkulsiven Oder, machen kann. Wie machen wir das? Nehmen wir an wir haben zwei Bilder und deren Pixel Arrays:</p>
<pre>
int pixel1 = array1[i][j];
int pixel2 = array2[i][j];</pre>
<p>
dann holen wir uns jeweils z.B. den Rot-Wert:</p>
<pre>
int r1 = GImage.getRed(pixel1);
int r2 = GImage.getRed(pixel2);</pre>
<p>
und genauso wie wir diese beide Werte z.B. addieren könnten, können wir auch die XOR Funktion '^' anwenden:</p>
<pre>
int xx = r1 ^ r2;</pre>
<p>
also, wir machen ein bitweises XOR der Bits von r1 mit denen von r2. Wenn wir als Bild-Beispiele das <em>Taj</em> und die <em>Mona Lisa</em> nehmen, dann kommt da eine lustige Mischung heraus auf der man weder das eine noch das andere erkennen kann. Interessant wird es wenn wir die Pixel dieses Mischlingwerkes nehmen und nochmal die XOR Funktion darüber laufen lassen: dann kommt nämlich wieder das ursprüngliche Bild zum Vorschein. Interessanterweise genau das Gegenstück. </p>
<p>
Auf dem gleich Prinzip basiert auch das RAID-5 System das für die Ausfallsicherheit von Festplatten sorgt [4].</p>
<p>
.</p>
<h2>
<img alt="" src="images/colorImage.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 111px; float: right;" />ColorImage</h2>
<p>
Eine häufige Anwendung der Bildmanipulation ist die Reduzierung der Farben in einem Bild. Das ist eine schöne Anwendung für die Ganzzahl Division (Integer Division). </p>
<pre>
int r = GImage.getRed(pixel);
r = (r / FACTOR) * FACTOR;</pre>
<p>
Ursprünglich kann <em>r</em> ja Werte zwischen 0 und 255 annehmen. Wenn wir diese Zahl durch z.B. 64 teilen, dann haben wir nur noch Werte zwischen 0 und 3. Multiplizieren wir das wieder mit 64, so haben wir nur noch die Werte 0, 64, 128 und 192. Also es gibt nur noch vier Rotwerte.</p>
<p>
.</p>
<h2>
<img alt="" src="images/imageFilterSimple.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 111px; float: right;" />ImageFilterSimple</h2>
<p>
Man kann auch viele andere Bildmanipulationen vornehmen. Z.B. kann man benachbarte Pixel einfach subtrahieren:</p>
<pre>
int r01 = GImage.getRed(array[i][j - 1]);
int r11 = GImage.getRed(array[i][j]);
// difference
int xx = r11 - r01;
xx *= 10;
edge[i][j] = GImage.createRGBPixel(xx, xx, xx);</pre>
<p>
Das Resultat entspricht einer einfachen Kantenerkennung.</p>
<p>
.</p>
<h2>
<img alt="" src="images/imageFilterMatrix.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 111px; float: right;" />ImageFilterMatrix</h2>
<p>
Viel interessantere Bildmanipulationen werden auf einmal möglich wenn man sich bewusst wird, dass Arrays eigentlich Matrizen sind. Das darf man nicht so laut sagen, sonst wird man verbrannt [6]. Aber wenn man das weiß, dann kann man ganz coole Sachen mit Bildern machen. Bei den Filtern sharpen, blur, edgeEnhance, edgeDetect, oder emboss wie sie aus jedem Bildbearbeitungsprogramm bekannt sind, handelt es sich eigentlich nur um die Anwendung einer Faltungsmatrix [5]. Z.B. sieht die Matrix um ein Bild schärfer zu machen folgendermaßen aus:</p>
<pre>
private int[][] currentFilter = {
{ 0, -1, 0 },
{ -1, 5, -1 },
{ 0, -1, 0 }
}; </pre>
<p>
Das Ausführen der Matrixmultiplikation (also der Anwendung des Filters auf das Bild) erledigt dann folgende Methode:</p>
<pre>
// int alpha = (color >> 32) & 0xFF;
// int red = (color >> 16) & 0xFF;
// int green = (color >> 8) & 0xFF;
// int blue = color & 0xFF;
private int applyFilterToPixel(int x, int y) {
int r = 0;
int g = 0;
int b = 0;
for (int i = 0; i <= 2; i++) {
for (int j = 0; j <= 2; j++) {
r += ((array[x + i][y + j] <span style="color:#0000ff;">>></span> 16) <span style="color:#0000ff;">&</span> 0xFF) * currentFilter[j][i];
g += ((array[x + i][y + j] >> 8) & 0xFF) * currentFilter[j][i];
b += ((array[x + i][y + j]) & 0xFF) * currentFilter[j][i];
}
}
return GImage.createRGBPixel(checkBounds(r / currentFactor), checkBounds(g
/ currentFactor), checkBounds(b / currentFactor));
}</pre>
<p>
die man für jeden Pixel des Ursprungsbildes aufrufen muss. Das Beispiel ist auch deswegen interessant weil man mal eine praktische Anwendung des Rechtsverschiebungs Operators ">>" (right shift) und des bitweisen Und Operators "&" sieht.</p>
<p>
.</p>
<h2>
<img alt="" src="images/Calculator.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 200px; float: right;" />Calculator</h2>
<p>
Die Anwendungen für Arrays sind wirklich vielfältig. Sehr häufig helfen sie einem ganz viel Code einzusparen. Ein schönes Beispiel ist der Calculator aus dem letzten Kapitel. Man kann natürlich die Knöpfe, also JButtons, alle einzeln erzeugen, man kann das aber auch effektiver machen:</p>
<pre>
private final String[] btnNames = { "7", "8", "9", "/", "4", "5", "6", "*",
"1", "2", "3", "-", ".", "0", "=", "+" };
public void init() {
...
setLayout(new GridLayout(4, 4));
for (int i = 0; i < btnNames.length; i++) {
JButton btn = new JButton(btnNames[i]);
add(btn);
}
...
}</pre>
<p>
.</p>
<h2>
<img alt="" src="images/tictactoe2.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 216px; float: right;" />TicTacToeLogic</h2>
<p>
Arrays können auch für Spiele ganz nützlich sein. Im vierten Kapitel haben wir ja schon die UI für das TicTacToe Spiel geschrieben. Jetzt sind wir soweit auch den Logik Teil zu verstehen. Das Spielfeld kann man nämlich als zwei-dimensionales Array auffassen:</p>
<pre>
private int[][] board = new int[3][3];</pre>
<p>
Ursprünglich sind alle Werte des Spielfelds auf 0 gesetzt. Wenn wir jetzt die Felder die Spieler eins besetzt hat mit einer 1 markieren und die die Spieler zwei besetzt hat mit einer 2 markieren, dann ist das eine perfekte Beschreibung des jeweiligen Spielstandes. </p>
<p>
Wenn wir jetzt testen wollen ob ein bestimmter Zug erlaubt ist, dann müssen wir lediglich testen ob der Werte des Spielfelds an der Stelle 0 ist:</p>
<pre>
public boolean isMoveAllowed(int player, int i, int j) {
if (board[i][j] == 0) {
board[i][j] = player;
return true;
}
return false;
}</pre>
<p>
Wenn wir testen wollen ob ein Spieler gewonnen hat, dann müssen wir nachsehen, ob einer der Spieler eine vertikale, horizontale oder diagonale Reihe besetzt hat. Für die vertikale Reihe könnte man das so testen:</p>
<pre>
private boolean checkVerticals() {
// player 1
for (int i = 0; i < <span style="color:#0000ff;">3</span>; i++) {
if ((board[i][0] == 1) && (board[i][1] == 1) && (board[i][2] == 1)) {
return true;
}
}
// player 2
for (int i = 0; i < <span style="color:#0000ff;">3</span>; i++) {
if ((board[i][0] == 2) && (board[i][1] == 2) && (board[i][2] == 2)) {
return true;
}
}
return false;
}</pre>
<p>
Wir gehen einfach eine Reihe nach der anderen durch (for Schleife) und schauen ob alle drei Werte auf 1 (für Spieler 1) oder 2 (für Spieler 2) gesetzt sind.</p>
<p>
.</p>
<h2>
<img alt="" src="images/battleShip.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 231px; float: right;" />BattleShip</h2>
<p>
Schiffe versenken [7] ist auch ein Spieleklassiker für dessen Umsetzung Arrays sehr nützlich sind. Unser BattleShip Spiel soll ein Spiel Mensch gegen Computer werden, soll heißen der Computer verteilt seine Schiffchen und wir müssen sie finden.</p>
<p>
Genauso wie bei TicTacToe verwenden wir für das Spielfeld ein Array von Ganzzahlen:</p>
<pre>
private int[][] board = new int[BOARD_SIZE][BOARD_SIZE];</pre>
<p>
Die Schiffe selbst werden durch Zahlen repräsentiert: 5 steht für einen AircraftCarrier, 4 für ein Battleship, 3 für ein Submarine oder einen Destroyer und 2 für ein PatrolBoat. Um festzulegen wieviele es von jeder Schiffsart gibt, können wir auch wieder ein Array verwenden:</p>
<pre>
private final int[] SHIP_SIZES = { 5, 4, 3, 3, 2 };</pre>
<p>
D.h., wenn wir noch ein paar PatrolBoat haben möchten, dann fügen einfach noch ein paar 2er ein.</p>
<p>
In der <em>setup()</em> Methode</p>
<pre>
private void setup() {
drawLines();
initBoard();
addMouseListeners();
}</pre>
<p>
zeichnen wir das Spielfeld, intialisieren die Boote, und fügen einen MouseListener hinzu. In der <em>initBoard()</em> Methode gehen wir einfach durch die Liste von Schiffen (SHIP_SIZES) und fügen eines nach dem anderen mittels der Methode <em>placeShip(int shipNr, int shipSize)</em> dem Spielfeld hinzu. Diese Methode kann ganz einfach sein, wenn man die Schiffe einfach nebeneinander plaziert, dann wird das Spiel aber ganz einfach, oder sie kann auch sehr kompliziert werden, wenn die Schiffe zufällig verteilt sein sollen. Für uns genügt die einfache Version.</p>
<p>
Bleibt nur noch die <em>mousePressed()</em> Methode zu implementieren. Wir verwenden wieder unseren Trick mit der Ganzzahl Division:</p>
<pre>
public void mousePressed(MouseEvent e) {
int i = e.getX() / STEP;
int j = e.getY() / STEP;
showLabelAt(i, j);
}</pre>
<p>
und es bleibt die <em>showLabelAt(int i, int j)</em> Methode zu implementieren. Diese schaut im <em>board</em> Array nach ob an der Stelle ein Schiff ist:</p>
<pre>
GLabel lbl = new GLabel("" + board[i][j]);
if (board[i][j] == 0) {
lbl = new GLabel(".");
}</pre>
<p>
Das ist wieder ein fieser Trick mit dem man sich ein paar unnötige Zeilen Code sparen kann. Den Label zeichnen wir dann einfach an der Position wo die Maus geklickt wurde. Und das wars.</p>
<p>
.</p>
<h2>
<img alt="" src="images/cityAtNight.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 150px; float: right;" />CityAtNight</h2>
<p>
Wiederverwendung ist ein ganz zentrales Konzept der Objektorientierung. Das kann man sowohl mit Vererbung als auch mit Komposition erreichen. Wir beginnen mit einem Beispiel zur <em>Komposition</em>. Erinnern wir uns an Kapitel 2, dort haben wir einen Skyscraper programmiert. Wenn wir jetzt eine ganze Stadt zeichnen möchten, dann wäre es ganz praktisch wenn wir unsere Skyscraper wiederverwenden könnten:</p>
<pre>
public class CityAtNight extends GraphicsProgram {
private RandomGenerator rgen = new RandomGenerator();
public void run() {
for (int i = 0; i < 8; i++) {
int cols = rgen.nextInt(4, 6);
int rows = rgen.nextInt(4, 8);
GSkyscraper h = new <span style="color:#0000ff;">GSkyscraper(rows, cols)</span>;
int x = rgen.nextInt(0, getWidth() - 40);
int y = rgen.nextInt(getHeight() / 4, getHeight()/2);
add(h, x, y);
}
}
}</pre>
<p>
Also bräuchten wir eine Klasse GSkyscraper die einen Skyscraper zeichnet. Da ein Skyscraper aus mehreren GRects besteht macht es Sinn, ähnlich wie beim GSmiley, das Ganze als GCompound aufzuziehen:</p>
<pre>
public class GSkyscraper extends <span style="color:#0000ff;">GCompound</span> {
...
}</pre>
<p>
Wie bei jeder Klasse benötigen wir einen Konstruktor</p>
<pre>
public GSkyscraper(int rows, int cols) {
...
}</pre>
<p>
in dem wir die Anzahl der Fensterreihen und -spalten übergeben. Je nachdem ob alle Skyscrapers gleich aussehen sollen oder unterschiedlich muss man dann noch etwas Zufall in die <em>addWindow()</em> Methode einfließen lassen.</p>
<p>
.</p>
<h2>
<img alt="" src="images/sevenSegmentDisplayProgram.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 150px; float: right;" />SevenSegmentDisplay</h2>
<p>
Ein weiteres schönes Beispiel für Wiederverwendung mittels <em>Komposition</em> ist die Siebensegmentanzeige. Erinnern wir uns an Kapitel 2, dort haben wir eine Siebensegmentanzeige programmiert. Wenn wir jetzt mehrere dieser Siebensegmentanzeigen benötigen, z.B. für einen Zähler, einen Taschenrechner oder ein Uhr, dann wäre es praktisch wenn es eine Klasse <em>SevenSegmentDisplay</em> geben würde, die wir einfach mehrmals verwenden könnten, ähnlich einem GRect.</p>
<p>
Da eine Siebensegmentanzeige aus mehreren GRects besteht macht es Sinn, ähnlich wie beim GSmiley, das Ganze als GCompound aufzuziehen:</p>
<pre>
public class SevenSegmentDisplay extends <span style="color:#0000ff;">GCompound</span> {
...
}</pre>
<p>
Wie bei jeder Klasse benötigen wir einen Konstruktor</p>
<pre>
public SevenSegmentDisplay(int width, int height, int ledWidth) {
...
}</pre>
<p>
in dem wir idealerweise die Breite und Höhe der Anzeige, sowie die Breite der LEDs vorgeben. Der Konstruktor sollte dann das Display aus GRects konstuieren.</p>
<p>
Wirklich praktisch wäre dann noch eine <em>displayNumber(char c)</em> Methode,</p>
<pre>
public void displayNumber(char c) {
turnAllSegmentsOff();
switch (c) {
case '0':
int[] code0 = { 1, 1, 1, 1, 1, 0, 1 };
turnSegmentsOn(code0);
break;
case '1':
...
}
}</pre>
<p>
der man einfach eine Ziffer übergibt, und die diese dann anzeigt. Die <em>turnSegmentsOn()</em> Methode könnte wie folgt aussehen:</p>
<pre>
private void turnSegmentsOn(int[] code) {
if (code[0] == 1) {
upperFrontVertical.setColor(colorOn);
}
...
}</pre>
<p>
Das SevenSegmentDisplay kann man dann ganz einfach in einem GraphicsProgram verwenden:</p>
<pre>
public class SevenSegmentDisplayProgram extends GraphicsProgram {
public void run() {
SevenSegmentDisplay ssd1 = new SevenSegmentDisplay(40, 80, 6);
add(ssd1);
ssd1.displayNumber('5');
}
}</pre>
<p>
Erweiterung: Anstelle des JTextField könnte man auch das SevenSegmentDisplay für den Calculator verwenden.</p>
<p>
.</p>
<h2>
<img alt="" src="images/birdFlocking.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 229px; float: right;" />BirdFlocking</h2>
<p>
Schwarmverhalten lässt sich bei Fischen, Vögeln und vielen anderen Tieren beobachten. Interessanterweise lässt sich Schwarmverhalten relativ einfach simulieren, die Individuen im Schwarm (auch Boids genannt) müssen lediglich drei einfache Regeln befolgen [11]:</p>
<ul>
<li>
Separation: halte Abstand von Deinen Nachbarn wenn Du ihnen zu nahe kommst (short range repulsion)</li>
<li>
Alignment: bewege Dich grob in die Richtung Deiner Nachbarn</li>
<li>
Cohesion: bewege Dich grob auf den gemeinsamen Mittelpunkt Deiner Nachbarn zu (long range attraction)</li>
</ul>
<p>
Die Simulation ist ähnlich wie im Planets Projekt, mit dem feinen Unterschied, dass anstelle von Newton's Schwerkraft, die Boid-Regeln gelten.</p>
<p>
.</p>
<h2>
<img alt="" src="images/gameOfLife.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 200px; float: right;" />GameOfLife</h2>
<p>
Das größte Genie des letzten Jahrhunderts, John von Neumann, versuchte eine hypothetische Maschine zu konstruieren, die Kopien von sich selbst anfertigen konnte. Dies gelang ihm auch, allerdings hatte das mathematische Modell seiner Maschine sehr komplizierte Regeln. Dem britischen Mathematiker John Horton Conway gelang es anfang der 70er von Neumanns Ideen drastisch zu vereinfachen, heute bekannt unter dem Namen Conway's <em>Game of Life</em> [8].</p>
<p>
Das Universum des Spiel des Lebens ist ein zweidimensionales Gitter aus quadratischen Zellen (GRects), von denen jede in einer von zwei möglichen Zuständen sein kann: lebend (schwarz) oder tot (weiß). Jede Zelle hat acht Nachbarn, und abhängig vom Zustand der Nachbarn entscheidet sich der eigene Zustand in der nächsten Runde nach folgenden Regeln:</p>
<ul>
<li>
jede lebende Zelle mit weniger als zwei lebenden Nachbarn stirbt (Unter-Bevölkerung)</li>
<li>
jede lebende Zelle mit zwei oder drei lebenden Nachbarn lebt</li>
<li>
jede lebende Zelle mit mehr als drei lebenden Nachbarn stirbt (Über-Bevölkerung)</li>
<li>
jede tote Zelle mit genau drei lebenden Nachbarn wird eine lebende Zelle (Fortpflanzung)</li>
</ul>
<p>
.</p>
<h2>
<img alt="" src="images/mandelBrot.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 222px; float: right;" />Mandelbrot</h2>
<p>
Die Apfelmännchen sind nach dem französischen Mathematiker Benoît Mandelbrot benannt. Es handelt sich dabei um sogenannte Fraktale, aber die meisten Leute finden sie einfach nur hübsch [9]. </p>
<p>
Die mathematische Gleichung die hinter der Mandelbrot Menge liegt ist sehr einfach:</p>
<pre>
z_n+1 = z_n * z_n + c</pre>
<p>
dabei sind <em>z</em> und <em>c</em> komplexe Zahlen. Es handelt sich hier um eine Iteration, d.h. wenn wir <em>z_n</em> kennen, dann können wir <em>z_n+1</em> ausrechnen. Die Anfangsbedingungen lauten, dass <em>z_0</em> gleich null sein soll und <em>c</em> ist der Punkt in der komplexen Ebene für den die Farbe ausgerechnet werden soll. Also wenn wir in x- und y-Koordinaten denken, dann ist </p>
<pre>
c = x + i y</pre>
<p>
die Anfangsbedingung. Alles was noch nötig ist, ist das Abbruchkriterium, wann sollen wir mit der Iteration aufhören? Entweder wenn z*z >= 4 ist oder wenn die Anzahl der Iterationen größer als ein maximal Wert ist:</p>
<pre>
while ( (x*x + y*y < 4) && (iteration < max_iteration) ) {
...
iteration++;
}</pre>
<p>
Damit das Ganze dann hübsch aussieht, nehmen wir die Anzahl der Iterationen und kodieren sie in Farbe:</p>
<pre>
int color = RAINBOW_COLORS[iteration % RAINBOW_NR_OF_COLORS];</pre>
<p>
Dabei ist <em>RAINBOW_COLORS</em> ein Farbarray, das wir beliebig initialisieren können. Zu guter Letzt brauchen wir noch eine <em>setPixel()</em> Methode, die es in der ACM Graphics Bibliothek eigentlich gar nicht gibt. Wir behelfen uns damit, dass wir kleine GRects zeichnen:</p>
<pre>
private void setPixel(double x, double y, Color color) {
int i = (int) (((x - xMin) * WIDTH) / (xMax - xMin));
int j = (int) (((y - yMin) * HEIGHT) / (yMax - yMin));
GRect r = new GRect(1, 1);
r.setColor(color);
add(r, i, j);
}
</pre>
<p>
Das ist nicht gerade die schnellst und effektivste Art, aber sie funktioniert.</p>
<p>
.</p>
<hr />
<h1>
Challenges</h1>
<p>
.</p>
<h2>
<img alt="" src="images/planets.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 200px; float: right;" />Planets</h2>
<p>
Ein schönes Beispiel für Wiederverwendung mittels <em>Vererbung</em> ist eine kleine Simulation des Sonne-Erde-Mond Systems. Visuell gesehen, sind Planeten nichts anderes als GOvals. Aber Planeten bewegen sich, d.h. sie haben eine Geschwindigkeit. GOvals haben aber keine Geschwindigkeit. Wir brauchen also ein GOval mit Geschwindigkeit. Genau das ist was Vererbung für uns tun kann:</p>
<pre>
class GPlanet extends GOval {
public double vx;
public double vy;
public GPlanet(int size) {
<span style="color:#0000ff;">super</span>(size, size);
}
public void move() {
move(vx, vy);
}
}</pre>
<p>
GPlanet ist also ein GOval, hat aber zusätzlich noch eine Geschwindigkeit <em>vx</em> und <em>vy</em>. Im Konstruktor rufen wir einfach den Konstruktor der Superklasse auf, also den Konstruktor von GOval, und der erzeugt ein GOval mit gegebener Höhe und Breite. Ansonsten benötigen wir lediglich eine <em>move()</em> Methode um unseren Planeten zu bewegen.</p>
<p>
In unserem Planets GraphicsProgram wollen wir jetzt im setup() drei Planeten erzeugen, also</p>
<pre>
private void setup() {
// create sun
sun = new GPlanet(SUN_MASS);
sun.setFilled(true);
sun.setColor(Color.YELLOW);
sun.vy = SUN_SPEED;
add(sun, (SIZE - SUN_MASS) / 2, (SIZE - SUN_MASS) / 2);
// create earth
...
// create earth
...
}</pre>
<p>
Wir setzen hier den Radius der Sonne gleich der Masse der Sonne. Das ist nicht ganz richtig, für die Simulation aber nicht weiter schlimm. Als nächstes betrachten wir den GameLoop:</p>
<pre>
while (true) {
sun.move();
earth.move();
moon.move();
calculateNewVelocities(sun, earth);
calculateNewVelocities(sun, moon);
calculateNewVelocities(earth, moon);
pause(DELAY);
}</pre>
<p>
Wie üblich im GameLoop, bewegen wir erst die einzelnen Planeten und danach berechnen wir die neuen Geschwindigkeiten. Die Methode <em>calculateNewVelocities()</em> sieht etwas kompliziert aus, ist aber nichts anderes als Newton's Gravitationsgesetz. </p>
<p>
An diesem Beispiel sieht man sehr schön, dass Simulationen nicht ganz einfach sind: denn nach der zweiten Umkreisung um die Sonne, verläßt uns unser Mond auf Nimmer-Wiedersehen... Schade.</p>
<p>
.</p>
<h2>
<img alt="" src="images/angryCanon.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 200px; float: right;" />AngryCanon</h2>
<p>
Unser erstes Projekt das mit Tastatur Events arbeitet wurde von einem populären Spiel mit Vögeln und Schweinen inspiriert. Wie üblich müssen wir die Dinge etwas vereinfachen. Das Ziel ist ein blaues GRect, das wir mit einer Kugel (grünes GOval) treffen sollen. Geschossen wird die Kugel von einer Kanone. </p>
<p>
Das schwierigste an diesem Spiel ist die Kanone: denn wir möchten, dass wir ihre Richtung ändern können, dass wir sie drehen können. Es stellt sich heraus, dass nur das GPolygon in der ACM Graphics Library diese Funktionalität bietet, nämlich eine <em>rotate()</em> Methode. Wir basteln unsere Kanone, also das Rohr, aus einem GPolygon. Damit das ganze dann hübsch aussieht verstecken wir die "Mechanik" der Kanone hinter einem roten GOval.</p>
<p>
In der <em>setup()</em> Methode also basteln wir die Kanone, das Ziel ein blaues Rechteck, und wir fügen noch den KeyListener hinzu. </p>
<p>
Als nächstes schreiben wir die <em>keyPressed()</em> Methode: dort wollen wir abhängig vom KeyCode, die Kanone entweder nach links oder rechts drehen,</p>
<pre>
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
switch (code) {
case 37:
angle += 5;
canon.rotate(5);
break;
case 39:
angle -= 5;
canon.rotate(-5);
break;
case 32:
fireBullet();
break;
}
}</pre>
<p>
oder wenn der Spieler auf die Leertaste drückt wollen wir die Kugel abfeuern. Wir benötigen den Winkel <em>angle</em> als Instanzvariable, damit wir beim Abfeuern die Anfangsgeschwindigkeiten der Kugel setzen können:</p>
<pre>
private void fireBullet() {
if (bullet == null) {
vx = -Math.sin(Math.toRadians(angle)) * BULLET_SPEED;
vy = -Math.cos(Math.toRadians(angle)) * BULLET_SPEED;
bullet = new GOval(BULLET_SIZE, BULLET_SIZE);
...