Complete.Org: Mailing Lists: Archives: freeciv-dev: March 2002:
[Freeciv-Dev] Re: [Patch][RFC] AI can fly
Home

[Freeciv-Dev] Re: [Patch][RFC] AI can fly

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: Gregory Berkolaiko <Gregory.Berkolaiko@xxxxxxxxxxxx>, freeciv-dev@xxxxxxxxxxx
Subject: [Freeciv-Dev] Re: [Patch][RFC] AI can fly
From: Raahul Kumar <raahul_da_man@xxxxxxxxx>
Date: Fri, 1 Mar 2002 22:50:05 -0800 (PST)

--- Gregory Berkolaiko <Gregory.Berkolaiko@xxxxxxxxxxxx> wrote:
> On Fri, 1 Mar 2002, Gregory Berkolaiko wrote:
> > Major defeciency: it only considers victims within 1-move reach of the 
> > unit.  So units don't go to another island if there is big fighting there. 
>

Bigger threshold then?
 
> > Other deficiencies: AI build selection is stupid but I had to follow it so 
> > it wouldn't build bombers over every other possible unit.  Also AI would 
> > always build barracks before building any attacking units and I did the 
> > same for air units.  As a consequence AI very rarely actually gets to 
> > building flying stuff.
> >

Bad. Airports and ports are very expensive to upkeep. If the AI should start
building them everywhere it's going to run out of cash. Some sort of revenue
count thing should be done so we don't keep building improvements if we are
running a deficit.
 
> > Please test and tell me your idea on how bombers _should_ function.
> > 
> > G.
> > 
> > 
> > 
> > 
> > Index: ai/advmilitary.c
> ===================================================================
> RCS file: /home/freeciv/CVS/freeciv/ai/advmilitary.c,v
> retrieving revision 1.93
> diff -u -r1.93 advmilitary.c
> --- ai/advmilitary.c  2002/03/01 14:07:28     1.93
> +++ ai/advmilitary.c  2002/03/01 20:15:19
> @@ -614,13 +614,11 @@
>                unit_types[i].move_type == HELI_MOVING) && acity &&
>                acity->ai.invasion == 2) b0 = f * SHIELD_WEIGHTING;
>        else {
> -        int a_squared = acity->ai.a * acity->ai.a;
> -
>          /* See aiunit.c:find_something_to_kill() for comments. */
>          
>          b0 = kill_desire(b, a, (f + (acity ? acity->ai.f : 0)), d, g);
> -        if (acity && b * a_squared > acity->ai.f * d) {
> -          b0 -= kill_desire(b, a_squared, acity->ai.f, d, g);
> +        if (acity && b * acity->ai.a * acity->ai.a > acity->ai.f * d) {

I'm not sure this is an improvement. You got rid of one variable, and
introduced 
a very hard to read line. The code is hard enough to read. Don't make it worse.

<snip>
> +      if (!myunit->id) {
> +     choice->choice = v;
> +     choice->type = CT_ATTACKER;
> +     choice->want = e;
> +     if (needferry) ai_choose_ferryboat(pplayer, pcity, choice);
> +     freelog(LOG_DEBUG, "%s has chosen attacker, %s, want=%d",
> +             pcity->name, unit_types[choice->choice].name, choice->want);
>        } else {
> -        if (!myunit->id) {
> -          choice->choice = v;
> -          choice->type = CT_ATTACKER;
> -          choice->want = e;
> -          if (needferry) ai_choose_ferryboat(pplayer, pcity, choice);
> -       freelog(LOG_DEBUG, "%s has chosen attacker, %s",
> -                     pcity->name, unit_types[choice->choice].name);
> -        } else {
> -          choice->choice = ai_choose_defender(pcity);
> -       freelog(LOG_DEBUG, "%s has chosen defender, %s",
> -                     pcity->name, unit_types[choice->choice].name);
> -          choice->type = CT_NONMIL;
> -          choice->want = e;
> -        }
> -        if (is_sailing_unit(myunit) && improvement_exists(B_PORT)
> -         && !city_got_building(pcity, B_PORT)) {
> -       Tech_Type_id tech = get_improvement_type(B_PORT)->tech_req;
> -          if (get_invention(pplayer, tech) == TECH_KNOWN) {
> -            choice->choice = B_PORT;
> -            choice->want = e;
> -            choice->type = CT_BUILDING;
> -          } else pplayer->ai.tech_want[tech] += e;
> -        }
> +     choice->choice = ai_choose_defender(pcity);
> +     freelog(LOG_DEBUG, "%s has chosen defender, %s, want=%d",
> +             pcity->name, unit_types[choice->choice].name, choice->want);
> +     choice->type = CT_NONMIL;
> +     choice->want = e;
>        }
>      }
>    }

Love it ;).
 
> +/*******************************************************************
> + * Chooses the best available and usable air unit and records it in 
> + * choice, if it's better than previous choice
> + * The interface is somewhat different from other ai_choose, but
> + * that's what it should be like, I believe -- GB
> + ******************************************************************/
> +bool ai_choose_attacker_air(struct player *pplayer, struct city *pcity, 
> +                         struct ai_choice *choice)
> +{
> +  Unit_Type_id u_type;
> +  bool want_something = FALSE;
> +
> +  /* military_advisor_choose_build does something idiotic, 
> +   * this function should not be called if there is danger... */
> +  if (choice->want >= 100 && choice->type != CT_ATTACKER) return 0;
> +
> +  if (!player_knows_techs_with_flag(pplayer, TF_BUILD_AIRBORNE)) return
> FALSE;
> +  /* TODO: unit_types_iterate */
> +  for (u_type = 0; u_type < game.num_unit_types; u_type++) {

I don't know if Raimar already did this, but possibly replace with 
unit_iterate.

> +    if (get_unit_type(u_type)->move_type != AIR_MOVING) continue;
> +    if (can_build_unit(pcity, u_type)){
> +      struct unit *virtual_unit = 
> +     create_unit_virtual(pplayer, pcity->x, pcity->y, u_type, TRUE);
> +      int profit = find_something_to_bomb(virtual_unit);

Comments please. It looks like comparing how well the virtual air unit would
do against opposition. I could have sworn this was already done elsewhere in
the AI code. Is the existing code only for ground and sea units?

> +/*********************************************************************
> + * Before building attacker, AI builds a barracks/port/airport
> + * TODO: something more sophisticated, like estimating future demand
> + * for military units, considering Sun Tzu instead.
> + ********************************************************************/
> +static void adjust_ai_attacker_choice(struct city *pcity, 
> +                                   struct ai_choice *choice)
> +{
> +  enum unit_move_type move_type;
> +  struct player *pplayer = city_owner(pcity);
> +
> +  if (choice->type != CT_ATTACKER) return;
> +  if (do_make_unit_veteran(pcity, choice->choice)) return;
> +
> +  move_type = get_unit_type(choice->choice)->move_type;
> +  if (improvement_variant(B_BARRACKS)==1) {
> +    /* Barracks will work for all units! */
> +    move_type = LAND_MOVING;
> +  }
> +
> +  switch(move_type) {
> +  case LAND_MOVING:
> +    if (player_knows_improvement_tech(pplayer, B_BARRACKS3)) {
> +      choice->choice = B_BARRACKS3;
> +    } else if (player_knows_improvement_tech(pplayer, B_BARRACKS2)) {
> +      choice->choice = B_BARRACKS2;
> +    } else {
> +      choice->choice = B_BARRACKS;
> +    }
> +    choice->type = CT_BUILDING;
> +    break;
> +  case SEA_MOVING:
> +    if (player_knows_improvement_tech(pplayer, B_PORT)) {
> +      choice->choice = B_PORT;
> +      choice->type = CT_BUILDING;
> +    }
> +    break;
> +  case HELI_MOVING:
> +  case AIR_MOVING:
> +    if (player_knows_improvement_tech(pplayer, B_AIRPORT)) {
> +      choice->choice = B_AIRPORT;
> +      choice->type = CT_BUILDING;
> +    }
> +    break;
> +  default:
> +    freelog(LOG_ERROR, "Unknown move_type in adjust_ai_attacker_choice");
> +  }
> +}
> +
<snip>
> +  if( is_barbarian(pplayer) )
> +    punit->fuel = BARBARIAN_LIFE;

Pure hackery using punit->fuel as life counter.



>  /**************************************************************************
> +Looks for nearest airbase for punit.
> +Returns 0 if not found.
> +**************************************************************************/
> +int find_nearest_airbase(int x, int y, struct unit *punit, int *xref, int
> *yref)
> +{
> +  struct player *pplayer = unit_owner(punit);

Greg, you might want to consider using allied cities.

> +  int moves_left = punit->moves_left / SINGLE_MOVE;
> +
> +  iterate_outward(x, y, moves_left, x1, y1) {
> +    if (is_airunit_refuel_point(x1, y1, pplayer, punit->type, 0)

Would this mean that aircraft carriers count as nearest airbase? If so,
what happens if an airunit gets there and the aircraft carrier has moved.

> +     && (air_can_move_between (moves_left, x, y, x1, y1, pplayer) >= 0)) {
> +      *xref = x1;
> +      *yref = y1;
> +      return TRUE;
> +    }
> +  } iterate_outward_end;
> +
> +  return FALSE;
> +}
> +
> +/********************************************************************
> + * Is it a city/fortress or will the whole stack die in an attack
> + * TODO: use new killstack thing
> + *******************************************************************/
> +static int is_stack_vulnerable(int x, int y)
> +{
> +  return !(map_get_city(x, y) != NULL ||
> +        map_has_special(x, y, S_FORTRESS) ||
> +        map_has_special(x, y, S_AIRBASE) );
> +}
> +

Finally. Here's hoping someone starts using this in the combat calcs.

> +/**********************************************************************
> +Returns an estimate for the profit gained through attack.
> +Assumes that the victim is within one day's flight
> +***********************************************************************/
> +int ai_evaluate_tile_for_attack(struct unit *punit, 
> +                             int dest_x, int dest_y)
> +{
> +  struct unit *pdefender = get_defender(punit, dest_x, dest_y);
> +  /* unit costs in shields */
> +  int balanced_cost, unit_cost, victim_cost = 0;
> +  /* unit stats */
> +  int unit_attack, victim_defence;
> +  /* final answer */
> +  int profit;
> +  /* time spent in the air */
> +  int sortie_time;
> +
> +  if ((pdefender == NULL) 
> +      || !can_unit_attack_unit_at_tile(punit, pdefender, dest_x, dest_y)) {
> +    return 0;
> +  }
> +
> +  /* Ok, we can attack, but is it worth it? */
> +

If the aircraft unit is not veteran, and there are enemy units easy to kill
around not in a stack or city, always attack. Maybe some sort of preferential
ranking of cities based on whether or not they have SAMS.

> +  /* Cost of our unit */
> +  unit_cost = unit_type(punit)->build_cost;
> +
> +  /* Determine cost of enemy units */
> +  if( is_stack_vulnerable(dest_x, dest_y) ) {
> +    /* lotsa people die */
> +    unit_list_iterate(map_get_tile(dest_x, dest_y)->units, aunit) {
> +      victim_cost += unit_type(aunit)->build_cost;
> +    } unit_list_iterate_end;
> +  } else {
> +    /* Only one unit dies if attack is successful */
> +    victim_cost = unit_type(pdefender)->build_cost;
> +  }
> +
> +  /* Missile would die 100% so we adjust the victim_cost -- GB */
> +  if (unit_flag(punit, F_MISSILE)) {
> +    victim_cost -= unit_type(punit)->build_cost;
> +  }
> +
> +  /* Attack value of our unit */
> +  unit_attack = unit_belligerence_basic(punit);
> +  /* Punish unhealthy units */ 
> +  unit_attack = unit_attack * punit->hp / unit_type(punit)->hp;
> +
> +  /* Defence value of the enemy */
> +  victim_defence = unit_vulnerability_basic(punit, pdefender);
> +
> +  balanced_cost = build_cost_balanced(punit->type);
> +
> +  sortie_time = (unit_flag(punit, F_ONEATTACK) ? 2 : 1);
> +  profit = kill_desire(victim_cost, unit_attack, unit_cost, victim_defence,
> 1) 
> +    - SHIELD_WEIGHTING + 2 * TRADE_WEIGHTING;
> +  if (profit > 0) {
> +    profit = military_amortize(profit, sortie_time, balanced_cost);
> +    freelog(LOG_NORMAL, 
> +         "%s at (%d, %d) is a worthy target with profit %d", 
> +         unit_type(pdefender)->name, dest_x, dest_y, profit);
> +  } else {
> +    freelog(LOG_NORMAL, 
> +         "%s at (%d, %d) is unworthy with profit %d", 
> +         unit_type(pdefender)->name, dest_x, dest_y, profit);
> +    profit = 0;
> +  }
> +
> +  return profit;
> +}

Excellent, well written.

Is it possible to unify f_s_t_k and f_s_t_b?

> +/**********************************************************************
> + * Find something to bomb
> + * Air-units specific victim search
> + * Returns the want for the best target, records target in goto_dest_x,y
> + * TODO: take dangers into account
> + *********************************************************************/
> +int find_something_to_bomb(struct unit *punit)
> +{
> +  struct player *pplayer = unit_owner(punit);
> +  int max_dist = punit->moves_left / SINGLE_MOVE;
> +  int x = punit->x;
> +  int y = punit->y;
> +  int best = 0;
> +  
> +  /* Adjust the max distance so Fighters can attack safely */
> +  if (punit->fuel < 2) {
> +    /* -1 is to take into account the attack itself */
> +    max_dist = (max_dist - 1) / 2;
> +  }
> +  
> +  /* Let's find something to bomb */
> +  iterate_outward(x, y, max_dist, x1, y1) {
> +    if (is_enemy_unit_tile(map_get_tile(x1,y1), pplayer)
> +     && (air_can_move_between (max_dist, x, y, x1, y1, pplayer) >= 0)){
> +      int new_best = ai_evaluate_tile_for_attack(punit, x1, y1);
> +      if (new_best > best) {
> +     punit->goto_dest_x = x1;
> +     punit->goto_dest_y = y1;
> +     best = new_best;
> +     freelog(LOG_DEBUG, "%s wants to attack tile (%d, %d)", 
> +             unit_type(punit)->name, x1, y1);
> +      }
> +    }
> +  } iterate_outward_end;
> +
> +  return best;
> +} 




 
<snip>

> +/************************************************************************
> + * Trying to manage bombers and stuff.
> + * If we are in the open {
> + *   if moving intelligently on a valid GOTO, {
> + *     carry on doing it.
> + *   } else {
> + *     go refuel
> + *   }
> + * } else {
> + *   try to attack something
> + * } 
> + * TODO: distant target selection, support for fuel > 2
> + ***********************************************************************/
> +static void ai_manage_airunit(struct player *pplayer, struct unit *punit)
> +{
> +  enum goto_result result = GR_FAILED;
> +
> +  if (!is_airunit_refuel_point(punit->x, punit->y, 
> +                            pplayer, punit->type, 0)) {
> +    /* We are out in the open, what shall we do? */
> +    int refuel_x, refuel_y;
> +
> +    if (punit->activity == ACTIVITY_GOTO
> +      /* We are on a GOTO.  Check if it will get us anywhere */
> +     && is_airunit_refuel_point(punit->goto_dest_x, punit->goto_dest_y, 
> +                                pplayer, punit->type, 0)
> +     && air_can_move_between (punit->moves_left/SINGLE_MOVE, punit->x, 
> punit->y,
> 
> +                              punit->goto_dest_x, punit->goto_dest_y,
> +                              pplayer) >= 0) {
> +      /* It's an ok GOTO, just go there */
> +      result = do_unit_goto(punit, GOTO_MOVE_ANY, 0);
> +    } else if (find_nearest_airbase(punit->x, punit->y, punit, 
> +                          &refuel_x, &refuel_y)) {
> +      /* Go refuelling */
> +      punit->goto_dest_x = refuel_x;
> +      punit->goto_dest_y = refuel_y;
> +      freelog(LOG_DEBUG, "Sent %s to refuel", unit_type(punit)->name);
> +      set_unit_activity(punit, ACTIVITY_GOTO);
> +      result = do_unit_goto(punit, GOTO_MOVE_ANY, 0);
> +    } else {
> +      if (punit->fuel == 1) {
> +     freelog(LOG_DEBUG, "Oops, %s is fallin outta sky", 
> +             unit_type(punit)->name);

Got to check if this last one happens, and what to do to prevent it.

> +      }
> +      return;
> +    }
> +
> +    /* Check if we got there okay */
> +    if (result != GR_ARRIVED) {
> +      freelog(LOG_DEBUG, "Something happened to our unit along the way");
> +      /* TODO: some rescuing, but not running into dead-loop */
> +    }
> +
> +  } else if (punit->fuel == unit_type(punit)->fuel
> +          && find_something_to_bomb(punit) > 0) {
> +
> +    /* Found target, coordinates are in punit->goto_dest_[xy]
> +     * TODO: separate attacking into a function, check for the best 
> +     * tile to attack from */
> +    set_unit_activity(punit, ACTIVITY_GOTO);
> +    do_unit_goto(punit, GOTO_MOVE_ANY, 0);
> +    /* goto would be aborted: "Aborting GOTO for AI attack procedures"
> +     * now actually need to attack */
> +
> +    /* We could use ai_military_findvictim here, but I don't trust it... */
> +    set_unit_activity(punit, ACTIVITY_IDLE);
> +    if (is_tiles_adjacent(punit->x, punit->y, 
> +                       punit->goto_dest_x, punit->goto_dest_y)) {
> +      int id = punit->id;
> +      handle_unit_move_request(punit, punit->goto_dest_x,
> punit->goto_dest_y, 
> +                            TRUE, FALSE);
> +      if ((punit = find_unit_by_id(id)) != NULL && punit->moves_left > 0) {
> +     /* Fly home now */
> +     ai_manage_airunit(pplayer, punit);
> +      }
> +    } else {
> +      /* Ooops.  Now better come back home */
> +      ai_manage_airunit(pplayer, punit);
> +    }
> +
> +  } else {
> +    freelog(LOG_DEBUG, "%s staying put", unit_type(punit)->name);
> +  }
> +
> +}
> +
 ===================================================================
> RCS file: /home/freeciv/CVS/freeciv/common/tech.c,v
> retrieving revision 1.45
> diff -u -r1.45 tech.c
> --- common/tech.c     2002/02/26 16:58:13     1.45
> +++ common/tech.c     2002/03/01 20:15:20
> @@ -36,7 +36,8 @@
>  static const char *flag_names[] = {
>    "Bonus_Tech", "Boat_Fast", "Bridge", "Railroad", "Fortress",
>    "Watchtower", "Population_Pollution_Inc", "Trade_Revenue_Reduce",
> -  "Airbase", "Farmland", "Reduce_Trireme_Loss1", "Reduce_Trireme_Loss2"
> +  "Airbase", "Farmland", "Reduce_Trireme_Loss1", "Reduce_Trireme_Loss2", 

My eyesight must be going. Those two lines above +- seem to be identical.

> +  "Build_Airborne"
>  };
>  /* Note that these strings must correspond with the enums in tech_flag_id,
>     in common/tech.h */
> @@ -547,3 +548,32 @@
>          || game.rgame.tech_cost_style == 2)
>         && game.rgame.tech_leakage == 0);
>  }
> +
> +/**********************************************************************
> + * Does this (newly-discovered) tech allow us to build airborne units?
> + *********************************************************************
> +static bool tech_allows_building_airborne(Tech_Type_id tech)
> +{
> +  UnitType_id j;
> +
> +  for(j=0; j<game.num_unit_types; ++j) {
> +    if (tech == get_unit_type(j)->tech_requirement
> +     && (get_unit_type(j)->move_type == AIR_MOVING
> +         || get_unit_type(j)->move_type == HELI_MOVING)) {
> +      return TRUE;
> +    } 
> +  }
> +  return FALSE;
> +}

Is the above function necessary? 

> +*/
> +
> +/*********************************************************************
> + * Can AI now do something else than just spawn?
> + ********************************************************************
> +static void update_ai_abilities(struct player *pplayer, Tech_Type_id tech)
> +{
> +  if (!(pplayer->ai.can_build_airborne) 
> +      && tech_allows_building_airborne(Tech_Type_id tech)) {
> +    pplayer->ai.can_build_airborne = TRUE;  
> +}

Is this updated every turn, every five turns or what? Ideally, you should
start updating every turn after you achieve pre-reqs to flight.

> +*/
> Index: common/tech.h


> +; "Build_Airborne" = from now on can build air units (for use by AI)
>  
>  [advance_advanced_flight]
>  name     = _("Advanced Flight")
> @@ -225,7 +226,7 @@
>  name     = _("Flight")
>  req1     = "Combustion"
>  req2     = "Theory of Gravity"
> -flags    = "Trade_Revenue_Reduce"
> +flags    = "Trade_Revenue_Reduce","Build_Airborne"
>  

New flag. I don't understand the ruleset and server parsing stuff, so I'll
trust you. Clearly this must have been necessary.



__________________________________________________
Do You Yahoo!?
Yahoo! Sports - sign up for Fantasy Baseball
http://sports.yahoo.com


[Prev in Thread] Current Thread [Next in Thread]