一个 Swift pyTanks 玩家客户端。
pyTanks 是一个供 Python AI 互相战斗的战场。
现有的 pyTanks.Player 期望客户端使用 Python 编写,但是,所有通信都通过 JSON 和 WebSockets 进行。由于这些都是开放标准,理论上可以使用任何语言编写客户端。本项目提供了一个用于 pyTank 玩家的 Swift 模板。
此软件包包含 3 个公开导出的产品
pyTanks — 一个运行 SimplePlayer 示例 AI 的可执行文件。PlayerSupport — 一个库,其他软件包可以使用它来构建自定义的 Player 大脑。ClientControl — 一个库,其他软件包可以使用它来构建自定义的 Player 可执行文件。您无需 fork 此仓库,只需将此软件包添加为您自己的软件包的依赖项,即可启动并运行您自己的 AI。
要编译玩家,请运行 Utils/build-executable <config>,其中 <config> 是 debug 或 release。这将在工作目录的顶层放置一个名为 start 的可执行程序。
要运行之前编译的可执行文件,请从顶层目录运行 ./start。
主要的客户端配置选项在 ClientConfiguration 结构体中指定。您可以直接在您自己的 fork 中更改它们,但默认情况下,其中一些可以在命令行中自定义
--log logLevel,其中 logLevel 对应于以下级别之一--debug 开启调试消息日志记录--ip address,其中 address 是您希望连接的 pyTanks 服务器的 IP 地址--port p,其中 p 是您希望连接的 pyTanks 服务器的端口默认玩家是 SimplePlayer,它只是在随机方向上移动并尝试向敌方坦克射击,而不考虑墙壁。
要在 Xcode 中处理 fork,请在工作目录内的命令行中运行 swift package generate-xcodeproj。打开新生成的 pyTanks.SwiftPlayer.xcodeproj 文件后,请务必将项目目标更改为 macOS 10.12 而不是 10.10。这使其可以在 Xcode 中构建、运行和调试。
要创建新的 AI
import PlayerSupport 并使一个对象遵循 Player 协议。import CustomPlayer
import ClientControl
let myPlayer = CustomPlayer()
Game(player: myPlayer).run(arguments: CommandLine.arguments)
任何遵循 Player 协议的对象都充当坦克的“大脑”。您可以创建自己的对象来遵循此协议,也可以使现有对象遵循此协议。Player 协议具有以下要求
var playerDescription: String? - 此变量必须提供对 AI 的可选文本描述的 get 访问权限。当用户单击关联的坦克名称时,这将在 pyTanks 查看器中显示。var log: Log! - 此变量必须提供 set 访问权限,以便游戏循环可以在您的玩家上设置适当的 Log 对象。此 Log 对象可用于以同步且感知日志级别的方式打印日志消息。var gameConfig: GameConfiguration! - 此变量必须提供 set 访问权限,以便游戏循环可以在您的玩家上设置 GameConfiguration 对象。此对象描述游戏元素的大小、速度和 FPS。func connectedToServer() - 一个可能发生变异的函数,将在首次连接到服务器后立即调用。在此处执行任何不依赖于当前回合的设置工作。如果您不想等到建立连接后再进行设置工作,也可以将设置工作放在 init 方法中。func roundStarting(withGameState: GameState) - 在回合开始时调用。func makeMove(withGameState: GameState) -> Command? - 在回合期间的每一帧调用。可以为坦克返回一个命令。对于回合中的第一个移动,这将使用与 roundStarting(withGameState:) 相同的 GameState 对象调用。func tankKilled() - 当坦克被击杀时调用,无论这是否导致回合结束。func roundOver() - 当回合结束时调用,即使刚刚调用了 tankKilled()。Player 上的调用顺序如下
log 和 gameConfig 在尝试连接到服务器之前设置。playerDescription,并将新的信息字符串发送到服务器以用于 AI。connectedToServer()roundStarting(withGameState:)makeMove(withGameState:) 每帧调用tankKilled() 仅在坦克被击杀时调用roundOver() 无论谁获胜都会调用在 makeMove(withGameState:) 函数内部,您为坦克返回一个可选的命令。命令在 Command 枚举中定义。有效命令包括 go、stop、turn 和 fire。请参阅 PlayerSupport/Commands.swift 中的文档。
一些需要记住的事情
在每个回合开始时和每一帧,您都会收到一个 GameState 对象,表示在某个时间点的棋盘状态。这使您可以访问有关您自己的坦克 (.myTank)、敌方坦克 (otherTanks)、当前飞行的炮弹 (shells) 和棋盘墙壁 (walls) 的信息。请注意,otherTanks 存储在 Dictionary 中,坦克的唯一 ID 作为其键。ID 不保证在运行之间持久存在。有关 GameState 的所有可用属性,请参阅 GameState.swift 中的文档。
在您的 AI 中,您可以随时使用在您的 Player 的 log 属性上设置的 Log 对象进行日志记录。只需调用 print(_:for:) 即可打印消息,该消息取决于是否请求了特定的日志类型。您可以传递 .debug 作为日志类型,将其视为调试消息,该消息仅应在命令行上指定 --debug 时打印。
在您自己的 fork 中,您还可以轻松修改哪些日志级别与哪些日志类型关联。在 Log 类 (PlayerSupport/Log.swift) 中是 LogTypes 结构体。此结构体遵循 OptionSet,并且仅充当日志类型的位掩码。您可以通过修改以下行来更改哪些类型与哪些级别关联
/// Includes everything in level 1 plus `gameEvents` and `aiLogic`
public static let level2: LogTypes = [
.level1,
.gameEvents,
.aiLogic
]