https://github.com/maxhumber/carlo
Monte Carlo Tree Search Library
https://github.com/maxhumber/carlo
monte-carlo-tree-search
Last synced: 11 months ago
JSON representation
Monte Carlo Tree Search Library
- Host: GitHub
- URL: https://github.com/maxhumber/carlo
- Owner: maxhumber
- License: mit
- Created: 2021-05-06T13:15:59.000Z (about 5 years ago)
- Default Branch: master
- Last Pushed: 2022-05-13T14:19:02.000Z (about 4 years ago)
- Last Synced: 2024-05-02T05:00:55.307Z (about 2 years ago)
- Topics: monte-carlo-tree-search
- Language: Swift
- Homepage:
- Size: 7.21 MB
- Stars: 6
- Watchers: 3
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
### About
A *Monte Carlo Tree Search* (MCTS) library for turn-based games built with Swift
### Import
Import Carlo by adding the following line to any `.swift` file:
```swift
import Carlo
```
### Implement
Implement Carlo by designing **player**, **move**, and **game** structs that conform to the `CarloGamePlayer`, `CarloGameMove`, and `CarloGame` protocols.
While the first two protocols don't explicitly require anything, "conforming" to them might look like this:
```swift
enum ConnectThreePlayer: Int, CarloGamePlayer, CustomStringConvertible {
case one = 1
case two = 2
var opposite: Self {
switch self {
case .one: return .two
case .two: return .one
}
}
var description: String {
"\(rawValue)"
}
}
typealias ConnectThreeMove = Int
extension ConnectThreeMove: CarloGameMove {}
```
Conforming to `CarloGame` requires the following:
```swift
public protocol CarloGame: Equatable {
associatedtype Player: CarloGamePlayer
associatedtype Move: CarloGameMove
var currentPlayer: Player { get }
func availableMoves() -> [Move]
func update(_ move: Move) -> Self
func evaluate(for player: Player) -> Evaluation
}
```
Properly implemented it might look like this:
```swift
struct ConnectThreeGame: CarloGame, CustomStringConvertible, Equatable {
typealias Player = ConnectThreePlayer
typealias Move = ConnectThreeMove
var array: Array
// REQUIRED
var currentPlayer: Player
init(length: Int = 10, currentPlayer: Player = .one) {
self.array = Array.init(repeating: 0, count: length)
self.currentPlayer = currentPlayer
}
// REQUIRED
func availableMoves() -> [Move] {
array
.enumerated()
.compactMap { $0.element == 0 ? Move($0.offset) : nil}
}
// REQUIRED
func update(_ move: Move) -> Self {
var copy = self
copy.array[move] = currentPlayer.rawValue
copy.currentPlayer = currentPlayer.opposite
return copy
}
// REQUIRED
func evaluate(for player: Player) -> Evaluation {
let player3 = three(for: player)
let oppo3 = three(for: player.opposite)
let remaining0 = array.contains(0)
switch (player3, oppo3, remaining0) {
case (true, true, _): return .draw
case (true, false, _): return .win
case (false, true, _): return .loss
case (false, false, false): return .draw
default: return .ongoing(0.5)
}
}
private func three(for player: Player) -> Bool {
var count = 0
for slot in array {
if slot == player.rawValue {
count += 1
} else {
count = 0
}
if count == 3 {
return true
}
}
return false
}
var description: String {
return array.reduce(into: "") { result, i in
result += String(i)
}
}
}
```
### Use
Use Carlo by scaffolding a `CarloTactician` on a `CarloGame`:
```swift
typealias Computer = CarloTactician
```
Instantiate with arguments for `CarloGamePlayer` and a limit for the number turns to rollout in a single search iteration:
```swift
let computer = Computer(for: .two, maxRolloutDepth: 5)
```
Call the `.iterate()` method to perform one search iteration in the tree, `.bestMove` to get the best move (so far) as found by search algorithm, and `.uproot(to:)` to recycle the tree and update the internal game state:
```swift
var game = ConnectThreeGame(length: 10, currentPlayer: .one)
/// 0000000000
game = game.update(4)
/// 0000100000
game = game.update(0)
/// 2000100000
game = game.update(7)
/// 2000100000
game = game.update(2)
/// 2020100000
game = game.update(9)
/// 2020100001 ... player 2 can win if move => 1
computer.uproot(to: game)
for _ in 0..<50 {
computer.iterate()
}
let move = computer.bestMove!
game = game.update(move)
/// 2220100001 ... game over
```
### Install
If you use [Swift Package Manager](https://swift.org/package-manager/) adding Carlo as a dependency is as easy as adding it to the `dependencies` of your `Package.swift`:
```swift
dependencies: [
.package(url: "https://github.com/maxhumber/Carlo.git", from: "1.0.2")
]
```