(Fortsetzung hiervon. Ich fürchte, diesmal noch technischer.) Wir haben: Ein halbwegs fertiges Modell für ein generisches Brettspiel, mit einem Spielbrett beliebiger Größe, bestehend aus rechtwinklig angeordneten Feldern (die, falls man das mal braucht, verschiedene Zustände annehmen können). Und wir haben Spielfiguren, die auf diesen Feldern stehen
Was noch fehlt, sind Spielregeln und irgendetwas Sichtbares. Und das habe ich nach dem Bauprinzip Model-View-Controller gelöst, das explizit im Lehrplan für die 11. Klasse steht. Da ich in meinem zweijährigen Notstudium nichts von Entwurfsmustern gehört hatte, musste ich mich erst mal schlau machen. In den beiden Schulbüchern, die ich heranzog, stand das jeweils sehr ähnlich, aber nicht ganz gleich, und nur mäßig verständlich. Die ISB-Handreichungen zu Informatik stellten wieder andere Aspekte in den Vordergrund, Wikipedia auch, und überhaupt gibt es MVC in verschiedenen Abwandlungen, die für Programmierer eine größere Rolle spielen als für Informatiklehrer und -schüler.
(Sehr anschaulich dargestellt ist MVC in den Programmierbeispielen dieses Threads eines Java-Forums. Wenn man die Beispiele kopiert und in einem eigenen Projekt ausprobiert, kapiert man schnell, worum es geht.)
Jedenfalls erkläre ich jetzt mein MVC.
Erstens: Das Model.
Das Model stellt quasi das Spielmaterial dar. Es besteht aus Objekten (Spielfiguren) mit bestimmten Zuständen (Position, Gesundheitszustand). Das Model stellt Methoden zur Verfügung, mit denen man diese Zustände verändern kann. Detaillierter ist das im letzten Beitrag beschrieben.
Zweitens: Model – und View als Darstellung.
Damit man das Modell auch anschauen kann und nicht blind Schach spielen muss, gibt es mindestens einen View (eine Ansicht). Der View stellt die Inhalte des Modells für den Benutzer grafisch dar. Er ist quasi die Ausgabeeinheit unseres Projekts. Immer, wenn sich am Modell etwas Nennenswertes ändert, schickt es Informationen an den View, der diese Informationen dann (sofern nötig) grafisch umsetzt.
Bei mir sieht das dann in der Rohform so aus:
Das ist ein 5×5-Spielfeld mit drei Figuren verschiedener Art darauf. Die einzelnen Felder sind von zweierlei Typen. Rechts sind Knöpfe und Informationen, die interessieren uns jetzt noch gar nicht.
Ich habe oben gesagt, dass es mindestens einen View gibt. Tatsächlich gibt es oft mehrere Ansichten, bei mir bisher zwei: die grafische Darstellung und eine Art schriftliche Mitprotokollierung. Das kennt man ja auch vom Schach, dass da die Züge aufgeschrieben werden.
Später möchte ich den Text natürlich noch etwas epischer verfassen. — Aber auch mehrere gleichzeitige grafische Views sind bei einem Spiel denkbar: eine Ansicht von oben und eine von der Seite, der Übersichtlichkeit halber. Oder ein View für Spieler 1 und ein zweiter für Spieler 2, bei dem jeweils die Typen der gegnerischen Spielfiguren ausgeblendet sind, wie man das für Stratego bräuchte. Deshalb ist MVC üblicherweise so angelegt, dass man leicht neue Views hinzufügen kann, ohne am Model auch nur das Geringste ändern zu müssen (=> Beobachter-Entwurfsmuster).
Knackpunkt bei dem Verhältnis zwischen Model und View(s) ist der, dass man vorher klarmachen und festhalten muss, welche Botschaften das Model an den View schicken kann. In meinem Fall sind das bisher:
figurBewegt(Spielfigur s)
(wohin, das kann man die Figur dann ja selber fragen)figurGeändert(Spielfigur s)
(und zwar den Zustand, abhängig davon ändert sich dann vielleicht auch das verwendete Symbol)feldtypAendern(int x, int y, Feldtyp typ)
(das mit den Koordinaten ist zwar nicht optimal, aber lassen wir das)figurGeschlagen(Spielfigur sieger, Spielfigur verlierer)
(das brauche ich eigentlich nur für die epische Textausgabe, damit ich dann solche Sätze protokollieren kann wie: „Die heldenhafte Sonja hat Sigurd im fairen Kampf bezwungen“)
Damit kann ich alle bisher denkbaren Änderungen am Modell an die Views übermitteln. Was die damit machen, ist dann deren Sache – darum soll sich das Team kümmern, dass die Ausgabe programmiert. Je nach Zustand und Art der Spielfigur wird ein bestimmtes Bildchen fürs Spielbrett ausgewählt (und eventuell durch ein anderes ersetzt, wenn sich der Zustand ändert); je nach Typ des Felds wird eine bestimmte Farbe oder ein bestimmtes Hintergrundbildchen für das Feld ausgewählt. Aber das kann man machen, wie man will, solange der Benuter versteht, was gemeint ist. Ansonsten schreibe ich hier nichts dazu, wie ich den View programmiert habe. (Nämlich ziemlich schlampig, aber das muss das Modell ja nicht wissen und ich kann es unabhängig davon später mal verbessern.) Die grafische Darstellung interessiert mich selber nämlich auch wenig. Aber die Schüler stehen total drauf.
Drittens: Model – und View als Benutzereingabe.
Ich will ja nicht nur zuschauen, wie sich das Modell in seinen Eigenschaften verändert, ich will ja auch eingreifen können in das System, über Tastatur oder Maus. Ich will Knöpfe drücken können! Auch darum kümmert sich der View. Hier zieht die Maus gerade Peterkin, den Kletterer, auf ein neues Feld:
Dabei kümmert sich der View überhaupt nicht darum, ob das ein legaler Zug ist oder nicht. Und wenn beim Schach der Bauer drei Felder auf einmal nimmt oder der König sich mitten ins Schach begibt, das ist dem View egal. Ausnahme wohl: Wenn man die Figur auf einen Ort ganz außerhalb des Spielbretts oder gar des ganzen Fensters zieht, dann sollte der View das schon als Quatsch erkennen. Der View nimmt ansonsten nur Benutzereingaben entgegen und schickt sie weiter – an jemanden, der sich darum kümmert.
Viertens: Model, View und Controller
Das ist dann der Controller:
Der Controller empfängt über den View, was der Benutzer gerne an Veränderungen am Modell hätte (Bewegen einer Figur, Einzahlung aus Konto). Das heißt aber noch nicht, dass diese Veränderungen immer auch durchgeführt werden. Sonst wäre das mit dem Kontostand einfach.
Auch den Controller kann durchaus ein ganz anderes Team programmieren als den View. Wichtig auch hier: Welche Informationen soll der View an den Controller schicken? Es sind letztlich ähnliche wie die, die wir oben schon hatten. Bei mir heißen sie:
boolean figurZiehen(Spielfigur s, int x, int y)
(welche Figur wohin gezogen werden soll)figurAendern()
undfeldAendern()
sind bei meinem Spiel nicht vorgesehen; der Spieler kann nicht aktiv den Typ eines Feldes oder den Zustand einer Spielfigur ändern, das macht das Programm jeweils allein – das könnte man natürlich auch anders haben wollen.- Ja, was soll der Spieler denn noch alles machen können, außer Figuren ziehen? Alle möglichen Knöpfe drücken und Menüpunkte auswählen, aber das sind View-Interna, die gehen uns nichts an. Wichtig ist, welche Auswirkungen das jeweils haben soll. Also vermutlich brauchen wir noch:
neustarten()
spielstandSpeichen()
spielstandLaden()
punktestandAusgeben()
passen()
(für ein Mehrspielerspiel, wenn das erlaubt sein soll)
und solche Sachen.
Man muss also vereinbaren, dass der Controller, der zum View gehört, diese Informationen empfangen kann (=>Interfaces). Dann können View-Team und Controller-Team unabhängig voneinander arbeiten.
Fünftens: Model, View und Controller, und wieder Model
Was macht jetzt der Controller mit diesen Wünschen vom View? Er überprüft, wie er darauf reagieren soll. Zum Teil weiß er das selber, meistens muss er aber das Model fragen.
Deshalb ist jetzt auch ein dritter Pfeil eingezeichnet, vom Controller zum Model hin. (Nicht wundern, dass da kein Pfeil zurückgeht. Die Pfeile geben nur an, wer wem Fragen stellen/Arbeiten anschaffen kann – für die Antwortmöglichkeit zeichnet man keine Pfeile ein, da auf Fragen immer Antworten kommen, weil alle Objekte höflich sind.)
Spätestens ab jetzt gibt es verschiedene Varianten der MVC-Bauweise. Das macht aber nichts. Wo stecken die Informationen zur Spiellogik, die Spielregeln? Bisher ist unser Model ja nur das Spielbrett, ohne jegliche Regeln. Demnach würden alle Regeln beim Controller sein: dass ein Bauer nicht drei Felder auf einmal ziehen kann oder dass ein König sich nicht ins Schach bewegen darf. Kann man machen. Herauskriegen, ob das Feld, auf das der Spieler den König ziehen will, gerade im Schach steht (von einer gegnerischen Figur bedroht wird), kann die Steuerung nur, indem sie das Model fragt. Denn nur das weiß, welche Figur gerade wo steht. Man könnte manche Information über die Zugmöglichkeit auch gleich ins Model legen, etwa wenn es Unterklassen zur Klasse Spielfigur gibt:
Man würde dabei für die Klassen König, Dame, Springer und so weiter jeweils die abstrakte Methode istErlaubt(alte Koordinaten, neue Koordinaten)
überschreiben, die jeweils zurückgibt, ob der Zug prinzipiell für diese Art Spielfigur erlaubt ist. Aber das ist eine Frage für später.
Irgendwie entscheidet der Controller jedenfalls, wie mit dem Benutzerwunsch umgegangen werden muss. Er lässt möglicherweise das Model seinen Zustand ändern, zum Beispiel eine Figur anders positionieren. Oder er ändert den Zustand einer Figur von „gesund“ auf „verwundet“. Diese Änderungen am Model leitet eben dieses sofort und schnurstracks an den View oder die Views weiter – wie oben schon unter „Zweitens“ berichtet.
Sechstens: Model, View und Controller (endgültig).
Außerdem sagt die Steuerung auch dem überwachten View Bescheid. Wenn der vom Spieler gemachte Zug zum Beispiel illegal war, sagt die Steuerung dem View, dass das als Text eingeblendet werden soll. Oder dass der Name des Spielers, der als nächster am Zug ist, in einem Textfenster erscheinen soll. Oder dass der View eine Highscore-Liste darstellen soll (weil auf einen entsprechenden Knopf gedrückt werden soll). Unklar ist mir noch, wie man bei einem illegalen Zug am besten vorgeht: soll die Steuerung dem View sagen, dass der das Bildchen wieder zurück an den alten Ort stellen soll, oder soll die Steuerung dem Modell sagen, dass die entsprechende Spielfigur eine neue Position hat (die allerdings gleich der alten ist), damit das Model dem View sagt, dass er die Darstellung aktualisieren soll? Im zweiten Fall würde meine einfache Mitprotokollierung und auch eine eventuelle Zugzählung nicht mehr ohne Weiteres funktionieren, deshalb habe ich mich für die erste Lösung entschieden. Aber das sind jetzt krause Gedanken, die sicher niemand nachvollziehen kann, der nicht gerade ebenfalls am Basteln ist. Aber ich schreibe das ja auch für mich zum Erinnern.
Siebtens: Hausaufgaben.
Wozu das ganze? Für das Programmierprojekt, das in 11 im Lehrplan steht. Zum Üben: wie könnte ein einfaches Model für das Spiel Backgammon aussehen? Welche Botschaften muss das Model an den View schicken? Welche Botschaften muss View an Controller schicken? Welche Fragen muss das Model dem Controller beantworten können? Wenn man das entworfen hat, kann das Programmieren beginnen.
Oder das gleiche Schiffe versenken. Da braucht man zwei verschiedene Views, für jeden Spieler einen. (Genauer: zwei verschiedene View-Objekte, die durchaus zur gleichen Klasse gehören können.) Auch Minesweeper kann man programmieren.
Gar nicht berücksichtigt ist bisher, dass man vielleicht mit dem Computer als Gegner spielen will. Das ist eine ganz andere Baustelle, aber auch nicht uninteressant.
Anhang: Umwege.
Ich bin selber natürlich nicht ganz so ordentlich vorgegangen und habe erst einmal drauflos programmiert. Zuerst habe ich das mit der Grafik so gelöst: Jede Figur bekam quasi als Partner eine grafische Darstellung seiner selbst. Die habe ich „Counter“ genannt, weil bei Brettspielern so die kleinen Pappchips heißen, die man auf die Spielfläche legt. (Und sie erbt von JLabel.)
Das ging auch gut und war praktisch: Wenn das Model eine Spielfigur-Änderung an den View schickt, kann der View die Spielfigur einfach fragen, welches Bildchen dazu gehört. Und wenn über den View ein Counter bewegt wird, kann man den fragen, welche Spielfigur dazu gehört, und das dem Controller melden. Zugegeben, mich hat von Anfang an gestört, dass das nicht sauberes MVC war. Denn über die Verbindung Spielfigur/Counter tauschten Model und View Informationen aus, die nicht über die gemeinsame Schnittstelle liefen. Die Klasse Spielfigur (aus dem Model) war direkt verknüpft mit der Klasse Counter (aus dem Model) – wenn sich am View-Entwurf etwas änderte (die Klasse Counter nämlich), mussten auch Elemente des Models angepasst werden (die Klasse Spielfigur). Ungeschickt. Und als ich dann später mal zwei grafische Views haben wollte, brach mein System zusammen. Wenn jede Figur 1 Counter hat, kann der nicht gleichzeitig an zwei Orten sein. Also zurück zu sauberem MVC.
Schreibe einen Kommentar