Add controller, external assembly loading and other fancy things

This commit is contained in:
Markus Himmel
2016-08-28 23:35:28 +02:00
parent c3abbf6966
commit ee4db90b0d
21 changed files with 560 additions and 20 deletions

View File

@@ -0,0 +1,41 @@
namespace ExternalBotTest.AssemblyInfo
open System.Reflection
open System.Runtime.CompilerServices
open System.Runtime.InteropServices
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[<assembly: AssemblyTitle("ExternalBotTest")>]
[<assembly: AssemblyDescription("")>]
[<assembly: AssemblyConfiguration("")>]
[<assembly: AssemblyCompany("")>]
[<assembly: AssemblyProduct("ExternalBotTest")>]
[<assembly: AssemblyCopyright("Copyright © 2016")>]
[<assembly: AssemblyTrademark("")>]
[<assembly: AssemblyCulture("")>]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[<assembly: ComVisible(false)>]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[<assembly: Guid("712da2c7-1b88-4ddb-8b79-46af5d5f6b19")>]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [<assembly: AssemblyVersion("1.0.*")>]
[<assembly: AssemblyVersion("1.0.0.0")>]
[<assembly: AssemblyFileVersion("1.0.0.0")>]
do
()

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>712da2c7-1b88-4ddb-8b79-46af5d5f6b19</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>ExternalBotTest</RootNamespace>
<AssemblyName>ExternalBotTest</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFSharpCoreVersion>4.4.0.0</TargetFSharpCoreVersion>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Name>ExternalBotTest</Name>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<Tailcalls>false</Tailcalls>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Debug\ExternalBotTest.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<Tailcalls>true</Tailcalls>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Release\ExternalBotTest.XML</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="Morris">
<HintPath>D:\Users\marku\Documents\Visual Studio 2015\Projects\Morris\Morris\bin\Debug\Morris.exe</HintPath>
</Reference>
<Reference Include="mscorlib" />
<Reference Include="FSharp.Core, Version=$(TargetFSharpCoreVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
</ItemGroup>
<ItemGroup>
<Compile Include="AssemblyInfo.fs" />
<Compile Include="FSharpRandomBot.fs" />
<None Include="Script.fsx" />
</ItemGroup>
<PropertyGroup>
<MinimumVisualStudioVersion Condition="'$(MinimumVisualStudioVersion)' == ''">11</MinimumVisualStudioVersion>
</PropertyGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '11.0'">
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets')">
<FSharpTargetsPath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</FSharpTargetsPath>
</PropertyGroup>
</Otherwise>
</Choose>
<Import Project="$(FSharpTargetsPath)" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,28 @@
(*
* FSharpRandomBot.fs
* Copyright (c) 2016 Markus Himmel
* This file is distributed under the terms of the MIT license
*)
namespace ExternalBotTest
open Morris
[<SelectorName("F# KI")>]
type FSharpRandomBot() =
let rng = System.Random ()
let chooseRandom n = Seq.item (Seq.length n |> rng.Next) n
interface IMoveProvider with
// Funktioniert exakt genauso wie das C#-Pendant
member this.GetNextMove state =
let chosen = state.BasicMoves () |> chooseRandom
match state.IsValidMove chosen with
| MoveValidity.ClosesMill ->
[0..GameState.FIELD_SIZE - 1]
|> Seq.where (fun d -> int state.Board.[d] = int (state.NextToMove.Opponent()))
|> chooseRandom
|> chosen.WithRemove
| _ -> chosen

View File

@@ -0,0 +1,8 @@
// Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project
// for more guidance on F# programming.
#load "Library1.fs"
open ExternalBotTest
// Define your library scripting code here

View File

@@ -5,6 +5,8 @@ VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Morris", "Morris\Morris.csproj", "{E3CCB2E8-5840-4442-8A66-177F5DF4C4F5}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Morris", "Morris\Morris.csproj", "{E3CCB2E8-5840-4442-8A66-177F5DF4C4F5}"
EndProject EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ExternalBotTest", "ExternalBotTest\ExternalBotTest.fsproj", "{712DA2C7-1B88-4DDB-8B79-46AF5D5F6B19}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -15,6 +17,10 @@ Global
{E3CCB2E8-5840-4442-8A66-177F5DF4C4F5}.Debug|Any CPU.Build.0 = Debug|Any CPU {E3CCB2E8-5840-4442-8A66-177F5DF4C4F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3CCB2E8-5840-4442-8A66-177F5DF4C4F5}.Release|Any CPU.ActiveCfg = Release|Any CPU {E3CCB2E8-5840-4442-8A66-177F5DF4C4F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3CCB2E8-5840-4442-8A66-177F5DF4C4F5}.Release|Any CPU.Build.0 = Release|Any CPU {E3CCB2E8-5840-4442-8A66-177F5DF4C4F5}.Release|Any CPU.Build.0 = Release|Any CPU
{712DA2C7-1B88-4DDB-8B79-46AF5D5F6B19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{712DA2C7-1B88-4DDB-8B79-46AF5D5F6B19}.Debug|Any CPU.Build.0 = Debug|Any CPU
{712DA2C7-1B88-4DDB-8B79-46AF5D5F6B19}.Release|Any CPU.ActiveCfg = Release|Any CPU
{712DA2C7-1B88-4DDB-8B79-46AF5D5F6B19}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -13,7 +13,8 @@ namespace Morris
/// <summary> /// <summary>
/// Ermöglicht Eingabe und Ausgabe der Spielsituation auf der Konsole. /// Ermöglicht Eingabe und Ausgabe der Spielsituation auf der Konsole.
/// </summary> /// </summary>
class ConsoleInteraction : IGameStateObserver, IMoveProvider [SelectorName("Konsole"), SingleInstance]
internal class ConsoleInteraction : IGameStateObserver, IMoveProvider
{ {
public ConsoleInteraction() public ConsoleInteraction()
{ {

23
Morris/Controller.xaml Normal file
View File

@@ -0,0 +1,23 @@
<Window x:Class="Morris.Controller"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Morris"
mc:Ignorable="d"
Title="Morris" Height="332.409" Width="285.955" Closed="Window_Closed">
<Grid>
<ComboBox x:Name="whiteBox" HorizontalAlignment="Left" Margin="54,35,0,0" VerticalAlignment="Top" Width="194" SelectionChanged="white_SelectionChanged"/>
<ComboBox x:Name="blackBox" HorizontalAlignment="Left" Margin="71,62,0,0" VerticalAlignment="Top" Width="177" SelectionChanged="black_SelectionChanged"/>
<Label Content="Weiß:" HorizontalAlignment="Left" Margin="10,35,0,0" VerticalAlignment="Top"/>
<Label Content="Schwarz:" HorizontalAlignment="Left" Margin="10,62,0,0" VerticalAlignment="Top"/>
<ListBox x:Name="displayBox" HorizontalAlignment="Left" Height="108" Margin="10,115,0,0" VerticalAlignment="Top" Width="238" SelectionMode="Multiple" SelectionChanged="displayBox_SelectionChanged"/>
<Button x:Name="newGame" Content="Neues Spiel" HorizontalAlignment="Left" Margin="124,10,0,0" VerticalAlignment="Top" Width="124" Height="20" Click="newGame_Click"/>
<Label x:Name="label" Content="Anzeigen:" HorizontalAlignment="Left" Margin="10,89,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.553,-0.462"/>
<Button x:Name="loadAssembly" Content="Assembly laden..." HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="109" Click="loadAssembly_Click"/>
<Slider x:Name="delay" HorizontalAlignment="Left" Margin="10,254,0,0" VerticalAlignment="Top" Width="238" Maximum="2000" SmallChange="1" TickFrequency="100" TickPlacement="BottomRight" ValueChanged="delay_ValueChanged"/>
<Label x:Name="label1" Content="Verzögerung:" HorizontalAlignment="Left" Margin="10,228,0,0" VerticalAlignment="Top"/>
</Grid>
</Window>

220
Morris/Controller.xaml.cs Normal file
View File

@@ -0,0 +1,220 @@
/*
* Controller.xaml.cs
* Copyright (c) 2016 Markus Himmel
* This file is distributed under the terms of the MIT license
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Reflection;
using System.Threading;
using System.Collections.ObjectModel;
using Microsoft.Win32;
namespace Morris
{
/// <summary>
/// Gibt dem Benutzer die Möglichkeit, ein Mühlespiel zu kontrollieren
/// </summary>
internal partial class Controller : Window
{
public Controller()
{
InitializeComponent();
populateLists(AppDomain.CurrentDomain.GetAssemblies());
whiteBox.ItemsSource = blackBox.ItemsSource = players;
displayBox.ItemsSource = displays;
}
// Das aktuelle Spiel und der Thread, auf dem es läuft
private Game theGame;
private Thread gameThread;
// Die Objekte, die die ComboBoxen und die ListBox nehmen und wieder zurückgeben
private ObservableCollection<SelectorType> players = new ObservableCollection<SelectorType>();
private ObservableCollection<SelectorType> displays = new ObservableCollection<SelectorType>();
// Instanzen, die gecacht werden, weil es von dem Typen nur eine Instanz geben darf
private Dictionary<Type, object> singleInstances = new Dictionary<Type, object>();
// Instanzen, die gecacht werden, weil das Display wieder abgewählt werden kann und wir die Instanz
// brauchen, um Game.RemoveObserver damit aufzurufen.
private Dictionary<Type, IGameStateObserver> displayObjects = new Dictionary<Type, IGameStateObserver>();
// Fügt alle relevatenten Typen in den angegebenen Assemblies in die entsprechenden Listen ein
private void populateLists(IEnumerable<Assembly> assemblies)
{
foreach (var type in getTypes(typeof(IMoveProvider), assemblies))
players.Add(type);
foreach (var type in getTypes(typeof(IGameStateObserver), assemblies))
displays.Add(type);
}
// Gibt eine Instanz des angegebenen Typen unter Beachtung der
// Möglichkeit, dass der Typ das SingleInstanceAttribute haben kann,
// zurück.
private object getInstance(SelectorType selectorType)
{
Type type = selectorType.Type;
// Anmerkung: Wir wissen an dieser Stelle sicher, dass type einen
// parameterlosen Konstruktor hat
// Für jede Verwendung eine neue Instanz?
if (type.GetCustomAttribute<SingleInstanceAttribute>() == null)
return Activator.CreateInstance(type);
// C# 7 bekommt out variables, dann wird das schöner
object result;
if (singleInstances.TryGetValue(type, out result))
return result;
return singleInstances[type] = Activator.CreateInstance(type);
}
// Gibt alle Typen in assemblies zurück, die @interface implementieren und einen parameterlosen Konstruktor haben
private IEnumerable<SelectorType> getTypes(Type @interface, IEnumerable<Assembly> assemblies)
{
return assemblies
.SelectMany(s => s.GetTypes())
.Where(type => type.IsClass && type.GetInterfaces().Contains(@interface) && type.GetConstructor(Type.EmptyTypes) != null)
.Select(type => new SelectorType(type));
}
// Versucht, ein neues Display zu registrieren
private void tryAddDisplay(SelectorType type)
{
if (!displayObjects.ContainsKey(type.Type))
{
// Objekt muss noch erstellt werden
IGameStateObserver o = getInstance(type) as IGameStateObserver;
if (o == null)
{
MessageBox.Show($"Anzeige {type} konnte nicht erstellt werden.");
}
displayObjects[type.Type] = o;
}
// Objekt existiert jetzt sicher, registrieren
theGame.AddObserver(displayObjects[type.Type]);
}
private void tryRemoveDisplay(SelectorType type)
{
IGameStateObserver obs;
if (!displayObjects.TryGetValue(type.Type, out obs))
return;
theGame.RemoveObserver(obs);
}
// Extrahiert einen IMoveProvider aus der ComboBox-Auswahl
private IMoveProvider getFromBox(ComboBox source)
{
var Type = source.SelectedItem as SelectorType;
if (Type == null)
{
MessageBox.Show("Bitte Spieler auswählen.");
return null;
}
IMoveProvider prov = getInstance(Type) as IMoveProvider;
if (prov == null)
{
MessageBox.Show("Spieler konnte nicht erstellt werden.");
return null;
}
return prov;
}
// Neues Spiel
private void newGame_Click(object sender, RoutedEventArgs e)
{
// Altes Spiel terminieren
if (gameThread != null)
gameThread.Abort();
var white = getFromBox(whiteBox);
var black = getFromBox(blackBox);
if (white == null || black == null)
return;
theGame = new Game(white, black, (int)delay.Value);
foreach (SelectorType type in displayBox.SelectedItems)
{
tryAddDisplay(type);
}
gameThread = new Thread(() => theGame.Run());
gameThread.Start();
}
private void white_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (theGame != null)
theGame.White = getFromBox(whiteBox);
}
private void black_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (theGame != null)
theGame.Black = getFromBox(blackBox);
}
private void displayBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (theGame == null)
return;
foreach (var item in e.AddedItems.OfType<SelectorType>())
tryAddDisplay(item);
foreach (var item in e.RemovedItems.OfType<SelectorType>())
tryRemoveDisplay(item);
}
private void Window_Closed(object sender, EventArgs e)
{
Application.Current.Shutdown();
}
private void loadAssembly_Click(object sender, RoutedEventArgs e)
{
var dialog = new OpenFileDialog()
{
Filter = "Dynamic Link Libraries (*.dll)|*.dll|Executables (*.exe)|*.exe|Alle Dateien (*.*)|*.*"
};
var result = dialog.ShowDialog();
if (result != true)
return;
try
{
var assembly = Assembly.LoadFrom(dialog.FileName);
if (assembly == null)
throw new Exception();
populateLists(new[] { assembly });
}
catch (Exception)
{
MessageBox.Show("Assembly konnte nicht geladen werden.");
}
}
private void delay_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (theGame != null)
theGame.Delay = (int)e.NewValue;
}
}
}

View File

@@ -13,7 +13,7 @@ namespace Morris
/// <summary> /// <summary>
/// Statische Klasse, die Methoden bereitstellt um Spielfeldpositionen zwischen verschiedenen Formaten zu überführen /// Statische Klasse, die Methoden bereitstellt um Spielfeldpositionen zwischen verschiedenen Formaten zu überführen
/// </summary> /// </summary>
static class CoordinateTranslator public static class CoordinateTranslator
{ {
private static Dictionary<string, int> humans = new Dictionary<string, int>() private static Dictionary<string, int> humans = new Dictionary<string, int>()
{ {

View File

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace Morris namespace Morris
{ {
internal static class ExtensionMethods public static class ExtensionMethods
{ {
/// <summary> /// <summary>
/// Gibt den Gegner des Spielers zurück /// Gibt den Gegner des Spielers zurück

View File

@@ -12,13 +12,43 @@ namespace Morris
/// <summary> /// <summary>
/// Repräsentiert ein einzelnes Mühlespiel /// Repräsentiert ein einzelnes Mühlespiel
/// </summary> /// </summary>
class Game internal class Game
{ {
private List<IGameStateObserver> observers = new List<IGameStateObserver>(); private List<IGameStateObserver> observers = new List<IGameStateObserver>();
private GameState state; private GameState state;
private Dictionary<Player, IMoveProvider> providers; private Dictionary<Player, IMoveProvider> providers;
public IMoveProvider White
{
get
{
return providers[Player.White];
}
set
{
providers[Player.White] = value;
}
}
public Game(IMoveProvider white, IMoveProvider black) public IMoveProvider Black
{
get
{
return providers[Player.Black];
}
set
{
providers[Player.Black] = value;
}
}
public int Delay
{
get;
set;
}
public Game(IMoveProvider white, IMoveProvider black, int delay)
{ {
state = new GameState(); state = new GameState();
providers = new Dictionary<Player, IMoveProvider>() providers = new Dictionary<Player, IMoveProvider>()
@@ -26,6 +56,7 @@ namespace Morris
[Player.White] = white, [Player.White] = white,
[Player.Black] = black [Player.Black] = black
}; };
Delay = delay;
} }
/// <summary> /// <summary>
@@ -35,7 +66,7 @@ namespace Morris
/// erfolgreichem Zug der nächste Zug angefordert wird (damit KI vs. KI-Spiele in einem /// erfolgreichem Zug der nächste Zug angefordert wird (damit KI vs. KI-Spiele in einem
/// angemessenen Tempo angesehen werden können)</param> /// angemessenen Tempo angesehen werden können)</param>
/// <returns>Das Spielergebnis</returns> /// <returns>Das Spielergebnis</returns>
public GameResult Run(int moveDelay = 0) public GameResult Run()
{ {
notifyOberservers(); notifyOberservers();
MoveResult res; MoveResult res;
@@ -50,7 +81,7 @@ namespace Morris
} while (res == MoveResult.InvalidMove); } while (res == MoveResult.InvalidMove);
notifyOberservers(); notifyOberservers();
Thread.Sleep(moveDelay); Thread.Sleep(Delay);
} while (state.Result == GameResult.Running); } while (state.Result == GameResult.Running);
return state.Result; return state.Result;
@@ -63,6 +94,7 @@ namespace Morris
public void AddObserver(IGameStateObserver observer) public void AddObserver(IGameStateObserver observer)
{ {
observers.Add(observer); observers.Add(observer);
observer.Notify(state);
} }
/// <summary> /// <summary>

View File

@@ -1,4 +1,5 @@
<Window x:Class="Morris.GameWindow" <Window x:Class="Morris.GameWindow"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

View File

@@ -18,7 +18,8 @@ namespace Morris
/// <summary> /// <summary>
/// Eine WPF-gestütze Mühle-GUI /// Eine WPF-gestütze Mühle-GUI
/// </summary> /// </summary>
public partial class GameWindow : Window, IGameStateObserver, IMoveProvider [SelectorName("GUI"), SingleInstance]
internal partial class GameWindow : Window, IGameStateObserver, IMoveProvider
{ {
// Diese konstanten Steuern das Aussehen des Spielfelds. // Diese konstanten Steuern das Aussehen des Spielfelds.
private const int BLOCK_SIZE = 100; // Sollte durch 2 teilbar sein private const int BLOCK_SIZE = 100; // Sollte durch 2 teilbar sein
@@ -134,6 +135,8 @@ namespace Morris
status.Margin = new Thickness(OFFSET_LEFT, STATUS_OFFSET_TOP, 0, 0); status.Margin = new Thickness(OFFSET_LEFT, STATUS_OFFSET_TOP, 0, 0);
status.FontSize = STATUS_SIZE; status.FontSize = STATUS_SIZE;
grid.Children.Add(status); grid.Children.Add(status);
Show();
} }
public void Notify(IReadOnlyGameState state) public void Notify(IReadOnlyGameState state)

View File

@@ -10,7 +10,7 @@ namespace Morris
/// Eine Entität, die ein Spiel "abbonieren" kann und dann über Änderungen /// Eine Entität, die ein Spiel "abbonieren" kann und dann über Änderungen
/// des Spielzustands in Kenntnis gesetzt wird /// des Spielzustands in Kenntnis gesetzt wird
/// </summary> /// </summary>
interface IGameStateObserver public interface IGameStateObserver
{ {
/// <summary> /// <summary>
/// Wird aufgerufen, wenn sich der aktuelle Spielzustand geändert hat /// Wird aufgerufen, wenn sich der aktuelle Spielzustand geändert hat

View File

@@ -12,7 +12,7 @@ namespace Morris
/// (also in der Regel entweder eine Benutzeroberfläche oder /// (also in der Regel entweder eine Benutzeroberfläche oder
/// ein Bot). /// ein Bot).
/// </summary> /// </summary>
interface IMoveProvider public interface IMoveProvider
{ {
/// <summary> /// <summary>
/// Bestimmt den nächsten Spielzug /// Bestimmt den nächsten Spielzug

View File

@@ -49,6 +49,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ConsoleInteraction.cs" /> <Compile Include="ConsoleInteraction.cs" />
<Compile Include="Controller.xaml.cs">
<DependentUpon>Controller.xaml</DependentUpon>
</Compile>
<Compile Include="CoordinateTranslator.cs" /> <Compile Include="CoordinateTranslator.cs" />
<Compile Include="ExtensionMethods.cs" /> <Compile Include="ExtensionMethods.cs" />
<Compile Include="Game.cs" /> <Compile Include="Game.cs" />
@@ -69,11 +72,18 @@
<Compile Include="Player.cs" /> <Compile Include="Player.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SelectorType.cs" />
<Compile Include="SelectorNameAttribute.cs" />
<Compile Include="SingleInstanceAttribute.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="App.config" /> <None Include="App.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Page Include="Controller.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="GameWindow.xaml"> <Page Include="GameWindow.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View File

@@ -7,19 +7,20 @@ using System.Threading.Tasks;
namespace Morris namespace Morris
{ {
class Program internal class Program
{ {
[STAThread] [STAThread]
static void Main(string[] args) static void Main(string[] args)
{ {
var a = new ConsoleInteraction(); //var a = new ConsoleInteraction();
var b = new RandomBot(); //var b = new RandomBot();
var w = new GameWindow(); //var w = new GameWindow();
var g = new Game(a, b); //var g = new Game(a, b);
g.AddObserver(a); //g.AddObserver(a);
g.AddObserver(w); //g.AddObserver(w);
Task.Run(() => g.Run(0)); //Task.Run(() => g.Run(0));
new Application().Run(w); //new Application().Run(w);
new Application().Run(new Controller());
} }
} }
} }

View File

@@ -12,7 +12,7 @@ namespace Morris
/// <summary> /// <summary>
/// Ein extrem einfacher KI-Spieler, der einen zufälligen gültigen Spielzug auswählt /// Ein extrem einfacher KI-Spieler, der einen zufälligen gültigen Spielzug auswählt
/// </summary> /// </summary>
class RandomBot : IMoveProvider internal class RandomBot : IMoveProvider
{ {
// Anhand dieser Klasse können wir sehen, wie einfach es ist, einen Computerspieler zu implementieren. // Anhand dieser Klasse können wir sehen, wie einfach es ist, einen Computerspieler zu implementieren.
// Es muss lediglich eine einzige, einfache Methode implementiert werden. Der Spielzustandparameter stellt // Es muss lediglich eine einzige, einfache Methode implementiert werden. Der Spielzustandparameter stellt

View File

@@ -0,0 +1,28 @@
/*
* SelectorNameAttribute.cs
* Copyright (c) 2016 Makrus Himmel
* This file is distributed un der the terms of the MIT license
*/
using System;
namespace Morris
{
/// <summary>
/// Ein Attribut, welches angibt, wie die Klasse im Auswahldialog benannt werden soll
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = true)]
public sealed class SelectorNameAttribute : Attribute
{
public string SelectorName
{
get;
private set;
}
public SelectorNameAttribute(string selectorName)
{
SelectorName = selectorName;
}
}
}

43
Morris/SelectorType.cs Normal file
View File

@@ -0,0 +1,43 @@
/*
* SelectorType.cs
* Copyright (c) 2016 Markus Himmel
* This file is distributed under the terms of the MIT license.
*/
using System;
using System.Reflection;
namespace Morris
{
/// <summary>
/// Hält einen Typen fest, der potentiell durch ein <see cref="SelectorNameAttribute"/> einen neuen Namen erhalten hat
/// </summary>
internal class SelectorType
{
public Type Type
{
get;
private set;
}
private string displayName;
public SelectorType(Type type)
{
Type = type;
// displayName ist SelectorName, falls ein SelectorNameAttribute existiert
// und ansonsten einfach der Typname
displayName = Type
.GetCustomAttribute<SelectorNameAttribute>()
?.SelectorName
?? type.ToString();
}
public override string ToString()
{
return displayName;
}
}
}

View File

@@ -0,0 +1,19 @@
/*
* SingleInstanceAttribute.cs
* Copyright (c) 2016 Markus Himmel
* This file ist distributed under the terms of the MIT license
*/
using System;
namespace Morris
{
/// <summary>
/// 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)
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = true)]
public sealed class SingleInstanceAttribute : Attribute
{
}
}