Wie ich auszog, ein Spiel zu programmieren (2): Das MVC-Entwurfsmuster

(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() und feldAendern() 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.

(Fortsetzung hier.)

Tagged: Tags

6 Thoughts to “Wie ich auszog, ein Spiel zu programmieren (2): Das MVC-Entwurfsmuster

  1. Sind die Skizzen zum MVC handgemalt und gescannt? Sehr ansprechender Stil, wäre schön eine Software zu haben, mit der man einfache Skizzen in der Form erstellen könnte.

  2. ZU MVC einfach mal ein paar Grundlagen von Ruby on Rails anschauen. Da geht alles nach MVC (macht aber nicht alles besser).

  3. Handgemalt und gescannt, genau. Mit Inkscape und einem Grafiktablett kann man fast so schön arbeiten – aber bei der Schrift wird es umständlich. Aber so ein gutes Skizzenprogramm fehlt mir auch noch, für iPad oder PC.

  4. Richtig krass, dass bei Dir die Schüler Dinge lernen, die im Informatik-Studium einige später auslassen. Ich habe das bei Holger Gast gelernt, der das Material dazu noch online hat: http://www-pu.informatik.uni-tuebingen.de/users/gast/ (Links unten zu „Software-Architektur I“, gibt’s mehrfach.)

    Design Patterns sind alles andere als einfach. Wenn man sie aber mal verstanden hat, dann versteht man auch Java viel besser. Viele Komponenten der Java API benutzen Design Patterns.

    Das ist Informatik-Unterricht, der den Namen auch verdient. Ich hatte in der Schule immer Bestnoten in Informatik, das es als Fach nur in 12 und 13 gab. Da wurde allerdings auch allen ernstes Microsoft Word unterrichtet als Teil des Speise… erm Lehrplans. Was daran „Informatik“ sein soll weiß ich bis heute noch nicht.

  5. Ich finde Design Patterns auch nicht einfach. Nähere mich nach und nach einzelnen davon, und wenn ich eines begriffen habe, freut mich das. Aber viele begreife ich noch nicht, und in der Schule kommt man mit Strategie, Adapter, MVC, Beobachter aus.

  6. Dein Beitrag ist zwar schon etwas älter, aber Google findet es doch! Daher nur eine kleine Richtigstellung.

    MVC ist ein Architekturmuster, kein Entwurfsmuster, wie z.B. Factory, Singleton, Observer, Proxy usw…

    Soll jetzt aber net blöd rüberkommen, finde deine Ideen und deinen Spaß prima! Ich bin halt nur darauf gestoßen, weil die Trennung von View und Controller nicht mehr wirklich modern ist und ich mit meinen Schülern prinzipiell MVC bzw. MVP mache und mal schauen wollte, ob es für dieses Dilemma schon Lösungen gibt.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.