fai 'new_recruitment.fai'

{ai/formula/lib/map_evaluation.fai}
{ai/formula/lib/util.fai}
{ai/formula/lib/recruitment.fai}

# Returns a list of terrain types of the input list locations #
def get_terrain_types(ai*, locations)
	map( locations, 'l', fast_map[l].id ) where fast_map = location_to_terrain_map(ai);

# Returns a List of important vilages which are inside locations_map #
def get_villages_on_terrain_map( ai*, locations_map )
	filter( villages, locations_map[self] );

# Returns location_map with the area around important villages set to 1000 (radius 1) #
def mark_important_locations(ai*, location_map )
	map( location_map, 
		if( tmp_villages[key], 
			1000, 
			value
		)
	)
	where	tmp_villages = tomap( get_locations_surroundings( ai,  get_villages_on_terrain_map( ai, location_map ), 1 ) );


# Returns a list of ALL keys(locs) of side_terrain #
def get_important_locations(ai* )
	keys(
		map( ai.vars.side_terrain, 
			value >= 1000 
		)
	);

# Returns a list of all enemy leaders #
def enemy_leaders( ai* )
	sum(
		map( enemies, 'enemy',
			filter( units_of_side[enemy], leader )
		)
	);

# Returns a Map #
# Key is a ememy leader #
# Value is the summed distace to all important hexes #
def find_enemies_part( ai*, locations_map)
	tomap(
		enemy_leaders,
		map( enemy_leaders, 'leader',
			sum(
				map( keys(locations_map), 'location',
					distance_between( location, leader.loc )
				)
			)
		)
	)
	where enemy_leaders = enemy_leaders( ai );

# Returns a List of those enemy leaders wich are near to the important locations #
def find_enemies( ai*, locations_map)
	filter( keys(enemies_part), 'leader',
		enemies_part[leader] <= average( values( enemies_part ) )
	)
	where enemies_part = find_enemies_part( ai, locations_map );

# Returns a List of all units of direct_enemies. #
def direct_enemies_units( ai* )
	sum(
		map( vars.direct_enemies, 'enemy',
			units_of_side[enemy.side]
		)
	);

# Returns a list of all recruits of direct_enemies. #
def direct_enemies_recruits( ai* )
	sum(
		map( vars.direct_enemies, 'enemy',
			recruits_of_side[enemy.side]
		)
	);

# returns map of important locations and number of their occurences on a gamemap #
# for example: [ 'forest' -> 10 ] #
# all terrains inside side_terrain are considered. #
def important_locations_map(ai*)
	tomap(get_terrain_types(ai, get_important_locations(ai)));

# Returns a map. Key is unit-id of my_recruits. Value is sum of all defenses in all important_locs. #
def my_recruits_defense(ai*)
	tomap( map(my_recruits, id),
		map( my_recruits, 'recruit',
			sum(values(map( important_locs,
				defense_on(recruit, id_translator[key]) * value
			)))
		)
	)
	where	important_locs = important_locations_map(ai),
		id_translator = id_to_location_map(ai);

# Returns a map. Key is unit-id of my_recruits. Value is sum of all movent_costs in all important_locs. #
def my_recruits_movement_cost(ai*)
	tomap( map(my_recruits, id),
		map( my_recruits, 'recruit',
			sum(values(map( important_locs,
				movement_cost(recruit, id_translator[key]) * value
			)))
		)
	)
	where	important_locs = important_locations_map(ai),
		id_translator = id_to_location_map(ai);

# Highest Value is set to 100. All others below 100 according to ratio #
def locally_normalize_to_highest( input_map )
	map( input_map, if(max=0,100,(value * 100)/max) )
	where max = highest_value(input_map);

# Lowest value is set to 100. All others over 100 according to ratio #
def locally_normalize_to_lowest( input_map )
	map( input_map, if(min=0,100,(value * 100)/min) )
	where min = lowest_value(input_map);



# Returns a List of all enemy leaders #
# (for some reason this function is defined twice) #
def enemy_leaders(ai*)
	map( enemies, 'enemy_side', find(units_of_side[enemy_side], leader ) );

# UNUSED FUNCTION #
# Returns a Map. #
# Key is a enemy #
# Value is the distance between my leader and enemies leader #
def distance_to_enemies(ai*)
	tomap( enemies, map( enemy_leaders(ai), distance_between( my_leader.loc, loc ) ));

# Returns a list of enemy-units #
# If number of units on the map is below 5 #
# It'll return the recruits #
def current_enemies(ai*)
	if( en_units.size > 5, en_units, en_recruis )
	where	en_units = direct_enemies_units(ai),
		en_recruis = direct_enemies_recruits(ai);


# Returns a integer. #
# Returns the sum of 12 times the maximum attack damage for all enemy_units #
def evaluate_attacker_against_opponents(ai*, unit, enemy_units)
	sum( 
		map( 
			enemy_units, 'enemy_unit', 
			sum( # sum over all 4 elements (which are the same). 3 * 4 = 12. #
				map(
					[
						max_possible_damage_with_retaliation( unit, enemy_unit )
						# gives [ <attacker_melee>, <attacker_ranged>, <defender_melee>, <defender_ranged> ] #
					],
					max(
						[
							self[0],
							self[1]
						]
					)*3 # Replaces all 4 elements in the list with 3 times the maximum of self[0] and self[1]. #
				) 
			)
		)
	);

# Returns a integer. #
# Returns the sum of 20 times the maximum attack damage that all enemie_units give unit. #
# This time the atacker can defend himself. The defender will chose the attack with the best #
# damage difference #
def evaluate_defender_against_opponents(ai*, unit, enemy_units)
	sum( 
		map( 
			enemy_units, 'enemy_unit', 
			sum(
				map(
					[
						max_possible_damage_with_retaliation( enemy_unit, unit )
					],
					max(
						[
							self[0] - self[2],
							self[1] - self[3]
						]
					)*5
				) 
			)
		)
	);


# Returns a List with the attacker damages of all recruits #
# The higher the better #
def evaluate_recruits_offensive_combat(ai*, recruits, enemy_units)
	map( recruits, 'recruit',
		evaluate_attacker_against_opponents( ai, recruit, enemy_units )
	);

#Returns a List with the defender damages for all recruits #
# The lower the better #
def evaluate_recruits_defensive_combat(ai*, recruits, enemy_units)
	map( recruits, 'recruit',
		evaluate_defender_against_opponents( ai, recruit, enemy_units )
	);

# Some helper functions for map calculations #
def combine_maps_mul( map_A, map_B )
	map( map_A, value * map_B[key] );

def combine_maps_sub( map_A, map_B )
	map( map_A, value - map_B[key] );

def combine_maps_add( map_A, map_B )
	map( map_A, value + map_B[key] );

def combine_maps_div( map_A, map_B )
	map( map_A, value / map_B[key] );

# Returns a map. #
# Key is unit_id #
# Value is (costs - avarage costs) * 80 + offset (offset is there so everything is positive) #
# High Value means high cost compared to the avarage cost of own units #
def consider_unit_cost(ai*)
	make_positive_only(map( cost_map, (value - average(values(cost_map))) * 80))
	where cost_map = tomap(map(my_recruits, id),map(my_recruits, cost ));

# Returns a Map. Key is unit_id of my_recruits. #
# Value is the summed defense on important terrain multiplied by hp * 15. #
# Normalized so the maximum is 100. #
def defense_hp_eval(ai*, recruits_id_map)
	locally_normalize_to_highest(
		debug_print('combined hp and defense ',combine_maps_mul(
			my_recruits_defense(self),
			tomap(recruits_id_map, map(
				my_recruits,
				hitpoints*15)
			)
		))
	);

# Returns a Map. Key is unit_id of my_recruits. #
# Value is how fast a unit can move through the important terrain. #
# Normalized so the maximum is 100. #
def movement_eval(ai*, recruits_id_map)
	locally_normalize_to_highest(
		make_positive_only(
			map(
				combine_maps_div(
					my_recruits_movement_cost( ai ),
					tomap( recruits_id_map,
						map( my_recruits,
							total_movement
						)
					)
				),
				-value
			)
		)
	);

# Returns a Map. #
# key is Unit id of my_recruits #
# Value is how good is the unit against all other units currently on the map. #
# The higher the better #
def fighting_eval(ai*, recruits_id_map)
	make_positive_only(
		combine_maps_sub(
			tomap(recruits_id_map, evaluate_recruits_offensive_combat( ai, my_recruits, current_enemies ) ),
			tomap(recruits_id_map, evaluate_recruits_defensive_combat( ai, my_recruits, current_enemies ) )
		)
	)
	where current_enemies = current_enemies(ai);

# Returns a map #
# Key is unit_id #
# Value is 40 if unit is level 0 #
def level_zero_malus(ai*)
	map( lvl_map,
		if(value = 0, 40, 0)
	)
	where lvl_map = tomap(
				map(my_recruits, id),
				map(my_recruits, level )
			);

# UNUSED FUNCTION #
def filter_out( input_map, comparable_map )
	filter( input_map, comparable_map[key] > comp_val)
	where comp_val = average(values(comparable_map))/2;

# CENTRAL EVALUATION FUNCTION #
# calls several subroutines to calculate a final score map of my_recruits #
# Normalizes then so the scores add up do 100 (%). #
# Note that fighting_eval is multiplied by 5 and gets a massive weight #
# Returns a Map with unit_id as key and score in % as value. #
def calculate_recruits(ai*)
	change_numbers_to_percents(
		combine_maps_sub(
			combine_maps_add(
				combine_maps_add( 
					debug_print('defense_hp_eval ', defense_hp_eval(ai, recruits_id_map ) ),
					debug_print('movement_eval ', movement_eval(ai, recruits_id_map) )
				),
				debug_print('fighting_eval ', multiply_values(
									fighting_eval(ai,recruits_id_map), 5
								) 
					)
			),
			combine_maps_add(
				debug_print('consider_unit_cost ', consider_unit_cost(ai) ),
				debug_print('level_zero_malus ', level_zero_malus(ai) )
			)
		)
	) where recruits_id_map = map(my_recruits, id); #recruits_id_map is a list of all recruit-ids#

def do_recruitment( ai* )
	if( unit_to_recruit,
		safe_call( 
			recruit( unit_to_recruit ),
			end
		),
		end
	)
	where unit_to_recruit = recruit_army(self, vars.recruits_map);

# 'MAIN FUNCTION' #
if( vars.side_terrain,
	if(vars.direct_enemies,
		if(vars.turn_initialized = turn,

			do_recruitment(self),

			[
			set_var(debug_print('turn_initialized'), turn) ,
			set_var(debug_print('recruits_map'), debug_print( 'RECRUITS ', calculate_recruits(self)) )
			]
			+
			if( 1,
				[
					null(
						sum(
							[
							values(map( vars.side_terrain, if( value > 900, debug_label( key, 'o'), debug_label(key, '.') ) )),
							map( vars.direct_enemies, debug_label( loc, '*****************☠') )
							]
						)
					)
				],
				[]
			)
		),

		set_var( 'direct_enemies', find_enemies(self, vars.side_terrain ) )
	),

	set_var('side_terrain',
			mark_important_locations( self,
				calculate_map_ownership(
					recruits_of_side,
					map(filter(sum(units_of_side), leader), loc),
					4, 7, 4
				)
			)
	)
)


faiend
