一个 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
]