Skip to content

agent2d底层代码导读 #2

@hfuuss

Description

@hfuuss

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三角形分割 + 线性插值

使用fedit2工具可以方便的对阵型文件进行修改:
使用fedit2工具修改阵型文件

关于阵型文件的读取,阵型策略的更换等,都在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,然后看动作链评估等。
千里之行,始于足下。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions