-
Notifications
You must be signed in to change notification settings - Fork 24
/
Copy pathi1world.tex
2009 lines (1917 loc) · 66.1 KB
/
i1world.tex
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
% Diese Datei ist Teil des Buchs "Schreibe Dein Programm!"
% Das Buch ist lizenziert unter der Creative-Commons-Lizenz
% "Namensnennung - Weitergabe unter gleichen Bedingungen 4.0 International (CC BY-SA 4.0)"
% https://creativecommons.org/licenses/by-sa/4.0/deed.de
\chapter{Videospiele programmieren}
\label{cha:representation-and-state}
In diesem Kapitel programmieren wir zum ersten Mal ein vollständiges,
benutzbares Programm, nämlich ein kleines Videospiel. Dafür brauchen
wir zwei Zusätze für die Lehrsprache in DrRacket, die Teachpacks
\texttt{image.rkt} und \texttt{universe.rkt}. Ersteres ist für das
Anzeigen der Grafik zuständig (wir haben es im ersten Kapitel schon
einmal benutzt), und \texttt{universe.rkt} ist dafür da, die Grafiken
zu bewegen und interaktiv zu machen.
Beide Teachpacks können noch viel mehr als in dieses Kapitel passt.
Zum Glück steht im Hilfezentrum von DrRacket umfangreiche
Dokumentation, allerdings leider derzeit nur auf Englisch: Für das
\texttt{image.rkt}-Teachpack muss man dort nach \texttt{2htdp/image}
suchen, für \texttt{universe.rkt} nach
\texttt{2htdp/universe}.\footnote{\texttt{2htdp} bezieht sich auf den
Titel des Buchs \textit{How to Design Programs} in der zweiten
Auflage~\cite{FelleisenFindlerFlattKrishnamurthi2001}, für das diese
Teachpacks ursprünglich entwickelt wurden.} Hier sind direkte Links
auf die Dokumentation:
%
\begin{flushleft}
\url{https://docs.racket-lang.org/teachpack/2htdpimage.html}\\
\url{https://docs.racket-lang.org/teachpack/2htdpuniverse.html}
\end{flushleft}
\section{Bilder mit \texttt{image.rkt}}
Für die Grafikprogrammierung mit \drscheme{} laden wir ein sogenanntes
\textit{Teachpack\index{Teachpack}}, ein kleiner Sprachzusatz, in
diesem Fall mit einer Reihe von Funktionen zur Erzeugung von Bildern.
Wir sind ihm bereits in Abschnitt~\ref{sec:rechnen-mit-bildern} auf
Seite~\pageref{sec:rechnen-mit-bildern} begegnet: Um das Teachpack zu
laden, musst Du im Menü \texttt{Sprache} (oder \texttt{Language} in
der englischen Ausgabe) den Punkt \texttt{Teachpack hinzufügen}
(\texttt{Add teachpack}) anwählen, und im dann erscheinenden
Auswahl-Dialog die Datei
\texttt{image.rkt\index{image.rkt@\texttt{image.rkt}}} auswählen.
\subsection{Einfache Bilder}
Im Teachpack \texttt{image.rkt} erzeugen verschiedene Funktionen
einfache Bilder. So hat zum Beispiel die Funktion \texttt{rectangle}
folgende Signatur:\indexvariable{rectangle}
%
\begin{lstlisting}
(: rectangle (real real mode color -> image))
\end{lstlisting}
%
Dabei sind die ersten beiden Argumente Breite und Höhe eines Rechtecks
in Pixeln.
Das Argument mit Signatur \lstinline{mode}\indexvariable{mode} ist eine Zeichenkette, die
entweder \lstinline{"solid"} oder \lstinline{"outline"} sein muss. Sie bestimmt,
ob das Rechteck als durchgängiger Klotz oder nur als Umriss gezeichnet
wird. Das Argument mit der Signatur \lstinline{color}\indexvariable{color} ist eine
Zeichenkette, die eine Farbe (auf Englisch) bezeichnet,
zum Beispiel \lstinline{"red"}, \lstinline{"blue"}, \lstinline{"yellow"},
\lstinline{"black"}, \lstinline{"white"} oder \lstinline{"gray"}. Als Ergebnis
liefert \lstinline{rectangle} ein Bild, das von der \drscheme{}-REPL
entsprechend angezeigt wird wie andere Werte auch. Beispiel:
%
\begin{lstlisting}
(rectangle 100 30 "outline" "brown")
|\evalsto| |\includegraphics[scale=0.5]{videospiele/rectangle}|
\end{lstlisting}
%
Als Farbe geht übrigens auch \lstinline{"transparent"}, die das Bild
durchsichtig beziehungsweise unsichtbar macht. (Wozu ist ein
unsichtbares Bild gut, fragst Du vielleicht. Zum Beispiel, wenn wir
ein kleineres Bild nicht ganz mittig platzieren wollen~-- dann können
es auf ein transparentes Bild legen.)
Es gibt es noch weitere Funktionen, die
geometrische Figuren zeichnen:\indexvariable{circle}
%
\begin{lstlisting}
(: circle (real mode color -> image))
\end{lstlisting}
%
Die \lstinline{circle}-Funktion liefert einen Kreis, wobei das erste
Argument den Radius angibt. Die \lstinline{mode}- und
\lstinline{color}-Argumente sind wie bei \lstinline{rectangle}.
Beispiel:
%
\begin{lstlisting}
(circle 50 "solid" "red")
|\evalsto| |\includegraphics[scale=0.5]{videospiele/circle}|
\end{lstlisting}
%
\begin{lstlisting}
(: ellipse (real real mode color -> image))
\end{lstlisting}
%
\indexvariable{ellipse}Diese Funktion liefert eine Ellipse,
wobei das erste Argument die Breite und das zweite die Höhe angibt.
Beispiel:
%
\begin{lstlisting}
(ellipse 50 100 "solid" "green")
|\evalsto| |\includegraphics[scale=0.5]{videospiele/ellipse}|
\end{lstlisting}
%
\begin{lstlisting}
(: triangle (real mode color -> image))
\end{lstlisting}
%
\indexvariable{triangle}Diese Funktion liefert ein nach oben
zeigendes gleichseitiges Dreieck, wobei das erste Argument die
Seitenlänge angibt. Beispiel:
%
\begin{lstlisting}
(triangle 50 "solid" "gold")
|\evalsto| |\includegraphics[scale=0.5]{videospiele/triangle}|
\end{lstlisting}
%
\begin{lstlisting}
(: line (real real color -> image))
\end{lstlisting}
%
\indexvariable{line}zeichnet eine Linie. Der Aufruf
\lstinline{(line $w$ $h$ $c$)} liefert ein Bild
mit Breite $w$ und Höhe $h$, in dem die Linie von der linken oberen in
die rechte untere Ecke verläuft. Falls die Linie entlang der anderen
Diagonale verlaufen soll, kannst Du einfach das Vorzeichen von $w$
oder von $h$ umdrehen.
Beispiele:
%
\begin{lstlisting}
(line 150 100 "blue")
|\evalsto| |\includegraphics[scale=0.5]{videospiele/line1}|
(line -150 100 "blue")
|\evalsto| |\includegraphics[scale=0.5]{videospiele/line2}|
\end{lstlisting}
%
Wir können auch ein Bild erzeugen, in dem Text steht, und zwar mit der
Funktion \lstinline{text}\indexvariable{text}, die folgende Signatur hat:
%
\begin{lstlisting}
(: text (string real color -> image))
\end{lstlisting}
%
Die Zahl ist die Höhe der Buchstaben. Beispiel:
%
\begin{lstlisting}
(text "Schreibe Dein Programm!" 20 "red")
|\evalsto| |\includegraphics[scale=0.5]{videospiele/text}|
\end{lstlisting}
%
Manchmal reichen einfache Rechtecke und Kreise nicht aus und wir
wollen komplexere Formen erzeugen. Eine Möglichkeit ist die Funktion
\lstinline{polygon}\indexvariable{polygon}, die ein n-Eck
zeichnet. Sie hat folgende Signatur:
%
\begin{lstlisting}
(: polygon ((list-of (mixed posn pulled-point)) mode color -> image))
\end{lstlisting}
%
Diese Funktion akzeptiert eine Liste von Eckpunkten und die üblichen
\lstinline{mode}- und \lstinline{color}"=Argumente. Ein Eckpunkt
muss entweder zur Signatur \lstinline{posn} oder zu
\lstinline{pulled-point} gehören. Das sind eingebaute Record-Typen,
bei denen \lstinline{posn}\indexvariable{posn} so definiert sein könnte:
%
\indexvariable{posn}
\begin{lstlisting}
(define-record posn
make-posn
posn?
(posn-x real)
(posn-y real))
\end{lstlisting}
%
Dieser Typ definiert also ganz normale kartesische Koordinaten mit X-
und Y-Komponente. Hier ist ein Beispiel für ein Polygon mit solchen
Eckpunkten:
%
\begin{lstlisting}
(polygon (list (make-posn 0 0)
(make-posn -20 40)
(make-posn 120 0)
(make-posn -20 -40))
"solid"
"plum")
|\evalsto| |\includegraphics[scale=0.5]{videospiele/polygon1}|
\end{lstlisting}
%
Bei \lstinline{posn}-Eckpunkten sind die Kanten also alles gerade
Linien. Mit \lstinline{pulled-point}-Eckpunkten ist es möglich, die
Kanten zu Kurven zu machen. Auch \lstinline{pulled-point} ist ein
Record-Typ:
%
\indexvariable{pulled-point}
\begin{lstlisting}
(define-record pulled-point
make-pulled-point
pulled-point?
(pulled-point-lpull real)
(pulled-point-langle real)
(pulled-point-x real)
(pulled-point-y real)
(pulled-point-rpull real)
(pulled-point-rangle real))
\end{lstlisting}
%
Wie \lstinline{posn} hat auch \lstinline{pulled-point} eine X- und
eine Y-Koordinate. Außerdem gibt es jeweils zwei "<Pull">- und
"<Angle">-Komponenten, die spezifizieren, wie die Kanten gebogen
werden. Abbildung~\ref{fig:pulled-point} zeigt, wie das
funktioniert: Für jeden Eckpunkt gibt es eine Kante vom
vorigen Punkt~-- die \emph{eingehende} Kante~-- und die Kante zum nächsten
Punkt~-- die \emph{ausgehende} Kante.
\begin{figure}[tb]
\centering
\def\svgwidth{0.4\textwidth}
\input{videospiele/pulled-point.pdf_tex}
\caption{Funktionsweise von \lstinline{pulled-point}}
\label{fig:pulled-point}
\end{figure}
%
Die \lstinline{pulled-point-langle}-Komponente gibt den Winkel an, mit
dem die eingehende Kante gebogen wird, die
\lstinline{pulled-point-rangle}-Komponente den Winkel ist für die
ausgehende Kante zuständig. Abbildung~\ref{fig:pulled-point} zeigt,
in welche Richtung \lstinline{langle} und \lstinline{rangle} die
beiden Kanten von Punkt Nummer~2 biegen: Die eingehende Kante kommt von Punkt~1 her, die
ausgehende Kante geht zu Punkt~3. Die Komponenten
\lstinline{pulled-point-lpull} und \lstinline{pulled-point-rpull}
geben an, wieviel die Kanten verbogen werden auf einer Skala zwischen $0$ und $1$.
%
\begin{lstlisting}
(polygon (list (make-pulled-point 1/2 0 0 0 1/2 -20)
(make-posn -20 40)
(make-pulled-point 1/2 -20 120 0 1/2 20)
(make-posn -20 -40))
"solid"
"plum")
|\evalsto| |\includegraphics[scale=0.5]{videospiele/polygon2}|
\end{lstlisting}
%
\begin{figure}[tb]
\centering
\includegraphics[height=0.4\textheight]{videospiele/insert-image}
\caption{Bilddatei in Programm einbetten}
\label{fig:image-insert}
\end{figure}
Es ist auch möglich, Bilder-Dateien in
\texttt{image.rkt}-Bilder zu verwandeln. Das macht der Menüpunkt
\texttt{Bild einfügen} im \texttt{Spezial}-Menü.
Alternativ ist es möglich,
Bilder in anderen Applikationen auszuwählen und zu kopieren und dann
in DrRacket einzufügen. Die eingefügten Bilder dienen dann als
Literale für Bild-Werte. Abbildung~\ref{fig:image-insert} zeigt ein
Beispiel.
Schließlich ermitteln die folgenden Funktionen Breite und Höhe
eines Bildes:\indexvariable{image-width}\indexvariable{image-height}
%
\begin{lstlisting}
(: image-width (image -> natural))
(: image-height (image -> natural))
\end{lstlisting}
%
\subsection{Bilder zusammensetzen und verändern}
Da diese geometrischen Formen für sich genommen langweilig sind,
können wir mehrere Bilder zu einem zusammensetzen.
Zum Aufeinanderlegen gibt es die Funktion
\lstinline{overlay}\indexvariable{overlay}, die ein Bild
mittig auf ein anderes Bild drauflegt. Das Ergebnis
ist groß genug, dass beide
Bilder hineinpassen. Signatur und Beispiel:
%
\begin{lstlisting}
(: overlay (image image -> image))
(overlay
(circle 50 "solid" "gold")
(rectangle 100 100 "solid" "blue"))
|\evalsto| |\includegraphics[scale=0.5]{videospiele/overlay}|
\end{lstlisting}
%
Die
Funktion~\lstinline{overlay/xy}\indexvariable{overlayxy}
erlaubt, das untere Bild gegenüber dem oberen in X- und Y-Richtung zu
verschieben. Signatur und Beispiel:%
\begin{lstlisting}
(: overlay/xy (image real real image -> image))
(overlay/xy
(circle 50 "solid" "gold")
10 -20
(rectangle 100 100 "solid" "blue"))
|\evalsto| |\includegraphics[scale=0.5]{videospiele/overlayxy}|
\end{lstlisting}
%
Das Beispiel zeigt, das auch \lstinline{overlay/xy} das Ergebnisbild
gerade so groß macht, dass die beiden Eingabebilder hineinpassen.
Manchmal macht auch \lstinline{overlay/xy} nicht das richtige, nämlich
wenn einfach nur ein Bild in eine "<Szene"> platziert werden soll,
zum Beispiel das Gürteltier auf einem Straßenabschnitt. In dem Fall
wollen wir genau wie bei \lstinline{overlay/xy} kontrollieren, an
welchen Koordinaten das daraufgelegte Bild erscheint, aber das Bild
soll nicht größer werden, wenn zum Beispiel ein Teil des Gürteltiers
über den Straßenabschnitt hinausragt. Diese Aufgabe erledigt die
Funktion
\lstinline{place-iamge}\indexvariable{place-image}, deren
Signatur der von \lstinline{overlay/xy} entspricht:
%
\begin{lstlisting}
(: place-image (image real real image -> image))
\end{lstlisting}
%
Die beiden Koordinaten werden anders als bei
\lstinline{overlay/xy} nicht relativ zur mittigen Anordnung
interpretiert. Stattdessen geben die beiden Koordinaten die Position
im zweiten Bild an, wo die Mitte des ersten Bilds platziert wird,
ausgehend von der oberen linken Ecke des ersten Bildes:
%
\begin{lstlisting}
(place-image
(circle 50 "solid" "gold")
40 70
(rectangle 100 100 "solid" "blue"))
|\evalsto| |\includegraphics[scale=0.5]{videospiele/place-image}|
\end{lstlisting}
%
Die Funktion \lstinline{place-image} hat noch eine große Schwester
\lstinline{place-image/align}.\indexvariable{place-image-align}\label{func:place-image-align}
Während \lstinline{place-image} den Bezugspunkt in der Mitte des
ersten Bilds setzt, erlaubt \lstinline{place-image} auch anderee
Bezugspunkte.
Hier die Signatur:
%
\begin{lstlisting}
(: place-image/align (image real real x-place y-place image -> image))
\end{lstlisting}
%
Die Signaturen \lstinline{x-place} und \lstinline{y-place} könnten
folgendermaßen definiert sein:
%
\indexvariable{x-place}
\indexvariable{y-place}
\begin{lstlisting}
(define x-place (signature (enum "left" "right" "center")))
(define y-place (signature (enum "top" "bottom" "center")))
\end{lstlisting}
%
Diese Argumente geben an, ob der Bezugspunkt horizontal am linken
oder rechten Rand oder in der Mitte liegt und vertikal oben, unten
oder in der Mitte:
%
\begin{lstlisting}
(place-image/align
(circle 20 "solid" "gold")
40 70 "left" "center"
(rectangle 100 100 "solid" "blue"))
|\evalsto| |\includegraphics[scale=0.5]{videospiele/place-image-align1}|
(place-image/align
(circle 20 "solid" "gold")
40 70 "right" "center"
(rectangle 100 100 "solid" "blue"))
|\evalsto| |\includegraphics[scale=0.5]{videospiele/place-image-align2}|
(place-image/align
(circle 20 "solid" "gold")
40 70 "center" "center"
(rectangle 100 100 "solid" "blue"))
|\evalsto| |\includegraphics[scale=0.5]{videospiele/place-image-align3}|
(place-image/align
(circle 20 "solid" "gold")
40 70 "center" "top"
(rectangle 100 100 "solid" "blue"))
|\evalsto| |\includegraphics[scale=0.5]{videospiele/place-image-align4}|
(place-image/align
(circle 20 "solid" "gold")
40 70 "center" "bottom"
(rectangle 100 100 "solid" "blue"))
|\evalsto| |\includegraphics[scale=0.5]{videospiele/place-image-align5}|
\end{lstlisting}
%
Die folgenden Funktionen \lstinline{above} und \lstinline{beside}
kennst Du vielleicht noch aus dem ersten Kapitel auf
Seite~\pageref{function:beside-above}. Sie setzten zwei Bilder
übereinander respektive nebeneinander:
%
\begin{lstlisting}
(: above (image image -> image))
(: beside (image image -> image))
\end{lstlisting}
%
Manchmal sieht ein Bild gut aus, ist aber zu klein oder zu groß
geraten. Die Funktion \lstinline{scale} kann es größer oder kleiner
machen. Signatur:
\begin{lstlisting}
(: scale (real image -> image))
\end{lstlisting}
%
Beispiel:
%
\begin{lstlisting}
(define c (circle 20 "solid" "green"))
(beside (scale 0.5 c)
c
(scale 1 c)
(scale 1.5 c)
(scale 2 c)
(scale 3 c))
|\evalsto| |\includegraphics[scale=0.5]{videospiele/scale}|
\end{lstlisting}
\section{Modelle und Ansichten}
\mentioncode{videospiele/dillo-world.rkt}
%
In unserem Videospiel geht es um Gürteltiere auf dem texanischen
Highway. Vielleicht erinnerst Du Dich, wir sind ihnen schon in
Abschnitt~\ref{sec:armadillo} auf Seite~\pageref{sec:armadillo}
begegnet:
%
\indexvariable{run-over-dillo}
\indexvariable{dillo}
\begin{lstlisting}
; Ein Gürteltier hat folgende Eigenschaften:
; - Gewicht (in g)
; - lebendig oder tot
(define-record dillo
make-dillo
dillo?
(dillo-weight natural)
(dillo-alive? boolean))
(: make-dillo (natural boolean -> dillo))
(: dillo-weight (dillo -> natural))
(: dillo-alive? (dillo -> boolean))
(define dillo1 (make-dillo 55000 #t)) ; 55 kg, lebendig
(define dillo2 (make-dillo 58000 #f)) ; 58 kg, tot
(define dillo3 (make-dillo 60000 #t)) ; 60 kg, lebendig
(define dillo4 (make-dillo 63000 #f)) ; 63 kg, tot
; Gürteltier überfahren
(: run-over-dillo (dillo -> dillo))
(define run-over-dillo
(lambda (dillo)
(make-dillo (dillo-weight dillo)
#f)))
\end{lstlisting}
%
Die Datendefinition mit dem zugehörigen Code hält zwar einige Eigenschaften
von Gürteltieren fest, für ein Videospiel fehlt aber eine
grafische Darstellung. Eine solche Repräsentation mit
zugehörigen Operationen heißt in der Softwararchitektur auch
\textit{Modell}\index{Modell} oder auch
\textit{Domänenlogik}\index{Domänenlogik}, und bei größeren Softwaresystemen
ist es eine gute Idee, das Modell von der grafischen Darstellung zu
trennen, damit das Modell stabil bleiben kann, auch wenn die grafische
Darstellung geändert wird.
Die grafische Darstellung eines Modells nennen wir dessen
\textit{Ansicht}\index{Ansicht} (auf englisch
\textit{View}\index{View}). Wenn wir uns also mit Gürteltieren als
Modell für ein Videospiel beschäftigen, müssen wir daraus eine Ansicht
in Form eines Bildes berechnen.
Wir warnen Dich schonmal vor: Der grafische Gestaltung in diesem
Kapitel fehlt es an Finesse. Wir hoffen, Du kannst
sie verbessern!
Die folgende Funktion macht aus einem \lstinline{dillo}-Wert ein Bild:
%
\begin{lstlisting}
; Gürteltier-Bild erzeugen
(: dillo-image (dillo -> image))
\end{lstlisting}
%
\begin{figure}[tb]
\centering
\includegraphics[width=0.6\textwidth]{videospiele/dillo}
\caption{Umriss eines Gürteltiers}
\label{fig:dillo-body}
\end{figure}
Als Basis dafür machen wir erstmal ein Bild mit dem Körper des
Gürteltiers. Zu diesem Zweck haben wir in
Abbildung~\ref{fig:dillo-body} zunächst von Hand eine
Zeichnung mit dem Umriss des Gürteltiers in ein Koordinatensystem
gezeichnet und die Punkte markiert, an denen sich die Richtung des
Strichs abrupt ändert~-- also quasi die Ecken. Diese Ecken haben wir
durchnummeriert und daraus haben wir dieses Polygon gemacht:
%
\indexvariable{dillo-body}
\begin{lstlisting}
(define dillo-body
(overlay/xy (polygon
(list (make-pulled-point 0.3 30
5 (- 60 5)
0.2 5)
(make-pulled-point 0.4 20
20 (- 60 32)
0.5 -70)
(make-pulled-point 0.5 120
90 (- 60 22)
0.2 -30)
(make-pulled-point 0.5 90
60 (- 60 10)
0.5 90)
(make-pulled-point 0 0
90 (- 60 22)
0.2 -30)
(make-pulled-point 0 0
55 (- 60 20)
0.3 -30)
(make-pulled-point 0 0
29 (- 60 17)
0.5 20))
"solid"
"brown")
0 -30
(rectangle 100 60
"solid" "transparent")))
\end{lstlisting}
%
Du siehst, da haben wir ganz schön viel herumprobiert, bis es
einigermaßen aussah, insbesondere bei den Zahlen für die
\lstinline{pulled-point}s. Immerhin haben wir die Koordinaten aus der
Zeichnung direkt übertragen, dabei ist uns aber aufgefallen, dass die
Koordinaten in der Mathematik von unten nach oben, im Teachpack aber
von oben nach unten laufen. Darum steht da zum Beispiel
\lstinline{(- 60 5)} für die Y-Koordinate 5, weil der obere Rand der
Zeichnung bei 60 liegt.
Außerdem ist das Gürteltier vor allem nach oben und rechts nach außen
gewölbt. DrRacket schneidet das Polygon bei der Anzeige ab, weswegen
wir es mit \lstinline{overlay/xy} noch auf einen transparenten
Hintergrund montiert haben. So sieht das Ergebnis aus:
%
\begin{lstlisting}
dillo-body
|\evalsto| |\includegraphics[scale=0.5]{videospiele/dillo-body}|
\end{lstlisting}
%
Jetzt wollen wir ja nicht jedes Gürteltier genau gleich darstellen,
sondern wir wollen dessen Eigenschaften bei der Darstellung
berücksichtigen. Dafür schreiben wir die Funktion
\lstinline{dillo-image}, von der wir ja schon Kurzbeschreibung und
Signatur gesehen haben. Hier ist das Gerüst mit der Schablone für
zusammengesetzte Daten als Eingabe:
%
\begin{lstlisting}
(define dillo-image
(lambda (dillo)
...
(dillo-weight dillo)
(dillo-alive? dillo)
...))
\end{lstlisting}
%
Wir sollten also noch berücksichtigen, wieviel das Gürteltier wiegt
und außerdem, ob es noch lebt. Das Gewicht berücksichtigen wir, indem
wir das Gürteltier größer oder kleiner machen:
%
\begin{lstlisting}
(define dillo-image
(lambda (dillo)
(scale (+ 1
(/ (- (dillo-weight dillo) 50000)
15000))
dillo-body)
...
(dillo-alive? dillo)
...))
\end{lstlisting}
%
Den Faktor bei \lstinline{scale} haben wir durch Probieren
ermittelt. Aber \lstinline{(dillo-alive? dillo)} steht noch da~-- wir
bauen das ein, indem wir einem toten Gürteltier "<tote Augen"> ins
Gesicht setzen:
%
\indexvariable{dillo-image}
\indexvariable{dead-eyes}
\begin{lstlisting}
(define dillo-image
(lambda (dillo)
(scale (+ 1
(/ (- (dillo-weight dillo) 50000)
15000))
(if (dillo-alive? dillo)
dillo-body
(overlay/xy dead-eyes
-25 -25
dillo-body)))))
(define dead-eyes
(overlay (line 10 10 "green")
(line -10 10 "green")))
\end{lstlisting}
%
\section{Den Highway modellieren}
\label{sec:highway-model}
\begin{figure}[tb]
\centering
\includegraphics[height=0.4\textheight]{videospiele/dillo-world}
\caption{Gürteltiere auf der Straße}
\label{fig:dillo-world}
\end{figure}
Ein Gürteltier allein macht noch kein Videospiel: Wir setzen gleich
mehrere Gürteltiere auf die Straße und~-- natürlich~-- überfahren sie
dann. Das sieht dann so aus wie in
Abbildung~\ref{fig:dillo-world}. Das Auto bewegt sich auf der Straße
und kann entweder auf die linke oder rechte Straßenseite wechseln, wo
es dann gegebenfalls über Gürteltiere fährt. Oben links steht ein
Punktestand~-- wieviele Gürteltiere schon vom lebenden Zustand in den
toten überführt wurden.
Damit wir das alles darstellen können, müssen für alles
Datendefinition schreiben, was auf dem Bild der Straße zu sehen sind:
%
\begin{itemize}
\item die Position des Autos
\item die Positionen der Gürteltiere zusammen mit ihren Zuständen
\item der Punktestand
\end{itemize}
%
Für diese Aspekte des Spiels entwickeln wir Datendefinitionen.
Man sieht schon, dass "<Positionen"> eine wichtige Rolle spielen,
sowohl für das Auto als auch für die Gürteltiere. In dem Bild kann
man sehen, dass dazu gehört, auf welcher Seite der Straße sich etwas
aufhält und bei welchem "<Straßenmeter">.
Hier sind Datendefinitionen für die Straßenseite und die Position heraus:
%
\indexvariable{side}
\indexvariable{position}
\begin{lstlisting}
; Straßenseite
(define side
(signature (enum "left" "right")))
; Eine Position auf der Straße besteht aus:
; - Straßenmeter (Abstand vom Straßenanfang in Meter)
; - Seite
(define-record position
make-position
position?
(position-m-from-start real)
(position-side side))
\end{lstlisting}
%
Als nächstes müssen wir die Idee "<Positionen der Gürteltiere zusammen
mit ihren Zuständen"> in eine Datendefinition umwandeln. Wir machen
das erstmal für ein einzelnes Gürteltier und nennen das Konzept
"<Gürteltier auf der Straße">:
%
\indexvariable{dillo-on-road}
\begin{lstlisting}
; Ein Gürteltier auf der Straße hat folgende Eigenschaften:
; - Gürteltier-Zustand
; - Position auf der Straße
(define-record dillo-on-road
make-dillo-on-road
dillo-on-road?
(dillo-on-road-state dillo)
(dillo-on-road-position position))
\end{lstlisting}
%
\section{Straßenansichten}
Jetzt wo wir die Welt modelliert haben können wir sie auch auf den
Bildschirm bringen. Wir fangen erstmal mit den einfachen und
offensichtlichen Elementen an: der Straße, inklusive der
Markierung auf dem Mittelstreifen und dem Auto. Die Gürteltiere
haben wir ja schon.
Damit wir einigermaßen realistisch über die Proportionen von allem
nachdenken können, messen wir alles in Metern ab und wandeln zwischen
Metern und Pixeln hin und her. Auf unserem Bildschirm sieht ein
Verhältnis von 1m:100 Pixel gut aus. Diese Funktionen konvertieren
hin und her:
\indexvariable{meters->pixels}
\indexvariable{pixels->meters}
\begin{lstlisting}
; Meter in Pixel umwandeln
(: meters->pixels (real -> real))
(define meters->pixels
(lambda (meters)
(* meters 100)))
; Pixel in Meter umwandeln
(: pixels->meters (real -> real))
(define pixels->meters
(lambda (pixels)
(/ pixels 100)))
\end{lstlisting}
%
Die Straße selbst ist nur ein schwarzes Rechteck, das ist einfach.
Schwieriger sind die Straßenmarkierungen, also abwechselnd ein weißer
und ein schwarzer Streifen. Damit das realistisch aussieht,
definieeren wir erst einmal die Länge der Markierungen und die der Lücken:
%
\indexvariable{marking-height}
\indexvariable{gap-height}
\begin{lstlisting}
(define marking-height 2) ; Höhe der Streifen
(define gap-height 1) ; Höhe der Lücken
\end{lstlisting}
%
Die Markierungen und die Lücken müssen wir abwechseln
aneinanderkleben. Wie oft fragst Du? Nun, der texanische Highway ist
unendlich lang, das wäre schwierig. Aber wir zeigen ja nur einen
Ausschnitt, und darum machen wir nur soviele Streifen, wie in den
Ausschnitt passen. Wieviele das sind, hängt von der Höhe des
Ausschnitts ab. Wir könnten natürlich von Hand nachzählen, das würde
aber bedeuten, dass wir uns frühzeitig auf die Höhe des Ausschnitts
festlegen müssten. Um das zu vermeiden, schreiben wir erst einmal
eine Funktion, die als Eingabe die Anzahl der Streifen akzeptiert.
Wir folgen der Schablone für Funktionen auf natürlichen Zahlen:
%
\indexvariable{markings}
\begin{lstlisting}
; Straßenmarkierung mit bestimmter Anzahl von Streifen malen
(: markings (natural -> image))
(define markings
(lambda (n)
(cond
((zero? n) empty-image)
((positive? n)
(above (rectangle (meters->pixels .20)
(meters->pixels marking-height)
"solid"
"white")
(rectangle (meters->pixels .20)
(meters->pixels gap-height)
"solid"
"black")
(markings (- n 1)))))))
\end{lstlisting}
%
Hier jetzt die tatsächliche Höhe des Straßenabschnitts:
%
\indexvariable{road-window-height}
\begin{lstlisting}
(define road-window-height 12) ; Höhe des Straßenausschnitts
\end{lstlisting}
%
Die Idee ist, dass Du später nur diese eine Definition ändern musst, falls
Dir die Ausschnittshöhe nicht passt, und sich dann alles andere
automatisch anpasst.
Um die Anzahl der nötigen Markierungen zu berechnen, teilen wir die
Höhe des Straßenausschnitts durch die Länge einer Markierung plus
Lücke. Zur Sicherheit addieren wir noch eins drauf: Die Division könnte
nicht ganz aufgehen. Außerdem wird möglicherweise am Ende nur ein
Teil eines Streifens zu sehen sein, auch dafür ist es gut, wenn im
Zweifelsfall einer mehr da ist.
%
\indexvariable{marking-count}
\begin{lstlisting}
; Anzahl der nötigen Markierungen
(define marking-count
(+ 1
(quotient road-window-height
(+ marking-height gap-height))))
\end{lstlisting}
%
Aus der Zahl machen wir schließlich
das Bild der Streifen:
%
\indexvariable{visible-markings}
\begin{lstlisting}
; sichtbare Markierungen
(define visible-markings
(markings marking-count))
\end{lstlisting}
%
Zur Erinnerung: Die Funktion \lstinline{quotient} haben wir auf
Seite~\pageref{func:quotient} eingeführt, sie teilt ganzzahlig.
Nun können wir das Bild des Straßenausschnitts zusammensetzen. Wir
brauchen neben der Höhe auch noch die Breite und machen erstmal eine
leere Szene nur aus schwarzem Asphalt:
%
\indexvariable{road-width}
\indexvariable{blank-road-window}
\begin{lstlisting}
; in Meter
(define road-width 5)
(define blank-road-window
(empty-scene (meters->pixels road-width)
(meters->pixels road-window-height)
"black"))
\end{lstlisting}
%
Wie Mittelstreifen auf dem Straßenausschnitt platziert werden, hängt
davon ab, an welchem Straßenmeter sich der Bildausschnitt befindet.
Wir schreiben also eine Funktion, welche die Straßenmeter akzeptiert
und ein passendes Bild liefert:
\begin{lstlisting}
; Straßenausschnitt anzeigen
(: road-window (real -> image))
\end{lstlisting}
%
Wir benutzen \lstinline{place-image/align} (siehe
Seite~\pageref{func:place-image-align}), um die Mittelstreifen auf den
Asphalt zu setzen:
%
\begin{lstlisting}
(define road-window
(lambda (meters)
(place-image/align visible-markings
...
...
... ...
blank-road-window)))
\end{lstlisting}
%
Wir setzen zunächst den Bezugspunkt beim Mittelstreifen nach oben in
die Mitte:
%
\begin{lstlisting}
(define road-window
(lambda (meters)
(place-image/align visible-markings
...
...
"center" "top"
blank-road-window)))
\end{lstlisting}
%
In X-Richtung muss der Mittelstreifen in die Mitte, da können wir
einfach die Breite der Straße halbieren.
Bei der Y-Koordinate ist es schwieriger: Die hängt von den
Straßenmetern ab. Die Straßenmeter sind aber (außer ganz am Anfang)
mehr als der Ausschnitt hoch beziehungsweise das Mittelstreifenbild
lang ist. Das Mittelstreifenbild wiederholt sich aber immer, wenn ein
Streifen plus Lücke vorbeigezogen ist. Wir rechnen also die Anzahl
der Pixel dafür aus:
%
\indexvariable{marking-segment-pixels}
\begin{lstlisting}
(define marking-segment-pixels
(meters->pixels (+ marking-height gap-height)))
\end{lstlisting}
%
Durch diese Zahl teilen wir und nehmen den Divisionsrest, um die
Y-Position zu bekommen. Das heißt nicht ganz~-- der Divisionsrest ist immer
positiv, und damit ist der Mittelstreifen zu weit unten: Wir ziehen
das Bild noch um einen Streifen nach oben, indem wir
\lstinline{marking-segment-pixels} abziehen. Das kommt dabei heraus:
%
\indexvariable{road-window}
\begin{lstlisting}
(define road-window
(lambda (meters)
(place-image/align visible-markings
(/ (image-width blank-road-window) 2)
(- (remainder (meters->pixels meters)
marking-segment-pixels)
marking-segment-pixels)
"center" "top"
blank-road-window)))
\end{lstlisting}
%
\begin{aufgabeinline}
Probiere \lstinline{road-window} aus. Entferne die Substraktion von
\lstinline{marking-segment-pixels} und untersucht, wie sich das
auswirkt!
\end{aufgabeinline}
%
\section{Bewegte Bilder mit \texttt{universe.rkt}}
Bisher haben wir nur statische, langweilige Bilder. Es ist Zeit, sie
in Bewegung zu setzen. Dafür brauchen wir ein weiteres Teachpack
namens \texttt{universe.rkt}. Um es zu aktivieren, musst Du nochmal
ins \texttt{Sprache}/\texttt{Language}-Menü und dort auf
\texttt{Teachpack hinzufügen}/\texttt{Add Teachpack} drücken und in
dem dann auftauchenden Dialog auf \texttt{universe.rkt} und
schließlich auf \texttt{OK} drücken.
Zum \texttt{universe.rkt}-Teachpack gehört eine Funktion namens
\lstinline{animate}\indexvariable{animate}. Was sie macht,
verdeutlicht am einfachsten ein Beispiel, dass Du in die REPL
eintippen kannst:
%
\begin{lstlisting}
(animate
(lambda (ticks)
(place-image/align (circle 5 "solid" "red")
100 ticks "center" "top"
(square 200 "solid" "black"))))
\end{lstlisting}
%
\begin{aufgabeinline}
Probier es aus!
\end{aufgabeinline}
%
Du siehst einen kleinen roten Kreis sich in einem quadratischen Bild
von oben nach unten bewegen.
Die neue Funktion \lstinline{animate} akzeptiert eine Funktion mit
einer Eingabe names \lstinline{ticks}, die ein Bild produziert:
%
\begin{lstlisting}
(: animate ((natural -> image) -> natural))
\end{lstlisting}
%
\lstinline{Animate} ruft die Funktion 28mal in der Sekunde auf und
zeigt die entstehenden Bilder hintereinander als Animation an. Dabei
übergibt \lstinline{animate} als \lstinline{ticks} eine natürliche
Zahl, die bei Null anfängt, und jedesmal um eins größer wird: Sie
misst also die Zeit, in sogenannten \textit{Ticks}\index{Ticks}.
Wir benutzen jetzt \lstinline{animate}, um das Bild des
Straßenausschnitts in Bewegung zu setzen~-- so, dass das Wagen genau
in der Mitte ist. Wir legen zuerst fest, wie schnell der Wagen fährt.
Wir haben uns für ein recht gemächliches Tempo entschieden, damit wir auch
wirklich alle Gürteltiere erwischen:
%
\indexvariable{meters-per-tick}
\begin{lstlisting}
; Meter pro Tick
(define meters-per-tick 0.1)
\end{lstlisting}
%
Diese Funktion ist so einfach, dass sich ausnahmsweise separates
Testen nicht lohnt:
%
\indexvariable{ticks->meters}
\begin{lstlisting}
; Ticks in Meter umwandeln
(: ticks->meters (natural -> rational))
(define ticks->meters
(lambda (ticks)
(* meters-per-tick ticks)))
\end{lstlisting}
%
Damit schreiben wir Funktion, die aus Ticks Straßenmeter
macht und \lstinline{road-window} aufruft:
%
\indexvariable{road-window-at-ticks}
\begin{lstlisting}
; Straßenausschnitt zu Zeitpunkt anzeigen
(: road-window-at-ticks (natural -> image))
(define road-window-at-ticks
(lambda (ticks)
(road-window (ticks->meters ticks))))
\end{lstlisting}
% Du kannst
also folgendes ausprobieren:
%
\begin{lstlisting}
(animate road-window-at-ticks)
\end{lstlisting}
%
\begin{aufgabeinline}
Schreibe eine andere Funktion Deiner Wahl mit der für
\lstinline{animate} passenden Signatur und probiere
\lstinline{animate} damit aus!
\end{aufgabeinline}
\section{Autos und Gürteltiere auf dem Highway}
Die Straße ist fertig, als nächstes ist das Auto dran. Wir fangen mit
einem Rad an:
%
\indexvariable{wheel}
\begin{lstlisting}
; Rad
(define wheel
(rectangle (meters->pixels 0.2) (meters->pixels .5)
"outline" "white"))
\end{lstlisting}
%
Als nächstes setzen wir zwei Räder zu einem Bild zusammen, das dann
links und rechts von der Karosserie platziert werden:
%