WE2009底层结构说明书 王宇航 yuhangw@mail.ustc.edu.cn
行为决策流程 普通球员这里主要返回行为(6种基本动作之一)。也可以返回 视觉或通讯决策。裁判主要作出通讯决策。 main()[main.cpp] 行为决策流程 Client::RunNormal()[Client.cpp] Client::MainLoop()[Client.cpp] Player::Run ()[Player.cpp] Coach::Run()[Coach.cpp] DecisionTree::Decision()[DecisionTree.cpp] DecisionTree::Search()[DecisionTree.cpp Coach::DoDecisionMaking()[Coach.cpp] 普通球员这里主要返回行为(6种基本动作之一)。也可以返回 视觉或通讯决策。裁判主要作出通讯决策。
视觉决策部分 视觉决策可以贯穿整个行为决策始终, 也可以单独建立一个文件来处理。 09Base中视觉决策为Player::Run()中 执行DecisionTree::Decision()后面的一 段代码。 相信打过CS的同学知道看不到对手位置的后果……
听觉处理接口 听觉信息的更新为 CommunicateSystem::Update() [CommunicateSystem.cpp] 代码中预留了几个空函数,仅做参考。大家可以根据自己球队的情况修改。 通讯部分虽然也很重要,不过如果没有精力处理,可以暂不考虑。 (一个良好的通讯系统是对有限视觉与感知的最佳扩充)
介绍一些常用类 PlayerState:[PlayerState.h]中定义 从属关系不再熬述,大家可以查看代码。一般的IDE都有很好的追踪能力。 该类为球员状态的虚拟,其中包括球员位置,速度,身体方向,脖子角度等信息。 WorldState:[WorldState.h]中定义 该类中包含server中的球、球员模型, 以及server发来的一些客观赛场信息如特殊模式,比赛时间等。 (这里客观表述比较含糊,而且事实上信息的分类 也很难使它们有明显的区分度,在这里找不到的信息,尝试到其他地方找找看吧)
Agent:[Agent.h]中定义 该类可理解为是对一个Client功能的虚拟。类中包含基本的 收发信息接口。函数名与功能是相关联的,大家查看代码应 该不难理解作用。 下面举个例子,比如在Agent作用域内增加代码: mAgent.Turn(90); 那么当程序运行到该处时,会拟定一条发给server的指令, 作用是右转90度。而何时如何发送,我们暂不关心。 而惯性的影响已在底层部分处理,所以这里可以直接传入 我们需要球员实际相对自身转动的角度。
Strategy:[Strategy.h]中定义 该类中含有一些通过简单计算后得到的(可能)常用的量。如谁是最快拿到球的队友、自己的截球点在哪等。不过请 大家不要过分依赖这些量,比如当你觉得这个类中提供的 信息已影响到部分决策判断时,不妨自己写些函数来算这 些量。 PositionInfo:[PositionInfo.h]中定义 该类中主要包含与位置相关的信息,如球员相对身体的角 度、球距某球员的距离等。
介绍一些常用的工具函数位置 较为常用的工具函数主要在Geometry和Utilities两个 文件中。例如对一个角度的标准化(把720度变换为0度)、 求某条直线与射线的交点等。 其中Geometry中的函数主要处理平面几何问题。 这里额外解释一个从名字不大容易猜出功能的函数 Line::GetProjectPoint,该函数传入一个点,返回直线上 距该点最近的点。
实例 下面结合一些例子来说明球队运作的整套流程。 下面举的例子将较为详细,主要目的是为大家回去后提 供些可能的帮助,在这里只做些口头的简要说明。 下面的流程可以在单步调试过程中慢慢熟悉。
我们从main()函数开始。 WE2009_Base的命令行参数: -team_name “队名” –host “server IP” Enter/–goalie on/-coach on 比如在WE2009_Base的binary目录下输入: ./WE2009_Base –team name “WE2009_E630” -host 192.168. 26.160 那么这个Client就会连接IP为192.168.26.160机器的server,通过 Logplayer看到的队名为WE2009_E630 如果在上述命令后面再加参数-goalie on,那么上场的这个队员将 成为守门员。 上述命令每执行一次只能使一个Client连接到server,如果要上 整支球队,需要启动12次。自动上队可以通过脚本来完成, WE2009_Base中已附带脚本,输入./startWE2009.sh即可。
接下来我们以一个球员自己为例。 进入main函数后,会陆续执行下面的代码。 其中, RegisterSignalHandler()函数为报错函数, 用于校验错误,大家可不必 理会。 而后的ServerParam::instance().init(argc, argv) 与PlayerParam::instance().init(argc, argv)是一些常量的 初始化。如球场宽度,球的最大速度,球员异构信息等。 下面我们可以直接看Player::Run()这个函数,在到它之前 的过程中程序在忙于等待server响应,回复server响应, 初始化一些类,根据server信息进行模型更新等准备工作。
随后我们可以直接看mpDecisionTree->Decision(*mpAgent) 这段代码。它前面的代码执行的是在比赛过程中对球场信息 队友信息的更新。该函数会执行至ActiveBehavior beh = Search(agent, 1),其中Search函数用来选取要执行的行为, 需要说的是,Search函数中使用了模版,大家如果理解起来 有困难,直接看MutexPlan的定义,后面尖括号内是哪个类, 就在MutexPlan中执行这个类的Plan()函数。执行过后传回的 是active_behavior_list。 而后回到DecisionTree::Decision(Agent & agent)函数, 执行return beh.Execute()。这里等于执行了behavior_list里面 第一个位置ActiveBehavior中BehaviorType对应的Behavior类 下的Execute()函数。
mpDecisionTree->Decision(*mpAgent)结束后, 执行至CommunicateSystem::instance().Decision(),该函数目 前为一空壳,(CommunicateSystem 类中的 ParseReceivedTeammateMsg(char *msg) 与ParseReceivedOurCoachMsg(char *msg)负责获取server 发来的通讯信息(say)。大家可以通过在这个类中增加 HearMessage类型的参数来保存信息。这些信息可以通过 WorldState::GetHearMessage()函数获得该类的实例来使用。 这里对信息的更新是CommunicateSystem::instance().Update() 函数实现的,该函数出现在Player::Run()函数前面几行) WE2009_Base中接下来是一个简单的视觉决策,最后记log并 向server发送命令。 本周期决策结束后,等待下周期的新视觉, 继续执行Client::MainLoop()中的while循环体。
附一:教练 Coach::Run()中首先进行更新, 随后执行DoDecisionMaking()函数。 WE2009_Base中教练为空壳,其中不做出任何决策。 教练信息获取自Observer,对球场的影响主要表现在与 球员间的通讯。 参考:教练可以统计几乎绝对准确的信息。
附二:异构 分异构由教练在上场时完成,执行部分在 Coach.cpp中Coach::SendOptionToServer()函数内。 其中, mpAgent->ChangePlayerType(i, j)函数 表示将i号球员分配为j(ID)异构。 参考:异构相克,很可能比水火相克来得厉害。
附三:几点说明 有关阵型的部分代码在Formation.cpp中, 由函数TeammateFormation::InitFormationInfo()配置。 有些文件中的函数较为繁杂(如Formation),大家参考使用。 对于底层代码中的注释,不可盲从,其中有部分注释存在问题。 未能及时整理,非常抱歉。 大部分函数的作用,要靠自己读代码来了解。 文件和类是可以创建和删除的,良好的代码风格是好习惯。 打击盗版,鼓励原创。