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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions