-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAgrar.html
More file actions
1054 lines (1042 loc) · 59.3 KB
/
Agrar.html
File metadata and controls
1054 lines (1042 loc) · 59.3 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/8380f6ab-e1dc-4095-96f6-e3aa733122d7.png" style="display: block; margin-left: auto; margin-right: auto; width: 233px; height: 223px;" /></p>
<h1>
Agrar</h1>
<p>
In diesem Kapitel fängt es an richtig interessant zu werden. Im letzten Kapitel haben wir den Top-Down Ansatz auf Kosten von Variablen etwas vernachlässigt, aber in diesem Kapitel werden wir sehen wie wir mit Methoden wieder zum Top-Down Ansatz zurückfinden. Und wir werden unsere ersten Animationen für Spiele schreiben.</p>
<p>
.</p>
<h2>
Methoden</h2>
<p>
Wir kennen Methoden schon relativ lange, bei Karel haben wir sie noch Kommandos genannt, z.B. <em>move()</em>, <em>turnLeft()</em> und <em>frontIsClear()</em>. Auch in GraphicsPrograms haben wir sie benutzt, damals hießen sie auch Nachrichten die wir an ein GRect schicken, z.B. <em>setColor()</em> und <em>setFilled()</em>. Selbst bei ConsolePrograms hatten wir sie, z.B. <em>readInt()</em> und <em>println()</em>. Also nix neues.</p>
<p>
Bei Karel gab es aber die coole Möglichkeit neue Kommandos zu erfinden, wie z.B. <em>turnRight()</em> oder <em>moveToWall()</em>. Das haben wir damals so gemacht:</p>
<pre>
public void turnRight() {
turnLeft();
turnLeft();
turnLeft();
}
</pre>
<p>
Wäre es nicht auch cool gewesen, wenn wir in unserem Target Programm folgendes neues Kommando hätten haben können:</p>
<pre>
public void drawCircle( int radius, Color color ) {
...
}
</pre>
<p>
oder für unser Wall Programm wäre eine Methode wie die folgende ganz praktisch gewesen:</p>
<pre>
public void drawRowOfNBricks( int numberOfBricks ) {
...
}
</pre>
<p>
Nun das schöne ist, das geht tatsächlich!</p>
<p>
<strong>SEP: Methoden tun immer etwas, deswegen sollten Methoden immer Tunwörter, also Verben, sein.</strong></p>
<p>
.</p>
<h2>
Neue Methoden definieren</h2>
<p>
Eine neue Methode anzulegen ist genauso einfach wie Karel neue Kommandos beizubringen. Der allgemeine Syntax einer Methodendeklaration ist folgender:</p>
<pre>
visibility type name( parameters ) {
... body...
}</pre>
<p>
Dabei bedeutet</p>
<ul>
<li>
<strong>visibility:</strong> die Sichtbarkeit der Methode, dies ist meist entweder <em>private</em> oder <em>public</em></li>
<li>
<strong>type:</strong> der Datentyp den die Methode zurückgibt, auch Rückgabewert genannt. Sehr häufig ist das <em>void</em>, was soviel heißt wie nichts, also die Methode gibt nichts zurück</li>
<li>
<strong>name:</strong> der Name der Methode, hier gelten die gleichen Regeln wie für Variablen und er sollte auch immer klein geschrieben werden</li>
<li>
<strong>parameters:</strong> die Übergabe Parameter. Die sind neu, die gab es bei Karel noch nicht, die sind aber super praktisch wie wir gleich sehen werden</li>
</ul>
<p>
<strong>SEP: Wenn möglich sollte die Sichtbarkeit von Methoden <em>private</em> sein.</strong></p>
<p>
.</p>
<h2>
<img alt="" src="images/Archery.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 133px; float: right;" />Übung: Archery</h2>
<p>
Schauen wir uns unser Archery Programm noch einmal an. Aber jetzt versuchen wir es mit Hilfe der Methode <em>drawCircle( int radius, Color color )</em>. Wir werden feststellen, dass der Code viel kürzer und auch lesbarer wird.</p>
<pre>
private void drawCircle(int radius, Color color) {
GOval ring = new GOval(2 * radius, 2 * radius);
ring.setColor(color);
ring.setFilled(true);
int x = 75 + 72 - radius;
add(ring, x, x);
}</pre>
<p>
.</p>
<h2>
<img alt="" src="images/wall.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 133px; float: right;" />Übung: Wall</h2>
<p>
Versuchen wir uns noch einmal an dem <em>Wall</em> Program, aber jetzt mit Hilfe von Methoden. Wir wollen also eine Mauer bestehend aus 15 Backsteinen (GRect) errichten. Dabei wollen wir aber die Methode</p>
<pre>
private void drawOneRowOfStones(int y) {
int x = 50; // beginning x position of wall
for (int i = 0; i < 5; i++) {
GRect brick = new GRect(30, 15);
add(brick, x, y);
x = x + 30;
}
}
</pre>
<p>
verwenden.</p>
<p>
Frage: Enthält die Methode Zauberzahlen (magic numbers)? Können wir das ändern?</p>
<p>
.</p>
<h2>
Rückgabewert</h2>
<p>
Bisher habe wir nur Methoden gesehen, die nichts (void) zurückgeben. Es gibt aber auch Methoden die etwas zurückgeben. Meistens rechnen die Methoden etwas aus, und geben das Resultat als Rückgabewert zurück. Ein schönes Beispiel ist die folgende Methode die zurückgibt wieviel Inches in einer gegebenen Anzahl von Feet sind:</p>
<pre>
private double feetToInches(double feet) {
double inches = 12 * feet;
return inches;
}</pre>
<p>
Wir können diese Methode jetzt in einem ConsoleProgram verwenden.</p>
<p>
.</p>
<h2>
<img alt="" src="images/ConvertFeetToInches.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 100px; float: right;" />Übung: ConvertFeetToInches</h2>
<p>
Wir wollen ein kleine ConsoleProgram schreiben, das Feet nach Inches konvertiert. Dazu fragt es den Benutzer nach einer Anzahl von Feet mittels <em>readDouble()</em> und ruft dann unsere feetToInches() Methode auf und gibt das Resultat im KonsolenFenster aus.</p>
<pre>
double feet = readDouble("Enter feet: ");
double inches = <span style="color:#0000ff;">feetToInches</span>(feet);
println(feet + " feet are " + inches + " inches.");
</pre>
<p>
Wenn wir genau hinschauen, sehen wir, dass wir die feetToInches() Methode genauso verwenden wie die readInt() oder println() Methoden. Der einzige Unterschied ist, dass wir sie selbst geschrieben haben.</p>
<p>
.</p>
<h2>
Objekte als Rückgabewert</h2>
<p>
Wir können nicht nur Zahlen als Rückgabewerte haben, sondern jeden möglichen Datentyp, also auch GRects zum Beispiel. Die folgende Methode generiert einen farbig augefüllte Kreis mit vorgegebenem Radius <em>r</em> an der Position <em>x</em> und <em>y</em>:</p>
<pre>
private GOval createFilledCircle(int x, int y, int r, Color color) {
GOval circle = new GOval(x-r, y-r, 2*r, 2*r);
circle.setFilled(true);
circle.setColor(color);
return circle;
}</pre>
<p>
Wir müssen in jetzt lediglich zu unserem GraphicsProgram hinzufügen.</p>
<p>
.</p>
<h2>
Lokale Variablen</h2>
<p>
Die Variablen die wir bisher kennengelernt haben nennen wir auch <em>lokale</em> Variablen. Lokal im Bezug auf eine Methode. Das soll heißen, dass Variablen nur in der Methode sichtbar sind in der sie deklariert wurden, außerhalb, also in anderen Methoden sind sie nicht sichtbar. Betrachten wir das an einem Beispiel:</p>
<pre>
public class LocalVariables extends ConsoleProgram {
public void run() {
double feet = readDouble("Enter feet: ");
println(feet);
}
private void feetToInches() {
double inches = 12;
println(inches);
}
}</pre>
<p>
In dem Beispiel gibt es eine Variable <em>feet</em> die in der Methode <em>run()</em> existiert und eine Variable <em>inches</em> die in der Methode <em>feetToInches()</em> existiert. Wenn wir versuchen in der Methode <em>feetToInches()</em> auf die Variable feet zuzugreifen, dann geht das nicht. Umgekehrt gilt das gleiche. Die Variablen sind also nur lokal in ihren jeweiligen Methoden sichtbar.</p>
<p>
Wie wir aber gesehen haben ist es aber viel besser wenn wir von einer Methode auf die Variablen einer anderen Methode zugreifen können. Deswegen benötigen wir Parameter. </p>
<pre>
private void feetToInches(double feet) {
double inches = 12 * feet;
println(inches);
}</pre>
<p>
Mit den Parametern übergeben wir Variablen von einer Methode an eine andere, deswegen nennt man sie auch Übergabeparameter. </p>
<p>
<img alt="" src="images/LocalVariables.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 100px; float: right;" />Allerdings wird nur eine Kopie der Variablen übergeben. Um das zu sehen, schauen wir uns das folgende Programm kurz an:</p>
<pre>
public class LocalVariables extends ConsoleProgram {
public void run() {
double <span style="color:#0000ff;">feet</span> = readDouble("Enter feet: ");
feetToInches(<span style="color:#0000ff;">feet</span>);
println(<span style="color:#0000ff;">feet</span>);
}
private void feetToInches(double <span style="color:#ff0000;">feet</span>) {
<span style="color:#ff0000;">feet</span> = 42;
}
}</pre>
<p>
Erst bitten wir den Benutzer uns einen Wert zu geben, z.B. '12'. Diesen Wert übergeben wir an die Methode <em>feetToInches()</em>. Wenn wir dann aber sagen, dass <span style="font-family:courier new,courier,monospace;">feet = 42</span> sein soll, dann gilt das aber nur für die Kopie, das Original bleibt unversehrt, wie wir sehen wenn wir uns anschauen was im Konsolenfenster ausgegeben wird. Dort erscheint nämlich die '12'.</p>
<p>
Deswegen ist es eigentlich auch egal, ob wir das rote <em>feet</em> auch <em>feet</em> nennen oder ihm einen anderen Namen geben:</p>
<pre>
public class LocalVariables extends ConsoleProgram {
public void run() {
double feet = readDouble("Enter feet: ");
feetToInches(feet);
println(feet);
}
private void feetToInches(double <span style="color:#ff0000;">fritz</span>) {
<span style="color:#ff0000;">fritz</span> = 42;
}
}</pre>
<p>
Jetzt wird hoffentlich auch klar, warum wir einen Rückgabewert benötigen. Denn wenn in der aufgerufenen Methode <em>feetToInches()</em> irgendetwas ausgerechnet wird, dann ist das nur lokal in der Methode sichtbar. Damit wir es wieder zurück in die aufrufende Methode bekommen, benötigen wir den Rückgabewert.</p>
<p>
Eine Anmerkung noch: eine Methode kann mehrere Parameter haben, sie kann aber nur einen Rückgabewert haben.</p>
<p>
.</p>
<h2>
Übung: Parameter und Rückgabewert mit Jeliot</h2>
<p>
Nach diesen Erklärungen ist es sinnvoll sich das ConvertFeetToInches Beispiel in Jeliot anzuschauen.</p>
<p>
Figure</p>
<p>
.</p>
<h2>
Animation</h2>
<p>
So, jetzt haben wir Methoden in unserer Toolbox. Das ist super, denn damit können wir anfangen wirklich coole Sachen zu machen, nämlich Animationen und Spiele. Wir fangen mit Animationen an. Dafür brauchen wir aber noch eine Sache.</p>
<p>
.</p>
<h2>
GObject</h2>
<p>
Erinnern wir uns an die Grafik Klassen die wir im zweiten Kapitel kennengelernt haben: GRect, GOval, GLine, GImage, GLabel, GArc und GPolygon. Diese Klassen sind nicht ganz unabhängig voneinandern, sondern sie haben sogar etwas gemeinsam, sie sind nämlich alle GObjects.</p>
<p>
<img alt="" src="images/ACM_Hierarchy.png" style="margin-left: 10px; margin-right: 10px; width: 438px; height: 100px;" /></p>
<p>
Die Klasse GObject nennt man auch Elternklasse. Und man sagt auch die Kinderklassen wie GRect und GOval erben von ihrer Elternklasse. Was erben sie? Die Methoden der Elternklasse. Das ist super praktisch wie wir sehen werden. Aber erst einmal schauen wir uns an welche Methoden das sind:</p>
<ul>
<li>
<strong>setLocation(x, y):</strong> schiebt ein GObject an die Stelle x, y</li>
<li>
<strong>move(dx, dy):</strong> verschiebt ein GObject um dx und dy</li>
<li>
<strong>getX():</strong> gibt die x Koordinate des GObjects</li>
<li>
<strong>getY():</strong> gibt die y Koordinate des GObjects</li>
<li>
<strong>getWidth():</strong> gibt die Breite des GObjects</li>
<li>
<strong>getHeight():</strong> gibt die Höhe des GObjects</li>
<li>
<strong>setColor(col):</strong> ändert die Farbe des GObjects</li>
<li>
<strong>sendToFront():</strong> schickt eine GObject nach vorne (z-order) </li>
<li>
<strong>sendToBack():</strong> schickt eine GObject nach hinten (z-order)</li>
</ul>
<p>
Also alle Grafik Klassen die wir bisher kennengelernt haben, haben diese Methoden.</p>
<p>
.</p>
<h2>
<img alt="" src="images/billiards.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 229px; float: right;" />Übung: Animationen mit dem Game Loop</h2>
<p>
Für unsere erste Animation haben wir uns Billiard ausgesucht: Wir wollen, dass sich eine scharze Kugel über den Tisch bewegt, und an den Seiten reflektiert wird.</p>
<p>
Jede Animation hat einen "<em>Game Loop</em>". Unsere <em>run()</em> Methode sieht wie folgt aus:</p>
<pre>
public void run() {
setup();
// game loop
<span style="color:#0000ff;">while (true) {</span>
moveBall();
checkForCollisionsWithWall();
<span style="color:#0000ff;">pause(40);</span>
}
}</pre>
<p>
Alles was mit dem Setup und der Initialisierung zu tun hat, kommt in die <em>setup()</em> Methode. Danach beginnt der <em>Game Loop</em>: das ist effektiv eine Endlosschleife. In dieser Endlosschleife werden verschiedene Schritte ausgeführt, wie z.B. <em>moveBall()</em> und <em>checkForCollisionsWithWall()</em>. Am Ende jedes Durchgangs wird die <em>pause()</em> Methode aufgerufen. Diese wartet einfach eine vorgegeben Anzahl von Millisekunden. In diesem Fall also 40 ms, was einer Framerate von 25 fps entspricht.</p>
<p>
Gehen wir jetzt weiter die Methoden im Einzelnen durch. In der <em>setup()</em> Methode legen wir die Größe des Fensters fest, danach setzen wir die Hintergrundfarbe auf grün, und schließlich kreiiren wir einen Ball:</p>
<pre>
private void setup() {
setSize(WIDTH, HEIGHT);
setBackground(Color.GREEN);
ball = new GOval(BALL_SIZE, BALL_SIZE);
ball.setFilled(true);
add(ball, WIDTH / 2, HEIGHT / 2);
}</pre>
<p>
Was auffallen sollte hier ist, dass es "ball =" und nicht "GOval ball =" heißt. Dazu gleich mehr. Die nächste Methode ist die <em>moveBall()</em> Methode:</p>
<pre>
private void moveBall() {
ball.move(vx, vy);
}</pre>
<p>
Die ist ganz einfach, wir rufen einfach die <em>move()</em> Methode der Klasse GOval auf. Interessant auch hier wieder, weder <em>ball</em> noch <em>vx</em> oder <em>vy</em> werden deklariert. Und schließlich sehen wir uns die <em>checkForCollisionsWithWall()</em> Methode an:</p>
<pre>
private void checkForCollisionsWithWall() {
double x = ball.getX();
double y = ball.getY();
if ((x < 0) || (x > WIDTH)) {
vx = -vx;
}
if ((y < 0) || (y > HEIGHT)) {
vy = -vy;
}
}</pre>
<p>
Wir lassen uns die momentane <em>x-</em> und <em>y</em>-Position des Balls geben, und testen ob diese innerhalb des Spielfeldes ist. Falls nicht, dann ändern wir das Vorzeichen der Geschwindigkeit. Wenn man möchte, könnte man die Geschwindigkeit bei jeder Kollision ein wenig reduzieren, tun wir aber nicht.</p>
<p>
Kommen wir jetzt zu dem Problem mit der fehlenden Deklaration von <em>ball</em>, <em>vx</em> und <em>vy</em>. Bisher kennen wir nur <em>lokale</em> Variablen. Das Problem mit lokalen Variablen ist, dass sie nur innerhalb einer Methode gültig sind. In unserem Billiard Beispiel, brauchen wir aber den Ball in drei Methoden: der <em>setup()</em>, der <em>moveBall()</em>, und der <em>checkForCollisionsWithWall()</em> Methode. Offensichtlich können wir also für den Ball (und auch <em>vx</em> und <em>vy</em>) keine lokale Variable verwenden. Anstelle verwenden wir eine <em>Instanzvariable</em>. Instanzvariablen werden am Anfang der Klasse deklariert, vor der run() Methode, und ganz wichtig, außerhalb der run() Methode (oder irgendeiner anderen Methode):</p>
<pre>
public class Billiards extends GraphicsProgram {
// instance variables
private GOval ball;
private int vx = 4;
private int vy = -3;
public void run() {
...
}
...
}
</pre>
<p>
Der Vorteil von Instanzvariablen ist, dass in jeder Methode auf sie zugegriffen werden kann. Im nächsten Kapitel werden wir noch mehr zu Instanzvariablen erfahren.</p>
<p>
.</p>
<h2>
Events</h2>
<p>
Also Animationen waren gar nicht so schwer. Kommen wir zu den Spielen: unsere Spiele sollen mit der Maus steuerbar sein. Dazu müssen wir zwei Sachen tun, wir müssen dem Programm sagen,</p>
<ol>
<li>
dass es auf die Maus hören soll und</li>
<li>
welche der Maus Events uns interessieren, zB. ob die Maustaste gedrückt wurde oder die Maus bewegt wurde.</li>
</ol>
<p>
Das erste erreichen wir indem wir am Anfang unseres Programms die Methode</p>
<pre>
addMouseListeners();</pre>
<p>
aufrufen. Das zweite erreichen wir indem wir eine von zwei Methoden überschreiben: Wenn wir hören wollen, ob die Maustaste gedrückt wurde, dann verwenden wir</p>
<pre>
public void mousePressed(MouseEvent e) { ... }</pre>
<p>
oder wenn wir hören wollen ob sich die Maus bewegt hat, dann verwenden wir</p>
<pre>
public void mouseMoved(MouseEvent e) { ... }</pre>
<p>
Wir können natürlich auch beide verwenden.</p>
<p>
.</p>
<h2>
<img alt="" src="images/builder.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 229px; float: right;" />Übung: Builder</h2>
<p>
Builder ist von Lego inspiriert: wir haben kleine Klötzchen (blocks) und die können wir beliebig auf dem Bildschirm setzen, in dem wir mit der Maus an die Position klicken wo der Klotz hin soll. Der Programmcode dafür ist total einfach. Als erstes müssen wir dem Programm sagen, dass wir auf die Maus hören möchten:</p>
<pre>
public void run() {
<span style="color:#0000ff;">addMouseListeners();</span>
}</pre>
<p>
Danach müssen wir nur noch sagen, was denn passieren soll, wenn die Maus geklickt wurde:</p>
<pre>
public void <span style="color:#0000ff;">mousePressed</span>(MouseEvent e) {
int x = e.getX();
int y = e.getY();
GRect block = new GRect(BLOCK_SIZE, BLOCK_SIZE );
block.setColor(Color.RED);
add(block, x, y);
}</pre>
<p>
Als Erstes benötigen wir die x- und y-Position der Maus: die erhalten wir vom <em>MouseEvent</em> e. Der MouseEvent hat zwei Methoden, <em>getX()</em> und <em>getY()</em> und diese beiden Methoden geben uns die x- und y-Position der Maus. Sobald wir die haben kreiiren wir ein neues GRect und platzieren es an die Position der Maus.</p>
<p>
Mit einem kleine Trick, kann man die Position der Klötzchen "quantisieren":</p>
<pre>
x = x / BLOCK_SIZE * BLOCK_SIZE;</pre>
<p>
Das funktioniert allerdings nur wenn <em>x</em> vom Datentyp <em>int</em> ist. Warum funktioniert das?</p>
<p>
.</p>
<h2>
<img alt="" src="images/mouseTracker.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 133px; float: right;" />Übung: MouseTracker</h2>
<p>
Was wir noch brauchen, ist der Mausbewegung zu verfolgen. Wie das geht demonstriert das folgende Programm. Wir beginnen wieder mit der <em>run()</em> Methode:</p>
<pre>
// instance variables
private GLabel lbl;
public void run() {
lbl = new GLabel("");
lbl.setFont("Arial-bold-20");
add(lbl, 0, 0);
addMouseListeners();
}</pre>
<p>
Um die Position der Maus anzuzeigen wollen wir einen GLabel namens <em>lbl</em> verwenden. Der ist eine Instanzvariable und muss initialisiert werden und zu unserem Canvas hinzugefügt werden. Danach sagen wir unserem Programm wieder, dass wir auf Maus Ereignisse hören möchten. In diesem Beispiel wollen wir wissen ob die Maus sich bewegt hat, deswegen übersschreiben wir die <em>mouseMoved()</em> Methode:</p>
<pre>
public void <span style="color:#0000ff;">mouseMoved</span>(MouseEvent e) {
int x = e.getX();
int y = e.getY();
lbl.setLabel("x=" + x + ",y=" + y);
lbl.setLocation(x, y);
}</pre>
<p>
Wir holen uns wieder die x- und y-Position der Maus, ändern den Text des Labels mit der <em>setLabel()</em> Methode, und verschieben den Label mit der <em>setLocation()</em> Methode an die Postion der Maus. Und das war's auch schon.</p>
<p>
.</p>
<h2>
RandomGenerator</h2>
<p>
Für viele Spiele benötigen wir Zufallszahlen. Dafür gibt es die Klasse <em>RandomGenerator</em>. Die kann nicht nur Zufallszahlen, sondern auch Zufallsfarben erzeugen. Der Code</p>
<pre>
RandomGenerator rgen = new RandomGenerator();
...
double width = rgen.nextDouble(0, 150);
Color col = rgen.nextColor();
</pre>
<p>
zeigt wie man Zufallszahlen und Zufallsfarben erzeugt. Es gibt auch die Methoden nextInt(a,b) und nextBoolean().</p>
<p>
.</p>
<h2>
GCanvas</h2>
<p>
An dieser Stelle macht es noch Sinn eine Klasse vorzustellen, die wir noch nicht explizit kennengelernt haben: die Klasse GCanvas. Verwendet haben wir sie aber schon, und zwar als wir unser GRect <em>lisa</em> hinzugefügt haben mit</p>
<pre>
add(lisa, 70, 50);</pre>
<p>
Die Frage die wir uns damals nämlich hätten stellen sollen ist wozu? Und die Antwort ist natürlich: zum GCanvas. GCanvas ist unser Filzbrett. Wenn wir was hinzufügen können, stellt sich als nächstes gleich die Frage, können wir auch was wegnehmen? Und was können wir denn sonst noch so machen mit dem GCanvas? Auch dafür gibt es eine Liste von Methoden:</p>
<ul>
<li>
<strong>add(object):</strong> fügt eine GObject zum GCanvas hinzu</li>
<li>
<strong>add(object, x, y):</strong> fügt eine GObject zum GCanvas an der Position x, y hinzu</li>
<li>
<strong>remove(object):</strong> entfernt das GObject object von GCanvas</li>
<li>
<strong>removeAll():</strong> entfernt alle GObjects von GCanvas</li>
<li>
<strong>getElementAt(x, y):</strong> gibt uns das vorderste GObject an der Position x, y, falls dort eines ist</li>
<li>
<strong>setBackground(c):</strong> ändert die Hintergrundfarbe des GCanvas</li>
</ul>
<p>
Außerdem, ist ein GCanvas auch ein GObject, d.h. alles was GObject kann, kann auch GCanvas.</p>
<p>
.</p>
<hr />
<h1>
Review</h1>
<p>
Eigentlich haben wir dieses Kapitel gar nicht so viel gemacht. Aber das ist nicht ganz richtig, in den Projekten werden wir gleich sehen, dass wir schon wirklich coole Sachen programmieren können. Was haben wir bisher in diesem Kapitel gelernt? Wir wissen jetzt was</p>
<ul>
<li>
Methoden</li>
<li>
Paremeter</li>
<li>
Rückgabewert</li>
<li>
und lokale Variablen</li>
</ul>
<p>
sind. Ausserdem haben wir</p>
<ul>
<li>
GObject</li>
<li>
GCanvas</li>
<li>
und den RandomGenerator</li>
</ul>
<p>
kennengelernt. Das wichtigste in diesem Kapitel war, dass wir Animation mit dem <em>GameLoop</em> erreichen können und auf Maus Events reagieren können.</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/stairs.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 133px; float: right;" />Stairs</h2>
<p>
Unser erstes Projekt ist eine kleine Treppe. Das Problem ist ganz ähnlich wie das Wall Problem. Daher macht es auch hier Sinn nach dem Top-Down Ansatz anzunehmen es gibt eine Methode drawRowOfNBricks<em>(int n)</em> gibt. Wir sollten auch darauf achten keine <em>magic numbers</em> zu verwenden, sondern nur Konstanten.</p>
<p>
.</p>
<p>
.</p>
<h2>
<img alt="" src="images/pyramid2.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 133px; float: right;" />Pyramid</h2>
<p>
Die Pyramide ist fast identisch zur Treppe. Der einzige Unterschied ist, dass die Stufen immer einen halben Stein versetzt sind. Die Treppe soll neun Steine in der untersten Reihe haben. Eigentlich brauchen wir den Code vom letzten Beispiel nur geringfügig zu ändern.</p>
<p>
.</p>
<p>
.</p>
<p>
.</p>
<h2>
<img alt="" src="images/chessBoard.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 229px; float: right;" />ChessBoard</h2>
<p>
Kehren wir zu unserem Schachbrett zurück. Dieses Mal wollen wir aber den Top-Down Ansatz verwenden. Dazu gibt es mehrere Ansätz, aber einer wäre eine Methode namens <em>drawOneRow()</em> zu deklarieren. Hier muß man sich genau überlegen welche Parameter man an die Methode übergibt. Man könnte auch zwei Methoden haben, eine für gerade und eine für ungerade Zeilen.</p>
<p>
.</p>
<p>
.</p>
<p>
.</p>
<p>
.</p>
<p>
.</p>
<p>
.</p>
<h2>
<img alt="" src="images/RGBColor.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 133px; float: right;" />RGBColor</h2>
<p>
Wir haben ja schon den Regenbogen gezeichnet, allerdings noch sehr "händisch". Jetzt wollen wir die HSV Farbpalette zeichnen [5]. In Java kann man eine beliebige Farbe mittels</p>
<pre>
Color col = new Color(r, g, b);</pre>
<p>
erzeugen, wobei die Variablen r, g, und b jeweils für den rot, grün und blau Anteil stehen. Diese müssen Werte zwischen 0 und 255 sein. Also z.B. Rot wird durch</p>
<pre>
Color colRed = new Color(255, 0, 0);</pre>
<p>
erzeugt.</p>
<p>
Wenn wir uns die Farben in der HSV Farbpalette genau ansehen bemerken wir, dass es beginnend mit Rot über die Farben Gelb, Grün, Cyan, Blau, Magenta wieder zu Rot zurück kommt. Insgesamt gibt es also 6 Farbabstufungen. Den ersten Übergang von Rot nach Gelb, könnte man z.B. durch folgende Zeilen erreichen:</p>
<pre>
for (int x = 0; x < 255; x++) {
Color col = new Color(255, x, 0);
GLine line = new GLine(x, 0, x, HEIGHT);
line.setColor(col);
add(line);
}</pre>
<p>
.</p>
<h2>
<img alt="" src="images/Moire.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 233px; float: right;" />Moire</h2>
<p>
Der Moiré-Effekt [6] ist normalerweise eher unerwünscht, aber er kann auch ganz hübsch sein. Man teilt zunächst die Länge und Breite in gleiche Teile auf, z.B. 11. Dann zeichnet man von jedem Punkt oben zu jedem Punkt unten eine Linie und das Gleiche von links nach rechts. Am besten man zeichnet sich erst einmal auf einem Stück Papier auf wie man selbst das zeichnen würde. Es läuft auf zwei verschachtelte <em>for</em> Scheifen hinaus.</p>
<p>
.</p>
<p>
.</p>
<p>
.</p>
<p>
.</p>
<h2>
<img alt="" src="images/randomSquares.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 200px; float: right;" />RandomSquares</h2>
<p>
Wir wollen uns weiter künstlerisch betätigen. Es geht darum verschieden farbige Rechtecke von zufälliger Größe und Position zu zeichnen. Man benutzt natürlich den RandomGenerator. Zunächst lässt man sich eine zufällige Breite und Höhe für ein GRect geben. Diese sollten vielleicht nicht zu groß oder zu klein sein. Dann gibt man dem Rechteck eine zufällige Farbe mit</p>
<pre>
rect.setFillColor(rgen.nextColor());</pre>
<p>
Un schließlich plaziert man es noch an eine zufällige x und y Position. Das Ganze kommt dann in eine Endlosschleife und man wartet vielleicht 100 ms.</p>
<p>
.</p>
<h2>
<img alt="" src="images/TwinkleTwinkle.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 133px; float: right;" />TwinkleTwinkle</h2>
<p>
Bei TwinkleTwinkle geht es darum einen zufälligen Sternenhimmel zu generieren. Die Sterne sind GOvals mit eine zufälligen Größe zwischen 1 und 4 Pixeln. Die werden zufällig auf dem Canvas verteilt. Vorher sollte man noch den Hintergrund mittels</p>
<pre>
setBackground(Color.BLACK);</pre>
<p>
auf Schwarz setzen. Das Ganze kommt dann in eine Endlosschleife und man wartet vielleicht 500 ms bis zur Erzeugung des nächsten Sterns.</p>
<p>
.</p>
<h2>
<img alt="" src="images/confetti.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 133px; float: right;" />Confetti</h2>
<p>
Wir alle lieben Confetti. Dabei sind die ganz einfach zu machen, entweder mit einem Locher oder mit GOvals. Die Confetti können alle gleich groß sein (z.B. 20 Pixel), müssen es aber nicht. Aber auf jeden Fall haben sie verschiedene Farben, wieder ein Fall für den RandomGenerator. Und natürlich sollte die Postion der Confetti zufällig sein, also das Ganze kommt wieder in eine Endlosschleife.</p>
<p>
.</p>
<h2>
<img alt="" src="images/animatedPacman.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 133px; float: right;" />AnimatedPacman</h2>
<p>
Wir haben ja schon vor zwei Kapiteln unseren ersten PacMan gemalt. Der war aber noch recht statisch. Wir wollen PacMan jetzt animieren. Dazu ist es nützlich zu wissen, dass es die beiden Kommandos für GArcs</p>
<pre>
pacman.setStartAngle(angle);
pacman.setSweepAngle(360 - 2 * angle);</pre>
<p>
gibt. Wenn wir jetzt die <em>angle</em> Variable zwischen 0 und 40 variieren lassen, und das alle 50 ms tun, dann erscheint das so wie wenn PacMan animiert wäre.</p>
<p>
.</p>
<h2>
<img alt="" src="images/trafficLight2.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 133px; float: right;" />TrafficLight</h2>
<p>
Auch die Ampel haben wir bereits vor zwei Kapiteln gezeichnet. Jetzt wollen wir aber die Ampel animieren. Die Ampel beginnt mit Rot, geht dann über zu Rot-Gelb, gefolgt von Grün. Schließlich geht es wieder über Gelb zurück nach rot. Der Übergang sollte jeweils eine Sekunde dauern, und das Ganze sollte wieder in einer Endlosschleife verpackt sein. Man könnte z.B. Instanzvariablen für die Lichter haben, und diese dann mittels</p>
<pre>
if (currentLight == 0) {
redLight.setColor(Color.RED);
yellowLight.setColor(Color.BLACK);
greenLight.setColor(Color.BLACK);
} ...
</pre>
<p>
ein- und ausschalten. Jetzt muss man sich noch überlegen wie man zwischen den verschiedenen Zuständen hin- und herschaltet. Das kann man sehr geschickt mit dem Remainder Operator % machen:</p>
<pre>
currentLight++;
currentLight = currentLight % 4;</pre>
<p>
.</p>
<h2>
<img alt="" src="images/analogClock.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 228px; float: right;" />AnalogClock</h2>
<p>
In der <em>setup()</em> Methode zeichnen wir den Kreis und die Uhrzeiten mittels GLabels. Dabei kann man die GLabels von Hand setzen, oder mittels Sinus und Kosinus ausrechnen wo sie denn am besten hinpassen. Beides dauert ungefähr gleich lange, das zweitere bedarf aber etwas mehr Gehirnschmalz.</p>
<p>
Allerdings wenn es darum geht die Zeiger zu zeichnen, kommen wir am Sinus und Kosinus [7] nicht mehr vorbei. </p>
<pre>
private void drawSeconds(int seconds) {
double radians = 2 * Math.PI * seconds / 60;
double lengthSecondHand = 250;
double x = SIZE / 2 + Math.sin(radians) * lengthSecondHand / 2;
double y = SIZE / 2 - Math.cos(radians) * lengthSecondHand / 2;
secondsHand.setEndPoint(x, y);
}</pre>
<p>
dabei handelt es sich bei <em>secondsHand</em> um eine GLine</p>
<pre>
private GLine secondsHand;</pre>
<p>
die als Instanzvariable deklariert wurde. Wie wir an die Stunden, Minuten und Sekunden kommen, haben wir ja bereits in dem Projekt "Time" des letzten Kapitel gesehen. Da die Uhr natürlich animiert sein soll, brauchen wir natürlich eine Endlosschleife, mit einer Pause von einer Sekunde, evtl sogar nur einer halben Sekunde.</p>
<p>
.</p>
<h2>
<img alt="" src="images/drunkenWalk.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 200px; float: right;" />DrunkenWalk</h2>
<p>
Wenn Karel mal einen über den Durst getrunken hat, dann läuft er nicht mehr so gerade. In diesem Beispiel fängt er in der Mitte an. Einmal pro Sekunde macht er dann einen Schritt, zufälliger Distanz in eine beliebige Richtung. Wobei er natürlich in einem Schritt nicht beliebig weit kommt. Mit GLines zeichnen wir die Schlangenlinien nach, um Karel am nächsten Morgen zu zeigen, dass er doch besser ein Taxi hätte nehmen sollen.</p>
<p>
.</p>
<p>
.</p>
<p>
.</p>
<h2>
<img alt="" src="images/tree.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 200px; float: right;" />Tree*</h2>
<p>
Bäume zu zeichnen stellt sich als relativ schwierig heraus. Eine beliebt Technik um dieses Problem zu lösen ist die Rekursion. Da wir aber noch nichts von Rekursion gehört haben, versuchen wir einen Baum ohne Rekursion zu zeichnen. Nach dieser Erfahrung, sind wir vielleicht motivierter im nächsten Semester die Geheimnisse der Rekursion kennen zu lernen.</p>
<p>
.</p>
<p>
.</p>
<p>
.</p>
<p>
.</p>
<h2>
<img alt="" src="images/asteroidCreator.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 133px; float: right;" />AsteroidCreator</h2>
<p>
Das Arcade-Spiel 'AsteroidCreator' war ein absoluter Hit in den späten 80er Jahren. In dem Spiel geht es darum, an der Stelle an der die Benutzerin mit der Maus klickt einen Asteroiden zu zeichnen. Dabei sind Asteroiden einfach GRects unterschiedlicher, zufälliger Größe mit schwarzem Rand. Wir müssen also wieder die <em>addMouseListeners()</em> Methode im Setup aufrufen. In der <em>mousePressed()</em> Methode malen wir dann einfach ein Rechteck an der Position an der die Benutzerin mit der Maus geklickt hat.</p>
<p>
.</p>
<h2>
<img alt="" src="images/connectTheClicks.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 200px; float: right;" />ConnectTheClicks</h2>
<p>
Ganz ähnlich wie das vorherige Spiel war auch 'ConnectTheClicks®' sehr populär in den späten 70er Jahren. Dabei handelt es sich um ein Spiel in dem der Benutzer mit der Maus an eine Stelle klickt, und diese dann mit der vorhergehenden verbunden wird. Was das Spiel etwas komplizierter macht, ist dass man sich merken muss, an welche Stelle der Benutzer vorher geklickt hat. Dazu verwenden wir einfach zwei Instanzvariablen:</p>
<pre>
private int x0 = -1;
private int y0 = -1;</pre>
<p>
Wenn wir die Variablen mit dem Wert "-1" initialisieren, dann ist das ein kleiner Trick mit dem wir feststellen können, ob das der erste Klick ist. Denn dann dürfen wir noch keine Linie ziehen! Ansonsten, zeichnen wir einfach eine Linie (GLine) bei jedem Klick von der alten Position auf die neue Maus-Position. </p>
<p>
.</p>
<h2>
<img alt="" src="images/tictactoe.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 216px; float: right;" />TicTacToe</h2>
<p>
TicTacToe kennt jeder aus dem Kindergarten oder der Schule, notfalls kann man in der Wikipedia dazu folgendes nachlesen [9]:</p>
<p>
"Auf einem quadratischen, 3×3 Felder großen Spielfeld setzen die beiden Spieler abwechselnd ihr Zeichen (ein Spieler Kreuze, der andere Kreise) in ein freies Feld. Der Spieler, der als Erster drei Zeichen in eine Zeile, Spalte oder Diagonale setzen kann, gewinnt."</p>
<p>
Im <em>setup()</em> zeichnen wir den Hintergrund, am einfachsten ist das als Bild, und sorgen mittels <em>addMouseListeners()</em>, dass wir auf Maus Ereignisse hören. Dazu müssen wir noch die <em>mousePressed()</em> Methode hinzufügen. Dort müssen wir dann einfach abwechselnd ein "X" oder ein "O" malen, je nachdem wer dran ist. Wie weiß man wer dran ist? Das kann man z.B. über eine Instanzvariable machen:</p>
<pre>
private int currentPlayer = 1;</pre>
<p>
Die darf zwei Werte haben, '1" für Spieler eins und "2" für Spieler zwei. Das Umschalten zwischen den beiden Spielern geht dann ganz einfach über:</p>
<pre>
if (currentPlayer == 1) {
...
currentPlayer = 2;
} else {
...
currentPlayer = 1;
}</pre>
<p>
Eine kleine Sache noch die ganz praktisch ist: man kann natürlich einfach die "X" und "O" da malen wo der Nutzer geklickt hat. Das schaut dann aber etwas schepps aus. Hier kann man einmal von der Integer Division profitieren:</p>
<pre>
public void mousePressed(MouseEvent e) {
int x = e.getX();
int i = x / CELL_WIDTH;
int xPos = i * CELL_WIDTH;
...
}</pre>
<p>
.</p>
<hr />
<h1>
Challenges</h1>
<p>
.</p>
<h2>
<img alt="" src="images/agar3.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 200px; float: right;" />Agrar</h2>
<p>
Das Spiel Agrar ist inspiriert von dem Spiel Agar.io, Dabei geht es laut Wikipedia [10] darum:</p>
<p>
"... eine Zelle zu navigieren, die dadurch wächst, dass sie Pellets und andere Zellen frisst. Sie darf dabei jedoch nicht selbst von größeren Zellen gefressen werden."</p>
<p>
Unsere Version des Spiels ist etwas einfacher, es gibt nämlich nur eine Zelle, und die kann nur Pellets futtern. Aber das ist auch schon recht anspruchsvoll.</p>
<p>
Wir brauchen zunächst eine Instanzvariable für die Zelle:</p>
<pre>
private GOval cell;</pre>
<p>
Die initialisieren wir in der <em>setup()</em> Methode, dort fügen wir auch den MouseListener hinzu. Als nächstes benötigen wir den <em>GameLoop</em>:</p>
<pre>
while (true) { // game loop
createRandomFood();
checkForCollision();
pause(DELAY);
}</pre>
<p>
in dem wir an einer zufälligen Position ein Pellet, also ein GOval mit zufälliger Farbe erzeugen. Als nächstes schauen wir ob es zwischen Zelle und Pellet zu einer Kollision gekommen ist. Wie wissen wir ob es zu einer Kollision gekommen ist? Dafür gibt es die Methode <em>getElementAt()</em>:</p>
<pre>
GObject collisonObject = <span style="color:#0000ff;">getElementAt(x, y)</span>;
if ( (collisonObject != <span style="color:#0000ff;">null</span>) && (collisonObject != cell) ) {
double w = cell.getWidth();
cell.setSize(w + 1, w + 1);
remove(collisonObject);
} </pre>
<p>
Diese Methode schaut ob sich an der Position x,y etwas befindet. Wenn da nichts ist, dann gibt die Methode den Wert "null" zurück. Ansonsten gibt sie uns das Objekt zurück, das sich an dieser Position befindet. Das könnte ein Pellet sein, das könnte aber auch die Zelle selbst sein. Deswegen müssen wir checken, dass es nicht "null" ist, und dass es nicht die Zelle ist. Da es ja sonst nichts gibt, wissen wir jetzt, dass das <em>collisonObject</em> ein Pellet sein muss. Das Pellet "essen" wir dann, was bedeutet, dass die Zelle fetter wird, und das die Pellet verschwindet.</p>
<p>
Und natürlich müssen wir noch die <em>mouseMoved()</em> Methode implementieren. Die ist aber ganz einfach, weil wir einfach die Zelle an die Position der Maus bewegen:</p>
<pre>
public void mouseMoved(MouseEvent e) {
int x = e.getX();
int y = e.getY();
cell.setLocation(x, y);
}</pre>
<p>
.</p>
<h2>
<img alt="" src="images/tetris.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 205px; float: right;" />Tetris</h2>
<p>
Tetris ist inzwischen ein Spiele Klassiker. Ursprünglich wurde es vom russischen Programmierer Alexei Paschitnow programmiert [11]. Tetris besitzt verschiedene Steinformen die etwas lateinischen Buchstaben ähneln (I, O, T, Z und L). Die Spieler müssen die einzelnen Steine die vom oberen Rand des Spielfelds herunterfallen in 90-Grad-Schritten drehen und sie so verschieben, dass sie am unteren Rand horizontale, möglichst lückenlose Reihen bilden. Wie üblich beschränken wir uns auf eine etwas einfachere Version, in der es nur vier Steinformen gibt: einen Einer-Block, einen horizontalen und einen vertikalen Zweier-Block, und einen Vierer-Block. Drehen kann man die Blöcke in unserer einfachen Version nicht.</p>
<p>
In der <em>setup()</em> Methode legen wir einen neuen Stein an:</p>
<pre>
private void setup() {
createNewBrick();
}</pre>
<p>
Die Methode <em>createNewBrick()</em> legt einen zufälligen neuen Stein an. Abhängig von einer Zufallszahl wird jeweils eine andere Steinform erzeugt:</p>
<pre>
private void createNewBrick() {
switch (rgen.nextInt(0, 3)) {
case 0:
brick = new GRect(WIDTH / 2, 0, BRICK_SIZE, BRICK_SIZE * 2);
break;
case 1:
...
}
brick.setFilled(true);
brick.setFillColor(rgen.nextColor());
add(brick);
}</pre>
<p>
Der Stein <em>brick</em> muss natürlich eine Instanzvariable sein, sonst funktioniert das nicht. Das Gleiche gilt für <em>rgen</em>. Damit die Steine dann anfangen herunterzufallen, benötigen wir einen <em>GameLoop</em>:</p>
<pre>
while (true) {
moveBrick();
checkForCollision();
pause(DELAY);
}</pre>
<p>
Die Methode <em>moveBrick()</em> bewegt den Stein einfach um eine Steinbreite nach unten. Die Methode <em>checkForCollision()</em> stellt fest ob der Stein weiterfallen darf. Ist der Stein unten angekommen, dann darf er nicht weiterfallen. Der Trick das zu bewerkstelligen ist eigentlich ganz einfach: wir kreiiren einen neuen Stein, und lassen den alten da wo er ist:</p>
<pre>
private void checkForCollision() {
if (brick.getY() > getHeight() - brick.getHeight()) {
createNewBrick();
}
...
}</pre>
<p>
Der andere Fall der eintreten kann, ist wenn unterhalb des fallenden Steins ein anderer Stein ist, dann darf er auch nicht weiterfallen. Da müssen wir mit der Methode <em>getElementAt()</em> arbeiten:</p>
<pre>
int x = brick.getX() + 1;
int y = brick.getY() + BRICK_SIZE;
GObject obj = <span style="color:#0000ff;">getElementAt(x, y)</span>;
if (obj != null) {
...
}
</pre>
<p>
Diese Methode sagt uns, ob es an der Position x,y ein GObject gibt. Falls nein, dann gibt die Methode "null" zurück. Auch hier kreiiren wir einfach einen neuen Stein.</p>
<p>
So, jetzt fallen also unsere Stein munter runter. Es fehlt nur noch die Steuerung durch die Tastatur. Die Tastatur hat Tasten, auf Englisch "Keys" und wenn eine Taste gedrückt wird dann kommt es zu einen <em>KeyEvent</em>. Das ist vollkommen analog zu den Maus Events. Deswegen müssen wir also in der setup() Methode noch einen KeyListener hinzufügen:</p>
<pre>
addKeyListeners();</pre>
<p>
und die Methode <em>keyPressed()</em> hinzufügen:</p>
<pre>
public void keyPressed(KeyEvent e) {
int cc = e.getKeyCode();
switch (cc) {
case 37:
// move left
brick.move(-BRICK_SIZE, 0);
break;
case 39:
// move right
brick.move(BRICK_SIZE, 0);
break;
}
}</pre>
<p>
Über den Keycode erfahren wir welche Taste gedrückt wurde: für die linke Pfeiltaste ist der Keycode die 37, für die rechte die 39. Und damit ist unsere einfache Tetris Version schon fertig.</p>
<p>
.</p>
<h2>
<img alt="" src="images/pong.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 133px; float: right;" />Pong</h2>
<p>
Pong wurde 1972 von Atari veröffentlich und gilt als Urvater der Videospiele [8]. Es ist ein Spiel für zwei Spieler, die versuchen ähnlich wie beim Tischtennis (ping-pong) einen Ball hin und her zu spielen. Zunächst benötigen wir drei Instanzvariablen</p>
<pre>
private GOval ball;
private GRect leftPaddle;
private GRect rightPaddle;</pre>
<p>
für den Ball und die beiden Paddles. Dann gibt es natürlich wieder einen <em>GameLoop</em>:</p>
<pre>
while (true) {
moveBall();
checkForCollision();
pause(DELAY);
}</pre>
<p>
Die Methode <em>moveBall()</em> bewegt den Ball einfach um einen gewissen Betrag in x und y Richtung. </p>
<p>
Die Methode <em>checkForCollision()</em> tut zwei Dinge: sie stellt fest ob es eine Kollision mit der Wand gab oder ob es eine Kollision mit einem der Paddles gab. Falls der Ball das Spielfeld nach oben oder unten verlassen möchte, wird er einfach reflektiert, falls er aber nach links oder rechts verschwindet, ist die Runde beendet. Falls der Ball mit den Paddles kollidiert wird er auch einfach reflektiert. </p>
<p>
Für die Kollisionen mit den Paddles verwenden wir wieder die <em>getElementAt()</em> Methode. Das Reflektieren ist eigentlich auch ganz einfach. Dazu müssen wir aber noch eine Instanzvariable für die Geschwindigkeit einführen:</p>
<pre>
private int vx = 2;
private int vy = 3;</pre>
<p>
Reflektion bedeutet dann einfach:</p>
<pre>
vy = -vy;</pre>
<p>
falls der Ball oben oder unten reflektiert werden soll. Natürlich muss die <em>moveBall()</em> Methode diese Variablen verwenden.</p>
<p>
Die Kontrolle der Paddles soll über die Tastatur erfolgen, also müssen wir wieder einen KeyListener hinzufügen und die Methode <em>keyPressed()</em> hinzufügen:</p>
<pre>
public void keyPressed(KeyEvent e) {
char c = e.getKeyChar();
switch (c) {
case 'p': // right paddle up
rightPaddle.move(0, -PADDLE_VY);
break;
case 'l': // right paddle down
...
}
</pre>
<p>
Wir könnten wieder mit <em>getKeyCode()</em> arbeiten, aber <em>getKeyChar()</em> ist hier viel praktischer. Der erste Spieler kontolliert seinen Paddle über die Tasten 'q' und 'a', der zweite Spieler über die Tasten 'p' und 'l'.</p>
<p>
.</p>
<h2>
<img alt="" src="images/breakout2.png" style="margin-left: 10px; margin-right: 10px; width: 200px; height: 300px; float: right;" />BrickBreaker</h2>
<p>
Das erste Breakout-Spiel wurde im April 1976 von Atari vorgestellt. Das Spielprinzip stammt von Nolan Bushnell. Steve Jobs, der damals bei Atari arbeitete, überredete seinen Freund Steve Wozniak (damals bei HP), dieses Spiel zu konstruieren. Steve Jobs bekam für das Spiel Breakout von Nolan Bushnell 5.000 Dollar bezahlt. Er gab seinem Freund Steve Wozniak, die Hälfte, also 350 Dollar [12].</p>
<p>
Das Spielfeld besteht aus Mauersteinen, einem Ball und einem Schläger. Der Ball bewegt sich durch das Spielfeld und wenn er auf einen Mauerstein trifft, dann verschwindet dieser. Von den Wänden und dem Schläger wird der Ball reflektiert. Außer bei der unteren Wand, also wenn der Ball nicht mit dem Schläger getroffen wird, dann ist das Spiel zu Ende. In unserem Spiel soll der Schläger durch die Maus kontrolliert werden.</p>
<p>
Als Instanzvariablen benötigen wir wieder den Ball und den Schläger:</p>
<pre>
private GOval ball;
private GRect paddle;</pre>
<p>
Dann folgt die <em>setup()</em> Methode. Dort wollen wir den Ball und den Schläger initialisieren und auch die Mauer malen.</p>
<pre>
private void setup() {
createBall();
createPaddle();
createBricks();
}</pre>
<p>
Beim Malen der Mauer, können wir aber die Methode <em>drawRowOfNBricks(int n)</em> aus dem Projekt Stairs borgen, damit wir es ganz einfach. Evtl wollen wir die Bricks noch verschieden farbig machen, dann sieht das Spiel gleich viel besser aus.</p>
<p>
Nach dem Setup kommen wir wieder zum <em>GameLoop</em>:</p>
<pre>
while (true) {
moveBall();
checkForCollision();
pause(DELAY);
}</pre>
<p>
Die <em>moveBall()</em> Methode ist identisch zu der in Pong. Auch <em>checkForCollision()</em> ist sehr ähnlich</p>
<pre>
private void checkForCollision() {
checkForCollisionWithWall();
checkForCollisionWithPaddleOrBricks();
}</pre>
<p>
Was ein bischen mehr Arbeit benötigt ist die <em>checkForCollisionWithPaddleOrBricks()</em> Methode. Wir verwenden wieder die <em>getElementAt()</em> Methode, schauen nach ob das Objekt nicht null ist. Dann kann es sich nur um den Schläger oder einen Mauerstein handeln, also</p>
<pre>
GObject obj = getElementAt(x, y);
if (obj != null) {
if (obj == paddle) {
vy = -vy;
} else {
// must be brick:
remove(obj);
vy = -vy;
}
}</pre>
<p>
Damit wäre der GameLoop erledigt, bleibt noch die Bewegung des Schlägers durch die Maus. Dazu müssen wir im Setup die <em>addMouseListeners()</em> Methode aufrufen und die Methode <em>mouseMoved()</em> implementieren:</p>
<pre>
public void mouseMoved(MouseEvent e) {
int x = e.getX();
paddle.setLocation(x, getHeight() - PADDLE_SEPARATION);
}</pre>
<p>
Und das war's auch schon.</p>
<p>
.</p>
<hr />
<h1>
Fragen</h1>
<ol>
<li>
Es gibt Methoden mit Rückgabewert und welche ohne. Woran erkennt man, dass eine Methode keinen Rückgabewert hat?<br />
</li>
<li>
Manche Methoden haben Parameter (auch Übergabeparameter genannt). Was sind Parameter?<br />
</li>
<li>
Was haben Parameter und Kilometer gemeinsam?<br />
</li>
<li>
Wir haben mehr als einmal den RandomGenerator 'rgen' verwendet. Wie heißt das Kommando um eine Zufallszahl (int) zwischen 1 und 6 zu erzeugen?<br />
</li>
<li>
Wenn man einen primitiven Datentyp an eine Methode als Parameter übergibt, wird dieser im Original oder als Kopie übergeben?<br />
</li>
<li>
Nennen Sie vier Unterklassen (Kinderklassen) der Klasse GObject.<br />
</li>
<li>
Zeichnen Sie Diagramm das grob die Klassenhierarchie der GObjects darstellt.<br />
</li>
<li>
Wir haben den Top-Down Ansatz kennengelernt. Dieser gibt Regeln bzgl der Namen von Methoden, wieviele Zeilen Code eine Methoden haben sollte, etc. Nennen Sie zwei dieser Richtlinien.<br />
</li>
<li>
Analysieren Sie das Spiel 'Agar.io' mit Hilfe des Top-Down (Von Oben Nach Unten) Ansatzes. Es geht nur um die Grobstruktur (high-level), nicht um detaillierten Code.<br />
</li>
<li>
Die Methode addThirteen() im folgenden Code funktioniert nicht wie erwartet. Was ist das Problem? Wie würden Sie es lösen?<br />
<br />
<strong><span style="font-family:courier new,courier,monospace;">private void addThirteen(int x ) {<br />
x += 12;<br />
}<br />
public void run() {<br />
<span style="font-family:courier new,courier,monospace;"> </span>int x = 4;<br />
<span style="font-family:courier new,courier,monospace;"> </span>addThirteen( x );<br />
<span style="font-family:courier new,courier,monospace;"> </span>println( "x = " + x );<br />
}</span></strong><br />
</li>
<li>
Das Arcade-Spiel 'RandomCircles' war ein absoluter Hit in den späten 80er Jahren. Es handelt sich dabei um ein Spiel, das an der Stelle an der die Benutzerin mit der Maus klickt einen bunten Kreis zeichnet. Es wäre etwas zu viel verlangt die Vollversion des Spiels zu implementieren, aber es ist relativ einfach, den Code zu schreiben, der<br />
1) einen farbigen Kreis auf dem Bildschirm zeichnet und<br />