├── MCTS
├── Settings.StyleCop
├── V1
│ ├── Interfaces
│ │ ├── IMove.cs
│ │ ├── IPlayer.cs
│ │ ├── IGameState.cs
│ │ └── INode.cs
│ ├── UCT
│ │ └── SingleThreaded.cs
│ └── Node
│ │ ├── SingleThreadedNode.cs
│ │ └── NodeBase.cs
├── V2
│ ├── InterfacesV2
│ │ ├── IPlayer.cs
│ │ ├── IMove.cs
│ │ ├── IGameState.cs
│ │ └── INode.cs
│ ├── UCT
│ │ ├── SingleThreaded.cs
│ │ └── RootParrelization.cs
│ └── Node
│ │ ├── SingleThreadedNode.cs
│ │ ├── MultiThreadedNode.cs
│ │ └── NodeBase.cs
├── Enum
│ └── EGameFinalStatus.cs
├── Utils
│ └── Shuffle.cs
├── Properties
│ └── AssemblyInfo.cs
└── MCTS.csproj
├── python
├── python.cmd
├── python.pyproj
└── python.py
├── packages
├── NUnit.2.6.4
│ ├── license.txt
│ └── NUnit.2.6.4.nupkg
├── FsUnit.1.3.1.0
│ ├── FsUnit.1.3.1.0.nupkg
│ ├── tools
│ │ └── install.ps1
│ └── lib
│ │ ├── FsUnit.CustomMatchers.xml
│ │ └── FsUnit.NUnit.XML
└── NUnitTestAdapter.2.0.0
│ ├── NUnitTestAdapter.2.0.0.nupkg
│ └── tools
│ └── install.ps1
├── docs
├── Scalable Distributed Monte-Carlo Tree Search.pdf
└── Monte-Carlo Tree Search A New Framework for Game AI.pdf
├── SimpleGames
├── packages.config
├── Script.fsx
├── app.config
├── AssemblyInfo.fs
├── Nims.fs
├── SimpleGames.fsproj
└── NimsTest.fs
├── MCTS.Test
├── packages.config
├── app.config
├── MCTSTest.cs
├── V1
│ ├── NimsMCTSTest.cs
│ └── Nims.cs
├── V2
│ ├── NimsMCTSTestV2.cs
│ └── Nims.cs
├── Properties
│ └── AssemblyInfo.cs
└── MCTSTest.csproj
├── MCTSMock
├── MockPlayer.cs
├── MockMove.cs
├── Properties
│ └── AssemblyInfo.cs
├── MockGame.cs
└── MCTSMock.csproj
├── README.md
├── .gitignore
├── MCTS.sln
└── LICENCE
/MCTS/Settings.StyleCop:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/python/python.cmd:
--------------------------------------------------------------------------------
1 | c:\Python34\python.exe C:\DEV\MCTS\python\python.py
2 | pause
3 |
--------------------------------------------------------------------------------
/packages/NUnit.2.6.4/license.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rfrerebe/MCTS/HEAD/packages/NUnit.2.6.4/license.txt
--------------------------------------------------------------------------------
/packages/NUnit.2.6.4/NUnit.2.6.4.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rfrerebe/MCTS/HEAD/packages/NUnit.2.6.4/NUnit.2.6.4.nupkg
--------------------------------------------------------------------------------
/packages/FsUnit.1.3.1.0/FsUnit.1.3.1.0.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rfrerebe/MCTS/HEAD/packages/FsUnit.1.3.1.0/FsUnit.1.3.1.0.nupkg
--------------------------------------------------------------------------------
/packages/FsUnit.1.3.1.0/tools/install.ps1:
--------------------------------------------------------------------------------
1 | param($rootPath, $toolsPath, $package, $project)
2 |
3 | Add-BindingRedirect $project.Name
--------------------------------------------------------------------------------
/MCTS/V1/Interfaces/IMove.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.V1.Interfaces
2 | {
3 | public interface IMove
4 | {
5 | string Name { get; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/MCTS/V1/Interfaces/IPlayer.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.V1.Interfaces
2 | {
3 | public interface IPlayer
4 | {
5 | string Name { get; }
6 | }
7 | }
--------------------------------------------------------------------------------
/MCTS/V2/InterfacesV2/IPlayer.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.V2.Interfaces
2 | {
3 | public interface IPlayer
4 | {
5 | string Name { get; }
6 | }
7 | }
--------------------------------------------------------------------------------
/docs/Scalable Distributed Monte-Carlo Tree Search.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rfrerebe/MCTS/HEAD/docs/Scalable Distributed Monte-Carlo Tree Search.pdf
--------------------------------------------------------------------------------
/docs/Monte-Carlo Tree Search A New Framework for Game AI.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rfrerebe/MCTS/HEAD/docs/Monte-Carlo Tree Search A New Framework for Game AI.pdf
--------------------------------------------------------------------------------
/packages/NUnitTestAdapter.2.0.0/NUnitTestAdapter.2.0.0.nupkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rfrerebe/MCTS/HEAD/packages/NUnitTestAdapter.2.0.0/NUnitTestAdapter.2.0.0.nupkg
--------------------------------------------------------------------------------
/MCTS/V2/InterfacesV2/IMove.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.V2.Interfaces
2 | {
3 | public interface IMove
4 | {
5 | string Name { get; }
6 |
7 | IGameState DoMove();
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/FsUnit.1.3.1.0/lib/FsUnit.CustomMatchers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | FsUnit.CustomMatchers
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/MCTS/Enum/EGameFinalStatus.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.Enum
2 | {
3 | public enum EGameFinalStatus
4 | {
5 | GameWon = 0,
6 |
7 | GameLost = 1,
8 |
9 | GameDraw = 2,
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/SimpleGames/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/MCTS.Test/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/SimpleGames/Script.fsx:
--------------------------------------------------------------------------------
1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project
2 | // for more guidance on F# programming.
3 |
4 | #load "Library1.fs"
5 | open SimpleGames
6 |
7 | // Define your library scripting code here
8 |
9 |
--------------------------------------------------------------------------------
/MCTSMock/MockPlayer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using MCTS.Interfaces;
3 |
4 | namespace MCTSMock
5 | {
6 | class MockPlayer : IPlayer
7 | {
8 | string IPlayer.Name
9 | {
10 | get
11 | {
12 | return "Alfred";
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MCTS
2 | Monte Carlo Tree Search in C#
3 |
4 | Inspired by http://mcts.ai/code/python.html
5 |
6 |
7 | [Monte-Carlo Tree Search A New Framework for Game AI](docs/Monte-Carlo%20Tree%20Search%20A%20New%20Framework%20for%20Game%20AI.pdf)
8 |
9 | [Scalable Distributed Monte-Carlo Tree Search] (docs/Scalable%20Distributed%20Monte-Carlo%20Tree%20Search.pdf)
10 |
--------------------------------------------------------------------------------
/MCTS/V2/InterfacesV2/IGameState.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.V2.Interfaces
2 | {
3 | using Enum;
4 | using System.Collections.Generic;
5 |
6 | public interface IGameState
7 | {
8 | IEnumerable GetMoves();
9 |
10 | IGameState PlayRandomlyUntilTheEnd();
11 |
12 | EGameFinalStatus GetResult(IPlayer player);
13 |
14 | IPlayer PlayerJustMoved { get; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.dll
2 |
3 | #ignore thumbnails created by windows
4 | Thumbs.db
5 | #Ignore files build by Visual Studio
6 | *.obj
7 | *.exe
8 | *.pdb
9 | *.user
10 | *.aps
11 | *.pch
12 | *.vspscc
13 | *_i.c
14 | *_p.c
15 | *.ncb
16 | *.suo
17 | *.tlb
18 | *.tlh
19 | *.bak
20 | *.cache
21 | *.ilk
22 | *.log
23 | [Bb]in
24 | [Dd]ebug*/
25 | *.lib
26 | *.sbr
27 | obj/
28 | [Rr]elease*/
29 | _ReSharper*/
30 | [Tt]est[Rr]esult*
31 |
--------------------------------------------------------------------------------
/packages/NUnitTestAdapter.2.0.0/tools/install.ps1:
--------------------------------------------------------------------------------
1 | param($installPath, $toolsPath, $package, $project)
2 | $asms = $package.AssemblyReferences | %{$_.Name}
3 | foreach ($reference in $project.Object.References)
4 | {
5 | if ($asms -contains $reference.Name + ".dll")
6 | {
7 | if ($reference.Name -ne "nunit.framework")
8 | {
9 | $reference.CopyLocal = $false;
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/MCTS.Test/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/SimpleGames/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/MCTS/V1/Interfaces/IGameState.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.V1.Interfaces
2 | {
3 | using Enum;
4 | using System.Collections.Generic;
5 |
6 | public interface IGameState
7 | {
8 | IEnumerable GetMoves();
9 |
10 | void PlayRandomlyUntilTheEnd();
11 |
12 | void DoMove(IMove move);
13 |
14 | EGameFinalStatus GetResult(IPlayer player);
15 |
16 | IPlayer PlayerJustMoved { get; }
17 |
18 | IGameState Clone();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/MCTS/Utils/Shuffle.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.Utils
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 |
7 | public static class ExtensionMethods
8 | {
9 | public static IEnumerable Shuffle(this IEnumerable list)
10 | {
11 | var r = new Random((int)DateTime.Now.Ticks);
12 | var shuffledList = list.Select(x => new { Number = r.Next(), Item = x }).OrderBy(x => x.Number).Select(x => x.Item);
13 | return shuffledList.ToList();
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/MCTS.Test/MCTSTest.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using System.Collections.Generic;
3 | using MCTS.Utils;
4 | namespace MCTSTest
5 | {
6 | public class MCTSTest
7 | {
8 | [Test]
9 | public void TestShuffleIsEquivalentAndIsNotOrdered()
10 | {
11 | var list = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
12 | var shuffled = new List(list).Shuffle();
13 | Assert.That(shuffled, Is.EquivalentTo(list));
14 | Assert.That(shuffled, Is.Not.Ordered);
15 | }
16 |
17 |
18 |
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/MCTS/V1/Interfaces/INode.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.V1.Interfaces
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | using Enum;
7 |
8 | internal interface INode
9 | {
10 | long Wins { get; }
11 |
12 | long Visits { get; }
13 |
14 | float UCTK { get; }
15 |
16 | INode Parent { get; }
17 |
18 | IEnumerable Childs { get; }
19 |
20 | IEnumerable UntriedMoves { get; }
21 |
22 | IMove Move { get; }
23 |
24 | bool NodeIsFullyExpandedAndNonterminal { get; }
25 |
26 | INode AddChild(Func nodeConstructor);
27 |
28 | Tuple GetRandomMoveOrIsFalse();
29 |
30 | IMove MostVisitedMove();
31 |
32 | INode UCTSelectChild();
33 |
34 | IPlayer PlayerJustMoved { get; }
35 |
36 | void Update(EGameFinalStatus status);
37 |
38 | string DisplayTree(int indent);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/MCTS/V2/InterfacesV2/INode.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.V2.Interfaces
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 |
6 | using Enum;
7 |
8 | internal interface INode
9 | {
10 | long Wins { get; }
11 |
12 | long Visits { get; }
13 |
14 | float UCTK { get; }
15 |
16 | INode Parent { get; }
17 |
18 | IEnumerable Childs { get; }
19 |
20 | IEnumerable UntriedMoves { get; }
21 |
22 | IMove Move { get; }
23 |
24 | bool NodeIsFullyExpandedAndNonterminal { get; }
25 |
26 | INode AddChild(Func nodeConstructor);
27 |
28 | Tuple GetRandomMoveOrIsFalse();
29 |
30 | IMove MostVisitedMove();
31 |
32 | INode UCTSelectChild();
33 |
34 | IPlayer PlayerJustMoved { get; }
35 |
36 | void Update(EGameFinalStatus status);
37 |
38 | string DisplayTree(int indent);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/MCTSMock/MockMove.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using MCTS.Interfaces;
3 |
4 | namespace MCTSMock
5 | {
6 | class MockMove : IMove
7 | {
8 | private readonly int winProbability;
9 |
10 | public MockMove(int winProbability)
11 | {
12 | if (winProbability >= 0 && winProbability <= 100)
13 | {
14 | this.winProbability = winProbability;
15 | }
16 | else throw new ArgumentOutOfRangeException("winProbability", winProbability, "Invalid winProbability. It must be between 0 and 100 included");
17 | }
18 |
19 | string IMove.Name
20 | {
21 | get
22 | {
23 | return this.winProbability.ToString();
24 | }
25 | }
26 |
27 | IGameState IMove.DoMove()
28 | {
29 | return new MockGame(this.winProbability);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/MCTS.Test/V1/NimsMCTSTest.cs:
--------------------------------------------------------------------------------
1 | namespace MCTSTest.V1
2 | {
3 | using System.Linq;
4 | using System;
5 |
6 | using NUnit.Framework;
7 |
8 | using MCTS.Enum;
9 | using MCTS.V1.Interfaces;
10 | using MCTS.V1.UCT;
11 |
12 | [TestFixture]
13 | public class NimsMCTSTest
14 | {
15 | [TestCase(5, EGameFinalStatus.GameWon)]
16 | [TestCase(17, EGameFinalStatus.GameWon)]
17 | [TestCase(4, EGameFinalStatus.GameLost)]
18 | [TestCase(16, EGameFinalStatus.GameLost)]
19 | public void NimsMctsV1(int token, EGameFinalStatus status)
20 | {
21 | Action print = s => Console.WriteLine(s);
22 | var firstPlayer = new NimPlayer(1);
23 | var nims = new NimState(token) as IGameState;
24 | while (nims.GetMoves().Any())
25 | {
26 | print(nims.ToString());
27 | IMove move = SingleThreaded.ComputeSingleThreadedUCT(nims, 1000, true, print, 0.7F);
28 | print(move.Name);
29 | nims.DoMove(move);
30 | }
31 | print(nims.GetResult(firstPlayer).ToString());
32 | Assert.IsTrue(nims.GetResult(firstPlayer) == status);
33 | }
34 |
35 |
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/MCTS.Test/V2/NimsMCTSTestV2.cs:
--------------------------------------------------------------------------------
1 | namespace MCTSTest
2 | {
3 | using System.Linq;
4 | using System;
5 |
6 | using NUnit.Framework;
7 |
8 | using MCTS.Enum;
9 | using MCTS.V2.Interfaces;
10 | using MCTS.V2.UCT;
11 |
12 | [TestFixture]
13 | public class NimsMCTSTestV2
14 | {
15 | [TestCase(5, EGameFinalStatus.GameWon)]
16 | [TestCase(17, EGameFinalStatus.GameWon)]
17 | [TestCase(4, EGameFinalStatus.GameLost)]
18 | [TestCase(16, EGameFinalStatus.GameLost)]
19 | public void NimsMctsV2(int token, EGameFinalStatus status)
20 | {
21 | Action print = s => Console.WriteLine(s);
22 | var firstPlayer = new NimPlayer(1);
23 | var nims = new NimState(token) as IGameState;
24 | while (nims.GetMoves().Any())
25 | {
26 | print(nims.ToString());
27 | IMove move = SingleThreaded.ComputeSingleThreadedUCT(nims, 1000, true, print, 0.7F);
28 | print(move.Name);
29 | nims = move.DoMove();
30 | }
31 | print(nims.GetResult(firstPlayer).ToString());
32 | Assert.IsTrue(nims.GetResult(firstPlayer) == status);
33 | }
34 |
35 |
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/MCTS/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("MCTS")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("MCTS")]
13 | [assembly: AssemblyCopyright("Copyright © 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("9aacf0aa-e46f-49d2-b195-2dcd73f6c4cf")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
38 | [assembly: InternalsVisibleTo("MCTSTest")]
--------------------------------------------------------------------------------
/MCTSMock/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("MCTSMock")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Société Générale")]
12 | [assembly: AssemblyProduct("MCTSMock")]
13 | [assembly: AssemblyCopyright("Copyright © Société Générale 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("77bf631b-4c20-4b06-986f-57d4b411831a")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/MCTS.Test/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("MCTS.Test")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Société Générale")]
12 | [assembly: AssemblyProduct("MCTS.Test")]
13 | [assembly: AssemblyCopyright("Copyright © Société Générale 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("9b7c1013-2540-4d2e-9d02-baf4df48174e")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/SimpleGames/AssemblyInfo.fs:
--------------------------------------------------------------------------------
1 | namespace SimpleGames.AssemblyInfo
2 |
3 | open System.Reflection
4 | open System.Runtime.CompilerServices
5 | open System.Runtime.InteropServices
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | []
11 | []
12 | []
13 | []
14 | []
15 | []
16 | []
17 | []
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | []
23 |
24 | // The following GUID is for the ID of the typelib if this project is exposed to COM
25 | []
26 |
27 | // Version information for an assembly consists of the following four values:
28 | //
29 | // Major Version
30 | // Minor Version
31 | // Build Number
32 | // Revision
33 | //
34 | // You can specify all the values or you can default the Build and Revision Numbers
35 | // by using the '*' as shown below:
36 | // []
37 | []
38 | []
39 |
40 | do
41 | ()
--------------------------------------------------------------------------------
/MCTS.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.23107.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MCTS", "MCTS\MCTS.csproj", "{9AACF0AA-E46F-49D2-B195-2DCD73F6C4CF}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MCTSTest", "MCTS.Test\MCTSTest.csproj", "{9B7C1013-2540-4D2E-9D02-BAF4DF48174E}"
9 | EndProject
10 | Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "python", "python\python.pyproj", "{268F1D26-2ECE-4888-9F87-DE7C26022AE7}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {9AACF0AA-E46F-49D2-B195-2DCD73F6C4CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {9AACF0AA-E46F-49D2-B195-2DCD73F6C4CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {9AACF0AA-E46F-49D2-B195-2DCD73F6C4CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {9AACF0AA-E46F-49D2-B195-2DCD73F6C4CF}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {9B7C1013-2540-4D2E-9D02-BAF4DF48174E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {9B7C1013-2540-4D2E-9D02-BAF4DF48174E}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {9B7C1013-2540-4D2E-9D02-BAF4DF48174E}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {9B7C1013-2540-4D2E-9D02-BAF4DF48174E}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {268F1D26-2ECE-4888-9F87-DE7C26022AE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {268F1D26-2ECE-4888-9F87-DE7C26022AE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | EndGlobalSection
29 | GlobalSection(SolutionProperties) = preSolution
30 | HideSolutionNode = FALSE
31 | EndGlobalSection
32 | EndGlobal
33 |
--------------------------------------------------------------------------------
/python/python.pyproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | 2.0
6 | 268f1d26-2ece-4888-9f87-de7c26022ae7
7 | .
8 | python.py
9 |
10 |
11 | .
12 | .
13 | python
14 | python
15 |
16 |
17 | true
18 | false
19 |
20 |
21 | true
22 | false
23 |
24 |
25 |
26 |
27 |
28 | 10.0
29 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets
30 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/MCTS/V1/UCT/SingleThreaded.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.V1.UCT
2 | {
3 | using System;
4 |
5 | using Interfaces;
6 | using Node;
7 |
8 | public static class SingleThreaded
9 | {
10 | public static IMove ComputeSingleThreadedUCT(IGameState gameState, int itermax, bool verbose, Action printfn, float uctk)
11 | {
12 | var rootNode = new SingleThreadedNode(null, null, gameState, uctk);
13 |
14 | for (var i = 0; i < itermax; i++)
15 | {
16 | INode node = rootNode;
17 | var state = gameState.Clone();
18 |
19 | // Select
20 | while (node.NodeIsFullyExpandedAndNonterminal)
21 | {
22 | //if (verbose)
23 | //{
24 | // printfn(node.DisplayUTC());
25 | //}
26 | node = node.UCTSelectChild();
27 | state.DoMove(node.Move);
28 | }
29 |
30 | // Expand
31 | var result = node.GetRandomMoveOrIsFalse();
32 | if (result.Item1)
33 | {
34 | var move = result.Item2;
35 | state.DoMove(move);
36 | Func constructor = () => new SingleThreadedNode(node, move, state, node.UCTK);
37 | node = node.AddChild(constructor);
38 | }
39 |
40 | // Rollout
41 | state.PlayRandomlyUntilTheEnd();
42 |
43 | // Backpropagate
44 | while (node != null)
45 | {
46 | node.Update(state.GetResult(node.PlayerJustMoved));
47 | node = node.Parent;
48 | }
49 | }
50 | if (verbose)
51 | {
52 | //printfn(rootNode.DisplayTree(0));
53 | printfn(rootNode.DisplayMostVisistedChild());
54 | }
55 |
56 | return rootNode.MostVisitedMove();
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/MCTS/V2/UCT/SingleThreaded.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.V2.UCT
2 | {
3 | using System;
4 |
5 | using Interfaces;
6 | using Node;
7 |
8 | public static class SingleThreaded
9 | {
10 | public static IMove ComputeSingleThreadedUCT(IGameState gameState, int itermax, bool verbose, Action printfn, float uctk)
11 | {
12 | var rootNode = new SingleThreadedNode(null, null, gameState, uctk);
13 |
14 | for (var i = 0; i < itermax; i++)
15 | {
16 | INode node = rootNode;
17 | var state = gameState;
18 |
19 | // Select
20 | while (node.NodeIsFullyExpandedAndNonterminal)
21 | {
22 | //if (verbose)
23 | //{
24 | // printfn(node.DisplayUTC());
25 | //}
26 | node = node.UCTSelectChild();
27 | state = node.Move.DoMove();
28 | }
29 |
30 | // Expand
31 | var result = node.GetRandomMoveOrIsFalse();
32 | if (result.Item1)
33 | {
34 | var move = result.Item2;
35 | state = move.DoMove();
36 | Func constructor = () => new SingleThreadedNode(node, move, state, node.UCTK);
37 | node = node.AddChild(constructor);
38 | }
39 |
40 | // Rollout
41 | state.PlayRandomlyUntilTheEnd();
42 |
43 | // Backpropagate
44 | while (node != null)
45 | {
46 | node.Update(state.GetResult(node.PlayerJustMoved));
47 | node = node.Parent;
48 | }
49 | }
50 | if (verbose)
51 | {
52 | //printfn(rootNode.DisplayTree(0));
53 | printfn(rootNode.DisplayMostVisistedChild());
54 | }
55 |
56 | return rootNode.MostVisitedMove();
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/MCTSMock/MockGame.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using MCTS.Enum;
4 | using MCTS.Interfaces;
5 | using MCTS.Utils;
6 | namespace MCTSMock
7 | {
8 | public class MockGame : IGameState
9 | {
10 | private readonly int winProbability;
11 |
12 | public MockGame(int winProbability)
13 | {
14 | if (winProbability >= 0 && winProbability < 100)
15 | {
16 | this.winProbability = winProbability;
17 | }
18 | else
19 | {
20 | throw new ArgumentOutOfRangeException(
21 | "winProbability",
22 | winProbability,
23 | "Invalid winProbability. It must be between 0 (included) and 100 (exluded)");
24 | }
25 | }
26 |
27 | public EGameFinalStatus GetResult(IPlayer player)
28 | {
29 | throw new NotImplementedException();
30 | }
31 |
32 | IPlayer IGameState.CurrentPlayer()
33 | {
34 | return new MockPlayer();
35 | }
36 |
37 | IEnumerable IGameState.GetMoves()
38 | {
39 | List list = new List();
40 | if (this.winProbability >=1 && this.winProbability < 99)
41 | {
42 | list.Add(new MockMove(this.winProbability + 1));
43 | list.Add(new MockMove(100 - this.winProbability - 1));
44 | }
45 | else
46 | {
47 |
48 | list.Add(new MockMove(99));
49 | list.Add(new MockMove(0));
50 | }
51 | return list.Shuffle();
52 | }
53 |
54 | EGameFinalStatus IGameState.PlayRandomlyUntilTheEnd(IPlayer player)
55 | {
56 | int seed = (int) DateTime.Now.Ticks* 7;
57 | var random = new Random(seed);
58 |
59 | if (random.Next(100) < this.winProbability)
60 | {
61 | return EGameFinalStatus.GameWon;
62 | }
63 | else
64 | {
65 | return EGameFinalStatus.GameLost;
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/MCTS/V1/Node/SingleThreadedNode.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.V1.Node
2 | {
3 | using System;
4 |
5 | using Enum;
6 | using Interfaces;
7 | using Utils;
8 | using System.Collections.Generic;
9 |
10 |
11 | internal class SingleThreadedNode : NodeBase
12 | {
13 | private readonly List childs;
14 | private readonly Stack untriedMoves;
15 |
16 | private long wins;
17 | private long visits;
18 |
19 | internal SingleThreadedNode(INode parent, IMove move, IGameState gameState, float uctk)
20 | : base(parent, move, uctk, gameState.PlayerJustMoved)
21 | {
22 | this.wins = 0L;
23 | this.visits = 0L;
24 | this.childs = new List();
25 | var moves = gameState.GetMoves();
26 | var shuffled = moves.Shuffle();
27 | this.untriedMoves = new Stack(shuffled); //randomize Moves
28 | }
29 |
30 | public override long Wins
31 | {
32 | get
33 | {
34 | return this.wins;
35 | }
36 | }
37 |
38 | public override long Visits
39 | {
40 | get
41 | {
42 | return this.visits;
43 | }
44 | }
45 |
46 | public override IEnumerable Childs
47 | {
48 | get
49 | {
50 | return this.childs;
51 | }
52 | }
53 |
54 | public override IEnumerable UntriedMoves
55 | {
56 | get
57 | {
58 | return this.untriedMoves;
59 | }
60 | }
61 |
62 | public override bool NodeIsFullyExpandedAndNonterminal
63 | {
64 | get
65 | {
66 | return (this.untriedMoves.Count == 0 && this.childs.Count != 0);
67 | }
68 | }
69 |
70 | public override Tuple GetRandomMoveOrIsFalse()
71 | {
72 | if (this.untriedMoves.Count != 0)
73 | {
74 | var move = this.untriedMoves.Pop();
75 | return new Tuple(true, move);
76 | }
77 | return new Tuple(false, null); ;
78 | }
79 |
80 | public override INode AddChild (Func nodeConstructor)
81 | {
82 | var node = nodeConstructor();
83 | this.childs.Add(node);
84 | return node;
85 | }
86 |
87 | public override void Update(EGameFinalStatus status)
88 | {
89 | this.visits++;
90 | if(status == EGameFinalStatus.GameWon)
91 | {
92 | this.wins++;
93 | }
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/MCTS/V2/Node/SingleThreadedNode.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.V2.Node
2 | {
3 | using System;
4 |
5 | using Enum;
6 | using Interfaces;
7 | using Utils;
8 | using System.Collections.Generic;
9 |
10 |
11 | internal class SingleThreadedNode : NodeBase
12 | {
13 | private readonly List childs;
14 | private readonly Stack untriedMoves;
15 |
16 | private long wins;
17 | private long visits;
18 |
19 | internal SingleThreadedNode(INode parent, IMove move, IGameState gameState, float uctk)
20 | : base(parent, move, uctk, gameState.PlayerJustMoved)
21 | {
22 | this.wins = 0L;
23 | this.visits = 0L;
24 | this.childs = new List();
25 | var moves = gameState.GetMoves();
26 | var shuffled = moves.Shuffle();
27 | this.untriedMoves = new Stack(shuffled); //randomize Moves
28 | }
29 |
30 | public override long Wins
31 | {
32 | get
33 | {
34 | return this.wins;
35 | }
36 | }
37 |
38 | public override long Visits
39 | {
40 | get
41 | {
42 | return this.visits;
43 | }
44 | }
45 |
46 | public override IEnumerable Childs
47 | {
48 | get
49 | {
50 | return this.childs;
51 | }
52 | }
53 |
54 | public override IEnumerable UntriedMoves
55 | {
56 | get
57 | {
58 | return this.untriedMoves;
59 | }
60 | }
61 |
62 | public override bool NodeIsFullyExpandedAndNonterminal
63 | {
64 | get
65 | {
66 | return (this.untriedMoves.Count == 0 && this.childs.Count != 0);
67 | }
68 | }
69 |
70 | public override Tuple GetRandomMoveOrIsFalse()
71 | {
72 | if (this.untriedMoves.Count != 0)
73 | {
74 | var move = this.untriedMoves.Pop();
75 | return new Tuple(true, move);
76 | }
77 | return new Tuple(false, null); ;
78 | }
79 |
80 | public override INode AddChild (Func nodeConstructor)
81 | {
82 | var node = nodeConstructor();
83 | this.childs.Add(node);
84 | return node;
85 | }
86 |
87 | public override void Update(EGameFinalStatus status)
88 | {
89 | this.visits++;
90 | if(status == EGameFinalStatus.GameWon)
91 | {
92 | this.wins++;
93 | }
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/SimpleGames/Nims.fs:
--------------------------------------------------------------------------------
1 | namespace SimpleGames
2 |
3 | module Nims =
4 | open MCTS.Interfaces
5 | open MCTS.Enum
6 |
7 | let random = System.Random()
8 |
9 | type Player =
10 | | Player1
11 | | Player2
12 | interface IPlayer with
13 | member this.Name =
14 | match this with
15 | | Player1 -> "Player1"
16 | | Player2 -> "Player2"
17 |
18 | type Move (chips) =
19 | interface IMove with
20 | member this.Name = chips.ToString()
21 |
22 |
23 |
24 | and Nims( tokenNumber : int, currentPlayer : IPlayer) =
25 | do
26 | if (tokenNumber < 0) then
27 | invalidArg "tokenNumber" "Should be positive or equal to 0"
28 |
29 | let nextPlayer (player : IPlayer)=
30 | match player.Name with
31 | | "Player1" -> Player2 :> IPlayer
32 | | "Player2" -> Player1 :> IPlayer
33 | | _ -> invalidOp "Invalid player name %A" player.Name
34 |
35 | /// used when token is 0
36 | /// previous player won
37 | let getResult (player : IPlayer) player2 =
38 | if ( player2 = player) then
39 | EGameFinalStatus.GameLost
40 | else
41 | EGameFinalStatus.GameWon
42 |
43 | new(tokenNumber) =
44 | Nims(tokenNumber, Player.Player1)
45 |
46 | interface IGameState with
47 | member this.CurrentPlayer() =
48 | currentPlayer
49 |
50 | member this.GetMoves() =
51 | let build n =
52 | seq{for i in 1..n do yield Move((tokenNumber - i), nextPlayer currentPlayer) :> IMove}
53 | if (tokenNumber - 3 >= 0) then
54 | build 3
55 | elif (tokenNumber - 2 >= 0) then
56 | build 2
57 | elif (tokenNumber - 1 >= 0) then
58 | build 1
59 | else
60 | Seq.empty
61 |
62 | member this.GetResult player =
63 | match tokenNumber with
64 | | x when x = 0 ->
65 | getResult player currentPlayer
66 | | _ ->
67 | invalidOp "Can't give result, game is not finished"
68 |
69 | member this.PlayRandomlyUntilTheEnd =
70 | let rec play token p =
71 | match token with
72 | | x when x = 0 ->
73 | ()
74 | | _ ->
75 | let min = min 4 (token + 1)
76 | let n = random.Next(1, min)
77 | play (token - n) (nextPlayer p)
78 | play tokenNumber currentPlayer
79 |
80 |
81 |
--------------------------------------------------------------------------------
/MCTSMock/MCTSMock.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {77BF631B-4C20-4B06-986F-57D4B411831A}
8 | Library
9 | Properties
10 | MCTSMock
11 | MCTSMock
12 | v4.6
13 | 512
14 |
15 |
16 | true
17 | full
18 | false
19 | bin\Debug\
20 | DEBUG;TRACE
21 | prompt
22 | 4
23 |
24 |
25 | pdbonly
26 | true
27 | bin\Release\
28 | TRACE
29 | prompt
30 | 4
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | {9aacf0aa-e46f-49d2-b195-2dcd73f6c4cf}
51 | MCTS
52 |
53 |
54 |
55 |
62 |
--------------------------------------------------------------------------------
/MCTS/V2/Node/MultiThreadedNode.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace MCTS.V2.Node
3 | {
4 | using Interfaces;
5 | using Enum;
6 | using System.Collections.Concurrent;
7 | using System.Threading;
8 | using Utils;
9 | using System.Linq;
10 | using System;
11 | using System.Text;
12 | using System.Collections.Generic;
13 |
14 | internal class MultiThreadedNode : NodeBase
15 | {
16 | private long wins;
17 | private long visits;
18 | private ConcurrentBag childs;
19 | private ConcurrentStack untriedMoves;
20 |
21 | internal MultiThreadedNode(INode parent, IMove move, IGameState gameState, float uctk)
22 | : base(parent, move, uctk, gameState.PlayerJustMoved)
23 | {
24 | this.wins = 0L;
25 | this.visits = 0L;
26 |
27 | this.childs = new ConcurrentBag();
28 | var moves = gameState.GetMoves();
29 | var shuffled = moves.Shuffle();
30 |
31 | this.untriedMoves = new ConcurrentStack(shuffled); //randomize Moves
32 | }
33 |
34 | public override long Wins
35 | {
36 | get
37 | {
38 | return Interlocked.Read(ref this.wins);
39 | }
40 | }
41 |
42 | public override long Visits
43 | {
44 | get
45 | {
46 | return Interlocked.Read(ref this.visits);
47 | }
48 | }
49 |
50 | public override IEnumerable Childs
51 | {
52 | get
53 | {
54 | return this.childs;
55 | }
56 | }
57 |
58 | public override IEnumerable UntriedMoves
59 | {
60 | get
61 | {
62 | return this.untriedMoves;
63 | }
64 | }
65 |
66 | public override bool NodeIsFullyExpandedAndNonterminal
67 | {
68 | get
69 | {
70 | return (this.untriedMoves.IsEmpty && this.childs.Any());
71 | }
72 | }
73 |
74 |
75 | public override Tuple GetRandomMoveOrIsFalse()
76 | {
77 | IMove move;
78 | var result = this.untriedMoves.TryPop(out move);
79 | return new Tuple(result, move);
80 | }
81 |
82 | public override INode AddChild(Func nodeConstructor)
83 | {
84 | var node = nodeConstructor();
85 | this.childs.Add(node);
86 | return node;
87 | }
88 |
89 | public override void Update(EGameFinalStatus status)
90 | {
91 | Interlocked.Increment(ref this.visits);
92 | if (status == EGameFinalStatus.GameWon)
93 | {
94 | Interlocked.Increment(ref this.wins);
95 | }
96 | }
97 | }
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/MCTS/MCTS.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {9AACF0AA-E46F-49D2-B195-2DCD73F6C4CF}
8 | Library
9 | Properties
10 | MCTS
11 | MCTS
12 | v4.6
13 | 512
14 |
15 |
16 |
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
70 |
--------------------------------------------------------------------------------
/MCTS/V2/UCT/RootParrelization.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace MCTS.V2.UCT
3 | {
4 | using System;
5 |
6 | using Interfaces;
7 | using Node;
8 | using System.Linq;
9 | using System.Threading.Tasks;
10 | using Enum;
11 |
12 | public static class MultiThreaded
13 | {
14 | public static IMove ComputeRootParallization(IGameState gameState, int itermax, bool verbose, Action printfn, float uctk)
15 | {
16 | var rootNode = new MultiThreadedNode(null, null, gameState, uctk);
17 | var player = gameState.PlayerJustMoved;
18 | var processors = Environment.ProcessorCount;
19 |
20 | var tasks1 = (Enumerable.Range(0, processors).Select(i => Task.Factory.StartNew(() => ComputeFirstNodes(rootNode, player, itermax, gameState)))).ToArray();
21 | Task.WaitAll(tasks1);
22 |
23 | var tasks2 = (Enumerable.Range(0, processors).Select(i => Task.Factory.StartNew(() => Compute(rootNode, player, itermax, gameState)))).ToArray();
24 | Task.WaitAll(tasks2);
25 | return rootNode.MostVisitedMove();
26 | }
27 |
28 | private static IGameState Select(INode node, IGameState state)
29 | {
30 | // Select
31 | while (node.NodeIsFullyExpandedAndNonterminal)
32 | {
33 | //if (verbose)
34 | //{
35 | // printfn(node.DisplayUTC());
36 | //}
37 | node = node.UCTSelectChild();
38 | return node.Move.DoMove();
39 | }
40 | return state;
41 | }
42 |
43 | private static bool Expand(INode node, IGameState state)
44 | {
45 | // Expand
46 | var result = node.GetRandomMoveOrIsFalse();
47 | if (result.Item1)
48 | {
49 | var move = result.Item2;
50 | state = move.DoMove();
51 | Func constructor = () => new SingleThreadedNode(node, move, state, node.UCTK);
52 | node = node.AddChild(constructor);
53 | return true;
54 | }
55 | return false;
56 | }
57 |
58 | private static IGameState Rollout(IGameState state)
59 | {
60 | return state.PlayRandomlyUntilTheEnd();
61 | }
62 |
63 | private static void Backpropagate(INode node, EGameFinalStatus status)
64 | {
65 | // Backpropagate
66 | while (node != null)
67 | {
68 | node.Update(status);
69 | node = node.Parent;
70 | }
71 | }
72 |
73 | private static void ComputeFirstNodes(INode rootNode, IPlayer player, int itermax, IGameState gameState)
74 | {
75 |
76 | INode node = rootNode;
77 | var state = gameState;
78 |
79 | if (Expand(node, state))
80 | {
81 | var status = Rollout(state).GetResult(player);
82 | Backpropagate(node, status);
83 | }
84 | }
85 |
86 | private static void Compute(INode rootNode, IPlayer player, int itermax, IGameState gameState)
87 | {
88 | for (var i = 0; i < itermax; i++)
89 | {
90 | INode node = rootNode;
91 | var state = gameState;
92 |
93 | state = Select(node, state);
94 |
95 | if (Expand(node, state))
96 | {
97 | var status = Rollout(state).GetResult(player);
98 | Backpropagate(node, status);
99 | }
100 |
101 | }
102 |
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/MCTS.Test/MCTSTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {9B7C1013-2540-4D2E-9D02-BAF4DF48174E}
8 | Library
9 | Properties
10 | MCTSTest
11 | MCTSTest
12 | v4.6
13 | 512
14 |
15 |
16 | true
17 | full
18 | false
19 | bin\Debug\
20 | DEBUG;TRACE
21 | prompt
22 | 4
23 |
24 |
25 | pdbonly
26 | true
27 | bin\Release\
28 | TRACE
29 | prompt
30 | 4
31 |
32 |
33 |
34 | ..\packages\NUnit.2.6.4\lib\nunit.framework.dll
35 | True
36 |
37 |
38 | ..\packages\NUnitTestAdapter.2.0.0\lib\NUnit.VisualStudio.TestAdapter.dll
39 | True
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | {9aacf0aa-e46f-49d2-b195-2dcd73f6c4cf}
61 | MCTS
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
79 |
--------------------------------------------------------------------------------
/MCTS.Test/V1/Nims.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using MCTS.Enum;
4 | using MCTS.V1.Interfaces;
5 | using System.Linq;
6 |
7 | namespace MCTSTest.V1
8 | {
9 | public class NimPlayer : IPlayer
10 | {
11 | private int player;
12 |
13 | public NimPlayer(int player)
14 | {
15 | this.player = player;
16 | }
17 |
18 | public string Name
19 | {
20 | get
21 | {
22 | return this.player.ToString();
23 | }
24 | }
25 | }
26 |
27 | public class NimsMove : IMove
28 | {
29 | private int move;
30 |
31 | public NimsMove(int move)
32 | {
33 | this.move = move;
34 | }
35 |
36 | public string Name
37 | {
38 | get
39 | {
40 | return this.move.ToString();
41 | }
42 | }
43 | }
44 |
45 | public class NimState : IGameState
46 | {
47 | private int playerJustMoved;
48 |
49 | private int chips;
50 |
51 | IPlayer IGameState.PlayerJustMoved
52 | {
53 | get
54 | {
55 | return new NimPlayer(this.playerJustMoved);
56 | }
57 | }
58 |
59 | public NimState(int chips)
60 | {
61 | this.playerJustMoved = 2;
62 | this.chips = chips;
63 | }
64 |
65 | private NimState(int chips, int playerJustMoved)
66 | {
67 | this.playerJustMoved = playerJustMoved;
68 | this.chips = chips;
69 | }
70 |
71 | public IGameState Clone()
72 | {
73 | return new NimState(this.chips, this.playerJustMoved);
74 | }
75 |
76 | public IEnumerable GetMoves()
77 | {
78 | return Enumerable.Range(1, Math.Min(3, this.chips)).Select(m => new NimsMove(m));
79 | }
80 |
81 | public void PlayRandomlyUntilTheEnd()
82 | {
83 | var r = new Random();
84 | while(this.chips != 0)
85 | {
86 | this.chips = this.chips - r.Next(1, Math.Min(3, this.chips) + 1);
87 | this.playerJustMoved = 3 - this.playerJustMoved;
88 | }
89 | }
90 |
91 | public void DoMove(IMove move)
92 | {
93 | int m;
94 | if(int.TryParse(move.Name, out m))
95 | {
96 | if (m >= 1 && m <= 3)
97 | {
98 | this.chips -= m;
99 | this.playerJustMoved = 3 - this.playerJustMoved;
100 | return;
101 | }
102 | }
103 | throw new InvalidOperationException(string.Format("Can't apply move {0}", move.Name));
104 | }
105 |
106 | public EGameFinalStatus GetResult(IPlayer player)
107 | {
108 | if (this.chips != 0)
109 | {
110 | throw new InvalidOperationException("Can't request GetResult if game is not ended");
111 | }
112 | int p;
113 | if (! int.TryParse(player.Name, out p))
114 | {
115 | throw new InvalidOperationException(string.Format("Not a proper name, name must be 1 or 2. It was {0}",player.Name));
116 | }
117 | if (p == this.playerJustMoved)
118 | {
119 | return EGameFinalStatus.GameWon;
120 | }
121 | else
122 | {
123 | return EGameFinalStatus.GameLost;
124 | }
125 | }
126 |
127 | public override string ToString()
128 | {
129 | return string.Format("Chips:{0} PlayerJustMoved:{1}", this.chips, this.playerJustMoved);
130 | }
131 |
132 | public IPlayer PlayerJustMoved()
133 | {
134 | return new NimPlayer(this.playerJustMoved);
135 | }
136 |
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/MCTS.Test/V2/Nims.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using MCTS.Enum;
4 | using MCTS.V2.Interfaces;
5 | using System.Linq;
6 |
7 | namespace MCTSTest
8 | {
9 | public class NimPlayer : IPlayer
10 | {
11 | private int player;
12 |
13 | public NimPlayer(int player)
14 | {
15 | this.player = player;
16 | }
17 |
18 | public string Name
19 | {
20 | get
21 | {
22 | return this.player.ToString();
23 | }
24 | }
25 | }
26 |
27 | public class NimsMove : IMove
28 | {
29 | private int remaining;
30 | private int player;
31 |
32 | public NimsMove(int remaining, int player)
33 | {
34 | if (0 > remaining)
35 | {
36 | throw new ArgumentException("Remaining can not be smaller than 0", "remaining");
37 | }
38 | if (player !=1 || player != 2)
39 | {
40 | throw new ArgumentException("Player can only be 1 or 2", "player");
41 | }
42 | this.remaining = remaining;
43 | this.player = player;
44 | }
45 |
46 | public string Name
47 | {
48 | get
49 | {
50 | return this.remaining.ToString();
51 | }
52 | }
53 |
54 | public IGameState DoMove()
55 | {
56 | return new NimState(this.remaining, this.player);
57 | }
58 | }
59 |
60 | public class NimState : IGameState
61 | {
62 | private int playerJustMoved;
63 |
64 | private int chips;
65 |
66 | IPlayer IGameState.PlayerJustMoved
67 | {
68 | get
69 | {
70 | return new NimPlayer(this.playerJustMoved);
71 | }
72 | }
73 |
74 | ///
75 | /// Used to start game
76 | ///
77 | ///
78 | public NimState(int chips)
79 | {
80 | this.playerJustMoved = 2;
81 | this.chips = chips;
82 | }
83 |
84 | ///
85 | /// Used internally by Move class
86 | ///
87 | ///
88 | ///
89 | internal NimState(int chips, int playerJustMoved)
90 | {
91 | this.playerJustMoved = playerJustMoved;
92 | this.chips = chips;
93 | }
94 |
95 | public IEnumerable GetMoves()
96 | {
97 | return Enumerable.Range(1, Math.Min(3, this.chips)).Select(m => new NimsMove(this.chips - m, 3 - this.playerJustMoved));
98 | }
99 |
100 | public IGameState PlayRandomlyUntilTheEnd()
101 | {
102 | var r = new Random();
103 | while(this.chips != 0)
104 | {
105 | this.chips = this.chips - r.Next(1, Math.Min(3, this.chips) + 1);
106 | this.playerJustMoved = 3 - this.playerJustMoved;
107 | }
108 | return this;
109 | }
110 |
111 | public EGameFinalStatus GetResult(IPlayer player)
112 | {
113 | if (this.chips != 0)
114 | {
115 | throw new InvalidOperationException("Can't request GetResult if game is not ended");
116 | }
117 | int p;
118 | if (! int.TryParse(player.Name, out p))
119 | {
120 | throw new InvalidOperationException(string.Format("Not a proper name, name must be 1 or 2. It was {0}",player.Name));
121 | }
122 | if (p == this.playerJustMoved)
123 | {
124 | return EGameFinalStatus.GameWon;
125 | }
126 | else
127 | {
128 | return EGameFinalStatus.GameLost;
129 | }
130 | }
131 |
132 | public override string ToString()
133 | {
134 | return string.Format("Chips:{0} PlayerJustMoved:{1}", this.chips, this.playerJustMoved);
135 | }
136 |
137 | public IPlayer PlayerJustMoved()
138 | {
139 | return new NimPlayer(this.playerJustMoved);
140 | }
141 |
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/SimpleGames/SimpleGames.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | 2.0
8 | 01c6793b-dc76-4a03-a8db-c0a790312605
9 | Library
10 | SimpleGames
11 | SimpleGames
12 | v4.6
13 | 4.4.0.0
14 | true
15 | SimpleGames
16 |
17 |
18 | true
19 | full
20 | false
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | 3
25 | bin\Debug\SimpleGames.XML
26 |
27 |
28 | pdbonly
29 | true
30 | true
31 | bin\Release\
32 | TRACE
33 | 3
34 | bin\Release\SimpleGames.XML
35 |
36 |
37 | 11
38 |
39 |
40 |
41 |
42 | $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets
43 |
44 |
45 |
46 |
47 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | ..\packages\FsUnit.1.3.1.0\lib\FsUnit.CustomMatchers.dll
63 | True
64 |
65 |
66 | ..\packages\FsUnit.1.3.1.0\lib\FsUnit.NUnit.dll
67 | True
68 |
69 |
70 |
71 | True
72 |
73 |
74 | ..\packages\NUnit.2.6.4\lib\nunit.framework.dll
75 | True
76 |
77 |
78 |
79 |
80 |
81 | MCTS
82 | {9aacf0aa-e46f-49d2-b195-2dcd73f6c4cf}
83 | True
84 |
85 |
86 |
93 |
--------------------------------------------------------------------------------
/SimpleGames/NimsTest.fs:
--------------------------------------------------------------------------------
1 | module NimsTest
2 |
3 | open NUnit.Framework
4 | open FsUnit
5 | open SimpleGames
6 | open MCTS.Interfaces
7 |
8 | []
9 | let ``Test that with one chips to pick, there is only one move possible``() =
10 | let nims = Nims.Nims(1) :> IGameState
11 | nims.GetMoves()
12 | |> Seq.toList
13 | |> should haveLength 1
14 |
15 | []
16 | let ``Test that with two chips to pick, there is two possible moves``() =
17 | let nims = Nims.Nims(2) :> IGameState
18 | nims.GetMoves()
19 | |> Seq.toList
20 | |> should haveLength 2
21 |
22 | []
23 | let ``Test that with three chips to pick, there is three possible moves``() =
24 | let nims = Nims.Nims(3) :> IGameState
25 | nims.GetMoves()
26 | |> Seq.toList
27 | |> should haveLength 3
28 |
29 | []
30 | let ``Test that with four chips to pick, there is three possible moves``() =
31 | let nims = Nims.Nims(4) :> IGameState
32 | nims.GetMoves()
33 | |> Seq.toList
34 | |> should haveLength 3
35 |
36 |
37 | []
38 | let ``Check we got good winner``() =
39 | let nims = Nims.Nims(1, Nims.Player.Player1) :> IGameState
40 | let moves =
41 | nims.GetMoves()
42 | |> Seq.toList
43 | let nims2 = moves.Head.DoMove()
44 | nims2.GetResult(Nims.Player1)
45 | |> should equal MCTS.Enum.EGameFinalStatus.GameWon
46 |
47 | []
48 | let ``Check we got good winner also with Player2``() =
49 | let nims = Nims.Nims(1, Nims.Player.Player2) :> IGameState
50 | let moves =
51 | nims.GetMoves()
52 | |> Seq.toList
53 | let nims2 = moves.Head.DoMove()
54 | nims2.GetResult(Nims.Player2)
55 | |> should equal MCTS.Enum.EGameFinalStatus.GameWon
56 |
57 | []
58 | let ``Check we got good loser``() =
59 | let nims = Nims.Nims(1, Nims.Player.Player1) :> IGameState
60 | let moves =
61 | nims.GetMoves()
62 | |> Seq.toList
63 | let nims2 = moves.Head.DoMove()
64 | nims2.GetResult(Nims.Player2)
65 | |> should equal MCTS.Enum.EGameFinalStatus.GameLost
66 |
67 | []
68 | let ``Check we got good loser also with Player2``() =
69 | let nims = Nims.Nims(1, Nims.Player.Player2) :> IGameState
70 | let moves =
71 | nims.GetMoves()
72 | |> Seq.toList
73 | let nims2 = moves.Head.DoMove()
74 | nims2.GetResult(Nims.Player1)
75 | |> should equal MCTS.Enum.EGameFinalStatus.GameLost
76 |
77 | []
78 | let ``Check CurrentPlayer() when starting``() =
79 | let nims = Nims.Nims(1, Nims.Player.Player1) :> IGameState
80 | nims.CurrentPlayer()
81 | |> should equal Nims.Player1
82 |
83 |
84 | []
85 | let ``Check CurrentPlayer() after a move``() =
86 | let nims = Nims.Nims(1, Nims.Player.Player1) :> IGameState
87 | let moves =
88 | nims.GetMoves()
89 | |> Seq.toList
90 | let nims2 = moves.Head.DoMove()
91 | nims2.CurrentPlayer()
92 | |> should equal Nims.Player2
93 |
94 | []
95 | let ``Check random game when only one move left is a win``() =
96 | let nims = Nims.Nims(1, Nims.Player.Player1) :> IGameState
97 | nims.PlayRandomlyUntilTheEnd(Nims.Player1)
98 | |> should equal MCTS.Enum.EGameFinalStatus.GameWon
99 |
100 | nims.PlayRandomlyUntilTheEnd(Nims.Player2)
101 | |> should equal MCTS.Enum.EGameFinalStatus.GameLost
102 |
103 |
104 | []
105 | let ``Check winner after a move``()=
106 | let nims = Nims.Nims(2, Nims.Player.Player1) :> IGameState
107 | let moves = nims.GetMoves();
108 | let move =
109 | moves
110 | |> Seq.find (fun m -> m.Name = "1")
111 | let nims2 = move.DoMove()
112 | nims2.PlayRandomlyUntilTheEnd(Nims.Player2)
113 | |> should equal MCTS.Enum.EGameFinalStatus.GameWon
114 |
115 | nims2.PlayRandomlyUntilTheEnd(Nims.Player1)
116 | |> should equal MCTS.Enum.EGameFinalStatus.GameLost
117 |
118 |
119 | []
120 | let ``Check Game used in MCTS Test should be a win for Player1``()=
121 | let pickMove name (seq : IMove seq) =
122 | seq
123 | |> Seq.find (fun item -> item.Name = name)
124 | let nims = Nims.Nims(5, Nims.Player.Player1) :> IGameState
125 | let moves = nims.GetMoves();
126 | // AI Player1 *should* pick this move
127 | let move =
128 | moves
129 | |> pickMove "4"
130 | // AI Player2 can pick any move Player1 can win next turn
131 | let nims2 = move.DoMove()
132 | let moves2 =
133 | nims2.GetMoves()
134 | // any move
135 | let n = System.Random().Next(1,4)
136 | let randomMove =
137 | moves2
138 | |> pickMove (n.ToString())
139 | let nims3 = randomMove.DoMove()
140 | nims3.GetMoves()
141 | |> Seq.exists (fun move -> move.Name = "0") // win move
142 | |> should be True
143 |
144 |
145 |
146 |
147 |
--------------------------------------------------------------------------------
/MCTS/V1/Node/NodeBase.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.V1.Node
2 | {
3 | using Interfaces;
4 | using Enum;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 |
10 | internal abstract class NodeBase : INode
11 | {
12 | private readonly INode parent;
13 | private readonly IMove move;
14 | private readonly float uctk;
15 | private readonly IPlayer playerJustMoved;
16 |
17 |
18 | internal NodeBase (INode parentNode, IMove move, float uctk, IPlayer player)
19 | {
20 | this.parent = parentNode;
21 | this.move = move;
22 | this.uctk = uctk;
23 | this.playerJustMoved = player;
24 | }
25 |
26 |
27 | /// Use the UCB1 formula to select a child node. Often a constant UCTK is applied so we have
28 | /// lambda c: c.wins/c.visits + UCTK * sqrt(2*log(self.visits)/c.visits) to vary the amount of
29 | /// exploration versus exploitation.
30 | internal double ComputeUTC(INode node)
31 | {
32 | return (node.Wins / node.Visits) + (this.UCTK * Math.Sqrt(2 * Math.Log(this.Visits) / node.Visits));
33 | }
34 |
35 |
36 |
37 | public abstract bool NodeIsFullyExpandedAndNonterminal
38 | {
39 | get;
40 | }
41 |
42 | public abstract long Visits
43 | {
44 | get;
45 | }
46 |
47 | public abstract long Wins
48 | {
49 | get;
50 | }
51 |
52 | public float UCTK
53 | {
54 | get
55 | {
56 | return this.uctk;
57 | }
58 | }
59 |
60 | public INode Parent
61 | {
62 | get
63 | {
64 | return this.parent;
65 | }
66 | }
67 |
68 | public abstract IEnumerable Childs
69 | {
70 | get;
71 | }
72 |
73 | public abstract IEnumerable UntriedMoves
74 | {
75 | get;
76 | }
77 |
78 | public IMove Move
79 | {
80 | get
81 | {
82 | return this.move;
83 | }
84 | }
85 |
86 | public IPlayer PlayerJustMoved
87 | {
88 | get
89 | {
90 | return this.playerJustMoved;
91 | }
92 | }
93 |
94 | public string DisplayMostVisistedChild()
95 | {
96 | var sb = new StringBuilder();
97 | foreach (var node in this.Childs)
98 | {
99 | sb.AppendFormat("N:{0} W/V:{1}/{2}", node.Move.Name, node.Wins, node.Visits);
100 | sb.AppendLine();
101 | }
102 | return sb.ToString();
103 | }
104 |
105 | //internal string DisplayTree()
106 | //{
107 | // throw new NotImplementedException();
108 | //}
109 |
110 | //internal string DisplayUTC()
111 | //{
112 | // throw new NotImplementedException();
113 | //}
114 |
115 | public abstract INode AddChild(Func nodeConstructor);
116 |
117 | public abstract Tuple GetRandomMoveOrIsFalse();
118 |
119 | public IMove MostVisitedMove()
120 | {
121 | var firstVisitOrdered = this.Childs.OrderByDescending(node => node.Visits).First();
122 |
123 | // most visited move is a really above other moves.
124 | // done to avoid :
125 | // Node 1 : Visit = 334, Win = 2 (selected)
126 | // Node 2 : Visit = 333, Win = 330 (most promising)
127 | // Node 3 : Visit = 333, Win = 5
128 | // return first
129 | if (firstVisitOrdered.Visits > (this.Visits / this.Childs.Count()) + 1)
130 | {
131 | return firstVisitOrdered.Move;
132 | }
133 | // otherwise return most wins
134 | else
135 | {
136 | return this.Childs.OrderByDescending(node => node.Wins).First().Move;
137 | }
138 | }
139 | public INode UCTSelectChild()
140 | {
141 | // bigger is first
142 | return this.Childs.OrderByDescending(ComputeUTC).First();
143 | }
144 |
145 | public abstract void Update(EGameFinalStatus status);
146 |
147 |
148 | internal string DisplayBestWinVisitRatioChild()
149 | {
150 | var values = this.Childs.Select(node => new Tuple(100 * node.Wins / node.Visits, node.Wins, node.Visits, DisplayNode(node))).OrderByDescending(t => t.Item1);
151 | StringBuilder sb = new StringBuilder();
152 |
153 | sb.Append("MVC :");
154 | foreach (var value in values)
155 | {
156 | sb.AppendFormat(" {0}%={1}/{2} {3}", value.Item1, value.Item2, value.Item3, value.Item4);
157 | }
158 | return sb.ToString();
159 | }
160 |
161 | internal string DisplayUTC()
162 | {
163 | var values = this.Childs.Select(node => new Tuple(ComputeUTC(node), DisplayNode(node))).OrderByDescending(t => t.Item1);
164 | StringBuilder sb = new StringBuilder();
165 |
166 | sb.Append("UTC : ");
167 | foreach (var value in values)
168 | {
169 | sb.AppendFormat("{0:0.00} {1},", value.Item1, value.Item2);
170 | }
171 | return sb.ToString();
172 | }
173 |
174 | internal string Display()
175 | {
176 | var move = this.Move != null ? this.Move.Name : "No Move";
177 | return string.Format("[M: {0} W/V:{1}/{2} U:{3} C:{4}]", move, this.Wins, this.Visits, this.UntriedMoves.Count(), this.Childs.Count());
178 | }
179 |
180 | public string DisplayTree(int indent)
181 | {
182 | StringBuilder sb = new StringBuilder();
183 | sb.AppendLine();
184 | sb.Append(new string(' ', indent));
185 | sb.Append(this.Display());
186 | foreach (var child in this.Childs)
187 | {
188 | sb.Append(child.DisplayTree(indent + 1));
189 | }
190 | return sb.ToString();
191 | }
192 |
193 | private string DisplayNode(INode node)
194 | {
195 | var list = new List();
196 | var sb = new StringBuilder();
197 | while (node.Move != null)
198 | {
199 | list.Add(node.Move.Name);
200 | node = node.Parent;
201 | }
202 |
203 | list.Reverse();
204 | foreach (var move in list)
205 | {
206 | sb.AppendFormat("->{0}", move);
207 | }
208 | return sb.ToString();
209 | }
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/MCTS/V2/Node/NodeBase.cs:
--------------------------------------------------------------------------------
1 | namespace MCTS.V2.Node
2 | {
3 | using Interfaces;
4 | using Enum;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 |
10 | internal abstract class NodeBase : INode
11 | {
12 | private readonly INode parent;
13 | private readonly IMove move;
14 | private readonly float uctk;
15 | private readonly IPlayer playerJustMoved;
16 |
17 |
18 | internal NodeBase (INode parentNode, IMove move, float uctk, IPlayer player)
19 | {
20 | this.parent = parentNode;
21 | this.move = move;
22 | this.uctk = uctk;
23 | this.playerJustMoved = player;
24 | }
25 |
26 |
27 | /// Use the UCB1 formula to select a child node. Often a constant UCTK is applied so we have
28 | /// lambda c: c.wins/c.visits + UCTK * sqrt(2*log(self.visits)/c.visits) to vary the amount of
29 | /// exploration versus exploitation.
30 | internal double ComputeUTC(INode node)
31 | {
32 | return (node.Wins / node.Visits) + (this.UCTK * Math.Sqrt(2 * Math.Log(this.Visits) / node.Visits));
33 | }
34 |
35 |
36 |
37 | public abstract bool NodeIsFullyExpandedAndNonterminal
38 | {
39 | get;
40 | }
41 |
42 | public abstract long Visits
43 | {
44 | get;
45 | }
46 |
47 | public abstract long Wins
48 | {
49 | get;
50 | }
51 |
52 | public float UCTK
53 | {
54 | get
55 | {
56 | return this.uctk;
57 | }
58 | }
59 |
60 | public INode Parent
61 | {
62 | get
63 | {
64 | return this.parent;
65 | }
66 | }
67 |
68 | public abstract IEnumerable Childs
69 | {
70 | get;
71 | }
72 |
73 | public abstract IEnumerable UntriedMoves
74 | {
75 | get;
76 | }
77 |
78 | public IMove Move
79 | {
80 | get
81 | {
82 | return this.move;
83 | }
84 | }
85 |
86 | public IPlayer PlayerJustMoved
87 | {
88 | get
89 | {
90 | return this.playerJustMoved;
91 | }
92 | }
93 |
94 | public string DisplayMostVisistedChild()
95 | {
96 | var sb = new StringBuilder();
97 | foreach (var node in this.Childs)
98 | {
99 | sb.AppendFormat("N:{0} W/V:{1}/{2}", node.Move.Name, node.Wins, node.Visits);
100 | sb.AppendLine();
101 | }
102 | return sb.ToString();
103 | }
104 |
105 | //internal string DisplayTree()
106 | //{
107 | // throw new NotImplementedException();
108 | //}
109 |
110 | //internal string DisplayUTC()
111 | //{
112 | // throw new NotImplementedException();
113 | //}
114 |
115 | public abstract INode AddChild(Func nodeConstructor);
116 |
117 | public abstract Tuple GetRandomMoveOrIsFalse();
118 |
119 | public IMove MostVisitedMove()
120 | {
121 | var firstVisitOrdered = this.Childs.OrderByDescending(node => node.Visits).First();
122 |
123 | // most visited move is a really above other moves.
124 | // done to avoid :
125 | // Node 1 : Visit = 334, Win = 2 (selected)
126 | // Node 2 : Visit = 333, Win = 330 (most promising)
127 | // Node 3 : Visit = 333, Win = 5
128 | // return first
129 | if (firstVisitOrdered.Visits > (this.Visits / this.Childs.Count()) + 1)
130 | {
131 | return firstVisitOrdered.Move;
132 | }
133 | // otherwise return most wins
134 | else
135 | {
136 | return this.Childs.OrderByDescending(node => node.Wins).First().Move;
137 | }
138 | }
139 | public INode UCTSelectChild()
140 | {
141 | // bigger is first
142 | return this.Childs.OrderByDescending(ComputeUTC).First();
143 | }
144 |
145 | public abstract void Update(EGameFinalStatus status);
146 |
147 |
148 | internal string DisplayBestWinVisitRatioChild()
149 | {
150 | var values = this.Childs.Select(node => new Tuple(100 * node.Wins / node.Visits, node.Wins, node.Visits, DisplayNode(node))).OrderByDescending(t => t.Item1);
151 | StringBuilder sb = new StringBuilder();
152 |
153 | sb.Append("MVC :");
154 | foreach (var value in values)
155 | {
156 | sb.AppendFormat(" {0}%={1}/{2} {3}", value.Item1, value.Item2, value.Item3, value.Item4);
157 | }
158 | return sb.ToString();
159 | }
160 |
161 | internal string DisplayUTC()
162 | {
163 | var values = this.Childs.Select(node => new Tuple(ComputeUTC(node), DisplayNode(node))).OrderByDescending(t => t.Item1);
164 | StringBuilder sb = new StringBuilder();
165 |
166 | sb.Append("UTC : ");
167 | foreach (var value in values)
168 | {
169 | sb.AppendFormat("{0:0.00} {1},", value.Item1, value.Item2);
170 | }
171 | return sb.ToString();
172 | }
173 |
174 | internal string Display()
175 | {
176 | var move = this.Move != null ? this.Move.Name : "No Move";
177 | return string.Format("[M: {0} W/V:{1}/{2} U:{3} C:{4}]", move, this.Wins, this.Visits, this.UntriedMoves.Count(), this.Childs.Count());
178 | }
179 |
180 | public string DisplayTree(int indent)
181 | {
182 | StringBuilder sb = new StringBuilder();
183 | sb.AppendLine();
184 | sb.Append(new string(' ', indent));
185 | sb.Append(this.Display());
186 | foreach (var child in this.Childs)
187 | {
188 | sb.Append(child.DisplayTree(indent + 1));
189 | }
190 | return sb.ToString();
191 | }
192 |
193 | private string DisplayNode(INode node)
194 | {
195 | var list = new List();
196 | var sb = new StringBuilder();
197 | while (node.Move != null)
198 | {
199 | list.Add(node.Move.Name);
200 | node = node.Parent;
201 | }
202 |
203 | list.Reverse();
204 | foreach (var move in list)
205 | {
206 | sb.AppendFormat("->{0}", move);
207 | }
208 | return sb.ToString();
209 | }
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/packages/FsUnit.1.3.1.0/lib/FsUnit.NUnit.XML:
--------------------------------------------------------------------------------
1 |
2 |
3 | FsUnit.NUnit
4 |
5 |
6 |
7 | Verifies that the object that is passed in is equal to 'null'.
8 | If the object is not 'null', then an NUnit.Framework.AssertException is thrown.
9 |
10 | The object that is to be tested.
11 | The message to display in case of failure.
12 | Array of objects to be used in formatting the message.
13 |
14 |
15 |
16 | Verifies that the object that is passed in is equal to 'null'.
17 | If the object is not 'null', then an NUnit.Framework.AssertException is thrown.
18 |
19 | The object that is to be tested.
20 | The message to display in case of failure.
21 |
22 |
23 |
24 | Verifies that the object that is passed in is equal to 'null'.
25 | If the object is not 'null', then an NUnit.Framework.AssertException is thrown.
26 |
27 | The object that is to be tested.
28 |
29 |
30 |
31 | Verifies that the object that is passed in is not equal to 'null'.
32 | If the object is 'null', then an NUnit.Framework.AssertException is thrown.
33 |
34 | The object that is to be tested.
35 | The message to display in case of failure.
36 | Array of objects to be used in formatting the message.
37 |
38 |
39 |
40 | Verifies that the object that is passed in is not equal to 'null'.
41 | If the object is 'null', then an NUnit.Framework.AssertException is thrown.
42 |
43 | The object that is to be tested.
44 | The message to display in case of failure.
45 |
46 |
47 |
48 | Verifies that the object that is passed in is not equal to 'null'.
49 | If the object is 'null', then an NUnit.Framework.AssertException is thrown.
50 |
51 | The object that is to be tested.
52 |
53 |
54 |
55 | Verifies that the first value is less than or equal to the second value.
56 | If it is not, then an NUnit.Framework.AssertException is thrown.
57 |
58 | The first value, expected to be less.
59 | The second value, expected to be greater.
60 | The message to display in case of failure.
61 | Array of objects to be used in formatting the message.
62 |
63 |
64 |
65 | Verifies that the first value is less than or equal to the second value.
66 | If it is not, then an NUnit.Framework.AssertException is thrown.
67 |
68 | The first value, expected to be less.
69 | The second value, expected to be greater.
70 | The message to display in case of failure.
71 |
72 |
73 |
74 | Verifies that the first value is less than or equal to the second value.
75 | If it is not, then an NUnit.Framework.AssertException is thrown.
76 |
77 | The first value, expected to be less.
78 | The second value, expected to be greater.
79 |
80 |
81 |
82 | Verifies that the first value is less than the second value.
83 | If it is not, then an NUnit.Framework.AssertException is thrown.
84 |
85 | The first value, expected to be less.
86 | The second value, expected to be greater.
87 | The message to display in case of failure.
88 | Array of objects to be used in formatting the message.
89 |
90 |
91 |
92 | Verifies that the first value is less than the second value.
93 | If it is not, then an NUnit.Framework.AssertException is thrown.
94 |
95 | The first value, expected to be less.
96 | The second value, expected to be greater.
97 | The message to display in case of failure.
98 |
99 |
100 |
101 | Verifies that the first value is less than the second value.
102 | If it is not, then an NUnit.Framework.AssertException is thrown.
103 |
104 | The first value, expected to be less.
105 | The second value, expected to be greater.
106 |
107 |
108 |
109 | Verifies that the first value is greater than or equal to than the second value.
110 | If it is not, then an NUnit.Framework.AssertException is thrown.
111 |
112 | The first value, expected to be greater.
113 | The second value, expected to be less.
114 | The message to display in case of failure.
115 | Array of objects to be used in formatting the message.
116 |
117 |
118 |
119 | Verifies that the first value is greater than or equal to than the second value.
120 | If it is not, then an NUnit.Framework.AssertException is thrown.
121 |
122 | The first value, expected to be greater.
123 | The second value, expected to be less.
124 | The message to display in case of failure.
125 |
126 |
127 |
128 | Verifies that the first value is greater than or equal to than the second value.
129 | If it is not, then an NUnit.Framework.AssertException is thrown.
130 |
131 | The first value, expected to be greater.
132 | The second value, expected to be less.
133 |
134 |
135 |
136 | Verifies that the first value is greater than the second value.
137 | If it is not, then an NUnit.Framework.AssertException is thrown.
138 |
139 | The first value, expected to be greater.
140 | The second value, expected to be less.
141 | The message to display in case of failure.
142 | Array of objects to be used in formatting the message.
143 |
144 |
145 |
146 | Verifies that the first value is greater than the second value.
147 | If it is not, then an NUnit.Framework.AssertException is thrown.
148 |
149 | The first value, expected to be greater.
150 | The second value, expected to be less.
151 | The message to display in case of failure.
152 |
153 |
154 |
155 | Verifies that the first value is greater than the second value.
156 | If it is not, then an NUnit.Framework.AssertException is thrown.
157 |
158 | The first value, expected to be greater.
159 | The second value, expected to be less.
160 |
161 |
162 |
163 | Asserts that an object is contained in a list.
164 |
165 | The expected object.
166 | The list to be examined.
167 | The message to display in case of failure.
168 | Array of objects to be used in formatting the message.
169 |
170 |
171 |
172 | Asserts that an object is contained in a list.
173 |
174 | The expected object.
175 | The list to be examined.
176 | The message to display in case of failure.
177 |
178 |
179 |
180 | Asserts that an object is contained in a list.
181 |
182 | The expected object.
183 | The list to be examined.
184 |
185 |
186 |
187 | Asserts that two objects refer to the same object.
188 | If they are not, then an NUnit.Framework.AssertException is thrown.
189 |
190 | The expected object.
191 | The actual object.
192 | The message to display in case of failure.
193 | Array of objects to be used in formatting the message.
194 |
195 |
196 |
197 | Asserts that two objects refer to the same object.
198 | If they are not, then an NUnit.Framework.AssertException is thrown.
199 |
200 | The expected object.
201 | The actual object.
202 | The message to display in case of failure.
203 |
204 |
205 |
206 | Asserts that two objects refer to the same object.
207 | If they are not, then an NUnit.Framework.AssertException is thrown.
208 |
209 | The expected object.
210 | The actual object.
211 |
212 |
213 |
214 | Asserts that two objects do not refer to the same object.
215 | If they are not, then an NUnit.Framework.AssertException is thrown.
216 |
217 | The expected object.
218 | The actual object.
219 | The message to display in case of failure.
220 | Array of objects to be used in formatting the message.
221 |
222 |
223 |
224 | Asserts that two objects do not refer to the same object.
225 | If they are not, then an NUnit.Framework.AssertException is thrown.
226 |
227 | The expected object.
228 | The actual object.
229 | The message to display in case of failure.
230 |
231 |
232 |
233 | Asserts that two objects do not refer to the same object.
234 | If they are not, then an NUnit.Framework.AssertException is thrown.
235 |
236 | The expected object.
237 | The actual object.
238 |
239 |
240 |
241 | Verifies that two values are not equal.
242 | If they are, then an NUnit.Framework.AssertException is thrown.
243 |
244 | The expected value.
245 | The actual value.
246 | The message to display in case of failure.
247 | Array of objects to be used in formatting the message.
248 |
249 |
250 |
251 | Verifies that two values are not equal.
252 | If they are, then an NUnit.Framework.AssertException is thrown.
253 |
254 | The expected value.
255 | The actual value.
256 | The message to display in case of failure.
257 |
258 |
259 |
260 | Verifies that two values are not equal.
261 | If they are, then an NUnit.Framework.AssertException is thrown.
262 |
263 | The expected value.
264 | The actual value.
265 |
266 |
267 |
268 | Verifies that two values are equal.
269 | If they are not, then an NUnit.Framework.AssertException is thrown.
270 |
271 | The expected value.
272 | The actual value.
273 | The message to display in case of failure.
274 | Array of objects to be used in formatting the message.
275 |
276 |
277 |
278 | Verifies that two values are equal.
279 | If they are not, then an NUnit.Framework.AssertException is thrown.
280 |
281 | The expected value.
282 | The actual value.
283 | The message to display in case of failure.
284 |
285 |
286 |
287 | Verifies that two values are equal.
288 | If they are not, then an NUnit.Framework.AssertException is thrown.
289 |
290 | The expected value.
291 | The actual value.
292 |
293 |
294 |
295 | Generic test assertions.
296 |
297 |
298 |
299 |
300 | Deprecated operators. These will be removed in a future version of FsUnit.
301 |
302 |
303 |
304 |
305 |
--------------------------------------------------------------------------------
/python/python.py:
--------------------------------------------------------------------------------
1 | # This is a very simple implementation of the UCT Monte Carlo Tree Search algorithm in Python 2.7.
2 | # The function UCT(rootstate, itermax, verbose = False) is towards the bottom of the code.
3 | # It aims to have the clearest and simplest possible code, and for the sake of clarity, the code
4 | # is orders of magnitude less efficient than it could be made, particularly by using a
5 | # state.GetRandomMove() or state.DoRandomRollout() function.
6 | #
7 | # Example GameState classes for Nim, OXO and Othello are included to give some idea of how you
8 | # can write your own GameState use UCT in your 2-player game. Change the game to be played in
9 | # the UCTPlayGame() function at the bottom of the code.
10 | #
11 | # Written by Peter Cowling, Ed Powley, Daniel Whitehouse (University of York, UK) September 2012.
12 | #
13 | # Licence is granted to freely use and distribute for any sensible/legal purpose so long as this comment
14 | # remains in any distributed code.
15 | #
16 | # For more information about Monte Carlo Tree Search check out our web site at www.mcts.ai
17 |
18 | from math import *
19 | import random
20 |
21 | class GameState:
22 | """ A state of the game, i.e. the game board. These are the only functions which are
23 | absolutely necessary to implement UCT in any 2-player complete information deterministic
24 | zero-sum game, although they can be enhanced and made quicker, for example by using a
25 | GetRandomMove() function to generate a random move during rollout.
26 | By convention the players are numbered 1 and 2.
27 | """
28 | def __init__(self):
29 | self.playerJustMoved = 2 # At the root pretend the player just moved is player 2 - player 1 has the first move
30 |
31 | def Clone(self):
32 | """ Create a deep clone of this game state.
33 | """
34 | st = GameState()
35 | st.playerJustMoved = self.playerJustMoved
36 | return st
37 |
38 | def DoMove(self, move):
39 | """ Update a state by carrying out the given move.
40 | Must update playerJustMoved.
41 | """
42 | self.playerJustMoved = 3 - self.playerJustMoved
43 |
44 | def GetMoves(self):
45 | """ Get all possible moves from this state.
46 | """
47 |
48 | def GetResult(self, playerjm):
49 | """ Get the game result from the viewpoint of playerjm.
50 | """
51 |
52 | def __repr__(self):
53 | """ Don't need this - but good style.
54 | """
55 | pass
56 |
57 |
58 | class NimState:
59 | """ A state of the game Nim. In Nim, players alternately take 1,2 or 3 chips with the
60 | winner being the player to take the last chip.
61 | In Nim any initial state of the form 4n+k for k = 1,2,3 is a win for player 1
62 | (by choosing k) chips.
63 | Any initial state of the form 4n is a win for player 2.
64 | """
65 | def __init__(self, ch):
66 | self.playerJustMoved = 2 # At the root pretend the player just moved is p2 - p1 has the first move
67 | self.chips = ch
68 |
69 | def Clone(self):
70 | """ Create a deep clone of this game state.
71 | """
72 | st = NimState(self.chips)
73 | st.playerJustMoved = self.playerJustMoved
74 | return st
75 |
76 | def DoMove(self, move):
77 | """ Update a state by carrying out the given move.
78 | Must update playerJustMoved.
79 | """
80 | assert move >= 1 and move <= 3 and move == int(move)
81 | self.chips -= move
82 | self.playerJustMoved = 3 - self.playerJustMoved
83 |
84 | def GetMoves(self):
85 | """ Get all possible moves from this state.
86 | """
87 | return list(range(1,min([4, self.chips + 1])))
88 |
89 | def GetResult(self, playerjm):
90 | """ Get the game result from the viewpoint of playerjm.
91 | """
92 | assert self.chips == 0
93 | if self.playerJustMoved == playerjm:
94 | return 1.0 # playerjm took the last chip and has won
95 | else:
96 | return 0.0 # playerjm's opponent took the last chip and has won
97 |
98 | def __repr__(self):
99 | s = "Chips:" + str(self.chips) + " JustPlayed:" + str(self.playerJustMoved)
100 | return s
101 |
102 | class OXOState:
103 | """ A state of the game, i.e. the game board.
104 | Squares in the board are in this arrangement
105 | 012
106 | 345
107 | 678
108 | where 0 = empty, 1 = player 1 (X), 2 = player 2 (O)
109 | """
110 | def __init__(self):
111 | self.playerJustMoved = 2 # At the root pretend the player just moved is p2 - p1 has the first move
112 | self.board = [0,0,0,0,0,0,0,0,0] # 0 = empty, 1 = player 1, 2 = player 2
113 |
114 | def Clone(self):
115 | """ Create a deep clone of this game state.
116 | """
117 | st = OXOState()
118 | st.playerJustMoved = self.playerJustMoved
119 | st.board = self.board[:]
120 | return st
121 |
122 | def DoMove(self, move):
123 | """ Update a state by carrying out the given move.
124 | Must update playerToMove.
125 | """
126 | assert move >= 0 and move <= 8 and move == int(move) and self.board[move] == 0
127 | self.playerJustMoved = 3 - self.playerJustMoved
128 | self.board[move] = self.playerJustMoved
129 |
130 | def GetMoves(self):
131 | """ Get all possible moves from this state.
132 | """
133 | return [i for i in range(9) if self.board[i] == 0]
134 |
135 | def GetResult(self, playerjm):
136 | """ Get the game result from the viewpoint of playerjm.
137 | """
138 | for (x,y,z) in [(0,1,2),(3,4,5),(6,7,8),(0,3,6),(1,4,7),(2,5,8),(0,4,8),(2,4,6)]:
139 | if self.board[x] == self.board[y] == self.board[z]:
140 | if self.board[x] == playerjm:
141 | return 1.0
142 | else:
143 | return 0.0
144 | if self.GetMoves() == []: return 0.5 # draw
145 | assert False # Should not be possible to get here
146 |
147 | def __repr__(self):
148 | s= ""
149 | for i in range(9):
150 | s += ".XO"[self.board[i]]
151 | if i % 3 == 2: s += "\n"
152 | return s
153 |
154 | class OthelloState:
155 | """ A state of the game of Othello, i.e. the game board.
156 | The board is a 2D array where 0 = empty (.), 1 = player 1 (X), 2 = player 2 (O).
157 | In Othello players alternately place pieces on a square board - each piece played
158 | has to sandwich opponent pieces between the piece played and pieces already on the
159 | board. Sandwiched pieces are flipped.
160 | This implementation modifies the rules to allow variable sized square boards and
161 | terminates the game as soon as the player about to move cannot make a move (whereas
162 | the standard game allows for a pass move).
163 | """
164 | def __init__(self,sz = 8):
165 | self.playerJustMoved = 2 # At the root pretend the player just moved is p2 - p1 has the first move
166 | self.board = [] # 0 = empty, 1 = player 1, 2 = player 2
167 | self.size = sz
168 | assert sz == int(sz) and sz % 2 == 0 # size must be integral and even
169 | for y in range(sz):
170 | self.board.append([0]*sz)
171 | self.board[sz/2][sz/2] = self.board[sz/2-1][sz/2-1] = 1
172 | self.board[sz/2][sz/2-1] = self.board[sz/2-1][sz/2] = 2
173 |
174 | def Clone(self):
175 | """ Create a deep clone of this game state.
176 | """
177 | st = OthelloState()
178 | st.playerJustMoved = self.playerJustMoved
179 | st.board = [self.board[i][:] for i in range(self.size)]
180 | st.size = self.size
181 | return st
182 |
183 | def DoMove(self, move):
184 | """ Update a state by carrying out the given move.
185 | Must update playerToMove.
186 | """
187 | (x,y)=(move[0],move[1])
188 | assert x == int(x) and y == int(y) and self.IsOnBoard(x,y) and self.board[x][y] == 0
189 | m = self.GetAllSandwichedCounters(x,y)
190 | self.playerJustMoved = 3 - self.playerJustMoved
191 | self.board[x][y] = self.playerJustMoved
192 | for (a,b) in m:
193 | self.board[a][b] = self.playerJustMoved
194 |
195 | def GetMoves(self):
196 | """ Get all possible moves from this state.
197 | """
198 | return [(x,y) for x in range(self.size) for y in range(self.size) if self.board[x][y] == 0 and self.ExistsSandwichedCounter(x,y)]
199 |
200 | def AdjacentToEnemy(self,x,y):
201 | """ Speeds up GetMoves by only considering squares which are adjacent to an enemy-occupied square.
202 | """
203 | for (dx,dy) in [(0,+1),(+1,+1),(+1,0),(+1,-1),(0,-1),(-1,-1),(-1,0),(-1,+1)]:
204 | if self.IsOnBoard(x+dx,y+dy) and self.board[x+dx][y+dy] == self.playerJustMoved:
205 | return True
206 | return False
207 |
208 | def AdjacentEnemyDirections(self,x,y):
209 | """ Speeds up GetMoves by only considering squares which are adjacent to an enemy-occupied square.
210 | """
211 | es = []
212 | for (dx,dy) in [(0,+1),(+1,+1),(+1,0),(+1,-1),(0,-1),(-1,-1),(-1,0),(-1,+1)]:
213 | if self.IsOnBoard(x+dx,y+dy) and self.board[x+dx][y+dy] == self.playerJustMoved:
214 | es.append((dx,dy))
215 | return es
216 |
217 | def ExistsSandwichedCounter(self,x,y):
218 | """ Does there exist at least one counter which would be flipped if my counter was placed at (x,y)?
219 | """
220 | for (dx,dy) in self.AdjacentEnemyDirections(x,y):
221 | if len(self.SandwichedCounters(x,y,dx,dy)) > 0:
222 | return True
223 | return False
224 |
225 | def GetAllSandwichedCounters(self, x, y):
226 | """ Is (x,y) a possible move (i.e. opponent counters are sandwiched between (x,y) and my counter in some direction)?
227 | """
228 | sandwiched = []
229 | for (dx,dy) in self.AdjacentEnemyDirections(x,y):
230 | sandwiched.extend(self.SandwichedCounters(x,y,dx,dy))
231 | return sandwiched
232 |
233 | def SandwichedCounters(self, x, y, dx, dy):
234 | """ Return the coordinates of all opponent counters sandwiched between (x,y) and my counter.
235 | """
236 | x += dx
237 | y += dy
238 | sandwiched = []
239 | while self.IsOnBoard(x,y) and self.board[x][y] == self.playerJustMoved:
240 | sandwiched.append((x,y))
241 | x += dx
242 | y += dy
243 | if self.IsOnBoard(x,y) and self.board[x][y] == 3 - self.playerJustMoved:
244 | return sandwiched
245 | else:
246 | return [] # nothing sandwiched
247 |
248 | def IsOnBoard(self, x, y):
249 | return x >= 0 and x < self.size and y >= 0 and y < self.size
250 |
251 | def GetResult(self, playerjm):
252 | """ Get the game result from the viewpoint of playerjm.
253 | """
254 | jmcount = len([(x,y) for x in range(self.size) for y in range(self.size) if self.board[x][y] == playerjm])
255 | notjmcount = len([(x,y) for x in range(self.size) for y in range(self.size) if self.board[x][y] == 3 - playerjm])
256 | if jmcount > notjmcount: return 1.0
257 | elif notjmcount > jmcount: return 0.0
258 | else: return 0.5 # draw
259 |
260 | def __repr__(self):
261 | s= ""
262 | for y in range(self.size-1,-1,-1):
263 | for x in range(self.size):
264 | s += ".XO"[self.board[x][y]]
265 | s += "\n"
266 | return s
267 |
268 | class Node:
269 | """ A node in the game tree. Note wins is always from the viewpoint of playerJustMoved.
270 | Crashes if state not specified.
271 | """
272 | def __init__(self, move = None, parent = None, state = None):
273 | self.move = move # the move that got us to this node - "None" for the root node
274 | self.parentNode = parent # "None" for the root node
275 | self.childNodes = []
276 | self.wins = 0
277 | self.visits = 0
278 | self.untriedMoves = state.GetMoves() # future child nodes
279 | self.playerJustMoved = state.playerJustMoved # the only part of the state that the Node needs later
280 |
281 | def UCTSelectChild(self):
282 | """ Use the UCB1 formula to select a child node. Often a constant UCTK is applied so we have
283 | lambda c: c.wins/c.visits + UCTK * sqrt(2*log(self.visits)/c.visits to vary the amount of
284 | exploration versus exploitation.
285 | """
286 | s = sorted(self.childNodes, key = lambda c: c.wins/c.visits + sqrt(2*log(self.visits)/c.visits))[-1]
287 | return s
288 |
289 | def AddChild(self, m, s):
290 | """ Remove m from untriedMoves and add a new child node for this move.
291 | Return the added child node
292 | """
293 | n = Node(move = m, parent = self, state = s)
294 | self.untriedMoves.remove(m)
295 | self.childNodes.append(n)
296 | return n
297 |
298 | def Update(self, result):
299 | """ Update this node - one additional visit and result additional wins. result must be from the viewpoint of playerJustmoved.
300 | """
301 | self.visits += 1
302 | self.wins += result
303 |
304 | def __repr__(self):
305 | return "[M:" + str(self.move) + " W/V:" + str(self.wins) + "/" + str(self.visits) + " U:" + str(self.untriedMoves) + "]"
306 |
307 | def TreeToString(self, indent):
308 | s = self.IndentString(indent) + str(self)
309 | for c in self.childNodes:
310 | s += c.TreeToString(indent+1)
311 | return s
312 |
313 | def IndentString(self,indent):
314 | s = "\n"
315 | for i in range (1,indent+1):
316 | s += "| "
317 | return s
318 |
319 | def ChildrenToString(self):
320 | s = ""
321 | for c in self.childNodes:
322 | s += str(c) + "\n"
323 | return s
324 |
325 |
326 | def UCT(rootstate, itermax, verbose = False):
327 | """ Conduct a UCT search for itermax iterations starting from rootstate.
328 | Return the best move from the rootstate.
329 | Assumes 2 alternating players (player 1 starts), with game results in the range [0.0, 1.0]."""
330 |
331 | rootnode = Node(state = rootstate)
332 |
333 | for i in range(itermax):
334 | node = rootnode
335 | state = rootstate.Clone()
336 |
337 | # Select
338 | while node.untriedMoves == [] and node.childNodes != []: # node is fully expanded and non-terminal
339 | node = node.UCTSelectChild()
340 | state.DoMove(node.move)
341 |
342 | # Expand
343 | if node.untriedMoves != []: # if we can expand (i.e. state/node is non-terminal)
344 | m = random.choice(node.untriedMoves)
345 | state.DoMove(m)
346 | node = node.AddChild(m,state) # add child and descend tree
347 |
348 | # Rollout - this can often be made orders of magnitude quicker using a state.GetRandomMove() function
349 | while state.GetMoves() != []: # while state is non-terminal
350 | state.DoMove(random.choice(state.GetMoves()))
351 |
352 | # Backpropagate
353 | while node != None: # backpropagate from the expanded node and work back to the root node
354 | node.Update(state.GetResult(node.playerJustMoved)) # state is terminal. Update node with result from POV of node.playerJustMoved
355 | node = node.parentNode
356 |
357 | # Output some information about the tree - can be omitted
358 | if (verbose): print(rootnode.TreeToString(0))
359 | else: print(rootnode.ChildrenToString())
360 |
361 | return sorted(rootnode.childNodes, key = lambda c: c.visits)[-1].move # return the move that was most visited
362 |
363 | def UCTPlayGame():
364 | """ Play a sample game between two UCT players where each player gets a different number
365 | of UCT iterations (= simulations = tree nodes).
366 | """
367 | # state = OthelloState(4) # uncomment to play Othello on a square board of the given size
368 | # state = OXOState() # uncomment to play OXO
369 | state = NimState(5) # uncomment to play Nim with the given number of starting chips
370 | while (state.GetMoves() != []):
371 | print(str(state))
372 | if state.playerJustMoved == 1:
373 | m = UCT(rootstate = state, itermax = 1000, verbose = False) # play with values for itermax and verbose = True
374 | else:
375 | m = UCT(rootstate = state, itermax = 1000, verbose = False)
376 | print("Best Move: " + str(m) + "\n")
377 | state.DoMove(m)
378 | if state.GetResult(state.playerJustMoved) == 1.0:
379 | print("Player " + str(state.playerJustMoved) + " wins!")
380 | elif state.GetResult(state.playerJustMoved) == 0.0:
381 | print("Player " + str(3 - state.playerJustMoved) + " wins!")
382 | else: print("Nobody wins!")
383 |
384 | if __name__ == "__main__":
385 | """ Play a single game to the end using UCT for both players."""
386 | UCTPlayGame()
387 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------