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

      Listing 1: Vierergruppe ermitteln.

       internal struct HorizontalerVierer : IVierer

       {

       private readonly int x;

       private readonly int y;

       public HorizontalerVierer(int x, int y) {

       this.x = x;

       this.y = y;

       }

       public Koordinate Eins {

       get { return new Koordinate(x, y); }

       }

       public Koordinate Zwei {

       get { return new Koordinate(x + 1, y); }

       }

       public Koordinate Drei {

       get { return new Koordinate(x + 2, y); }

       }

       public Koordinate Vier {

       get { return new Koordinate(x + 3, y); }

       }

       public override string ToString() {

       return string.Format("Horizontal X: {0},

       Y: {1}", x, y);

       }

       }

      Zunächst fällt auf, dass die Klasse internal ist. Sie wird im Rahmen der Spiellogik nur intern benötigt, daher soll sie nicht außerhalb der Komponente sichtbar sein. Damit Unit-Tests für die Klasse möglich sind, habe ich auf der Assembly das Attribut InternalsVisibleTo gesetzt. Dadurch kann die Assembly, welche die Tests enthält, auf die internen Details zugreifen.

      Aufgabe der Klasse HorizontalerVierer ist es, vier Koordinaten zu horizontal nebeneinander liegenden Spielfeldern zu liefern. Dies erfolgt in den Properties Eins, Zwei, Drei und Vier. Dort werden jeweils die Indizes ermittelt.

      Das Ermitteln eines Gewinners geschieht anschließend in einem Flow aus zwei Schritten. Im ersten Schritt wird aus einem Spielfeld die Liste der möglichen Vierergruppen bestimmt. Im zweiten Schritt wird aus dem Spielfeld und den möglichen Vierergruppen ermittelt, ob eine der Vierergruppen Steine derselben Farbe enthält.

      Die beiden Schritte des Flows sind als Extension Methods realisiert. Dadurch sind sie leicht isoliert zu testen. Anschließend können sie hintereinander ausgeführt, also als Flow zusammengeschaltet werden:

      var gewinnerVierer = spielfeld

       .AlleVierer()

       .SelbeFarbe(spielfeld);

      Der Flow wird an zwei Stellen verwendet: zum einen beim Ermitteln des Gewinners, zum anderen, um zu bestimmen, welche Steine zum Sieg geführt haben. Da die Methode AlleVierer() ein IEnumerable liefert und SelbeFarbe() dies als ersten Parameter erwartet, können die beiden Extension Methods hintereinander geschrieben werden. Da das Spielfeld in beiden Methoden benötigt wird, verfügt SelbeFarbe() über zwei Parameter.

      Das Ermitteln von vier jeweils nebeneinander liegenden Feldern übernimmt die Methode AlleVierer(). Ein kurzer Ausschnitt zeigt die Arbeitsweise:

      internal static IEnumerable<IVierer>

       AlleVierer(this int[,] feld) {

       for (var x = 0; x <=

       feld.GetLength(0) - 4; x++) {

       for (var y = 0; y <

       feld.GetLength(1); y++) {

       yield return new

       HorizontalerVierer(x, y);

       }

       }

       // Ebenso für Vertikal und Diagonal

       }

      Auch diese Methode ist internal, da sie außerhalb der Komponente nicht benötigt wird. In zwei geschachtelten Schleifen werden die Anfangsindizes von horizontalen Vierergruppen ermittelt. Für jeden Anfangsindex wird mit yield return eine Instanz eines HorizontalerVierers geliefert. Dieser übernimmt das Ermitteln der drei anderen Indizes.

      Eine Alternative zur gezeigten Methode wäre, die möglichen Vierer als Konstanten zu hinterlegen. Es würde dann die Berechnung in AlleVierer() entfallen, ferner die Klassen HorizontalerVierer et cetera. Ob die Felder einer Vierergruppe alle mit Steinen der gleichen Farbe besetzt sind, analysiert die Methode SelbeFarbe(). Durch die Verwendung der Klassen HorizontalerVierer et cetera ist dies einfach: Jeder Vierer liefert seine vier Koordinaten. Damit muss nur noch im Spielfeld nachgesehen werden, ob sich an allen vier Koordinaten Steine gleicher Farbe befinden, siehe Listing 2.

      Listing 2: Prüfen auf gleiche Farben.

       internal static IEnumerable<IVierer> SelbeFarbe(this IEnumerable<IVierer> vierer,

       int[,] feld) {

       foreach (var vier in vierer) {

       if ((feld[vier.Eins.X, vier.Eins.Y] != 0) &&

       (feld[vier.Eins.X, vier.Eins.Y] == feld[vier.Zwei.X, vier.Zwei.Y]) &&

       (feld[vier.Eins.X, vier.Eins.Y] == feld[vier.Drei.X, vier.Drei.Y]) &&

       (feld[vier.Eins.X, vier.Eins.Y] == feld[vier.Vier.X, vier.Vier.Y])) {

       yield return vier; } } }

      Am Ende müssen die einzelnen Funktionseinheiten nur noch gemeinsam verwendet werden. Die dafür verantwortliche Klasse heißt VierGewinntSpiel. Sie ist public und repräsentiert nach außen die Komponente. Die Klasse ist für die Spielregeln zuständig. Da das abwechselnde Ziehen so einfach ist, habe ich mich entschlossen, diese Logik nicht auszulagern. In der Methode LegeSteinInSpalte(int spalte) wird der Zustand des Spiels aktualisiert. Dies geht ansatzweise wie folgt:

      if (Zustand == Zustaende.RotIstAmZug) {

       spielbrett.SpieleStein(Spieler.Rot,

       spalte);

       Zustand = Zustaende.GelbIstAmZug;

       }

       Es wird also ein entsprechender Spielstein

       gelegt und anschließend ermittelt,

       wer am Zug ist. Etwas später folgt dann die

       Auswertung eines möglichen Gewinners:

       if (spielbrett.Gewinner == Spieler.Rot){

       Zustand = Zustaende.RotHatGewonnen;

       }

      Die Ermittlung eines Gewinners erfolgt also im Spielbrett, während hier nur der Zustand des Spiels verwaltet wird.

      Fazit: Die richtigen Vorüberlegungen sind der Schlüssel zu einer erfolgreichen Implementierung. [ml]

      [1] Stefan Lieser, Wer übt, gewinnt, dotnetpro 3/2010, Seite 118 f., www.dotnetpro.de/A1003dojo

      AUFGABE

       INotifyPropertyChanged-Logik automatisiert testen

      Zauberwort

      DataBinding ist eine tolle Sache: Objekt an Formular binden und wie von Zauberhand stellen die Controls die Eigenschaftswerte des Objekts dar. DataBinding ist aber auch knifflig. Stefan, kannst du dazu eine Aufgabe stellen?

Wer übt, gewinnt
In jeder dotnetpro finden Sie eine Übungsaufgabe von Stefan Lieser, die in maximal drei Stunden zu lösen 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

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