/* Functions common to all AIs.
   Copyright (C) 1992-1997, 1999 Stanley T. Shebs.

Xconq 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 2, or (at your option)
any later version.  See the file COPYING.  */

#include "conq.h"
#include "kernel.h"
/* (should do a better separation of kernel in set_side_ai) */
#include "kpublic.h"
extern void try_to_draw(Side *side, int flag, char *ainame);
extern void give_up(Side *side, char *ainame);
#include "ai.h"

/* Declarations of all AI types. */

extern AI_ops mplayer_ops;

AI_ops **all_ai_ops;

void
init_ai_types()
{
    /* Fill in the table of pointers to AI types. */
    all_ai_ops = (AI_ops **) xmalloc(numaitypes * sizeof(AI_ops *));
    all_ai_ops[0] = (AI_ops *) xmalloc(sizeof(AI_ops));
    all_ai_ops[1] = &mplayer_ops;
}

void
init_ai(side)
Side *side;
{
    int i;
    char *typename;
    int (*test)(void);
    void (*fn)(Side *side);

    if (side_wants_ai(side)) {
	/* (should merge with same bit in set_side_ai) */
	if (strcmp(side->player->aitypename, "ai") == 0) {
	    /* (should use the "best" default for this game and side) */
	    side->player->aitypename = "mplayer";
	}
	for (i = 0; i < numaitypes; ++i) {
	    typename = (all_ai_ops[i])->name;
	    if (typename != NULL
		&& strcmp(typename, side->player->aitypename) == 0) {
		test = (all_ai_ops[i])->to_test_compat;
		if (test == NULL || (*test)()) {
		    side->aitype = i;
		    fn = (all_ai_ops[i])->to_init;
		    if (fn)
		      (*fn)(side);
		} else {
		    /* desired aitype not avail - should complain */
		}
	    }
	}
	if (!side_has_ai(side)) {
	    init_warning("could not make an AI (type %s) for %s",
			 side->player->aitypename, side_desig(side));
	}
    }
}

/* Change the AI running a side.  This has to clean up if the AI is
   being turned off. */

void
set_side_ai(side, typename)
Side *side;
char *typename;
{
    Unit *unit;

    if (!empty_string(typename) && strcmp(typename, "ai") == 0) {
	/* (should use the "best" default for this game and side) */
	typename = "mplayer";
    }
    /* (only copy if not an already-defined name, otherwise assign from
       static list) */
    if (typename != NULL)
      typename = copy_string(typename);
    side->player->aitypename = typename;
    /* If remote, do nothing beyond the recording the name of the AI. */
    if (numremotes > 0 && side->player->rid != my_rid)
      return;
    /* If switching to human, clear out everything that was set up by an AI. */
    if (empty_string(typename)) {
	for_all_side_units(side, unit) {
	    unit->aihook = NULL;
	    if (unit->plan && unit->plan->aicontrol) {
		net_force_replan(side, unit, FALSE);
	    }
	    /* Still let units finish their currently buffered action. */
	}
    }
    if (empty_string(typename)) {
	/* (should just deactivate instead of erasing and maybe
           reallocating later) */
	side->ai = NULL;
    } else {
	init_ai(side);
	/* Turn on automation for all units when switching on the
	   side's AI. Note that the reverse is NOT true. Switching
	   from AI to human control will therefore leave all units on
	   automatic. */
	for_all_side_units(side, unit) {
	    unit->autoplan = TRUE;
	    unit->autoresearch = TRUE;
	    unit->autobuild = TRUE;
	}
    }
}

/* The following functions dispatch on AI type. */

void
ai_init_turn(side)
Side *side;
{
    void (*fn)(Side *side);

    fn = (all_ai_ops[side->aitype])->to_init_turn;
    if (fn)
      (*fn)(side);
}

void
ai_decide_plan(side, unit)
Side *side;
Unit *unit;
{
    void (*fn)(Side *side, Unit *unit);

    fn = ((all_ai_ops[side->aitype])->to_decide_plan);
    if (fn)
      (*fn)(side, unit);
}

/* Forward an action result to the appropriate AI routine. */

void
ai_react_to_action_result(side, unit, rslt)
Side *side;
Unit *unit;
int rslt;
{
    void (*fn)(Side *side, Unit *unit, int rslt);

    fn = ((all_ai_ops[side->aitype])->to_react_to_action_result);
    if (fn)
      (*fn)(side, unit, rslt);
}

/* Forward a task result to the appropriate AI routine. */

void
ai_react_to_task_result(side, unit, task, rslt)
Side *side;
Unit *unit;
Task *task;
TaskOutcome rslt;
{
    void (*fn)(Side *side, Unit *unit, Task *task, TaskOutcome rslt);

    fn = ((all_ai_ops[side->aitype])->to_react_to_task_result);
    if (fn)
      (*fn)(side, unit, task, rslt);
}

void
ai_react_to_new_side(side, side2)
Side *side, *side2;
{
    void (*fn)(Side *side, Side *side2);

    fn = ((all_ai_ops[side->aitype])->to_react_to_new_side);
    if (fn)
      (*fn)(side, side2);
}

int
ai_adjust_plan(side, unit)
Side *side;
Unit *unit;
{
    int (*fn)(Side *side, Unit *unit);

    fn = ((all_ai_ops[side->aitype])->to_adjust_plan);
    if (fn)
      return (*fn)(side, unit);
    else
      return FALSE;
}

int
ai_update_plan(Side *side, Unit *unit)
{
    int (*fn)(Unit *unit);

    fn = ((all_ai_ops[side->aitype])->to_update_plan);
    if (fn)
      return (*fn)(unit);
    else
      return FALSE;
}

int
ai_handle_emergency(Side *side, Unit *unit)
{
    int (*fn)(Unit *unit);

    fn = ((all_ai_ops[side->aitype])->to_handle_emergency);
    if (fn)
      return (*fn)(unit);
    else
      return FALSE;
}

void
ai_finish_movement(side)
Side *side;
{
    void (*fn)(Side *side);

    fn = ((all_ai_ops[side->aitype])->to_finish_movement);
    if (fn)
      (*fn)(side);
}

/* Forward a textual message to the appropriate AI routine. */

void
ai_receive_message(side, sender, str)
Side *side, *sender;
char *str;
{
    void (*fn)(Side *side, Side *sender, char *str);

    fn = ((all_ai_ops[side->aitype])->to_receive_message);
    if (fn)
      (*fn)(side, sender, str);
}

void
ai_save_state(side)
Side *side;
{
    Obj *(*fn)(Side *side), *state = lispnil;

    fn = ((all_ai_ops[side->aitype])->to_save_state);
    if (fn)
      state = (*fn)(side);
    /* Don't bother if there's no AI state to mess with. */
    if (side->aidata == lispnil && state == lispnil)
      return;
    side->aidata = replace_at_key(side->aidata, side->player->aitypename,
				  state);
}

/* Return a numerical value indicate the AI's use of the given location.
   For instance, each number could represent a region or theater of
   operations. */

int
ai_region_at(side, x, y)
Side *side;
int x, y;
{
    int (*fn)(Side *side, int x, int y);

    fn = ((all_ai_ops[side->aitype])->region_at);
    if (fn)
      return (*fn)(side, x, y);
    else
      return 0;
}

/* Provide textual information about the AI's usage or opinion of the
   given location. */

char *
ai_at_desig(side, x, y)
Side *side;
int x, y;
{
    char *(*fn)(Side *side, int x, int y);

    fn = ((all_ai_ops[side->aitype])->at_desig);
    if (fn)
      return (*fn)(side, x, y);
    else
      return NULL;
}

/* Set willingness to declare a draw. */

void
try_to_draw(Side *side, int flag, char *ainame)
{
    /* If no change, nothing to say or do. */
    if (flag == side->willingtodraw)
      return;
    /* If there is a human player and the decision has not been
       delegated, allow the human to make the final decision. */
    if (side_has_display(side) && !side->ai_may_resign) {
	if (flag)
	  notify(side, "Your AI %s recommends being willing to draw.",
		 ainame);
	else
	  notify(side, "Your AI %s recommends not being willing to draw.",
		 ainame);
	return;
    }
    net_set_willing_to_draw(side, flag);
}

/* If an AI resigns, it tries to help its friends. */

void
give_up(Side *side, char *ainame)
{
    Side *side1;

    /* Can't resign if we've been forbidden to do so. */
    if (side_has_display(side) && !side->ai_may_resign) {
	/* But speak up about it. */
	notify(side, "Your AI %s recommends resignation", ainame);
	return;
    }
    /* Try to give away all of our units to an ally. */
    for_all_sides(side1) {
	if (side != side1 && allied_side(side, side1) && side1->ingame) {
	    if (side_has_display(side))
	      notify(side, "Your AI resigns, giving its assets to %s!",
		     short_side_title(side1));
	    net_resign_game(side, side1);
	    return;
	}
    }
    /* No allies left in game, leave everything to its fate. */
    if (side_has_display(side))
      notify(side, "Your AI resigns!");
    net_resign_game(side, NULL);
}

/* (should go elsewhere eventually?) */

/* Goal handling. */

GoalDefn goaldefns[] = {

#undef  DEF_GOAL
#define DEF_GOAL(NAME,code,ARGTYPES) { NAME, ARGTYPES },

#include "goal.def"

    { NULL, NULL }
};


/* General handling of goals. */

Goal *
create_goal(type, side, tf)
GoalType type;
Side *side;
int tf;
{
    Goal *goal = (Goal *) xmalloc(sizeof(Goal));

    goal->type = type;
    goal->side = side;
    goal->tf = tf;
    return goal;
}

int
lookup_goal_type(name)
char *name;
{
    int i;

    for (i = 0; goaldefns[i].name != NULL; ++i)
      if (strcmp(name, goaldefns[i].name) == 0)
	return i; /* should get real enum? */
    return GOAL_NO;
}

int
cell_unknown(x, y)
int x, y;
{
    return (!all_see_all && !tmpside->see_all && terrain_view(tmpside, x, y) == UNSEEN);
}

int
enemies_present(x, y)
int x, y;
{
    if (units_visible(tmpside, x, y)) {
    	return (unit_at(x, y) != NULL && !trusted_side(tmpside, unit_at(x, y)->side));
    } else if (terrain_view(tmpside, x, y) != UNSEEN) {
    	return (vside(unit_view(tmpside, x, y)) != side_number(tmpside));
    } else {
    	return FALSE;
    }
}

/* Test a goal to see if it is true for side, as specified. */

int
goal_truth(side, goal)
Side *side;
Goal *goal;
{
    int x, y;
    Side *side2 = NULL;

    if (goal == NULL) return 0;
    switch (goal->type) {
      case GOAL_WON_GAME:
	side2 = goal->side;
	return (side2 ? (side_won(side2) ? 100 : -100) : 0);
      case GOAL_LOST_GAME:
	side2 = goal->side;
	return (side2 ? (side_lost(side2) ? 100 : -100) : 0);
      case GOAL_POSITIONS_KNOWN:
	/* what if no enemies present? then this is undefined? */
	/* should goals have preconditions or prerequisites? */
	return 0;
      case GOAL_WORLD_KNOWN:
	tmpside = side;
	for_all_interior_cells(x, y) {
	    if (cell_unknown(x, y)) return -100;
	}
	return 100;
      case GOAL_VICINITY_KNOWN:
	tmpside = side;
	if (search_around(goal->args[0], goal->args[1], goal->args[2],
			  cell_unknown, &x, &y, 1)) {
	    return -100;
	} else {
	    return 100;
	}
      case GOAL_VICINITY_HELD:
      	tmpside = side;
	if (search_around(goal->args[0], goal->args[1], goal->args[2],
			  enemies_present, &x, &y, 1)) {
	    return -100;
	} else {
	    return 100;
	}
      case GOAL_CELL_OCCUPIED:
	return 0;
      case GOAL_UNIT_OCCUPIED:
	return 0;
      case GOAL_COLONIZE:
	return 0;
      case GOAL_HAS_UNIT_TYPE:
	return 0;
      case GOAL_HAS_UNIT_TYPE_NEAR:
	return 0;
      case GOAL_HAS_MATERIAL_TYPE:
	return 0;
      default:
	case_panic("goal type", goal->type);
	return 0;
    }
}

/* (might eventually want another evaluator that guesses at another
   side's goals) */

char *goalbuf = NULL;

char *
goal_desig(goal)
Goal *goal;
{
    int numargs, i, arg;
    char *argtypes;

    if (goal == NULL)
      return "<null goal>";
    if (goalbuf == NULL)
      goalbuf = xmalloc(BUFSIZE);
    sprintf(goalbuf, "<goal s%d %s%s",
	    side_number(goal->side), (goal->tf ? "" : "not "),
	    goaldefns[goal->type].name);
    argtypes = goaldefns[goal->type].argtypes;
    numargs = strlen(argtypes);
    for (i = 0; i < numargs; ++i) {
	arg = goal->args[i];
	switch (argtypes[i]) {
	  case 'h':
	    tprintf(goalbuf, "%d", arg);
	    break;
	  case 'm':
	    if (is_material_type(arg))
	      tprintf(goalbuf, " %s", m_type_name(arg));
	    else
	      tprintf(goalbuf, " m%d?", arg);
	    break;
	  case 'S':
	    tprintf(goalbuf, " `%s'", side_desig(side_n(arg)));
	    break;
	  case 'u':
	    if (is_unit_type(arg))
	      tprintf(goalbuf, " %s", u_type_name(arg));
	    else
	      tprintf(goalbuf, " m%d?", arg);
	    break;
	  case 'U':
	    tprintf(goalbuf, " `%s'", unit_desig(find_unit(arg)));
	    break;
	  case 'w':
	    tprintf(goalbuf, " %dx", arg);
	    break;
	  case 'x':
	    tprintf(goalbuf, " %d,", arg);
	    break;
	  case 'y':
	    tprintf(goalbuf, "%d", arg);
	    break;
	  default:
	    tprintf(goalbuf, " %d", arg);
	    break;
	}
    }
    strcat(goalbuf, ">");
    return goalbuf;
}


/* General collections of numbers used by all machine players and unit plans. */

int bhw_max;

/* Init used by all machine players.  Precompute useful information
   relating to unit types in general, and that usually gets referenced
   in inner loops. */

void
ai_init_shared()
{
    int u, u1, u2, t, m1, numbuilders, tmp;
    
    /* Need 3 scratch layers for routefinding. */
    allocate_area_scratch(3);

    /* Recognize unit types that are bases */
    for_all_unit_types(u1) {
	set_u_is_base(u1, FALSE);
	tmp = FALSE;
	for_all_material_types(m1) {
	    if (um_base_production(u1, m1) > 0) {
		tmp = TRUE;
		break;
	    }
	}
	if (tmp) {
	    for_all_unit_types(u2) {
		if ((u1 != u2) && could_carry(u1,u2)) {
		    set_u_is_base(u1, TRUE);
		    continue;
		}
	    }
	}
    }
    /* Note that is_base_builder is set to the type of base that can */
    /* be built.  That means that unit zero can not be a base which */
    /* can be built. */
    for_all_unit_types(u1) {
	set_u_is_transport(u1, FALSE);
	set_u_is_carrier(u1, FALSE);
	set_u_is_base_builder(u1, FALSE);
	set_u_can_make(u1, FALSE);
	set_u_can_capture(u1, FALSE);
	set_u_is_ground_mobile(u1, FALSE);
	set_u_is_naval_mobile(u1, FALSE);
	set_u_is_air_mobile(u1, FALSE);
	set_u_is_colonizer(u1, FALSE);
	set_u_is_facility(u1, FALSE);

	/* DON'T set u_advanced to FALSE. It is already set by the
           game file. */

	numbuilders = 0;
/*	ave_build_time[u1] = 0;  */
	for_all_unit_types(u2) {
	    if (u_is_base(u2) &&
		could_create(u1, u2) &&
		1 /* can be made quickly? */) {
		set_u_is_base_builder(u1, u2);
	    }
	    if (u_speed(u1) > 0 && could_carry(u1, u2)) {
		set_u_is_transport(u1, TRUE);
	    }
	    if (could_create(u2, u1)) {
		numbuilders++;
/*		ave_build_time[u1] += uu_make(u2,u1);  */
		set_u_can_make(u2, TRUE);
		/* A colonizer is a mobile builder of advanced units. */
		if (u_advanced(u2) && mobile(u1))
		  set_u_is_colonizer(u1, TRUE);
	    }
	    if (uu_capture(u1, u2) > 0 || uu_indep_capture(u1, u2) > 0) {
		set_u_can_capture(u1, TRUE);
	    }
	}
/*	if (numbuilders > 0)
	  ave_build_time[u1] /= numbuilders;  */
    }
    /* a carrier is a unit that is a mobile base, but that cannot
       move a passenger anywhere the passenger could not go itself. */
    for_all_unit_types(u1) {
	if (u_is_transport(u1)) {
	    set_u_is_carrier(u1, TRUE);
	    for_all_unit_types(u2) {
		if (could_carry(u1, u2)) {
		    for_all_terrain_types(t) {
			if (could_be_on(u1, t) && !could_be_on(u2, t))
			  set_u_is_carrier(u1, FALSE);
		    }
		}
	    }
	}
    }
    for_all_unit_types(u) {
	set_u_bw(u, basic_worth(u));
	set_u_offensive_worth(u, offensive_worth(u));
	set_u_defensive_worth(u, defensive_worth(u));
	set_u_explorer_worth(u, exploring_worth(u));
	set_u_colonizer_worth(u, colonizing_worth(u));
	set_u_facility_worth(u, facility_worth(u));
	set_u_random_worth(u, random_worth(u));
	set_u_siege_worth(u, siege_worth(u));

	if (mobile(u)) {
	    int maxacp = u_acp(u);
		
	    if (u_acp_turn_max(u) >= 0)
	      maxacp = min(maxacp, u_acp_turn_max(u));
	    maxacp = (u_acp_max(u) < 0 ? maxacp : u_acp_max(u));
	    /* Assume this is an air unit by default. */
	    set_u_is_air_mobile(u, TRUE);
	    for_all_terrain_types(t) {
	        /* Check that our unit can have enough mps to enter the
		   terrain. */
	        if (ut_mp_to_enter(u, t) * u_acp_to_move(u) <= maxacp	
		    /* And that there is room for our unit in the terrain. */
		    && ut_size(u, t) <= t_capacity(t)
		    /* And that it does not vanish in the terrain. */	
		    && ut_vanishes_on(u, t) != TRUE) {
		    /* It is naval mobile if it can enter at least one
		       liquid terrain. */
		    if (t_liquid(t))
		      set_u_is_naval_mobile(u, TRUE);
		    /* It is ground mobile if it can enter at least one
		       non-liquid terrain. */
		    if (!t_liquid(t))
		      set_u_is_ground_mobile(u, TRUE);
		    /* Air units should have zero size in all terrains. */
		    if (ut_size(u, t) > 0)
		      set_u_is_air_mobile(u, FALSE);
		} else {
		    /* Air units should be able to enter all terrains. */
		    set_u_is_air_mobile(u, FALSE);
	        }
	    }
	/* Kind of crude, but OK for now. */
	} else
	  set_u_is_facility(u, TRUE);
    }
    for_all_unit_types(u) {
	for_all_unit_types(u2) {
	    set_uu_bhw(u, u2, basic_hit_worth(u, u2));
	    set_uu_bfw(u, u2, basic_fire_worth(u, u2));
	    set_uu_bcw(u, u2, basic_capture_worth(u, u2));
	    set_uu_btw(u, u2, basic_transport_worth(u, u2));
	    if (uu_zz_bhw(u, u2) > bhw_max)
	      bhw_max = uu_zz_bhw(u, u2);
	}
    }
    /* Tell how things rated. */
    if (DebugM)
      display_assessment();
}

int basic_transport_worth(int u1, int u2);
void set_uu_btw(int u1, int u2, int v);

#define DICE(N,NUMDICE,SPOTS,OFFSET)  \
  (((N) >> 14 == 0 || (N) >> 14 == 3) ?  \
   (NUMDICE = 0, SPOTS = 0, OFFSET = (N)) :  \
   (NUMDICE = ((N) >> 11) & 0x07, SPOTS = ((N) >> 7) & 0x0f, OFFSET = (N) & 0x7f))

/* A crude estimate of the worth of having one type of unit. */

int
basic_worth(u)
int u;
{
    int worth = 0, u2, r, range;
  
    worth += u_hp(u) * 10;
    for_all_unit_types(u2) {
	if (could_create(u, u2))
	  worth += (u_bw(u2) * (50)) / 1 /* uu_make(u, u2) */;
	/* (should account for shared capacity) */
	if (could_carry(u, u2))
	  worth += (1 + u_speed(u)) * uu_capacity_x(u, u2) *
	    (u_is_base(u) ? 10 : 1) * u_bw(u2) / 30;
    }
    range = 12345;
    for_all_material_types(r) {
	worth += um_base_production(u, r) * (u_is_base(u) ? 4 : 1);
	if (um_consumption_per_move(u, r) > 0)
	  range = min(range, um_storage_x(u, r) / max(1, um_consumption_per_move(u, r)));
	if (um_base_consumption(u, r) > 0) 
	  range =
	    min(range, u_speed(u) * um_storage_x(u, r) / max(1, um_base_consumption(u, r)));
    }
    worth += u_speed(u) * u_hp(u);
    worth += (range == 12345 ? area.maxdim : range)
      * u_hp(u) / max(1, 10 - u_speed(u));
    for_all_unit_types(u2) {
	worth += (worth * uu_capture(u, u2)) / 150;
    }
    worth = isqrt(worth);
    DMprintf("unit type %s worth %d \n ", u_type_name(u), worth);
    return worth;
}

int
offensive_worth(int u)
{
    int offvalue = 0;
    int defenders = 0;
    int worth = 0;
    int u2;

    for_all_unit_types(u2) {
	if (uu_acp_to_attack(u, u2) > 0) {
	    ++defenders;
	    offvalue += uu_hit(u, u2) * uu_damage(u, u2);
	}
	if (u_acp_to_fire(u) > 0) {
	    ++defenders;
	    offvalue += (uu_fire_hit(u, u2) != -1 ? uu_fire_hit(u, u2) : uu_hit(u, u2))
	      * (uu_fire_damage(u, u2) != -1 ? uu_fire_damage(u, u2) : uu_damage(u, u2));
	}
    }
    if (defenders > 0)
      offvalue /= defenders;
    worth =  u_acp(u) * offvalue * u_range(u) * u_speed(u) / 100;
    DMprintf("unit type %s offensive worth %d \n ", u_type_name(u), worth);
    return worth;
}

int
defensive_worth(int u)
{
    int defvalue = 0;
    int attackers = 0;
    int worth = 0;
    int u2;

    for_all_unit_types(u2) {
	if (uu_acp_to_attack(u2, u) > 0) {
	    ++attackers;
	    defvalue += 100 * u_hp_max(u) - uu_hit(u2, u) * uu_damage(u2, u);
	}
	if (u_acp_to_fire(u2) > 0) {
	    ++attackers;   
	    defvalue += 100 * u_hp_max(u) - (uu_fire_hit(u2, u) != -1 ? uu_fire_hit(u2, u) : uu_hit(u2, u)) 
	      * (uu_fire_damage(u2, u) != -1 ? uu_fire_damage(u2, u) : uu_damage(u2, u));
	}
    }
    if (attackers > 0)
      defvalue /= attackers;
    worth = u_acp(u) * defvalue / 10;
    DMprintf("unit type %s defensive worth %d \n ", u_type_name(u), worth);
    return worth;
}

int
exploring_worth(int u)
{
    int worth = u_acp(u) * u_speed(u) / 100;

    DMprintf("unit type %s exploring worth %d \n ", u_type_name(u), worth);
    return worth;
}

int
colonizing_worth(int u)
{
    int colvalue = 0;
    int worth = 0;
    int u2;
 
    /* A colonizer is a mobile builder of advanced units. */
    if (!mobile(u))
      return 0;
    for_all_unit_types(u2) {
	if (could_create(u, u2)
	    && u_advanced(u2)) {
	    /* It is more valuable if it can build bigger cities. */
	    colvalue += u_reach(u2) + 1;
	}
    }    	
    worth = u_acp(u) * u_speed(u) * colvalue / 10;
    DMprintf("unit type %s colonizing worth %d \n ", u_type_name(u), worth);
    return worth;
}

int
facility_worth(int u)
{
    int worth = 0;

    /* Kind of crude, but OK for now. */
    if (!mobile(u))
      worth = 1;

    DMprintf("unit type %s facility worth %d \n ", u_type_name(u), worth);
    return worth;
}

int
siege_worth(int u)
{
    int siegevalue = 0;
    int worth = 0;
    int u2;
 
    for_all_unit_types(u2) {
	siegevalue += uu_capture(u, u2);
    }           
    siegevalue = siegevalue / numutypes;
 
    worth = u_acp(u) * u_range(u) * siegevalue; 

    DMprintf("unit type %s siege worth %d \n ", u_type_name(u), worth);
    return worth;
}

/* Assign the same worth to all unit types. */

int
random_worth(int u)
{
    return 1;
}

/* A basic estimate of the payoff of one unit type attacking another
   type directly.  This is "context-free", does not account for
   overall goals etc. */

/* (should account for number of attacks possible in one turn) */

int
basic_hit_worth(u, e)
int u, e;
{
    int dam, numdice, numspots, offset, avgdamage, worth = 0, anti = 0;

    if (uu_acp_to_attack(u, e) < 1)
      return -9999;
    dam = uu_damage(u, e);
    DICE(dam, numdice, numspots, offset);
    avgdamage = offset + (numdice * numspots) / 2;
    if (avgdamage > u_hp(e))
      avgdamage = u_hp(e);
    worth = (uu_hit(u, e) * avgdamage) / u_hp(e);
    if (1 /* strength of counterattack */) {
	dam = uu_damage(e, u);
	DICE(dam, numdice, numspots, offset);
	avgdamage = offset + (numdice * numspots) / 2;
	if (avgdamage > u_hp(u))
	  avgdamage = u_hp(u);
	anti = (uu_hit(e, u) * avgdamage) / u_hp(u);
    }
    return worth - (anti * 9) / 10;
}

int
basic_fire_worth(u, e)
int u, e;
{
    int hitchance, dam, numdice, numspots, offset, avgdamage, worth = 0;

    if (u_acp_to_fire(u) < 1)
      return -9999;
    if (uu_fire_hit(u, e) != -1)
      hitchance = uu_fire_hit(u, e);
    else
      hitchance = uu_hit(u, e);
    if (uu_fire_damage(u, e) != -1)
      dam = uu_fire_damage(u, e);
    else
      dam = uu_damage(u, e);
    DICE(dam, numdice, numspots, offset);
    avgdamage = offset + (numdice * numspots) / 2;
    if (avgdamage > u_hp(e))
      avgdamage = u_hp(e);
    worth = (hitchance * avgdamage) / u_hp(e);
    return worth;
}

/* A crude estimate of the payoff of one unit type trying to capture. */

int
basic_capture_worth(u, e)
int u, e;
{
    int acp, worth1 = 0, worth2 = 0;

    acp = max(uu_acp_to_capture(u, e), uu_acp_to_attack(u, e));
    if (acp < 1)
      return -9999;
    if (uu_capture(u, e) > 0) {
	worth1 = (uu_capture(u, e) * u_acp(u)) / acp;
    }
    if (uu_indep_capture(u, e) > 0) {
	worth2 = (uu_indep_capture(u, e) * u_acp(u)) / acp;
    }
    /* (should account for chance of dying in attempt) */
    return max(worth1, worth2);
}

int
basic_transport_worth(u, u2)
int u, u2;
{
    int worth = 0;

    if (could_carry(u, u2)) {
	worth += 1;
    }
    return worth;
}

/* Some notion of the unit's "strength"? */

int
unit_strength(u)
int u;
{
    return 1;
}

/* Display the results of our calculations. */

void
display_assessment()
{
    int u, u2;

    DMprintf("\nUnit Attributes:\n");
    for_all_unit_types(u) {
	DMprintf(" %-3.3s : base %d, transport %d, carrier %d, worth %d\n",
	       shortest_unique_name(u), u_is_base(u),
	       u_is_transport(u), u_is_carrier(u), u_bw(u));
	DMprintf("    Operate between ranges %d and %d\n", operating_range_worst(u), operating_range_best(u));
    }
    DMprintf("\nUnit vs Unit Combat:\n");
    for_all_unit_types(u) {
	DMprintf(" %-3.3s:", shortest_unique_name(u));
	for_all_unit_types(u2)
	  DMprintf("%5d", uu_zz_bhw(u, u2));
	DMprintf("\n");
    }
    DMprintf("\nUnit vs Unit Fire:\n");
    for_all_unit_types(u) {
	DMprintf(" %-3.3s:", shortest_unique_name(u));
	for_all_unit_types(u2)
	  DMprintf("%5d", uu_zz_bfw(u, u2));
	DMprintf("\n");
    }
    DMprintf("\nUnit vs Unit Capture:\n");
    for_all_unit_types(u) {
	DMprintf(" %-3.3s:", shortest_unique_name(u));
	for_all_unit_types(u2)
	  DMprintf(" %4d", uu_zz_bcw(u, u2));
	DMprintf("\n");
    }
    DMprintf("\nUnit vs Unit Transport:\n");
    for_all_unit_types(u) {
	DMprintf(" %-3.3s:", shortest_unique_name(u));
	for_all_unit_types(u2)
	  DMprintf(" %4d", uu_zz_btw(u, u2));
	DMprintf("\n");
    }
    DMprintf("\n");
}

int
is_base_for(u1, u2)
int u1, u2;
{
    return (u_speed(u1) == 0
	    && (uu_capacity_x(u2, u1) > 0
		|| (uu_size(u2, u1) <= u_capacity(u1))));
}

int
is_carrier_for(u1, u2)
int u1, u2;
{
    return (u_speed(u1) > 0
	    && (uu_capacity_x(u2, u1) > 0
		|| (uu_size(u2, u1) <= u_capacity(u1))));
}

/* Since *.def parameters don't have setters usually, we have to supply
   some here.  These are very sensitive to how the parameters are organized,
   and they don't do any checking, so you have to careful about using them. */

void set_u_is_base(u, n) int u, n; {  utypes[u].is_base = n;  }
void set_u_is_transport(u, n) int u, n; {  utypes[u].is_transport = n;  }
void set_u_is_carrier(u, n) int u, n; {  utypes[u].is_carrier = n;  }
void set_u_is_base_builder(u, n) int u, n; {  utypes[u].is_base_builder = n;  }
void set_u_can_make(u, n) int u, n; {  utypes[u].can_make = n;  }
void set_u_can_capture(u, n) int u, n; {  utypes[u].can_capture = n;  }
void set_u_bw(u, n) int u, n; {  utypes[u].bw = n;  }

void set_u_offensive_worth(u, n) int u, n; {  utypes[u].ow = n;  }
void set_u_defensive_worth(u, n) int u, n; {  utypes[u].dw = n;  }
void set_u_explorer_worth(u, n) int u, n; {  utypes[u].ew = n;  }
void set_u_colonizer_worth(u, n) int u, n; {  utypes[u].cw = n;  }
void set_u_facility_worth(u, n) int u, n; {  utypes[u].fw = n;  }
void set_u_random_worth(u, n) int u, n; {  utypes[u].rw = n;  }
void set_u_siege_worth(u, n) int u, n; {  utypes[u].sw = n;  }

void set_u_is_ground_mobile(u, n) int u, n; {  utypes[u].is_ground_mobile = n;  }
void set_u_is_naval_mobile(u, n) int u, n; {  utypes[u].is_naval_mobile = n;  }
void set_u_is_air_mobile(u, n) int u, n; {  utypes[u].is_air_mobile = n;  }
void set_u_is_advanced(u, n) int u, n; {  utypes[u].is_advanced = n;  }
void set_u_is_colonizer(u, n) int u, n; {  utypes[u].is_colonizer = n;  }
void set_u_is_facility(u, n) int u, n; {  utypes[u].is_facility = n;  }
	
int bhwtab = -1;
int bfwtab = -1;
int bcwtab = -1;
int btwtab = -1;

void
set_uu_bhw(u1, u2, v)
int u1, u2, v;
{
    if (bhwtab < 0) {
	for (bhwtab = 0; tabledefns[bhwtab].name != NULL; ++bhwtab) {
	    if (strcmp("zz-basic-hit-worth", tabledefns[bhwtab].name) == 0) {
		allocate_table(bhwtab, FALSE);
		break;
	    }
	}
    }
    if (tabledefns[bhwtab].table == NULL)
      run_error("no bhw table allocated");
    (*(tabledefns[bhwtab].table))[numutypes * u1 + u2] = v;
}

void
set_uu_bfw(u1, u2, v)
int u1, u2, v;
{
    if (bfwtab < 0) {
	for (bfwtab = 0; tabledefns[bfwtab].name != NULL; ++bfwtab) {
	    if (strcmp("zz-basic-fire-worth", tabledefns[bfwtab].name) == 0) {
		allocate_table(bfwtab, FALSE);
		break;
	    }
	}
    }
    if (tabledefns[bfwtab].table == NULL)
      run_error("no bfw table allocated");
    (*(tabledefns[bfwtab].table))[numutypes * u1 + u2] = v;
}

void
set_uu_bcw(u1, u2, v)
int u1, u2, v;
{
    if (bcwtab < 0) {
	for (bcwtab = 0; tabledefns[bcwtab].name != NULL; ++bcwtab) {
	    if (strcmp("zz-basic-capture-worth", tabledefns[bcwtab].name) == 0) {
		allocate_table(bcwtab, FALSE);
		break;
	    }
	}
    }
    if (tabledefns[bcwtab].table == NULL)
      run_error("no bcw table allocated");
    (*(tabledefns[bcwtab].table))[numutypes * u1 + u2] = v;
}


void
set_uu_btw(u1, u2, v)
int u1, u2, v;
{
    if (btwtab < 0) {
	for (btwtab = 0; tabledefns[btwtab].name != NULL; ++btwtab) {
	    if (strcmp("zz-basic-transport-worth", tabledefns[btwtab].name) == 0) {
		allocate_table(btwtab, FALSE);
		break;
	    }
	}
    }
    if (tabledefns[btwtab].table == NULL)
      run_error("no btw table allocated");
    (*(tabledefns[btwtab].table))[numutypes * u1 + u2] = v;
}
