diff --git a/Documentation/Controller.PNG b/Documentation/Controller.PNG new file mode 100644 index 0000000..da921bf Binary files /dev/null and b/Documentation/Controller.PNG differ diff --git a/Documentation/GUI.PNG b/Documentation/GUI.PNG new file mode 100644 index 0000000..e42480c Binary files /dev/null and b/Documentation/GUI.PNG differ diff --git a/Documentation/Konsole.PNG b/Documentation/Konsole.PNG new file mode 100644 index 0000000..831c092 Binary files /dev/null and b/Documentation/Konsole.PNG differ diff --git a/Documentation/StepEight.PNG b/Documentation/StepEight.PNG new file mode 100644 index 0000000..3614e22 Binary files /dev/null and b/Documentation/StepEight.PNG differ diff --git a/Documentation/StepEighteen.PNG b/Documentation/StepEighteen.PNG new file mode 100644 index 0000000..b0563a5 Binary files /dev/null and b/Documentation/StepEighteen.PNG differ diff --git a/Documentation/StepEleven.PNG b/Documentation/StepEleven.PNG new file mode 100644 index 0000000..aa6397c Binary files /dev/null and b/Documentation/StepEleven.PNG differ diff --git a/Documentation/StepFifteen.png b/Documentation/StepFifteen.png new file mode 100644 index 0000000..b9392c4 Binary files /dev/null and b/Documentation/StepFifteen.png differ diff --git a/Documentation/StepFive.PNG b/Documentation/StepFive.PNG new file mode 100644 index 0000000..e748631 Binary files /dev/null and b/Documentation/StepFive.PNG differ diff --git a/Documentation/StepFour.png b/Documentation/StepFour.png new file mode 100644 index 0000000..670bbb1 Binary files /dev/null and b/Documentation/StepFour.png differ diff --git a/Documentation/StepFourteen.PNG b/Documentation/StepFourteen.PNG new file mode 100644 index 0000000..06f275a Binary files /dev/null and b/Documentation/StepFourteen.PNG differ diff --git a/Documentation/StepNine.PNG b/Documentation/StepNine.PNG new file mode 100644 index 0000000..678188c Binary files /dev/null and b/Documentation/StepNine.PNG differ diff --git a/Documentation/StepSeven.PNG b/Documentation/StepSeven.PNG new file mode 100644 index 0000000..d7405ff Binary files /dev/null and b/Documentation/StepSeven.PNG differ diff --git a/Documentation/StepSeventeen.PNG b/Documentation/StepSeventeen.PNG new file mode 100644 index 0000000..b707e89 Binary files /dev/null and b/Documentation/StepSeventeen.PNG differ diff --git a/Documentation/StepSix.PNG b/Documentation/StepSix.PNG new file mode 100644 index 0000000..e2bd4e4 Binary files /dev/null and b/Documentation/StepSix.PNG differ diff --git a/Documentation/StepSixteen.PNG b/Documentation/StepSixteen.PNG new file mode 100644 index 0000000..1e0b64f Binary files /dev/null and b/Documentation/StepSixteen.PNG differ diff --git a/Documentation/StepTen.PNG b/Documentation/StepTen.PNG new file mode 100644 index 0000000..5675e7f Binary files /dev/null and b/Documentation/StepTen.PNG differ diff --git a/Documentation/StepThirteen.PNG b/Documentation/StepThirteen.PNG new file mode 100644 index 0000000..cdbc87f Binary files /dev/null and b/Documentation/StepThirteen.PNG differ diff --git a/Documentation/StepThree.PNG b/Documentation/StepThree.PNG new file mode 100644 index 0000000..1307ece Binary files /dev/null and b/Documentation/StepThree.PNG differ diff --git a/Documentation/StepTwelve.PNG b/Documentation/StepTwelve.PNG new file mode 100644 index 0000000..d19bacf Binary files /dev/null and b/Documentation/StepTwelve.PNG differ diff --git a/Morris/AI/StupidAI.cs b/Morris/AI/StupidAI.cs index a80e430..1f0efde 100644 --- a/Morris/AI/StupidAI.cs +++ b/Morris/AI/StupidAI.cs @@ -47,6 +47,7 @@ namespace Morris .Where(point => state.Board[point].IsOccupiedBy(state.NextToMove.Opponent())) .All(point => GameState.Mills.Any(mill => mill.Contains(point) && mill.All(mp => state.Board[mp].IsOccupiedBy(state.NextToMove.Opponent())))); + // filter enthält, falls wir nicht alle gegnerische Steine schlagen dürfen, eine engere Auswahl, die für die Steine, die wir schlagen dürfen, wahr zurückgibt Func filter; if (allInMill) filter = _ => true; @@ -54,6 +55,7 @@ namespace Morris // Wenn es Steine gibt, die in keiner Mühle sind, müssen wir einen solchen Stein entfernen filter = point => GameState.Mills.All(mill => !mill.Contains(point) || mill.Any(mp => !state.Board[mp].IsOccupiedBy(state.NextToMove.Opponent()))); + // Erweitere unseren Zug mit dem Befehl einen zufälligen Stein zu entfernen, der dem Gegner gehört, dem Filter entspricht und gemäß der Methode scoreRemove maximal ist return move.WithRemove( Enumerable.Range(0, GameState.FIELD_SIZE) .Where(point => state.Board[point].IsOccupiedBy(state.NextToMove.Opponent()) && filter(point)) diff --git a/Morris/Control/Program.cs b/Morris/Control/Program.cs index 6849619..6533234 100644 --- a/Morris/Control/Program.cs +++ b/Morris/Control/Program.cs @@ -9,6 +9,9 @@ using System.Windows; namespace Morris { + /// + /// Enthält den Haupteinsprungspunkt für unser Programm + /// internal class Program { [STAThread] diff --git a/Morris/Control/SelectorNameAttribute.cs b/Morris/Control/SelectorNameAttribute.cs index ae87384..2968655 100644 --- a/Morris/Control/SelectorNameAttribute.cs +++ b/Morris/Control/SelectorNameAttribute.cs @@ -9,7 +9,9 @@ using System; namespace Morris { /// - /// Ein Attribut, welches angibt, wie die Klasse im Auswahldialog benannt werden soll + /// Ein Attribut, welches angibt, wie die Klasse im Auswahldialog benannt werden soll. + /// Man kann Anzeigen und KIs damit dekorieren, damit nicht der Klassenname (z.B. Morris.AI.NegamaxAI) + /// im Selektor steht, sondern etwas lesefreundlicheres wie Einfacher Negamax /// [AttributeUsage(AttributeTargets.Class, Inherited = true)] public sealed class SelectorNameAttribute : Attribute diff --git a/Morris/Control/SelectorType.cs b/Morris/Control/SelectorType.cs index abe6b22..d7466a0 100644 --- a/Morris/Control/SelectorType.cs +++ b/Morris/Control/SelectorType.cs @@ -11,6 +11,9 @@ namespace Morris { /// /// Hält einen Typen fest, der potentiell durch ein einen neuen Namen erhalten hat + /// Objekte dieses Typs können dann in eine ListBox oder ComboBox eingefügt werden. Diese rufen .ToString() auf, und + /// zeigen so den gewünschten Namen an. Wir erhalten dieses Objekt dann zurück von ListBox/ComboBox durch SelectedItem + /// und können dann über die Type-Eigenschaft erfahren, welche KI oder Anzeige wir instanziieren sollen. /// internal class SelectorType { diff --git a/Morris/Control/SingleInstanceAttribute.cs b/Morris/Control/SingleInstanceAttribute.cs index 548e7b5..4a567a4 100644 --- a/Morris/Control/SingleInstanceAttribute.cs +++ b/Morris/Control/SingleInstanceAttribute.cs @@ -10,7 +10,8 @@ namespace Morris { /// /// Signalisiert, dass der Controller nur eine Instanz dieser Klasse erstellen sollte, - /// wenn sie mehrfach angefordert ist (z.B. nur eine GUI, die Input nimmt und ausgibt) + /// wenn sie mehrfach angefordert ist (z.B. nur eine GUI, die Input nimmt und ausgibt, + /// ansonsten würden z.B. mehrere Fenster erstellt werden) /// [AttributeUsage(AttributeTargets.Class, Inherited = true)] public sealed class SingleInstanceAttribute : Attribute diff --git a/Morris/Core/Game.cs b/Morris/Core/Game.cs index 1fcb3d8..9592d66 100644 --- a/Morris/Core/Game.cs +++ b/Morris/Core/Game.cs @@ -70,12 +70,14 @@ namespace Morris } } + // Verzögerung, damit man einen Zug betrachten kann public int Delay { get; set; } + // Wird gesperrt, wenn moves bzw. movesReadOnly verändert wird private static object movesLock = new object(); public Game(IMoveProvider white, IMoveProvider black, int delay) diff --git a/Morris/Core/GameState.cs b/Morris/Core/GameState.cs index bf1b6ef..d36a015 100644 --- a/Morris/Core/GameState.cs +++ b/Morris/Core/GameState.cs @@ -75,6 +75,9 @@ namespace Morris } } + /// + /// Erstellt einen Spielzustand zu Beginn eines Mühlespiels + /// public GameState() { // Leeres Feld @@ -103,6 +106,9 @@ namespace Morris MovesSinceLastStoneCountChange = 0; } + /// + /// Erstellt eine modifizierbare Arbeitskopie eines Spielzustands + /// public GameState(IReadOnlyGameState other) { Board = other.Board.ToArray(); @@ -116,6 +122,9 @@ namespace Morris history = other.History.Select(elem => elem.ToArray()).ToList(); } + /// + /// Alle Vergangenen Zustände des Spielfelds + /// public IEnumerable> History { get @@ -124,6 +133,9 @@ namespace Morris } } + /// + /// Belegungsinformationen zum Spielfeld + /// ReadOnlyCollection IReadOnlyGameState.Board { get diff --git a/Morris/Core/Player.cs b/Morris/Core/Player.cs index 20d3e88..83d70ba 100644 --- a/Morris/Core/Player.cs +++ b/Morris/Core/Player.cs @@ -11,6 +11,7 @@ namespace Morris /// public enum Player : byte { + // Sinn dieser Belegung ist, dass der eine Spieler durch den Bitwise-Not-Operator (~) in den anderen überführbar ist White = 170, // (10101010) base 2 Black = 85 // (01010101) base 2 } diff --git a/Morris/UI/GameWindow.xaml.cs b/Morris/UI/GameWindow.xaml.cs index 8526ff4..166f7ad 100644 --- a/Morris/UI/GameWindow.xaml.cs +++ b/Morris/UI/GameWindow.xaml.cs @@ -239,7 +239,7 @@ namespace Morris private void ellipseMouseDown(object sender, MouseEventArgs e) { Ellipse ellipse = sender as Ellipse; - int index = Array.IndexOf(pieces, ellipse); + int index = Array.IndexOf(pieces, ellipse); // Welches Feld wurde angegklickt? if (index == -1) return; @@ -247,6 +247,7 @@ namespace Morris switch (mode) { case Mode.ExpectingClick: + // Wir wollten einen Klick haben if (!validSources[index]) break; @@ -255,13 +256,14 @@ namespace Morris break; case Mode.ExpectingDrag: + // Dieser Klick repräsentiert den Anfang eines "Ziehprozesses" if (!validSources[index]) break; - source = index; - mode = Mode.Dragging; - offset = e.GetPosition(ellipse); - Mouse.Capture(ellipse); + source = index; // Wo Drag&Drop begonnen hat + mode = Mode.Dragging; // Wir sind jetzt am ziehen + offset = e.GetPosition(ellipse); // Wo befindet sich der Mausweiger im Spielstein? + Mouse.Capture(ellipse); // Alle Mausevents werden jetzt an diesen Spielstein gesendet break; } } @@ -302,7 +304,7 @@ namespace Morris error = true; } - // Maus freigeben + // Maus freigeben (Mausevents werden jetzt wieder an das Element gesendet, das sich unter dem Mauszeiger befindet) Mouse.Capture(null); sync.Set(); // Zurück zum Spielthread } diff --git a/README.md b/README.md new file mode 100644 index 0000000..e58c8e9 --- /dev/null +++ b/README.md @@ -0,0 +1,292 @@ +# Morris + +Morris ist eine Implementation des klassischen Mühlespiels für die IT-Talents +Code Competition. Die Kerngedanken beim Design der Architektur der Software +waren Modularität und Erweiterbarkeit. Konkret heißt das, dass man bei Morris +nicht nur auf *einer* GUI gegen *eine* KI spielen kann, sondern vollkommen frei +und beliebig verschiedene GUIs, Methoden der Benutzereingaben und KIs an- und +abschalten kann. Die Kernsoftware stellt bereits einige dieser Module bereit, +allerdings ist es sehr einfach, selbst neue KIs und Benutzeroberflächen +hinzuzufügen. Dafür ist es nicht notwendig, den Quellcode von Morris selbst zu +modifizieren (tatsächlich ist es nicht einmal notwendig, den Quellcode zu +besitzen, die kompilierte ausführbare Datei ist ausreichend, um die Software zu +erweitern). Mehr dazu weiter unten. + + +## Inhalt dieser Readme-Datei + +1. Benutzung der Software + 1. Übersicht + 2. Regelwerk + 3. Der Controller + 4. Ein- und Ausgabesysteme + 5. Die verfügbaren KIs + 6. Malom2Morris + 1. Was ist das? + 2. Kompilation und Datenbankerstellung +2. Überblick über die Architektur der Software und den Quellcode + 1. Hinweise zum Lesen des Quellcodes + 2. Grobüberblick über die Architektur + 3. Verzeichnis nicht automatisch generierter Quellcodedateien +3. Selbst Erweiterungen Entwickeln + 1. Basics + 2. Attribute + + +## Benutzung der Software + +### Übersicht + +Beim Start der Software öffnet sich der sogenannte Controller. Hier kann +festgelegt werden, wer gegen wen spielt (dabei kann es sich sowohl um +menschliche Spieler als auch KIs handeln, das heißt es sind auch Runden +von Mensch gegen Mensch oder KI gegen KI möglich) und auf welchen Formen von +GUI dies angezeigt werden soll. Wenn das Spiel gestartet wird, öffnen sich dann +die ausgewählten GUIs, auf denen dann das Spiel gespielt bzw. angeschaut werden +kann. + +### Regelwerk + +Die vorliegende Implementation stützt sich auf die Mühle-Regeln nach [1] mit einer +Zusatzregel. Folgende Regeln kommen zum Einsatz: + +* Wenn in einem Zug mindestens eine Mühle geschlossen wird, wird immer exakt ein + Stein entfernt. In anderen Worten: Wenn zwei Mühlen geschlossen werden, darf + trotzdem nur ein Stein entfernt werden. Befinden sich alle gegnerischen Steine + in einer Mühle, darf ein beliebiger gegnerischer Stein entfernt werden. +* Das Spiel endet unentschieden, wenn eine Spielsituation einmal wiederholt wird +* Das Spiel endet außerdem unentschieden, wenn für 50 Züge kein Stein platziert + oder geschlagen wurde. +* Ansonsten verliert ein Spieler, wenn er nur noch zwei Steine hat oder keinen + gültigen Spielzug machen kann. + +### Der Controller + +![Eine Abbildung des Controllers](../blob/master/Documentation/Controller.PNG) + +Der Controller kontrollliert das aktuelle Spiel. Die beiden Dropdowns legen +fest, welche KI oder welcher Spieler die entprechende Partei im Spiel +kontrollieren. Durch die Liste "Anzeigen" wird festgelegt, auf welchen Formen +der GUI das Spiel angezeigt wird. Mehrfachauswahlen sind hier möglich. **Sowohl +die Anzeigen als auch die Spieler/KIs können geändert werden, während ein Spiel +im Gang ist.** + +Der +Slider "Verzögerung" legt fest, wie lange gewartet werden soll, bis nach einem +Zug der nächste Zug angefordert wird. Diese Funktion kann verwendet werden, um +Spiele zwischen zwei KIs in einem angenehmen Tempo anzuschauen. Ein Klick auf +"Neues Spiel" bricht ggf. das aktuelle Spiel ab und startet ein neues Spiel. + +Auf der rechten Seite des Controllers befindet sich eine Liste mit den bisherigen +Spielzügen. Mit einem Doppelklick auf einen Spielzug wird das Spiel bis zu +diesem Spielzug "zurückgespult". + +Mit dem Button "Assembly laden..." können Module geladen werden, die weitere +KIs oder GUIs enthalten. + +### Ein- und Ausgabesysteme + +Morris stellt von Haus aus zwei Möglichkeiten bereit, ein Spiel anzusehen und +zu Spielen: Ein Konsoleninterface und eine GUI. + +#### Konsole + +![Eine Abbildung der Konsole](../blob/master/Documentation/Konsole.PNG) + +Die Konsole ist in der Lage, das Spielfeld wie in der obigen Abbildung zu +zeichnen und Züge entgegenzunehmen. Züge müssen sich im selben Format wie im +Controller befinden (mit Ausnahme der Großschreibung), das heißt: + +* A1 bedeutet, dass ein neuer Stein auf A1 gelegt werden soll. +* A1-D1 bedeutet, dass der Stein auf A1 nach D1 geschoben werden soll. +* ,G1 an einen Zug anzuhängen bedeutet, den gegenerischen Stein auf G1 + zu entfernen + +Sollte ein eingegebener Zug nicht dem Format entsprechen oder in der aktuellen +Spielsituation ungültig sein, fragt die Konsole direkt nach einem neuen Zug. + +#### GUI + +![Eine Abbildung der GUI](../blob/master/Documentation/GUI.PNG) + +Die GUI sollte im Großen und Ganzen selbsterklärend sein. In der +Statuszeile oben steht, welche Form von Benutzereingabe gerade erwartet wird. +Spielsteine können wie erwartet durch Drag&Drop gezogen bzw. per Mausklick +gesetzt werden. + +### Die verfügbaren KIs + +Morris stellt ein paar KIs bereit: + +Name | Beschreibung +--- | --- +Zufalls-KI | Die dümmste aller KIs. Sie wählt einen zufälligen gültigen Spielzug aus. +Dumme KI | Eine sehr eindimensional denkende KI, die nicht vorausdenkt, sondern einfach nur den besten direkt folgenden Zug basierend auf einer schwachen Heuristik auswählt, der sehr anfällig für lokale Maxima ist. +Einfacher Negamax | Eine KI, die den [Negamax-Algorithmus](https://en.wikipedia.org/wiki/Negamax) in einfacher Form realisiert und vier Züge vorausdenkt, sich aber zum Teil ein paar Sekunden Zeit für einen Zug lässt. +Malom3: Heuristische KI\* | Eine recht fortgeschrittene KI basierend auf dem Minimax-Algorithmus mit Alpha-Beta-Pruning. +Malom3: Perfekter Spieler\* | Eine KI, die basierend auf einer im Voraus berechneten Datenbank stets den spieltheoretischen Wert der Spielsituation erreicht (d.h., wenn es bei perfekt spielendem Gegner möglich ist, in der Situation zu gewinnen, gewinnt sie auch). Es ist nicht möglich, gegen diese KI besser als unentschieden zu spielen. + +\*) Diese KIs sind im Modul Malom2Morris enthalten. Siehe hierzu den nächsten Abschnitt. + +### Malom2Morris + +#### Was ist das? + +Das klassische Mühlespiel wurde Mitte der 90er zum ersten Mal gelöst (im Sinne von, +es wurde bewiesen, dass zwei perfekte Spieler stets unentschieden spielen) [1]. +Was damals noch Supercomputer vorraussetzte, kann mittlerweile auf dem eigenen +Rechner nachgemacht werden. 2014 haben Wissenschaftler aus Ungarn eine Software namens Malom3 +veröffentlicht, die (unter anderem) einen perfekten Spieler für das herkömmliche +Mühlespiel implementiert [2]. Wäre es nicht cool, wenn diese perfekte KI auch in +dieser Plattform, Morris, verfügbar wäre? + +Zu diesem Zweck habe ich das Modul Malom2Morris entwickelt, welches sich in Malom +einklinkt und die beiden in Malom enthaltenen Computerspieler für Morris verfügbar macht. +Dies wird realisiert, indem Malom2Morris den Spielzustand von dem in Morris verwendeten +Format in das Malom3-Format übersetzt, den Malom3-Spieler dann mit dieser übersetzten +Spielinformation füttert und dann den berechneten Zug vom Malom3-Format wieder in +das Morris-Format übersetzt, sodass dieser dann von Morris verwendet werden kann. + +#### Kompilation und Datenbankerstellung + +**Anmerkung:** Malom3 stützt sich auf eine ca. 78 GB große Datenbank, in der sich +praktisch alle Mühlespielsituationen befinden. Da diese Datenbank +Online nicht verfügbar ist, muss sie auf dem eigenen Rechner berechnet werden. +Dies benötigt mindestens 16GB Arbeitsspeicher, logischerweise 78GB freien Festplattenspeicher +und ca. 24 Stunden Rechenzeit. Sehr geehrte Mitarbeiter von IT-Talents und der Gauselmann-AG, +ich kann verstehen, wenn Ihnen das für eine Einsendung zu viel Aufwand ist. Für +diesen Fall finden sie [hier]() ein Video, wie es aussieht, gegen diese Spieler zu +spielen. Ich kann aber auch wärmstens empfehlen, sich die Mühe zu machen und selbst +gegen diese KI zu spielen, da der vorausschauende Spielstil des perfekten Spielers +sehr faszinierend ist und gleichzeitig beeindruckend, wie man vollständig in +Grund und Boden gespielt wird. + +Um Malom3/Malom2Morris zu kompilieren wird Visual Studio 2015 benötigt. Die +kostenlose Community Edition kann [hier](https://beta.visualstudio.com/vs/community/) +heruntergeladen werden. + +1. Der Quellcode von Malom3 und Malom2Morris kann [hier](https://github.com/TwoFX/Malom2Morris/releases/tag/v1.0) +als Zip-Datei heruntergeladen werden. Die Zip-Datei sollte dann entpackt werden. +2. Als nächstes sollte die Datei `Malom_megoldas.sln` im Ordner `Malom_megoldas` +mit Visual Studio geöffnet werden. +3. Wählen Sie in den drei Dropdowns in der Toolbar die Einträge `Release`, `Any CPU` und `Controller`. + +![Auswahl in der Toolbar](../blob/master/Documentation/StepThree.PNG) + +4. Klicken Sie rechts im Solution Explorer mit der rechten Maustaste auf `Controller` +und wählen Sie `Properties`. + +![Properties](../blob/master/Documentation/StepFour.png) + +5. Wählen Sie in dem sich öffnenden Dialog den Reiter `Debug` und fügen Sie bei `Working directory` +den Pfad zu dem Ordner `working` ein, der mit der Zip-Datei entpackt wurde. + +![Working directory](../blob/master/Documentation/StepFive.PNG) + +6. Öffnen Sie durch den Solution Explorer die Datei `common.h` wie abgebildet. + +![common.h](../blob/master/Documentation/StepSix.PNG) + +7. Navigieren Sie in der Datei zu Zeile 145. Ersetzen Sie den Dateipfad dort zu +dem Ordner `dataaux`, den Sie mit der Zip-Datei entpackt haben. + +![Pfad ersetzen](../blob/master/Documentation/StepSeven.PNG) + +8. Machen Sie den Eintrag `Morris` an der abgebildeten Stelle im Solution Explorer +ausfindig. Entfernen Sie ihn mit der Entfernen-Taste. + +![Referenz](../blob/master/Documentation/StepEight.PNG) + +9. Rechtsklicken Sie auf den Eintrag `References` und wählen Sie `Add Reference...` + +![Add](../blob/master/Documentation/StepNine.PNG) + +10. Klicken Sie in dem Dialog unten auf Browse, navigieren Sie zu Morris.exe, die +Sie entweder heruntergeladen oder kompiliert haben und bestätigen Sie die Auswahl. +Schließen Sie den Dialog dann mit `OK`. + +![Confirm](../blob/master/Documentation/StepTen.PNG) + +11. Wählen Sie im Menü `Build` nun `Build Solution`. + +![Build](../blob/master/Documentation/StepEleven.PNG) + +12. Warten Sie ca. 30 Sekunden, bis am unteren Rand des Fensters `Rebuild All succeeded` erscheint. + +![Rebuild All succeeded](../blob/master/Documentation/StepTwelve.PNG) + +13. Klicken Sie in der Toolbar nun auf `Start`. + +![Start](../blob/master/Documentation/StepThirteea.PNG) + +14. Das Fenster zur Berechnung der Datenbank öffnet sich. Setzen Sie einen Haken bei `Automatic`. + +![Auto](../blob/master/Documentation/StepFourteen.PNG) + +15. Warten Sie ca. 24 Stunden, bis beide Fortschrittsbalken dreimal durchgelaufen sind. +Sobald die Berechnung abgeschlossen ist, öffnet sich ein Dialog mit dem Inhalt `Everything Done`. + +![Fertig](../blob/master/Documentation/StepFifteen.PNG) + +16. Kopieren Sie nun die 9 Dateien, die sich im Ordner `Malom2Morris-1.0\Malom_megoldas\Malom2Morris\bin\Release`, +befinden, in den Ordner `Malom2Morris-1.0\working`. + +![Kopiert](../blob/master/Documentation/StepSixteen.PNG) + +17. Öffnen Sie nun `Morris.exe` und importieren Sie durch Klick auf `Assembly laden...` `Malom2Morris.dll`. + +![Laden](../blob/master/Documentation/StepSeventeen.PNG) + +18. Fertig! Sie können nun gegen den perfekten Spieler verlieren. + +![Verloren](../blob/master/Documentation/StepEighteen.PNG) + +## Überblick über die Architektur der Software und den Quellcode + +### Hinweise zum Lesen des Quellcodes + +Die Software ist in C# 6 verfasst. Ob der längeren Liste der erlaubten Sprachen +gehe ich davon aus, dass nicht jeder Leser dieses Quellcodes zu einhundert Prozent +mit dieser Sprache vertraut ist. Da der Code davon extrem starken Gebrauch macht, +empfehle ich dem geneigten Lesen, sich vor der Lektüre des Codes, falls notwendig, +mit Lamda-Ausdrücken und LINQ (wieder) vertraut zu machen. + +### Grobüberblick über die Architektur + +Wie eingangs erwähnt lag der Kernfokus beim Design der Applikation auf Modularität. +Die Software besteht aus mehreren relativ autonomen Teilen: Zentral ist die Kernlogik +(Codedateien im Ordner `Core`). Hier befindet sich die Logik des Mühlespiels in Klassen +wie `Game`, `GameState`, etc., in denen das Spiel selbst ausgetragen wird. Die Kernlogik +kommuniziert über zwei Punkte mit dem Rest des Projekts: Auf der einen Seite über die Klasse +`Game`, weil mit dieser ein tatsächliches Mühle-Spiel erstellt und verwaltet werden kann; +auf der anderen Seite über die beiden Interfaces `IMoveProvider` und `IGameStateObserver`, +die auf austauschbare Weise einen Spieler bzw. eine Anzeige kapseln. Klassen, die damit +zutun haben, das Spiel zu kontrollieren, befinden sich im Ordner `Control`. Klassen, die +Spieler oder Anzeigen bereitstellen, befinden sich in den Ordner `UI` und `AI`. + +Jede Klasse besitzt im Quellcode einen ``-Block, der erklärt, was die Funktion der Klasse +ist. Weiter möchte ich in dieser Readme-Datei über den Code auch gar nicht verlieren, +da ich den Versuch unternommen habe, den gesamten Quellcode leserlich zu gestalten +und zu kommentieren (wobei ich bitte, meine teilweise etwas ausschweifenden Kommentare +zu entschuldigen). + +### Zum Quellcode vom Malom2Morris + +Der Großteil des Codes im Malom2Morris-Repository wurde nicht von mir verfasst, +sondern ist Teil von Malom. Die einzige vollständig von mir verfasste Datei ist +die Brücke selbst, welche sich in der Datei [`BridgedPlayer.cs`](https://github.com/TwoFX/Malom2Morris/blob/v1.0/Malom_megoldas/Malom2Morris/BridgedPlayer.cs) +befindet. Weiterhin habe ich am Malom3-Quellcode diverse Änderungen (Hacks) unternommen, +die notwendig waren, weil a) die KIs in Malom3 sehr eng gekoppelt mit der +Benutzeroberfläche sind und Änderungen notwendig waren, um überhaupt an die Züge +heranzukommen und b) als indirekte Konsequenz aus Grund A eine anderes Threading-Konzept +als Morris hat, sodass weitere Änderungen notwendig waren, damit der Zug dann auf +dem richtigen Thread verfügbar war. Diese Änderungen sind nicht sonderlich interessant, +falls aber auch irgendeinem Grund dennoch Interesse daran besteht, können die Änderungen +[hier](https://github.com/TwoFX/Malom2Morris/compare/04922f...v1.0) eingesehen werden +(dafür ist das Projekt ja Open Source). + +## Literatur + +1. Gasser, Ralph. "Solving nine men’s morris." Games of no chance, MSRI Publications 29 (1998): 101-113. + Online verfügbar unter [http://library.msri.org/books/Book29/files/gasser.pdf](http://library.msri.org/books/Book29/files/gasser.pdf). +2. http://compalg.inf.elte.hu/~ggevay/mills/ (zuletzt abgerufen am 31. August 2016).