// -*-c++-*-

/*!
  \file short_dribble_generator.cpp
  \brief short step dribble course generator Source File
*/

/*
 *Copyright:

 Copyright (C) Hidehisa AKIYAMA

 This code is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 3, or (at your option)
 any later version.

 This code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this code; see the file COPYING.  If not, write to
 the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

 *EndCopyright:
 */

/////////////////////////////////////////////////////////////////////

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "short_dribble_generator.h"

#include "dribble.h"
#include "field_analyzer.h"

#include <rcsc/player/world_model.h>
#include <rcsc/player/intercept_table.h>
#include <rcsc/common/server_param.h>
#include <rcsc/common/logger.h>
#include <rcsc/geom/circle_2d.h>
#include <rcsc/geom/segment_2d.h>
#include <rcsc/timer.h>

#include <algorithm>
#include <limits>

#include <cmath>

#define DEBUG_PROFILE
// #define DEBUG_PRINT
// #define DEBUG_PRINT_SIMULATE_DASHES
// #define DEBUG_PRINT_OPPONENT

// #define DEBUG_PRINT_SUCCESS_COURSE
// #define DEBUG_PRINT_FAILED_COURSE

using namespace rcsc;

namespace {

inline
void
debug_paint_failed( const int count,
                    const Vector2D & receive_point )
{
    dlog.addCircle( Logger::DRIBBLE,
                    receive_point.x, receive_point.y, 0.1,
                    "#ff0000" );
    char num[8];
    snprintf( num, 8, "%d", count );
    dlog.addMessage( Logger::DRIBBLE,
                     receive_point, num );
}

}

/*-------------------------------------------------------------------*/
/*!

 */
ShortDribbleGenerator::ShortDribbleGenerator()
    : M_queued_action_time( 0, 0 )
{
    M_courses.reserve( 128 );

    clear();
}

/*-------------------------------------------------------------------*/
/*!

 */
ShortDribbleGenerator &
ShortDribbleGenerator::instance()
{
    static thread_local ShortDribbleGenerator s_instance;
    return s_instance;
}

/*-------------------------------------------------------------------*/
/*!

 */
void
ShortDribbleGenerator::clear()
{
    M_total_count = 0;
    M_first_ball_pos = Vector2D::INVALIDATED;
    M_first_ball_vel.assign( 0.0, 0.0 );
    M_courses.clear();
}

/*-------------------------------------------------------------------*/
/*!

 */
void
ShortDribbleGenerator::generate( const WorldModel & wm )
{
    if ( M_update_time == wm.time() )
    {
        return;
    }
    M_update_time = wm.time();

    clear();

    if ( wm.gameMode().type() != GameMode::PlayOn
         && ! wm.gameMode().isPenaltyKickMode() )
    {
        return;
    }

    //
    // check queued action
    //

    if ( M_queued_action_time != wm.time() )
    {
        M_queued_action.reset();
    }
    else
    {
        M_courses.push_back( M_queued_action );
    }

    //
    // updater ball holder
    //
    if ( wm.self().isKickable()
         && ! wm.self().isFrozen() )
    {
        M_first_ball_pos = wm.ball().pos();
        M_first_ball_vel = wm.ball().vel();
    }
    // else if ( ! wm.existKickableTeammate()
    //           && ! wm.existKickableOpponent()
    //           && wm.interceptTable()->selfReachCycle() <= 1 )
    // {
    //     M_first_ball_pos = wm.ball().pos() + wm.ball()vel();
    //     M_first_ball_vel = wm.ball().vel() + ServerParam::i().ballDecay();
    // }
    else
    {
        return;
    }

#ifdef DEBUG_PROFILE
    Timer timer;
#endif

    createCourses( wm );

    std::sort( M_courses.begin(), M_courses.end(),
               CooperativeAction::DistCompare( ServerParam::i().theirTeamGoalPos() ) );

#ifdef DEBUG_PROFILE
    dlog.addText( Logger::DRIBBLE,
                  __FILE__": (generate) PROFILE size=%d/%d elapsed %.3f [ms]",
                  (int)M_courses.size(),
                  M_total_count,
                  timer.elapsedReal() );
#endif
}

/*-------------------------------------------------------------------*/
/*!

 */
void
ShortDribbleGenerator::setQueuedAction( const rcsc::WorldModel & wm,
                                        CooperativeAction::Ptr action )
{
    M_queued_action_time = wm.time();
    M_queued_action = action;
}

/*-------------------------------------------------------------------*/
/*!

 */
void
ShortDribbleGenerator::createCourses( const WorldModel & wm )
{
    static const int angle_div = 16;
    static const double angle_step = 360.0 / angle_div;

    const ServerParam & SP = ServerParam::i();

    const PlayerType & ptype = wm.self().playerType();

    const double my_first_speed = wm.self().vel().r();

    //
    // angle loop
    //

    for ( int a = 0; a < angle_div; ++a )
    {
        AngleDeg dash_angle = wm.self().body() + ( angle_step * a );

        //
        // angle filter
        //

        if ( wm.self().pos().x < 16.0
             && dash_angle.abs() > 100.0 )
        {
#ifdef DEBUG_PRINT
            dlog.addText( Logger::DRIBBLE,
                          __FILE__": (createTargetPoints) canceled(1) dash_angle=%.1f",
                          dash_angle.degree() );
#endif
            continue;
        }

        if ( wm.self().pos().x < -36.0
             && wm.self().pos().absY() < 20.0
             && dash_angle.abs() > 45.0 )
        {
#ifdef DEBUG_PRINT
            dlog.addText( Logger::DRIBBLE,
                          __FILE__": (createTargetPoints) canceled(2) dash_angle=%.1f",
                          dash_angle.degree() );
#endif
            continue;
        }

        int n_turn = 0;

        double my_speed = my_first_speed * ptype.playerDecay(); // first action is kick
        double dir_diff = AngleDeg( angle_step * a ).abs();

        while ( dir_diff > 10.0 )
        {
            dir_diff -= ptype.effectiveTurn( SP.maxMoment(), my_speed );
            if ( dir_diff < 0.0 ) dir_diff = 0.0;
            my_speed *= ptype.playerDecay();
            ++n_turn;
        }

        if ( n_turn >= 3 )
        {
#ifdef DEBUG_PRINT
            dlog.addText( Logger::DRIBBLE,
                          __FILE__": (createTargetPoints) canceled dash_angle=%.1f n_turn=%d",
                          dash_angle.degree(), n_turn );
#endif
            continue;
        }

#if 0
        if ( a == 0 )
        {
            //
            // simulate only dashes (no kick, no turn)
            //
            simulateDashes( wm );
        }
#endif

        if ( angle_step * a < 180.0 )
        {
            dash_angle -= dir_diff;
        }
        else
        {
            dash_angle += dir_diff;
        }

        simulateKickTurnsDashes( wm, dash_angle, n_turn );
    }
}

/*-------------------------------------------------------------------*/
/*!

 */
void
ShortDribbleGenerator::simulateDashes( const WorldModel & wm )
{
    const int opponent_reach_step = wm.interceptTable()->opponentReachCycle();
    if ( opponent_reach_step <= 1 )
    {
#ifdef DEBUG_PRINT_SIMULATE_DASHES
        dlog.addText( Logger::DRIBBLE,
                      __FILE__": (simulateDashes) exist reachable opponent" );
#endif
        return;
    }

    const ServerParam & SP = ServerParam::i();

    const double max_x = SP.pitchHalfLength() - 0.5;
    const double max_y = SP.pitchHalfWidth() - 0.5;

    const PlayerType & ptype = wm.self().playerType();
    const double kickable_area = ptype.kickableArea();

    const AngleDeg dash_angle = wm.self().body();

    const Vector2D first_self_pos = wm.self().pos();
    const Vector2D first_ball_pos = wm.ball().pos();

    const Vector2D unit_accel = Vector2D::polar2vector( 1.0, dash_angle );

    Vector2D self_pos = first_self_pos;
    Vector2D self_vel = wm.self().vel();
    StaminaModel stamina = wm.self().staminaModel();

    Vector2D ball_pos = first_ball_pos;
    Vector2D ball_vel = wm.ball().vel();

    for ( int n_dash = 1; n_dash <= 20; ++n_dash )
    {
        if ( opponent_reach_step <= n_dash )
        {
#ifdef DEBUG_PRINT_SIMULATE_DASHES
            dlog.addText( Logger::DRIBBLE,
                          "(simulateDashes) NG n_dash=%d <= opponent_reach_step=%d",
                          n_dash, opponent_reach_step );
#endif
            break;
        }

        ball_pos += ball_vel;

        if ( ball_pos.absX() > max_x
             || ball_pos.absY() > max_y )
        {
#ifdef DEBUG_PRINT_SIMULATE_DASHES
            dlog.addText( Logger::DRIBBLE,
                          "(simulateDashes) NG n_dash=%d ball_pos(%.2f %.2f) out of pitch",
                          n_dash,
                          ball_pos.x, ball_pos.y );
#endif
            break;
        }

        self_pos += self_vel;

        Vector2D ball_rel = ball_pos - self_pos;
        ball_rel.rotate( -dash_angle );

        if ( ball_rel.x < -kickable_area )
        {
#ifdef DEBUG_PRINT_SIMULATE_DASHES
            dlog.addText( Logger::DRIBBLE,
                          "(simulateDashes) NG n_dash=%d. ball_rel(%.2f %.2f) ball is back",
                          n_dash,
                          ball_rel.x, ball_rel.y );
#endif
            break;
        }

        if ( ball_rel.absY() > kickable_area )
        {
#ifdef DEBUG_PRINT_SIMULATE_DASHES
            dlog.addText( Logger::DRIBBLE,
                          "(simulateDashes) NG n_dash=%d. ball_rel(%.2f %.2f) y > kickable=%.3f",
                          n_dash,
                          ball_rel.x, ball_rel.y,
                          kickable_area );
#endif
            break;
        }

        //
        // check kickable area
        //

        double self_noise = std::min( 0.1, first_self_pos.dist( self_pos ) * SP.playerRand() );
        double ball_noise = std::min( 0.1, first_ball_pos.dist( ball_pos ) * SP.ballRand() );

        double dash_power = stamina.getSafetyDashPower( ptype,
                                                        SP.maxDashPower() );
        double max_accel_len = dash_power * ptype.dashPowerRate() * stamina.effort();

        if ( ball_rel.r2()
             > std::pow( max_accel_len + kickable_area - self_noise - ball_noise - 0.15, 2 ) )
        {
            // never kickable
#ifdef DEBUG_PRINT_SIMULATE_DASHES
            dlog.addText( Logger::DRIBBLE,
                          "(simulateDashes) NG n_dash=%d. ball_rel(%.2f %.2f) dist=%.3f never kickable",
                          n_dash,
                          ball_rel.x, ball_rel.y,
                          ball_rel.r() );
#endif
            break;
        }

        Vector2D max_dash_accel = unit_accel * max_accel_len;
        Segment2D accel_segment( self_pos, self_pos + max_dash_accel );

        if ( accel_segment.dist( ball_pos )
             > kickable_area - self_noise - ball_noise - 0.15 )
        {
            // never kickable
#ifdef DEBUG_PRINT_SIMULATE_DASHES
            dlog.addText( Logger::DRIBBLE,
                          "(simulateDashes) NG n_dash=%d. ball_rel(%.2f %.2f) segment_dist=%.3f never kickable",
                          n_dash,
                          ball_rel.x, ball_rel.y,
                          accel_segment.dist( ball_pos ) );
#endif
            break;
        }

        //
        // simulate next dash
        //

#ifdef DEBUG_PRINT_SIMULATE_DASHES
        dlog.addText( Logger::DRIBBLE,
                      "(simulateDashes) n_dash=%d. self=(%.2f %.2f) ball=(%.2f %.2f)",
                      n_dash,
                      self_pos.x, self_pos.y,
                      ball_pos.x, ball_pos.y );
#endif

        double dash_accel_len = -1.0;
        const double min_accel_len = std::min( 0.3, max_accel_len - 0.001 );
        for ( double len = max_accel_len; len > min_accel_len; len -= 0.05 )
        {
            Vector2D tmp_accel = unit_accel * len;
            Vector2D tmp_self_pos = self_pos + tmp_accel;

            double ball_dist = tmp_self_pos.dist( ball_pos );

#ifdef DEBUG_PRINT_SIMULATE_DASHES
            dlog.addText( Logger::DRIBBLE,
                          "____ test: accel=%.3f self=(%.2f %.2f) bdist=%.3f kickable=%.3f(%.3f s%.3f b%.3f) col=%.3f",
                          len,
                          tmp_self_pos.x, tmp_self_pos.y,
                          ball_dist,
                          kickable_area - self_noise - ball_noise - 0.15,
                          kickable_area, self_noise, ball_noise,
                          ptype.playerSize() + SP.ballSize() + 0.2 - 0.1*n_dash );
#endif

            if ( ball_dist < kickable_area - self_noise - ball_noise - 0.15
                 && ball_dist > ptype.playerSize() + SP.ballSize() + 0.2 - 0.1*n_dash )
            {
                dash_accel_len = len;
                break;
            }
        }

        if ( dash_accel_len < 0.0 )
        {
#ifdef DEBUG_PRINT_SIMULATE_DASHES
            dlog.addText( Logger::DRIBBLE,
                          "(simulateDashes) NG n_dash=%d. not found",
                          n_dash );
#endif
            break;
        }

        double adjust_dash_power = dash_accel_len / ( ptype.dashPowerRate() * stamina.effort() );
        Vector2D adjust_dash_accel = unit_accel * dash_accel_len;
        self_pos += adjust_dash_accel; // add accel
        self_vel += adjust_dash_accel;
        self_vel *= ptype.playerDecay();
        stamina.simulateDash( ptype, adjust_dash_power );

        ball_vel *= SP.ballDecay();

        CooperativeAction::Ptr ptr( new Dribble( wm.self().unum(),
                                                 ball_pos,
                                                 wm.ball().vel().r(),
                                                 0, // n_kick
                                                 0, // n_turn
                                                 n_dash,
                                                 "nokickDribble" ) );
        ptr->setIndex( M_total_count );
        ptr->setFirstDashPower( adjust_dash_power );
        M_courses.push_back( ptr );

#ifdef DEBUG_PRINT_SIMULATE_DASHES
        dlog.addText( Logger::DRIBBLE,
                      "(simulateDashes) ok n_dash=%d register dash_power=%.2f accel=(%.2f %.2f)",
                      n_dash,
                      adjust_dash_power,
                      adjust_dash_accel.x, adjust_dash_accel.y );
#endif
    }
}

/*-------------------------------------------------------------------*/
/*!

 */
void
ShortDribbleGenerator::simulateKickTurnsDashes( const WorldModel & wm,
                                                const AngleDeg & dash_angle,
                                                const int n_turn )
{
    //static const int max_dash = 5;
    static const int max_dash = 4; // 2009-06-29
    static const int min_dash = 2;
    //static const int min_dash = 1;

    static thread_local std::vector< Vector2D > self_cache;

    //
    // create self position cache
    //
    createSelfCache( wm, dash_angle, n_turn, max_dash, self_cache );

    const ServerParam & SP = ServerParam::i();
    const PlayerType & ptype = wm.self().playerType();

    const Vector2D trap_rel
        = Vector2D::polar2vector( ptype.playerSize() + ptype.kickableMargin() * 0.2 + SP.ballSize(),
                                  dash_angle );

    const double max_x = ( SP.keepawayMode()
                           ? SP.keepawayLength() * 0.5 - 1.5
                           : SP.pitchHalfLength() - 1.0 );
    const double max_y = ( SP.keepawayMode()
                           ? SP.keepawayWidth() * 0.5 - 1.5
                           : SP.pitchHalfWidth() - 1.0 );

#ifdef DEBUG_PRINT
    dlog.addText( Logger::DRIBBLE,
                  __FILE__": (simulateKickTurnsDashes) dash_angle=%.1f n_turn=%d",
                  dash_angle.degree(), n_turn );
#endif

    for ( int n_dash = max_dash; n_dash >= min_dash; --n_dash )
    {
        const Vector2D ball_trap_pos = self_cache[n_turn + n_dash] + trap_rel;

        ++M_total_count;

#ifdef DEBUG_PRINT
        dlog.addText( Logger::DRIBBLE,
                      "%d: n_turn=%d n_dash=%d ball_trap=(%.3f %.3f)",
                      M_total_count,
                      n_turn, n_dash,
                      ball_trap_pos.x, ball_trap_pos.y );
#endif
        if ( ball_trap_pos.absX() > max_x
             || ball_trap_pos.absY() > max_y )
        {
#ifdef DEBUG_PRINT_FAILED_COURSE
            dlog.addText( Logger::DRIBBLE,
                          "%d: xxx out of pitch" );
            debug_paint_failed( M_total_count, ball_trap_pos );
#endif
            continue;
        }

        const double term
            = ( 1.0 - std::pow( SP.ballDecay(), 1 + n_turn + n_dash ) )
            / ( 1.0 - SP.ballDecay() );
        const Vector2D first_vel = ( ball_trap_pos - M_first_ball_pos ) / term;
        const Vector2D kick_accel = first_vel - M_first_ball_vel;
        const double kick_power = kick_accel.r() / wm.self().kickRate();

        if ( kick_power > SP.maxPower()
             || kick_accel.r2() > std::pow( SP.ballAccelMax(), 2 )
             || first_vel.r2() > std::pow( SP.ballSpeedMax(), 2 ) )
        {
#ifdef DEBUG_PRINT_FAILED_COURSE
            dlog.addText( Logger::DRIBBLE,
                          "%d: xxx cannot kick. first_vel=(%.1f %.1f, r=%.2f) accel=(%.1f %.1f)r=%.2f power=%.1f",
                          M_total_count,
                          first_vel.x, first_vel.y, first_vel.r(),
                          kick_accel.x, kick_accel.y, kick_accel.r(),
                          kick_power );
            debug_paint_failed( M_total_count, ball_trap_pos );
#endif
            continue;
        }

        if ( ( M_first_ball_pos + first_vel ).dist2( self_cache[0] )
             < std::pow( ptype.playerSize() + SP.ballSize() + 0.1, 2 ) )
        {
#ifdef DEBUG_PRINT_FAILED_COURSE
            dlog.addText( Logger::DRIBBLE,
                          "%d: xxx collision. first_vel=(%.1f %.1f, r=%.2f) accel=(%.1f %.1f)r=%.2f power=%.1f",
                          M_total_count,
                          first_vel.x, first_vel.y, first_vel.r(),
                          kick_accel.x, kick_accel.y, kick_accel.r(),
                          kick_power );
            debug_paint_failed( M_total_count, ball_trap_pos );
#endif
            continue;
        }

        if ( checkOpponent( wm, ball_trap_pos, 1 + n_turn + n_dash ) )
        {
            CooperativeAction::Ptr ptr( new Dribble( wm.self().unum(),
                                                     ball_trap_pos,
                                                     first_vel.r(),
                                                     1, // n_kick
                                                     n_turn,
                                                     n_dash,
                                                     "shortDribble" ) );
            ptr->setIndex( M_total_count );
            M_courses.push_back( ptr );

#ifdef DEBUG_PRINT_SUCCESS_COURSE
            dlog.addText( Logger::DRIBBLE,
                          "%d: ok trap_pos=(%.2f %.2f) first_vel=(%.1f %.1f, r=%.2f) n_turn=%d n_dash=%d",
                          M_total_count,
                          ball_trap_pos.x, ball_trap_pos.y,
                          first_vel.x, first_vel.y, first_vel.r(),
                          n_turn, n_dash );
            dlog.addCircle( Logger::DRIBBLE,
                            ball_trap_pos.x, ball_trap_pos.y, 0.1,
                            "#00ff00" );
            char num[8]; snprintf( num, 8, "%d", M_total_count );
            dlog.addMessage( Logger::DRIBBLE,
                             ball_trap_pos, num );
#endif
        }
    }
}

/*-------------------------------------------------------------------*/
/*!

 */
void
ShortDribbleGenerator::createSelfCache( const WorldModel & wm,
                                        const AngleDeg & dash_angle,
                                        const int n_turn,
                                        const int n_dash,
                                        std::vector< Vector2D > & self_cache )
{
    const ServerParam & SP = ServerParam::i();
    const PlayerType & ptype = wm.self().playerType();

    self_cache.clear();

    StaminaModel stamina_model = wm.self().staminaModel();

    Vector2D my_pos = wm.self().pos();
    Vector2D my_vel = wm.self().vel();

    my_pos += my_vel;
    my_vel *= ptype.playerDecay();

    self_cache.push_back( my_pos ); // first element is next cycle just after kick

    for ( int i = 0; i < n_turn; ++i )
    {
        my_pos += my_vel;
        my_vel *= ptype.playerDecay();
        self_cache.push_back( my_pos );
    }

    stamina_model.simulateWaits( ptype, 1 + n_turn );

    const Vector2D unit_vec = Vector2D::polar2vector( 1.0, dash_angle );

    for ( int i = 0; i < n_dash; ++i )
    {
        double available_stamina = std::max( 0.0,
                                             stamina_model.stamina() - SP.recoverDecThrValue() - 300.0 );
        double dash_power = std::min( available_stamina, SP.maxDashPower() );
        Vector2D dash_accel = unit_vec.setLengthVector( dash_power * ptype.dashPowerRate() * stamina_model.effort() );

        my_vel += dash_accel;
        my_pos += my_vel;
        my_vel *= ptype.playerDecay();

        stamina_model.simulateDash( ptype, dash_power );

        self_cache.push_back( my_pos );
    }

}

/*-------------------------------------------------------------------*/
/*!

 */
bool
ShortDribbleGenerator::checkOpponent( const WorldModel & wm,
                                      const Vector2D & ball_trap_pos,
                                      const int dribble_step )
{
    const ServerParam & SP = ServerParam::i();

    //const double control_area = SP.tackleDist() - 0.2;
    const rcsc::AngleDeg ball_move_angle = ( ball_trap_pos - M_first_ball_pos ).th();

    const PlayerPtrCont::const_iterator o_end = wm.opponentsFromSelf().end();
    for ( PlayerPtrCont::const_iterator o = wm.opponentsFromSelf().begin();
          o != o_end;
          ++o )
    {
        if ( (*o)->distFromSelf() > 20.0 ) break;

        const PlayerType * ptype = (*o)->playerTypePtr();

        const double control_area
            = ( (*o)->goalie()
                && ball_trap_pos.x > SP.theirPenaltyAreaLineX()
                && ball_trap_pos.absY() < SP.penaltyAreaHalfWidth() )
            ? SP.catchableArea()
            : ptype->kickableArea();

        const Vector2D opp_pos = (*o)->inertiaPoint( dribble_step );

        const Vector2D ball_to_opp_rel = ( (*o)->pos() - M_first_ball_pos ).rotatedVector( -ball_move_angle );

        if ( ball_to_opp_rel.x < -4.0 )
        {
#ifdef DEBUG_PRINT_OPPONENT
            dlog.addText( Logger::DRIBBLE,
                          "%d: opponent[%d](%.2f %.2f) relx=%.2f",
                          M_total_count,
                          (*o)->unum(),
                          (*o)->pos().x, (*o)->pos().y,
                          ball_to_opp_rel.x );
#endif
            continue;
        }


        double target_dist = opp_pos.dist( ball_trap_pos );

        if ( target_dist - control_area < 0.001 )
        {
#ifdef DEBUG_PRINT_FAILED_COURSE
            dlog.addText( Logger::DRIBBLE,
                          "%d: xxx opponent %d(%.1f %.1f) kickable.",
                          M_total_count,
                          (*o)->unum(),
                          (*o)->pos().x, (*o)->pos().y );
            debug_paint_failed( M_total_count, ball_trap_pos );
#endif
            return false;
        }

        //
        // dash
        //

        double dash_dist = target_dist;
        dash_dist -= control_area * 0.5;
        dash_dist -= 0.2;

        int n_dash = ptype->cyclesToReachDistance( dash_dist );

        //
        // turn
        //

        int n_turn = ( (*o)->bodyCount() > 1
                       ? 1
                       : FieldAnalyzer::predict_player_turn_cycle( ptype,
                                                                   (*o)->body(),
                                                                   (*o)->vel().r(),
                                                                   target_dist,
                                                                   ( ball_trap_pos - opp_pos ).th(),
                                                                   control_area,
                                                                   true ) );


        int n_step = ( n_turn == 0
                       ? n_turn + n_dash
                       : n_turn + n_dash + 1 );

        int bonus_step = 0;

        if ( ball_trap_pos.x < 30.0 )
        {
            bonus_step += 1;
        }

        if ( ball_trap_pos.x < 0.0 )
        {
            bonus_step += 1;
        }

        if ( (*o)->isTackling() )
        {
            bonus_step = -5;
        }

        if ( ball_to_opp_rel.x > 0.5 )
        {
            bonus_step += bound( 0, (*o)->posCount(), 8 );
        }
        else
        {
            bonus_step += bound( 0, (*o)->posCount(), 4 );
        }

        if ( n_step - bonus_step <= dribble_step )
        {
#ifdef DEBUG_PRINT_FAILED_COURSE
            dlog.addText( Logger::DRIBBLE,
                          "%d: xxx opponent %d(%.1f %.1f) can reach."
                          " myStep=%d oppStep=%d(t:%d,d:%d) bonus=%d",
                          M_total_count,
                          (*o)->unum(),
                          (*o)->pos().x, (*o)->pos().y,
                          dribble_step,
                          n_step,
                          n_turn,
                          n_dash,
                          bonus_step );
            debug_paint_failed( M_total_count, ball_trap_pos );
#endif
            return false;
        }

#ifdef DEBUG_PRINT_OPPONENT
        dlog.addText( Logger::DRIBBLE,
                      "%d: (opponent) myStep=%d opponent[%d](%.1f %.1f)"
                      " dashDist=%.2f oppStep=%d(t:%d,d:%d) bonus=%d",
                      M_total_count,
                      dribble_step,
                      (*o)->unum(),
                      (*o)->pos().x, (*o)->pos().y,
                      dash_dist,
                      n_step,
                      n_turn,
                      n_dash,
                      bonus_step );
#endif
    }

    return true;
}