| 

.NET C# Java Javascript Exception

7
Nachdem hier schon wieder einige Zeit nichts veröffentlicht wurde, kommt nun wieder ein umfangreicherer Beitrag. Ich verstehe auch nicht wirklich woher andere Blog Schreiber die Zeit nehmen. Ich habe jedenfalls immer einiges zu tun, was mir wichtiger erscheint. In der letzten Zeit habe ich mich eingehender mit Event based components beschäftigt, und ich fange an […]

Nachdem hier schon wieder einige Zeit nichts veröffentlicht wurde, kommt nun wieder ein umfangreicherer Beitrag. Ich verstehe auch nicht wirklich woher andere Blog Schreiber die Zeit nehmen. Ich habe jedenfalls immer einiges zu tun, was mir wichtiger erscheint. In der letzten Zeit habe ich mich eingehender mit Event based components beschäftigt, und ich fange an sie sehr zu mögen. Inzwischen verwende ich diese Art der Implementierung in allen Projekten. Was meine Vorliebe zu MEF angeht, sie ist noch da aber ich verwende sie nur sehr sparsam in Verbindung mit EBC. Kommen wir zur Tic Tac Toe Implementierung.

Ich habe einige Implementierungen im Internet gefunden und auch frühere Versionen eigener Implementierungen angesehen. Ich bin mir sicher es geht besser, und ich versuche das heute mit EBC. Nach einigen Projekten erscheint es mir der folgende Ablauf sinnvoll :

  1. Konzept erstellen, und wenn es nur ein paar Skizzen auf Papier sind
  2. Implementierung der Datenobjekte
  3. Implementierung der Komponentenschnittstellen
  4. Implementierung der Tests gegen die Komponenten
  5. Ausbau der Komponenten und Verdrahtung

Kommen wir zum Konzept, eine einfache Skizze reicht. Etwas Erfahrung benötigt man trotzdem, sonst landen einige Entwürfe im Papierkorb. Ich erspare mir dadurch einiges an Ärger, die Zeit lohnt sich also. Leider habe ich noch kein gutes Tool gefunden mit dem die Visualisierung einfach und schnell möglich ist.

Die Player sind vorerst nicht so wichtig, die bekommen für die Ermittlung einen Zuges den aktuellen Status und geben dafür ein Feld zurück das gesetzt werden soll. Den Spieler auszuwählen übernimmt ein Sequencer anhand des Status. Ich fange hier aber mit dem Game, der eigentlichen Logik an. Zunächst benötige ich jedoch meine Datenklassen. Einige wurden bereits genannt, wir haben nun Board, Field, Figure, Mode und State.

public class State
{
 /// <summary>
 /// Initialisieren
 /// </summary>
 public State()
 {
 Board = new Board();
 CurrentMode = Mode.Running;
 }

 /// <summary>
 /// Spielfeld, Aktueller Zustand
 /// </summary>
 public Board Board { get; private set; }

 /// <summary>
 /// Aktueller Spielstatus
 /// </summary>
 public Mode CurrentMode { get; internal set; }

 /// <summary>
 /// Aktueller Spieler
 /// </summary>
 public Figure? CurrentFigure
 {
 get
 {
 if (CurrentMode != Mode.Running)
 return null;

 return Board.Moves % 2 == 0
 ? Figure.Cross
 : Figure.Circle;
 }
 }

 /// <summary>
 /// Gewinner des Spiels
 /// </summary>
 public Figure? Winner
 {
 get
 {
 if (CurrentMode != Mode.Winning)
 return null;

 return Board.Moves % 2 == 0
 ? Figure.Circle
 : Figure.Cross; 
 }
 }

}

Der Spielstatus enthält alle Informationen um einen Spieler die Berechnung eines Zuges und einem UI die Anzeige zu ermöglichen. Hier ist auch etwas Logik enthalten, jedoch datenbezogen. Der aktuelle Spieler oder Gewinner wird aus der Zuganzahl und dem Mode ermittelt

public enum Mode
{
 Running,
 Winning,
 Ended
}

Der Mode ist wohl selbserklärend, ebenso wie die Figure Klasse. Figure wird als Nullable verwendet, auf ein None wurde hier bewusst verzichtet.

public enum Figure
{
 Cross,
 Circle
}

Die Felder des Board werden auf den Nummernblock für manuelle Spieler abgebildet.

public enum Field
{
 Num7,
 Num8,
 Num9,
 Num4,
 Num5,
 Num6,
 Num1,
 Num2,
 Num3
}

Für das Board verwende ich vorerst ein Dictionary. Ein Array hätte es mit angepasster Logik auch getan. Ich hatte eine Lösung gefunden in welcher ein String verwendet wurde um darauf Regular Expressions anzuwenden um einen Gewinner zu ermitteln; sehr interessant. Die Auskommentierung verhindert das Überschreiben eines bereits belegten Feldes.

public class Board 
{

 private Dictionary<Field, Figure> data = new Dictionary<Field, Figure>();

 public Figure? this[Field field]
 {
 get
 {
 if (data.ContainsKey(field))
 return data[field];
 return null;
 }
 internal set
 {
 if (!value.HasValue)
 return;

 if (!data.ContainsKey(field))
 data.Add(field, value.Value); 
 //else
 // data[field] = value.Value; 
 }
 }

 public int Moves
 {
 get
 {
 return data.Count;
 }
 }

}

Damit können wir mit Punkt 3. weitermachen, den ich hier mit Punkt 5. verbinde. Mit welcher Klassen anfangen? Das ist eigentlich egal, weil zunächst nur die Schnittstellen definiert werden. Was passiert wenn der Spieler ein Feld setzt? Wir müssen prüfen ob der Zug ausgeführt werden kann und müssen diesen auch ausführen, wenn möglich. Hier haben wir wenig zu tun, das Board nimmt uns die Arbeit bereits ab, es setzt nur mögliche Züge. Und der aktuelle Spieler für den nächsten Zug wird aus der Zuganzahl bestimmt, also wäre der Spieler bei einem falschen Zug nochmals an der Reihe.

class Moving
{
 public void MoveStarted(State state, Field field)
 {
 state.Board[field] = state.CurrentFigure;
 MoveEnded(state);
 }

 public event Action<State> MoveEnded = delegate { };
}

Die Winning Klasse übernimmt die Erkennung ob das Spiel gewonnen wurde oder unentschieden ausgegangen ist. Das Spiel ist als unentschieden definiert, wenn alle Felder belegt wurden und keine Gewinnposition ermittelt wurde. Der Statuswechsel für Mode ist hier definiert.

public class Winning
{
 public void Detect(State state)
 {
 if (state.Board.Moves == 9)
 state.CurrentMode = Mode.Ended;

 if (IsWinning(state.Board))
 state.CurrentMode = Mode.Winning;
 
 Changed(state);
 }

 public event Action<State> Changed = delegate { };

 private bool IsWinning(Board board)
 {
 return SameFigure(board, Field.Num1, Field.Num2, Field.Num3)
 || SameFigure(board, Field.Num4, Field.Num5, Field.Num6)
 || SameFigure(board, Field.Num7, Field.Num8, Field.Num9)
 || SameFigure(board, Field.Num1, Field.Num4, Field.Num7)
 || SameFigure(board, Field.Num2, Field.Num5, Field.Num8)
 || SameFigure(board, Field.Num3, Field.Num6, Field.Num9)
 || SameFigure(board, Field.Num1, Field.Num5, Field.Num9)
 || SameFigure(board, Field.Num7, Field.Num5, Field.Num3);
 } 

 private bool SameFigure(Board board, Field f1, Field f2, Field f3)
 {
 var fig1 = board[f1];
 var fig2 = board[f2];
 var fig3 = board[f3];
 return fig1.HasValue && fig2.HasValue && fig3.HasValue
 && fig1 == fig2 && fig2 == fig3;
 }

}

Die Gewinnermittlung ist wie das Board sicher eleganter zu lösen, hier geht es aber vorrangig um EBC, wovon wir bisher nur wenig gesehen haben. Die Verdrahtung und Verwendung der bisher gezeigten Klassen erfolgt in der Klasse Game. Aber auch hier ist nicht viel zu tun.

public class Game
 {

 private Join<State, Field> join;
 private Winning winning;
 private Moving move;

 public Game ()
 { 
 join = new Join<State, Field>();
 winning = new Winning();
 move = new Moving();

 join.OnCompleted += move.MoveStarted;
 move.MoveEnded += winning.Detect;
 winning.Changed += Changed;
 }

 private void Changed(State state)
 { 
 join.InputA(state);
 StateChanged(state);
 }

 /// <summary>
 /// Neues Spiel beginnen
 /// </summary>
 public void Start()
 {
 Changed(new State()); 
 }

 /// <summary>
 /// Spielzug ausführen
 /// </summary>
 public void Move(Field field)
 {
 join.InputB(field);
 }

 /// <summary>
 /// Spielstatus, Aufforderung für Spielzug
 /// </summary>
 public event Action<State> StateChanged = delegate { };
 
 }

Es erfolgt die Verdrahtung nach dem zuvor gezeigten Konzept. Hier wird eine Vereinfachung, der aus dem Tooling oder anderen Quellen bekannten Join Klasse verwendet um auf den Zug eines Spielers zu warten. Zunächst sollte Start aufgerufen werden um das Spiel zu beginnen, dabei wird ein neuer Status erzeugt und bereitgestellt. Jetzt wartet Join auf den Zug eines Spielers der mit Move vorgenommen werden kann. Mit dem Zug wird die Moving und Winning Komponenten durchlaufen. Der geänderte Spielstatus wird wie beim Start weiter verwendet. Das war es auch schon… der Sequencer vielleicht noch

        [TestClass]
        public class GameTests
        {
        
         [TestMethod]
         public void Spieler_X_hat_den_ersten_Zug()
         {
         var target = new State();
         var game = new Game();
         game.StateChanged += t => target = t;
         game.Start();
        
         Assert.AreEqual(Mode.Running, target.CurrentMode);
         Assert.AreEqual(Figure.Cross, target.CurrentFigure);
         }
        
         [TestMethod]
         public void Spieler_ziehen_abwechselnd()
         {
         var target = new State();
         var game = new Game();
         game.StateChanged += t => target = t;
         game.Start();
         game.Move(Field.Num5);
        
         Assert.AreEqual(Figure.Circle, target.CurrentFigure);
         }
        
         [TestMethod]
         public void Spieler_ist_bei_ungueltigem_Zug_nochmal_dran()
         {
         var target = new State();
         var game = new Game();
         game.StateChanged += t => target = t;
         game.Start();
         game.Move(Field.Num5);
         game.Move(Field.Num5);
        
         Assert.AreEqual(Figure.Circle, target.CurrentFigure);
         }
        
         [TestMethod]
         public void Ein_Spieler_kann_gewinnen()
         {
         // X X X
         // O O
         //
         var target = new State();
         var game = new Game();
         game.StateChanged += t => target = t;
         game.Start();
         game.Move(Field.Num7);
         game.Move(Field.Num4);
         game.Move(Field.Num8);
         game.Move(Field.Num5);
         game.Move(Field.Num9);
        
         Assert.AreEqual(Mode.Winning, target.CurrentMode);
         Assert.AreEqual(null, target.CurrentFigure);
         Assert.AreEqual(Figure.Cross, target.Winner);
         }
        
         [TestMethod]
         public void Spiel_kann_unentschieden_ausgehen()
         {
         // X O X
         // X O X
         // O X O
         var target = new State();
         var game = new Game();
         game.StateChanged += t => target = t;
         game.Start();
        
         game.Move(Field.Num7);
         game.Move(Field.Num8);
         game.Move(Field.Num9);
         game.Move(Field.Num5);
         game.Move(Field.Num4);
         game.Move(Field.Num1);
         game.Move(Field.Num2);
         game.Move(Field.Num6);
         game.Move(Field.Num3);
        
         Assert.AreEqual(Mode.Ended, target.CurrentMode);
         Assert.AreEqual(null, target.CurrentFigure);
         Assert.AreEqual(null, target.Winner);
        
         }
        }

        In einem nächsten Posting werde ich mit um einen Simulator kümmern (Tic Tac Toe Arena) in welchem verschiedenen Computerspieler gegeneinander antreten können. Auch einige Optimierungen und ein UI wäre nicht schlecht, mal sehen was meine Zeit zulässt.


        event-based-components flow tictactoe code-kata
        Schreibe einen Kommentar:
        Themen:
        code-kata tictactoe flow event-based-components
        Entweder einloggen... ...oder ohne Wartezeit registrieren
        Benutzername
        Passwort
        Passwort wiederholen
        E-Mail