Früchte meiner letzten Programmierarbeit

So. Mein generisches Spiel ist soweit fertig. Ich kann Spielfelder von beliebigen Dimensionen erstellen, Figuren und Hintergründe darauf verteilen und Regeln erstellen. Jetzt müsste ich mir ein konkretes Spiel ausdenken, das ich damit umsetzen will. Und dann schöne Chips für die Figuren und Hintergründe erstellen. Aber dazu habe ich jetz erst mal keine Lust – aber vielleicht wird ja mal in der Schule etwas daraus.

Hier eine klickbare Demo (allerdings nicht im Feedreader): Ein 5x5-Feld mit drei verschiedenen Figuren, die sich gegenseitig schlagen dürfen. Alle haben bestimmte Zugbegrenzungen, und auf die Mittelfelder darf sowieso keiner.

„Früchte meiner letzten Programmierarbeit“ weiterlesen

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 5x5-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.)

Wie ich auszog, ein Spiel zu programmieren (1)

Ich bin kein großer Programmierer, aber das Programmieren macht Spaß und ich brauchte einen Anlass zum Üben. Also wollte ich ein Spiel programmieren, etwa Schach oder Mensch ärgere dich nicht. Aber welches Mensch ärgere dich nicht? Es gibt so viele:

(von Startaq, CC-BY-SA)

(von Micha L. Rieser, CC-BY-SA)

(von Micha L. Rieser, CC-BY-SA)

(von Micha L. Rieser, CC-BY-SA)

Das war natürlich eine Fangfrage. Auch wenn diese Spiele verschieden ausschauen, man kann doch immer das gleiche Mensch ärgere dich nicht damit spielen. Und dieses immer gleiche Spiel ist es, das man programmieren will. Wie es aussieht, darum kümmern wir uns später. Programmiert wird erst einmal das unsichtbare, abstrakte Spiel, das den sichtbaren Formen zugrunde liegt.

(Für das Spielvergnügen ist die Oberfläche allerdings trotzdem wichtig, genau wie es bei Denksportaufgaben einen großen Unterschied macht, ob man sie in eine Krimihandlung packt oder anders verpuzzelt.)

Ich habe dann allerdings doch lieber ein anderes Spiel programmiert. Schach, mehr oder weniger. Das heißt, ich wollte mich da noch nicht festlegen – ich wollte ein Art framework, ein Programm, dass es mir erlauben würde, alle möglichen Spiele zu spielen, die man mit einem Schachbrett und Figuren dazu spielen kann. Schach, Dame, Go, Chinesisches Schach, oder auch erweiterte Versionen mit unterschiedlichem Gelände, wo die einzelnen Spielfelder unterschiedliche Funktionen haben. (Archon, anybody?)

Das abstrakte Grundgerüst:

  1. Es gibt ein Spielfeld, das aus einzelnen Feldern besteht. (Der Einfachkeit halber sind die Felder orthogonal, also schachbrettartig angelegt. Also erst mal keine hexagonalen Felder.)
  2. Das Spielbrett besteht in der Breite und Höhe aus beliebig vielen Feldern. Man kann also ein 8x8-Spiel machen für Schach, ein 10x10 für internationales Dame-Spiel, ein 6x100 für ein Pferderennspiel.
  3. Die Felder können verschiedene Typen haben. Zum Beispiel: schwarz oder weiß, aber auch Dschungel, Gebirge, Fluss.
  4. Es gibt Spielfiguren, die sich auf den Feldern bewegen können.
  5. Außerdem soll jede Spielfigur verschiedene Zustände annehmen können – gesund, ohnmächtig, verwundert, tot, wie das etwa bei Cry Havoc verwendet wurde.

(Erst später kam heraus, dass ich einige Sache übersehen hatte. Das ist aber immer so. Es sollte erstens auch noch Würfel geben, und vor allem hatte ich mir keine Gedanken darüber gemacht, ob mehr als eine Spielfigur auf einem Feld stehen darf oder nicht. Implizit gab es auch die Regel, dass eine Spielfigur nur auf höchstens einem Spielfeld stehen kann – also nicht etwa auf zwei nebeneinander liegenden, bei besonders großen Figuren.)

Zuerst erstellte ich mir für dieses Spiel ein Klassendiagramm:

(Die Klassen Feldtyp und Zustand sind dabei erst mal nebensächlich. Im Prinzip gebe ich in ihnen an einer Stelle zentral an, welche möglichen Werte es für diese beiden Eigenschaften im jeweiligen Spiel gibt.)

Damit hatte ich also das Spielmaterial modelliert. Jede Spielfigur kennt ihre Position auf dem Spielbrett, das Spielbrett verwaltet die Figuren und die Felder. Damit sich an dem Spiel überhaupt etwas verändern kann, braucht es noch Methoden, um die Eigenschaften der Modell-Elemente verändern zu können. (Das Modell soll ja insgesamt verschiedene Zustände annehmen können, mal mit vielen Figuren auf dem Brett, mal mit wenig, in Ausgangsposition oder im Endspiel.)

Gewünscht hatte ich mir am Anfang:

  • Eine Spielfigur muss man auf eine bestimmte Position auf dem Spielbrett verschieben oder auch ganz davon entfernen können
  • Man muss den Typ eines Feldes ändern können (schwarz, weiß, Dschungel, Fluss).
  • Man muss den Zustand einer Figur ändern können (gesund, ohnmächtig, verwundet).
  • Man muss die Position einer Figur ändern können (also auf welchen Koordinaten).

Das reicht eigentlich fürs Spielmaterial. Mein erster Entwurf für Model war fertig:

Damit man kann schon Spielfiguren erzeugen, platzieren und bewegen. Viel mehr Spielmaterial braucht man nicht.

Aber natürlich sieht man von alledem noch nichts. Das ist ja immer noch das abstrakte Spiel. Und Regeln fehlen ja auch noch. Gemach.

Da solche Aufgaben wie meine, also die mit dem Spiel, beim Programmieren sehr oft vorkommen, gibt es natürlich schon bewährte Ansätze, wie man diese Aufgaben löst. Dazu gehören sogenannte Entwurfsmuster (design patterns). Die haben coole Namen wie “Adapter”, “Fabrik”, “Fliegengewicht”, “Chain of Responsibility”, “Besucher” oder Fliegengewicht”. Kunststück, viele bekannten stammen aus einem Buch, deren Autoren als “Gang of Four” bezeichnet werden. “GoF” ist quasi Fachsprache beim Programmieren.

Und ein solches Entwurfsmuster, das explizit im Lehrplan für die 11. Klasse steht, heißt Model-View-Controller. Damit geht es dann im nächsten Teil weiter.