Скачать книгу

sein sollte.Wer die Zeit investiert, gewinnt in jedem Fall – wenn auch keine materiellen Dinge, so doch Erfahrung und Wissen. Es gilt :Falsche Lösungen gibt es nicht. Es gibt möglicherweise elegantere, kürzere oder schnellere Lösungen, aber keine falschen.Wichtig ist, dass Sie reflektieren, was Sie gemacht haben. Das können Sie, indem Sie Ihre Lösung mit der vergleichen, die Sie eine Ausgabe später in der dotnetpro finden.Übung macht den Meister. Also − los geht’s. Aber Sie wollten doch nicht etwa sofort Visual Studio starten...

      Klar, können wir machen. Wie wäre es beispielsweise mit dem Spiel 4 gewinnt? Bei dieser Aufgabe geht es vor allem um eine geeignete Architektur und die Implementierung der Logik und nicht so sehr um eine schicke Benutzeroberfläche.

      4 gewinnt wird mit einem aufrecht stehenden Spielfeld von sieben Spalten gespielt. In jede Spalte können von oben maximal sechs Spielsteine geworfen werden. Ein Spielstein fällt nach unten, bis er entweder auf den Boden trifft, wenn es der erste Stein in der Spalte ist, oder auf den schon in der Spalte liegenden Steinen zu liegen kommt. Die beiden Spieler legen ihre gelben beziehungsweise roten Spielsteine abwechselnd in das Spielfeld. Gewonnen hat der Spieler, der zuerst vier Steine direkt übereinander, nebeneinander oder diagonal im Spielfeld platzierenkonnte.

      Implementieren Sie ein Spiel...

      Ein Spiel, das zwei Spieler gegeneinander spielen. Die Implementierung soll die Spielregeln überwachen. So soll angezeigt werden, welcher Spieler am Zug ist (Rot oder Gelb). Ferner soll angezeigt werden, ob ein Spieler gewonnen hat. Diese Auswertung erfolgt nach jedem Zug, sodass nach jedem Zug angezeigt wird, entweder welcher Spieler an der Reihe ist oder wer gewonnen hat. Hat ein Spieler gewonnen, ist das Spiel zu Ende und kann neu gestartet werden.

      Damit es unter den Spielern keinen Streit gibt, werden die Steine, die zum Gewinn führten, ermittelt. Bei einer grafischen Benutzeroberfläche könnten die vier Steine dazu farblich markiert oder eingerahmt werden. Bei einer Konsolenoberfläche können die Koordinaten der Steine ausgegeben werden.

      Die Bedienung der Anwendung erfolgt so, dass der Spieler, der am Zug ist, die Spalte angibt, in die er einen Stein werfen will. Dazu sind die Spalten von eins bis sieben nummeriert. Bei einer grafischen Benutzeroberfläche können die Spalten je durch einen Button gewählt werden. Wird das Spiel als Konsolenanwendung implementiert, genügt die Eingabe der jeweiligen Spaltennummer per Tastatur.

      Die Abbildungen 1 und 2 zeigen, wie eine Oberfläche aussehen könnte. Ist die Spalte, in die der Spieler seinen Stein legen möchte, bereits ganz mit Steinen gefüllt, erfolgt eine Fehlermeldung, und der Spieler muss erneut einen Spielstein platzieren.

      

      [Abb. 1 und 2] Eine mögliche Oberfläche (links) und die Anzeige der siegreichen vier Steine (rechts). Aber auf die Oberfläche kommt es bei dieser Übung nicht an.

      Programmieraufgabe

      Die Programmieraufgabe lautet, ein Spiel 4 gewinnt zu implementieren. Dabei liegt der Schwerpunkt auf dem Entwurf einer angemessenen Architektur, der Implementierung der Spiellogik und zugehörigen automatisierten Tests.

      Die Benutzerschnittstelle des Spiels steht eher im Hintergrund. Ob animierte WPF-Oberfläche, WinForms, ASP.NET oder Konsolenanwendung, das ist nicht wichtig. ImVor-dergrund soll eine Lösung stehen, die leicht in eine beliebige Oberflächentechnologie integriert werden kann. Evolvierbarkeit und Korrektheit sollen hier also stärker bewertet werden als eine superschicke Oberfläche.

      Im nächsten Heft zeigen wir eine exemplarische Musterlösung. „Die" Lösungkann es in einem solchen Fall bekanntlich eh nicht geben. Damit möchte ich Sie, lieber Leser, noch mal ermutigen, sich der Aufgabe anzunehmen. Investieren Sie etwas Zeit, und erarbeiten Sie eine eigene Lösung. Die können Sie dann später mit der hier vorgestellten vergleichen. Viel Spaß!

LÖSUNG Eine Übung, bei der Sie nur gewinnen konnten

      Vier gewinnt. Eine Lösung

      Die Aufgabe war, das Spiel „Vier gewinnt" zu implementieren. Auf den ersten Blick ist das eine eher leichte Übung. Erst bei genauerem Hinsehen erkennt man die Schwierigkeiten. Wie zerlegt man beispielsweise die Aufgabenstellung, um überschaubare Codeeinheiten zu erhalten?

      Leser, die sich der Aufgabe angenommen haben, ein Vier-gewinnt-Spiel zu implementieren [1], werden es gemerkt haben: Der Teufel steckt im Detail. Der Umgang mit dem Spielfeld, das Erkennen von Vierergruppen, wo soll man nur anfangen? Wer zu früh gezuckt hat und sofort mit der Codeeingabe begonnen hat, wird es vielleicht gemerkt haben: Die Aufgabe läuft aus dem Ruder, wächst einem über den Kopf.

      Das ging mir nicht anders. Früher. Heute setze ich mich erst mit einem Blatt Papier hin, bevor ich beginne, Code zu schreiben. Denn die erste Herausforderung besteht nicht darin, das Problem zu lösen, sondern es zu verstehen.

      Beim Vier-gewinnt-Spiel war eine Anforderung bewusst ausgeklammert: die Benutzerschnittstelle. In der Aufgabe geht es um die Logik des Spiels. Am Ende soll demnach eine Assembly entstehen, in der die Spiellogik enthalten ist. Diese kann dann in einer beliebigen Benutzerschnittstelle verwendet werden.

      Beim Spiel selbst hilft es, sich die Regeln vor Augen zu führen. Zwei Spieler legen abwechselnd gelbe und rote Spielsteine in ein 7 x 6 Felder großes Spielfeld. Derjenige, der als Erster vier Steine seiner Farbe nebeneinander liegen hat, hat das Spiel gewonnen. Hier hilft es, sich mögliche Vierergruppen aufzumalen, um zu erkennen, welche Konstellationen im Spielfeld auftreten können.

      Nachdem ich das Problem durchdrungen habe, zeichnet sich eine algorithmische Lösung ab. Erst jetzt beginne ich, die gesamte Aufgabenstellung in Funktionseinheiten zu zerlegen. Ich lasse zu diesem Zeitpunkt ganz bewusst offen, ob eine Funktionseinheit am Ende eine Methode, Klasse oder Komponente ist. Wichtig ist erst einmal, dass jede Funktionseinheit eine klar definierte Aufgabe hat.

      Hat sie mehr als eine Aufgabe, zerlege ich sie in mehrere Funktionseinheiten. Stellt man sich die Funktionseinheiten als Baum vor, in dem die Abhängigkeiten die verschiedenen Einheiten verbinden, dann steht auf oberster Ebene das gesamte Spiel. Es zerfällt in weitere Funktionseinheiten, die eine Ebene tiefer angesiedelt sind. Diese können wiederum zerlegt werden. Bei der Zerlegung können zwei unterschiedliche Fälle betrachtet werden:

       vertikale Zerlegung,

       horizontale Zerlegung.

      Der Wurzelknoten des Baums ist das gesamte Spiel. Diese Funktionseinheit ist jedoch zu komplex, um sie „in einem Rutsch" zu implementieren. Also wird sie zerlegt. Durch die Zerlegung entsteht eine weitere Ebene im Baum. Dieses Vorgehen bezeichne ich daher als vertikale Zerlegung.

      Kümmert sich eine Funktionseinheit um mehr als eine Sache, wird sie horizontal zerlegt. Wäre es beispielsweise möglich, einen Spielzustand in eine Datei zu speichern, könnte das Speichern im ersten Schritt in der Funktionseinheit Spiellogik angesiedelt sein. Dann stellt man jedoch fest, dass diese Funktionseinheit für mehr als eine Verantwortlichkeit zuständig wäre, und zieht das Speichern heraus in eine eigene Funktionseinheit. Dies bezeichne ich als horizontale Zerlegung.

      Erst wenn die Funktionseinheiten hinreichend klein sind, kann ich mir Gedanken darum machen, wie ich sie implementiere. Im Falle des Vier-gewinnt-Spiels zerfällt das Problem in die eigentliche Spiellogik und die Benutzerschnittstelle. Die Benutzerschnittstelle muss in diesem Fall nicht weiter zerlegt werden. Das mag in komplexen Anwendungen auch mal anders sein. Diese erste Zerlegung der Gesamtaufgabe zeigt Abbildung 1.

Скачать книгу