{"id":23231220,"url":"https://github.com/maxhumber/carlo","last_synced_at":"2025-08-19T16:33:33.189Z","repository":{"id":40370109,"uuid":"364916361","full_name":"maxhumber/Carlo","owner":"maxhumber","description":"Monte Carlo Tree Search Library","archived":false,"fork":false,"pushed_at":"2022-05-13T14:19:02.000Z","size":7560,"stargazers_count":6,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-05-02T05:00:55.307Z","etag":null,"topics":["monte-carlo-tree-search"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maxhumber.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-05-06T13:15:59.000Z","updated_at":"2024-04-20T22:41:28.000Z","dependencies_parsed_at":"2022-08-09T18:41:59.942Z","dependency_job_id":null,"html_url":"https://github.com/maxhumber/Carlo","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxhumber%2FCarlo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxhumber%2FCarlo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxhumber%2FCarlo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxhumber%2FCarlo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maxhumber","download_url":"https://codeload.github.com/maxhumber/Carlo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230363970,"owners_count":18214717,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["monte-carlo-tree-search"],"created_at":"2024-12-19T02:13:37.751Z","updated_at":"2024-12-19T02:13:38.194Z","avatar_url":"https://github.com/maxhumber.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch3 align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/maxhumber/Carlo/master/Images/Carlo.png\" height=\"200px\" alt=\"Carlo\"\u003e\n\u003c/h3\u003e\n\n\n### About\n\nA *Monte Carlo Tree Search* (MCTS) library for turn-based games built with Swift\n\n\n### Import\n\nImport Carlo by adding the following line to any `.swift` file:\n\n```swift\nimport Carlo\n```\n\n\n### Implement\n\nImplement Carlo by designing **player**, **move**, and **game** structs that conform to the `CarloGamePlayer`, `CarloGameMove`, and `CarloGame` protocols. \n\nWhile the first two protocols don't explicitly require anything, \"conforming\" to them might look like this: \n\n```swift\nenum ConnectThreePlayer: Int, CarloGamePlayer, CustomStringConvertible {\n    case one = 1\n    case two = 2\n    \n    var opposite: Self {\n        switch self {\n        case .one: return .two\n        case .two: return .one\n        }\n    }\n    \n    var description: String {\n        \"\\(rawValue)\"\n    }\n}\n\ntypealias ConnectThreeMove = Int\nextension ConnectThreeMove: CarloGameMove {}\n```\n\nConforming to `CarloGame` requires the following: \n\n```swift\npublic protocol CarloGame: Equatable {\n    associatedtype Player: CarloGamePlayer\n    associatedtype Move: CarloGameMove\n  \n    var currentPlayer: Player { get }\n    func availableMoves() -\u003e [Move]\n    func update(_ move: Move) -\u003e Self\n    func evaluate(for player: Player) -\u003e Evaluation\n}\n```\n\nProperly implemented it might look like this:\n\n```swift\nstruct ConnectThreeGame: CarloGame, CustomStringConvertible, Equatable {\n    typealias Player = ConnectThreePlayer\n    typealias Move = ConnectThreeMove\n\n    var array: Array\u003cInt\u003e\n  \n    // REQUIRED\n    var currentPlayer: Player\n    \n    init(length: Int = 10, currentPlayer: Player = .one) {\n        self.array = Array.init(repeating: 0, count: length)\n        self.currentPlayer = currentPlayer\n    }\n\n    // REQUIRED\n    func availableMoves() -\u003e [Move] {\n        array\n            .enumerated()\n            .compactMap { $0.element == 0 ? Move($0.offset) : nil}\n    }\n    \n    // REQUIRED\n    func update(_ move: Move) -\u003e Self {\n        var copy = self\n        copy.array[move] = currentPlayer.rawValue\n        copy.currentPlayer = currentPlayer.opposite\n        return copy\n    }\n    \n    // REQUIRED\n    func evaluate(for player: Player) -\u003e Evaluation {\n        let player3 = three(for: player)\n        let oppo3 = three(for: player.opposite)\n        let remaining0 = array.contains(0)\n        switch (player3, oppo3, remaining0) {\n        case (true, true, _): return .draw\n        case (true, false, _): return .win\n        case (false, true, _): return .loss\n        case (false, false, false): return .draw\n        default: return .ongoing(0.5)\n        }\n    }\n    \n    private func three(for player: Player) -\u003e Bool {\n        var count = 0\n        for slot in array {\n            if slot == player.rawValue {\n                count += 1\n            } else {\n                count = 0\n            }\n            if count == 3 {\n                return true\n            }\n        }\n        return false\n    }\n    \n    var description: String {\n        return array.reduce(into: \"\") { result, i in\n            result += String(i)\n        }\n    }\n}\n```\n\n\n### Use\n\nUse Carlo by scaffolding a `CarloTactician` on a `CarloGame`:\n\n```swift\ntypealias Computer = CarloTactician\u003cConnectThreeGame\u003e\n```\n\nInstantiate with arguments for `CarloGamePlayer` and a limit for the number turns to rollout in a single search iteration:\n\n```swift\nlet computer = Computer(for: .two, maxRolloutDepth: 5)\n```\n\nCall 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:\n\n```swift\nvar game = ConnectThreeGame(length: 10, currentPlayer: .one)\n/// 0000000000\ngame = game.update(4)\n/// 0000100000\ngame = game.update(0)\n/// 2000100000\ngame = game.update(7)\n/// 2000100000\ngame = game.update(2)\n/// 2020100000\ngame = game.update(9)\n/// 2020100001 ... player 2 can win if move =\u003e 1\n\ncomputer.uproot(to: game)\nfor _ in 0..\u003c50 {\n    computer.iterate()\n}\n\nlet move = computer.bestMove!\ngame = game.update(move)\n/// 2220100001 ... game over\n```\n\n\n### Install\n\nIf 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`:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/maxhumber/Carlo.git\", from: \"1.0.2\")\n]\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxhumber%2Fcarlo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaxhumber%2Fcarlo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxhumber%2Fcarlo/lists"}