-
Notifications
You must be signed in to change notification settings - Fork 0
Description
yangzheng0515:
agent2d目录结构
rcsc:
rcsc/action 动作类(重点)
rcsc/ann 人工神经网络类
rcsc/coach 在线教练类
rcsc/common 公共的类
rcsc/formation 一些阵型类(agent2d只使用了DT跑位)
rcsc/geom 一些几何类
rcsc/net 一些与server交换数据的类
rcsc/param 一些参数类
rcsc/player 一些球员类(重点)
rcsc/time 时间类(一般用不到)
rcsc/trainer 一些离线教练类
rcsc/util game_mode math version
src:
以bhv开头的都是在各种情况下的动作执行类(重要)
以role开头的都是角色类
以sample开头的都是示例,可以模仿其结构修改代码
以intertion开头的是意图类
以neck开头的是转脖子动作
chain_action动作链,核心
data/formations-dt 阵型文件(重要)
…
执行流程
这里可以参考YuShan12年的TDP:
Agent2D底层执行的过程如下:先由Main()函数开始,首先是一些环境变量设置,启动球员类。进入BasicClient类中,执行Run()函数,RunOnline()调用PlayerAgent类的 HandleMessage ()函数处理获得的信息,HandleMessage () 函数调用在PlayerAgent类中的Action()函数进行动作决策和Server参数的解析parse()函数。在Action()函数中依次执行ActionImpl()函数,DoArmAction()函数,DoViewAction()函数,DoNeckAction()函数以及CommunicationImpl()函数。其中ActionImpl()函数是主要的决策函数的框架。基于球员在场上的角色(Role)以及场上位置(Home_Position),执行相应的Role策略,这种基于角色的策略增加了球员的灵活性,使不同类型的球员具有不同的策略,对于球场动态环境具有更强的自适应性。
球员决策流程
每个球员的决策流程从sample_player.cpp中的actionImpl()函数开始。
/*-------------------------------------------------------------------*/
/*!
main decision
virtual method in super class
*/
void
SamplePlayer::actionImpl()
{
//
// update strategy and analyzer
//
Strategy::instance().update( world() );
FieldAnalyzer::instance().update( world() );
//
// prepare action chain
//
M_field_evaluator = createFieldEvaluator();
M_action_generator = createActionGenerator();
ActionChainHolder::instance().setFieldEvaluator( M_field_evaluator );
ActionChainHolder::instance().setActionGenerator( M_action_generator );
//
// special situations (tackle, objects accuracy, intention...)
//
if ( doPreprocess() )
{
dlog.addText( Logger::TEAM,
__FILE__": preprocess done" );
return;
}
//
// update action chain
//
ActionChainHolder::instance().update( world() );
//
// create current role
//
SoccerRole::Ptr role_ptr;
{
role_ptr = Strategy::i().createRole( world().self().unum(), world() );
if ( ! role_ptr )
{
std::cerr << config().teamName() << ": "
<< world().self().unum()
<< " Error. Role is not registerd.\nExit ..."
<< std::endl;
M_client->setServerAlive( false );
return;
}
}
//
// override execute if role accept
//
if ( role_ptr->acceptExecution( world() ) )
{
role_ptr->execute( this );
return;
}
//
// play_on mode
//
if ( world().gameMode().type() == GameMode::PlayOn )
{
role_ptr->execute( this );
return;
}
//
// penalty kick mode
//
if ( world().gameMode().isPenaltyKickMode() )
{
dlog.addText( Logger::TEAM,
__FILE__": penalty kick" );
Bhv_PenaltyKick().execute( this );
return;
}
//
// other set play mode
//
Bhv_SetPlay().execute( this );
}注释上写清楚每个模块是干嘛的了。
Play_on模式下球员的决策
分为有球策略doKick和无球策略doMove,即能踢则执行踢球策略,不能踢则执行跑位策略。
这里以CenterForward角色为例:
/*-------------------------------------------------------------------*/
/*!
*/
bool
RoleCenterForward::execute( rcsc::PlayerAgent* agent )
{
bool kickable = agent->world().self().isKickable();
if ( agent->world().existKickableTeammate()
&& agent->world().teammatesFromBall().front()->distFromBall()
< agent->world().ball().distFromSelf() )
{
kickable = false;
}
if ( kickable )
{
doKick( agent );
}
else
{
doMove( agent );
}
}
/*-------------------------------------------------------------------*/
/*!
*/
void
RoleCenterForward::doKick( PlayerAgent * agent )
{
if ( Bhv_ChainAction().execute( agent ) )
{
dlog.addText( Logger::TEAM,
__FILE__": (execute) do chain action" );
agent->debugClient().addMessage( "ChainAction" );
return;
}
Bhv_BasicOffensiveKick().execute( agent );
}
/*-------------------------------------------------------------------*/
/*!
*/
void
RoleCenterForward::doMove( PlayerAgent * agent )
{
Bhv_BasicMove().execute( agent );
}底层的跑位策略
/*-------------------------------------------------------------------*/
/*!
*/
bool
Bhv_BasicMove::execute( PlayerAgent * agent )
{
dlog.addText( Logger::TEAM,
__FILE__": Bhv_BasicMove" );
//-----------------------------------------------
// tackle
if ( Bhv_BasicTackle( 0.8, 80.0 ).execute( agent ) )
{
return true;
}
const WorldModel & wm = agent->world();
/*--------------------------------------------------------*/
// chase ball
const int self_min = wm.interceptTable()->selfReachCycle();
const int mate_min = wm.interceptTable()->teammateReachCycle();
const int opp_min = wm.interceptTable()->opponentReachCycle();
if ( ! wm.existKickableTeammate()
&& ( self_min <= 3
|| ( self_min <= mate_min
&& self_min < opp_min + 3 )
)
)
{
dlog.addText( Logger::TEAM,
__FILE__": intercept" );
Body_Intercept().execute( agent );
agent->setNeckAction( new Neck_OffensiveInterceptNeck() );
return true;
}
const Vector2D target_point = Strategy::i().getPosition( wm.self().unum() );
const double dash_power = Strategy::get_normal_dash_power( wm );
double dist_thr = wm.ball().distFromSelf() * 0.1;
if ( dist_thr < 1.0 ) dist_thr = 1.0;
dlog.addText( Logger::TEAM,
__FILE__": Bhv_BasicMove target=(%.1f %.1f) dist_thr=%.2f",
target_point.x, target_point.y,
dist_thr );
agent->debugClient().addMessage( "BasicMove%.0f", dash_power );
agent->debugClient().setTarget( target_point );
agent->debugClient().addCircle( target_point, dist_thr );
if ( ! Body_GoToPoint( target_point, dist_thr, dash_power
).execute( agent ) )
{
Body_TurnToBall().execute( agent );
}
if ( wm.existKickableOpponent()
&& wm.ball().distFromSelf() < 18.0 )
{
agent->setNeckAction( new Neck_TurnToBall() );
}
else
{
agent->setNeckAction( new Neck_TurnToBallOrScan() );
}
return true;
}首先考虑是否铲球,必要时去截球,否则跑本位点来维持阵型。
可见,底层的跑位策略过于简单,需要大幅度的修改,添加各种策略。
阵型
在现实的比赛中,一支球队总是有 适合他的阵型,比如说4-3-3或者4-4-2等等,而在agent2d底层代码中,也是自带了阵型文件,存储在agent2d-2.1.0/src/formations-dt文件夹中,其中normal-formation.conf文件就是对应于playon模式下球队的阵型。所谓的阵型,就是给每个角色的球员一个基准点,球员的跑位就是根据球的位置和基准点的位置而确定的(可以理解为z=f(x,y),z是球员的跑位目标的坐标,x为球的坐标,y为该类角色球员的基准点坐标)。具体的阵型配置代码在球队的策略类Strategy中以及底层库的librcsc-3.1.1/rcsc/formation文件夹中。
根据自身球员编号获取阵型点:
const Vector2D target_point = Strategy::i().getPosition( wm.self().unum() );normal-formation.conf
Formation DelaunayTriangulation 2
Begin Roles
1 Goalie 0
2 CenterBack -1
3 CenterBack 2
4 SideBack -1
5 SideBack 4
6 DefensiveHalf 0
7 OffensiveHalf -1
8 OffensiveHalf 7
9 SideForward -1
10 SideForward 9
11 CenterForward 0
End Roles
Begin Samples 2 115
----- 0 ------
Ball 54.5 -36
1 -50 0
2 -0.72 -12
3 -0.84 1.08
4 4.9 -27.3
5 10 8
6 27.43 -16.5
7 33.12 -27
8 38.22 -3.5
9 44.22 -30.85
10 46 6.8
11 46.28 -14
----- 1 -----
Ball 54.5 36
1 -50 -0
2 -0.84 -1.08
3 -0.72 12
4 10 -8
5 4.9 27.3
6 27.43 16.5
7 38.22 3.5
8 33.12 27
9 46 -6.8
10 44.22 30.85
11 46.28 14
----- 2 -----
.
.
.
原理:Delaunay三角形分割 + 线性插值
关于阵型文件的读取,阵型策略的更换等,都在strategy.cpp中,具体可以看看read() getFormation() updatePosition()等等函数,就不贴代码了。
底层的阵型文件,例如:normal-formation.conf、offense-formation.conf、defense-formation.conf都是一样的,所以阵型文件的修改也是重点。
底层的有球策略
/*-------------------------------------------------------------------*/
/*!
*/
bool
Bhv_ChainAction::execute( PlayerAgent * agent )
{
dlog.addText( Logger::TEAM,
__FILE__": Bhv_ChainAction" );
if ( doTurnToForward( agent ) )
{
return true;
}
const ServerParam & SP = ServerParam::i();
const WorldModel & wm = agent->world();
const CooperativeAction & first_action = M_chain_graph.getFirstAction();
ActionChainGraph::debug_send_chain( agent, M_chain_graph.getAllChain() );
const Vector2D goal_pos = SP.theirTeamGoalPos();
agent->setNeckAction( new Neck_TurnToReceiver( M_chain_graph ) );
switch ( first_action.category() ) {
case CooperativeAction::Shoot:
{
dlog.addText( Logger::TEAM,
__FILE__" (Bhv_ChainAction) shoot" );
if ( Body_ForceShoot().execute( agent ) )
{
agent->setNeckAction( new Neck_TurnToGoalieOrScan() );
return true;
}
break;
}
case CooperativeAction::Dribble:
{
if ( wm.gameMode().type() != GameMode::PlayOn
&& ! wm.gameMode().isPenaltyKickMode() )
{
agent->debugClient().addMessage( "CancelChainDribble" );
dlog.addText( Logger::TEAM,
__FILE__" (Bhv_ChainAction) cancel dribble" );
return false;
}
const Vector2D & dribble_target = first_action.targetPoint();
dlog.addText( Logger::TEAM,
__FILE__" (Bhv_ChainAction) dribble target=(%.1f %.1f)",
dribble_target.x, dribble_target.y );
NeckAction::Ptr neck;
double goal_dist = goal_pos.dist( dribble_target );
if ( goal_dist < 18.0 )
{
int count_thr = 0;
if ( goal_dist < 13.0 )
{
count_thr = -1;
}
agent->debugClient().addMessage( "ChainDribble:LookGoalie" );
neck = NeckAction::Ptr( new Neck_TurnToGoalieOrScan( count_thr ) );
}
if ( Bhv_NormalDribble( first_action, neck ).execute( agent ) )
{
return true;
}
break;
}
case CooperativeAction::Hold:
{
if ( wm.gameMode().type() != GameMode::PlayOn )
{
agent->debugClient().addMessage( "CancelChainHold" );
dlog.addText( Logger::TEAM,
__FILE__" (Bhv_ChainAction) cancel hold" );
return false;
}
if ( wm.ball().pos().x < -SP.pitchHalfLength() + 8.0
&& wm.ball().pos().absY() < SP.goalHalfWidth() + 1.0 )
{
agent->debugClient().addMessage( "ChainHold:Clear" );
dlog.addText( Logger::TEAM,
__FILE__" (Bhv_ChainAction) cancel hold. clear ball" );
Body_ClearBall().execute( agent );
agent->setNeckAction( new Neck_ScanField() );
return true;
}
agent->debugClient().addMessage( "hold" );
dlog.addText( Logger::TEAM,
__FILE__" (Bhv_ChainAction) hold" );
Body_HoldBall().execute( agent );
agent->setNeckAction( new Neck_ScanField() );
return true;
break;
}
case CooperativeAction::Pass:
{
dlog.addText( Logger::TEAM,
__FILE__" (Bhv_ChainAction) pass" );
Bhv_PassKickFindReceiver( M_chain_graph ).execute( agent );
return true;
break;
}
case CooperativeAction::Move:
{
dlog.addText( Logger::TEAM,
__FILE__" (Bhv_ChainAction) move" );
if ( Body_GoToPoint( first_action.targetPoint(),
1.0,
SP.maxDashPower() ).execute( agent ) )
{
agent->setNeckAction( new Neck_ScanField() );
return true;
}
break;
}
case CooperativeAction::NoAction:
{
dlog.addText( Logger::TEAM,
__FILE__" (Bhv_ChainAction) no action" );
return true;
break;
}
default:
dlog.addText( Logger::TEAM,
__FILE__" (Bhv_ChainAction) invalid category" );
break;
}
return false;
}
其中const CooperativeAction & first_action = M_chain_graph.getFirstAction();是获取评分最高的链的第一个动作。
显而易见下面的代码是执行该动作。
动作链机制
动作链机制是agent2d的决策的核心,代码也比较复杂,需要很深的功底才能在其上进行修改和优化。作为初学者,先看懂代码、理解运行机制即可。
有球策略的流程大致是:
动作推理机生成可执行的动作和该动作执行后的状态,根据预测的状态再生成可执行的动作和状态,从而形成了链。根据搜索算法和评估器,对链进行评分,最终选取评分最高的链来执行它的第一个动作。
具体的代码还是在sample_player.cpp的actionImpl()函数中:
//
// prepare action chain
//
M_field_evaluator = createFieldEvaluator();
M_action_generator = createActionGenerator();
ActionChainHolder::instance().setFieldEvaluator( M_field_evaluator );
ActionChainHolder::instance().setActionGenerator( M_action_generator );
//
// update action chain
//
ActionChainHolder::instance().update( world() );关于动作的生成,看actgen_*.cpp
搜索算法看action_chain_graph.cpp中的calculateResultBestFirstSearch函数
评估器看sample_field_evaluator.cpp
动作的执行看Bhv_ChainAction::execute()
评估器
评估器相当于持球者的大脑,持球、带球、传球等动作的选择是很重要的,尤其是在一些复杂、关键的情况下,如何能决策出合理的动作是进球的关键。
/*-------------------------------------------------------------------*/
/*!
*/
static
double
evaluate_state( const PredictState & state )
{
const ServerParam & SP = ServerParam::i();
const AbstractPlayerObject * holder = state.ballHolder();
#ifdef DEBUG_PRINT
dlog.addText( Logger::ACTION_CHAIN,
"========= (evaluate_state) ==========" );
#endif
//
// if holder is invalid, return bad evaluation
//
if ( ! holder )
{
#ifdef DEBUG_PRINT
dlog.addText( Logger::ACTION_CHAIN,
"(eval) XXX null holder" );
#endif
return - DBL_MAX / 2.0;
}
const int holder_unum = holder->unum();
//
// ball is in opponent goal
//
if ( state.ball().pos().x > + ( SP.pitchHalfLength() - 0.1 )
&& state.ball().pos().absY() < SP.goalHalfWidth() + 2.0 )
{
#ifdef DEBUG_PRINT
dlog.addText( Logger::ACTION_CHAIN,
"(eval) *** in opponent goal" );
#endif
return +1.0e+7;
}
//
// ball is in our goal
//
if ( state.ball().pos().x < - ( SP.pitchHalfLength() - 0.1 )
&& state.ball().pos().absY() < SP.goalHalfWidth() )
{
#ifdef DEBUG_PRINT
dlog.addText( Logger::ACTION_CHAIN,
"(eval) XXX in our goal" );
#endif
return -1.0e+7;
}
//
// out of pitch
//
if ( state.ball().pos().absX() > SP.pitchHalfLength()
|| state.ball().pos().absY() > SP.pitchHalfWidth() )
{
#ifdef DEBUG_PRINT
dlog.addText( Logger::ACTION_CHAIN,
"(eval) XXX out of pitch" );
#endif
return - DBL_MAX / 2.0;
}
//
// set basic evaluation
//
double point = state.ball().pos().x;
point += std::max( 0.0,
40.0 - ServerParam::i().theirTeamGoalPos().dist( state.ball().pos() ) );
#ifdef DEBUG_PRINT
dlog.addText( Logger::ACTION_CHAIN,
"(eval) ball pos (%f, %f)",
state.ball().pos().x, state.ball().pos().y );
dlog.addText( Logger::ACTION_CHAIN,
"(eval) initial value (%f)", point );
#endif
//
// add bonus for goal, free situation near offside line
//
if ( FieldAnalyzer::can_shoot_from
( holder->unum() == state.self().unum(),
holder->pos(),
state.getPlayerCont( new OpponentOrUnknownPlayerPredicate( state.ourSide() ) ),
VALID_PLAYER_THRESHOLD ) )
{
point += 1.0e+6;
#ifdef DEBUG_PRINT
dlog.addText( Logger::ACTION_CHAIN,
"(eval) bonus for goal %f (%f)", 1.0e+6, point );
#endif
if ( holder_unum == state.self().unum() )
{
point += 5.0e+5;
#ifdef DEBUG_PRINT
dlog.addText( Logger::ACTION_CHAIN,
"(eval) bonus for goal self %f (%f)", 5.0e+5, point );
#endif
}
}
return point;
}底层的评估过于简单,除了一些特殊情况外,仅仅根据球的目标点的位置来评分。
所以这里是改动的核心。
总结
就说这么多。当然agent2d底层可远远不止这些内容。
个人建议,新手还是先从main函数开始,把底层的执行流程看懂,然后是从sample_player开始看懂球员的决策流程。然后看跑位basic_move,然后看动作链评估等。
千里之行,始于足下。

