-- | Bot for the Google AI contest, implemented by Kenneth Hoste (boegel).
--
-- Steered by parameters, to allow tuning with a genetic algorithm.
--
module Main where

import PlanetWars

import Data.List (find, partition, sortBy, sort)
import Data.Maybe (fromJust)
import Data.Ord (comparing)

-- bot parameters
data BotParameters = BotParameters {
	-- mode controls
	enableConquer :: Bool,
	enableReinforce :: Bool,
	enableDefend :: Bool,
	enableAttack :: Bool,
	-- score thresholds
        defendScoreThreshold :: Double,
        conquerScoreThreshold :: Double,
        attackScoreThreshold :: Double,
	-- risk levels
	useFutureRisk :: Bool,
	safetyFactor :: Double,
    	lowestRisk :: Double,
    	lowRisk :: Double,
    	someRisk :: Double,
        moderateRisk :: Double,
        highRisk :: Double,
        fullRisk :: Double,
	-- conquer (score) parameters
	useFailFactorInConquerScore :: Bool,
	failFactorConquerScore :: Double,
	useDistsFactorInConquerScore :: Bool,
	useTurnsFactorInConquerScore :: Bool,
	useShipsFactorInConquerScore :: Bool,
	useFutureWinFactorConquerScoreThreshold :: Bool,
	-- reinforce parameters
	reinforcementSize :: Int,
	maxReinforcementsPerPlanet :: Int,
	reinforceFirstN :: Int,
	useUpdatedBudgetForReinforcing :: Bool
        } deriving (Show)

-- different budgets for a particular planet
data Budgets = Budgets {
	getBudget :: Int,
	getUpdatedBudget :: Int,
	getConservativeBudget :: Int,
	getRiskBudget :: Int,
	getPlanet :: Planet 
	} deriving (Show)

--
-- *** UTILITY FUNCTIONS ***
        
-- mean value of a list of doubles
mean :: [Double] -> Double
mean xs = (sum xs) / (fromIntegral . length) xs

-- mean value of a list of ints
meanInts :: [Int] -> Double
meanInts xs = (fromIntegral . sum) xs / (fromIntegral . length) xs

-- determine win factor: product of ratio of ships and ratio of production
detWinFactor :: (Int,Int) -> (Int,Int) -> Double
detWinFactor prod ships = prodRatio * shipsRatio
  where
    -- production of both allied and enemy planets in next turn
    (prodAllied, prodHostile) = (fst prod, snd prod)
    -- ships of both allied and enemy planets and fleets
    (alliedShips, hostileShips) = (fst ships, snd ships)
    -- production ratio: allied vs hostile
    --prodRatio = fromIntegral (1+prodAllied^2) / (1.10 * fromIntegral (1+prodHostile^2))
    prodRatio = fromIntegral (1+prodAllied^2) / (1.10 * fromIntegral (1+prodHostile^2))
    -- ship ratio: allied vs hostile
    --shipsRatio = fromIntegral (1+alliedShips) / (1.10 * fromIntegral (1+hostileShips))
    shipsRatio = fromIntegral (1+alliedShips) / (1.10 * fromIntegral (1+hostileShips))

-- determine future win factor, after processing of fleets (and growth of planets)
detFutureWinFactor :: [Planet] -> [Fleet] -> Double
detFutureWinFactor ps [] = detWinFactor (prodAllied, prodHostile) (alliedShips, hostileShips)
  where
    alliedPlanets = filter isAllied ps
    hostilePlanets = filter isHostile ps
    prodAllied = sum $ map planetGrowthRate alliedPlanets
    prodHostile = sum $ map planetGrowthRate hostilePlanets
    alliedShips = sum $ map planetShips alliedPlanets
    hostileShips = sum $ map planetShips hostilePlanets
detFutureWinFactor ps fleets = detFutureWinFactor (map (\p -> simulateFleets p (planetFleets p) 0) ps) []
  where
    fleets' = mergeAndSortFleets fleets
    planetFleets p = filter ((==) (planetId p) . fleetDestination) fleets'


-- determine risk
-- value between 0.0 and 1.0 (or over 1.0)
-- higher means more risk, i.e. that we're behind and need to catch up
detRisk :: BotParameters -> (Int,Int) -> (Int,Int) -> Double
detRisk params prod ships 

	-- notes with risk based on future state (vs ParameterBotImproved2_20101112)
	-- 	* no risk when we're >=: 2 wins, 4 losses, 24 draws (6.7% wins)
	-- 	* some risk (25%) if we're less than 5% ahead: 8 wins, 18 losses, 4 draws (26.7% wins)
	-- 	* some risk (25%) if we're less than 10% ahead: 6 wins, 20 losses, 4 draws (20% wins)
	-- notes with risk based on current state (vs ParameterBotImproved2_20101112)
	-- 	* no risk when we're >=: 0 wins, 5 losses, 25 draws (0% wins)
	-- 	* some risk (25%) if we're less than 5% ahead: 4 wins, 22 losses, 4 draws (13.3% wins)
	-- 	* some risk (25%) if we're less than 10% ahead: 3 wins, 23 losses, 4 draws (10% wins)
	-- notes with risk based on future state (vs ParameterBotImproved2_20101112)
	-- 	* no risk when we're >=: 2 wins, 4 losses, 24 draws (6.7% wins)
	-- 	* some risk (10%) if we're less than 5% ahead: 6 wins, 19 losses, 5 draws (20% wins)
	-- 	* some risk (10%) if we're less than 10% ahead: 10 wins, 15 losses, 5 draws (33.3% wins)
	-- 	* some risk (10%) if we're less than 20% ahead: 10 wins, 15 losses, 5 draws (33.3% wins)

	-- don't take any risks when we're slightly ahead ahead
	| prodAllied >= (safetyFactor params) * prodHostile && alliedShips >= (safetyFactor params) * hostileShips = lowestRisk params
	-- take some risk if we're ahead, but not enough (see ^)
	| prodAllied >= prodHostile && alliedShips >= hostileShips = lowRisk params --lowestRisk params --someRisk params
        -- more than 20% behind in either number of ships or production -> full blown risks
        | prodAllied < 0.8 * prodHostile && alliedShips < hostileShips = fullRisk params 
        | prodAllied < prodHostile && alliedShips < hostileShips / 3 = fullRisk params
        -- more than 10% behind in either number of ships or production -> take risks, but keep taking hostile fleets into account (updated budget)
        | prodAllied < 0.9 * prodHostile && alliedShips < hostileShips = highRisk params
        | prodAllied < prodHostile && alliedShips < hostileShips / 2 = highRisk params
        -- clear advantage in either growth or ships -> no risks (don't panic too early)
        | prodAllied >= 1.2 * prodHostile || alliedShips >= 3 * hostileShips = lowestRisk params
	-- some advantage, but not enough to be comfortable -> limited amount of risk
	| prodAllied >= 1.1 * prodHostile && alliedShips < hostileShips = someRisk params
	| prodAllied < prodHostile && alliedShips >= 2 * hostileShips = someRisk params
	-- slightly behind, take some risks
	| otherwise = moderateRisk params
  where
    -- production of both allied and enemy planets in next turn
    (prodAllied, prodHostile) = (fromIntegral $ fst prod, fromIntegral $ snd prod)
    -- total production
    totProd = prodAllied + prodHostile
    -- ships of both allied and enemy planets and fleets
    (alliedShips, hostileShips) = (fromIntegral $ fst ships, fromIntegral $ snd ships)
    -- total number of ships in play
    totShips = alliedShips + hostileShips

--
-- *** ORDER FUNCTIONS ***
--
destPlanetForOrder :: [Planet] -> Order -> Planet
destPlanetForOrder ps o = fromJust $ find ((==) (orderDestination o) . planetId) ps

--
-- *** PLANET FUNCTIONS ***
--

-- adjust planets by specified orders
adjustByOrders :: [Planet] -> [Order] -> [Planet]
-- no orders => same planets
adjustByOrders ps [] = ps
-- at least one order => adjust target planet
adjustByOrders ps ((Order sid tid n):os) = adjustByOrders ps' os
  where
    ps' = removeShips ps (sid, n)

-- empty a planet of all its ships
emptyPlanet :: Planet -> Planet
emptyPlanet p = p {planetShips = 0}

-- determine different budget for a particular planet
detBudgets :: Double -> [Fleet] -> [Planet] -> Planet -> Budgets
detBudgets risk fleets allPlanets p = budget `seq` updatedBudget `seq` conservativeBudget `seq` riskBudget `seq` 
			             (Budgets budget updatedBudget conservativeBudget riskBudget p)
  where
    budget = detBudget p
    updatedBudget = detBudgetUpdated fleets p
    conservativeBudget = detBudgetConservative fleets allPlanets p
    riskBudget = if risk <= 1.0
		-- balance between updated budget and conservative budget
		then round $ risk * (fromIntegral updatedBudget) + (1 - risk) * (fromIntegral conservativeBudget)
		-- full blown risks, spend it all if it helps
		else budget

-- determine number of turns until planet turns hostile
-- WARNING: assumes fleets are sorted by number of remaining turns !!!
turnsUntil :: (Planet -> Bool) -> Planet -> [Fleet] -> Int -> Int
-- planet never satisfies condition
turnsUntil cond _   []      _     = -1
turnsUntil cond p (f:fs) turnCorr
        -- condition is already true, no turns needed
        | cond p = 0
        -- condition becomes true after applying fleet
        | cond p' = fleetTurnsRemaining f
        -- condition not true yet, apply following fleets
        | otherwise = turnsUntil cond p' fs turnCorr'
  where
        -- simulated outcome of planet after arrival of fleet f
        p' = simulateFleet p f turnCorr
        -- turn correction (for growth)
        turnCorr' = fleetTurnsRemaining f

-- adjust list of planets, subtract number of ships from planet with given id
removeShips :: [Planet] -> (Int,Int) -> [Planet]
removeShips [] _ = []
removeShips (p:ps) (pid,size) = if planetId p == pid
                                   then addShips p (negate size) : ps
                                   else p : removeShips ps (pid,size)

-- adjust list of planets, subtract number of ships from planet with given id
removeBudgetShips :: Double -> [Budgets] -> (Int,Int) -> [Budgets]
removeBudgetShips _ [] _ = []
removeBudgetShips risk (x:xs) (pid,size) = if planetId p == pid
                                  		 then (Budgets b' ub' cb' rb' (addShips p $ negate size)) : xs
                                   		 else x : removeBudgetShips risk xs (pid,size)
  where
    p = getPlanet x
    -- updated budgets
    -- FIXME: is this 100% correct? aren't budget punished too much? growth?
    b' = min 0 $ getBudget x - size
    ub' = min 0 $ getUpdatedBudget x - size
    cb' = min 0 $ getConservativeBudget x - size
    rb' = if risk <= 1.0
		then round $ risk * (fromIntegral ub') + (1 - risk) * (fromIntegral cb')
		else b'

-- determine budget of ships to spend for a planet
detBudget :: Planet -> Int
detBudget p = if isAllied p
                then planetShips p
                else 0

-- determine minimal number of ships that should be available to resist fleet attacks
-- WARNING: doesn't check whether fleets are targetted to planet 
--          or whether they are sorted by number of remaining turns !!!
detSurvivalBudget :: Planet -> [Fleet] -> Int -> Int -> Int
-- no fleets, so just return specified minimum budget
detSurvivalBudget p   []       _    minBudget = minBudget
-- one fleet, so return smallest budget
-- multiple fleets, determine budget recursively
detSurvivalBudget p (f:fs) turnCorr minBudget = detSurvivalBudget p' fs turnCorr' minBudget'
  where
    -- updated planet after fleet
    p' = simulateFleet p f turnCorr
    -- new turn correction
    turnCorr' = fleetTurnsRemaining f
    -- new budget
    minBudget' = if isAllied p'
                  -- planet is still allied: smallest of budgets 
                  then min minBudget (planetShips p' - 1) 
                  -- planet is no longer allied: no budget left
                  else 0

-- determine budget of ships to spend for a planet, without losing it
-- after updating the planet's state using the given set of fleets
detBudgetUpdated :: [Fleet] -> Planet -> Int
detBudgetUpdated fs p = if isAllied p
                         -- allied planet: determine survival budget
                         then detSurvivalBudget p fs' 0 (planetShips p)
                         -- not allied: no budget whatsoever
                         else 0
  where
    -- filtered set of fleets, after sorting and merging
    fs' = filter ((==) (planetId p) . fleetDestination) $ mergeAndSortFleets fs

-- determine fictive hostile fleets
-- sent from current or future hostile planets
allFictHostileFleets :: Planet -> [Planet] -> [Fleet] -> [Fleet]
allFictHostileFleets p allPlanets fleets = enemyFleets ++ futureHostileFleets
  where
    -- planet id
    pid = planetId p
    -- enemy planets
    hostilePlanets = filter ((/=) pid . planetId) $ filter isHostile allPlanets
    -- distance in turns between two planets
    dist p' = turnsBetween p p'
    -- fictive enemy fleets: send everything the enemy planet has
    enemyFleets = map (\p' -> let d = dist p' in d `seq` Fleet 2 (planetShips p') (planetId p') pid d d) hostilePlanets
    -- filtered and sorted fleets for a particular planet
    fleets' x = filter ((==) (planetId x) . fleetDestination) $ mergeAndSortFleets fleets
    -- fictive future enemy fleets: fleets a future enemy planet can send
    futureHostileFleets = concat $ map (\p' -> let sfs = fleets' p' in length sfs `seq` fictiveHostileFleets p' p sfs) allPlanets

-- determine budget of ships to spend for a planet, without losing it
-- be very conservative: don't take just current fleets into account, but
-- also add the worst case fleets of the enemy (i.e. all ships from enemy planets)
detBudgetConservative :: [Fleet] -> [Planet] -> Planet -> Int
detBudgetConservative fleets allPlanets p = length allFleets `seq` detBudgetUpdated allFleets p
  where
    -- fictive enemy fleets
    fictHostileFleets = allFictHostileFleets p allPlanets fleets
    -- all fleets
    allFleets = fleets ++ fictHostileFleets

--
-- *** FLEET FUNCTIONS ***
--

-- combine two fleets together
-- WARNING! assumptions made: same trip length, numbers of turns remaining, destination
combineFleets :: Fleet -> Fleet -> Fleet
combineFleets f1 f2
	| (shipsF1 > shipsF2) = f1 { fleetShips = shipsF1 - shipsF2 }
	| (shipsF1 < shipsF2) = f2 { fleetShips = shipsF2 - shipsF1 }
	| otherwise = error "combineFleets: combining two fleets with same number of ships would result in a zero-sized fleet (doesn't make sense!)"
  where
    shipsF1 = fleetShips f1
    shipsF2 = fleetShips f2

-- create fleet for given order and context (planets which include source planet and target planet)
fleetForOrder :: [Planet] -> Order -> Fleet
fleetForOrder ps (Order sPid tPid ships) 
        | null srcs = error "ERROR! fleetForOrder: source planet not found in list."
        | null trgts = error "ERROR! fleetForOrder: target planet not found in list."
        | otherwise = Fleet 1 ships sPid tPid dist dist
  where
    -- source planet
    srcs = filter ((==) sPid . planetId) ps
    -- target planet
    trgts = filter ((==) tPid . planetId) ps
    -- distance from source to destination
    dist = turnsBetween (head srcs) (head trgts)

-- merge a single fleet with a list of fleets
-- WARNING: assumes that fleets in list have same owner, destination and number of remaining turns !
mergeFleetWith :: Fleet -> [Fleet] -> Fleet
-- no fleets (left), no merging
mergeFleetWith f [] = f
-- adjust fleet by adding total number of ships in fleets
mergeFleetWith f fs = f {fleetShips = fleetShips f + ships'}
  where
    -- sum of ships in fleets
    ships' = sum $ map fleetShips fs

-- merge fleets with same owner, same destination, and same number of turns
mergeFleets :: [Fleet] -> [Fleet]
-- empty list
mergeFleets [] = []
-- one fleet (left), no merging
mergeFleets (f:[]) = [f]
-- merge by partitioning list of fleets on match with head fleet
-- WARNING: source or trip length will no longer have meaning !
-- TESTED (20101004): empty list, single fleet, no merging, merging 2 fleets, merging 2+3 fleets
mergeFleets (f:fs) = mergeFleetWith f matchingFleets : mergeFleets fs'
  where
    -- fleet owner
    owner = fleetOwner f
    -- fleet destination
    dest = fleetDestination f
    -- number of turns left in fleet
    turnsLeft = fleetTurnsRemaining f
    -- match fleets
    -- WARNING: only owner, destination and # turns left (not source, trip length)
    (matchingFleets, fs') = partition (\x ->    fleetOwner x == owner 
                                             && fleetDestination x == dest
                                             && fleetTurnsRemaining x == turnsLeft) fs

-- sort fleets by turns remaining and owner, and merge (same owner, dest, turns rem.)
mergeAndSortFleets :: [Fleet] -> [Fleet]
mergeAndSortFleets fleets = mergeFleets . sortBy cmpTurns . sortBy cmpOwner $ fleets
  where
    cmpTurns = comparing fleetTurnsRemaining
    cmpOwner = comparing fleetOwner

-- update planet after a single fleet
-- take growth and turn correction term into account
-- WARNING: doesn't check whether fleets are targetted to planet !!!
simulateFleet :: Planet -> Fleet -> Int -> Planet
simulateFleet p f turnCorr = engage (addShips p growth) f
  where
    -- number of turns left (takes turn correction into account)
    turnsLeft = fleetTurnsRemaining f - turnCorr
    -- growth, taking into account turns correction term
    growth = if isNeutral p then 0 else planetGrowthRate p * turnsLeft

-- update planet after set of fleets
-- take into account turn correction
-- WARNING: assumes that fleets are targetted to planet 
--          and are sorted by number of remaining turns
--          and are merged, so that each planet has only one incoming fleet per turn per owner  
simulateFleets :: Planet -> [Fleet] -> Int -> Planet
-- no fleets => same planet
simulateFleets p   []      _     = p
-- one single fleet, simulate it's effect
simulateFleets p (f:[]) turnCorr = simulateFleet p f turnCorr
-- multiple fleets
simulateFleets p (f:f':fs) turnCorr 
        -- special case: fleets of different owners arriving at the same time
        | (fleetTurnsRemaining f == fleetTurnsRemaining f')
          && (fleetOwner f /= fleetOwner f') 
             -- ^ not strictly necessary, there should be only one fleet per turn/owner
                    
              = if fleetShips f /= fleetShips f'
                 -- different number of ships => largest fleet wins
                 -- FIXME: combine both fleets and apply
                 then let cf = combineFleets f f' in simulateFleets p (cf:fs) (fleetTurnsRemaining cf)
                 -- same number of ships => nobody wins, neutral planet becomes weaker
                 else if isNeutral p
                       then if (planetShips p > fleetShips f) 
                             -- planet has more ships remaining than fleet contains => subtract
                             then simulateFleets (addShips p . negate $ fleetShips f) fs turnCorr
                             -- fleet contains more ships than planet has => planet with zero ships
                             else simulateFleets (addShips p . negate $ planetShips p) fs turnCorr
                       -- same number of ships, not a neutral planet => nothing changes     
                       else simulateFleets p fs turnCorr
        -- fleets f and f' arrive at different times: regular case
        | otherwise = simulateFleets (simulateFleet p f turnCorr) (f':fs) (fleetTurnsRemaining f)

-- determine fictive enemy fleets in the future
fictiveHostileFleets :: Planet -> Planet -> [Fleet] -> [Fleet]
-- no existing fleets, so no fictive enemy fleets in between fleets
fictiveHostileFleets source target  [] = []
-- at least one existing fleet
fictiveHostileFleets source target (f:fs)
        | sid == tid = []
	| isHostile source' = fictHostileFleet : fictiveHostileFleets source'' target fs
        | otherwise         = fictiveHostileFleets source' target fs
  where
    -- source planet id
    sid = planetId source
    -- target planet id
    tid = planetId target
    -- updated source planet after simulating fleet
    source' = simulateFleet source f 0
    -- number of ships in fleet: current number + growth before allied
    ships = planetShips source'
    -- distance from source to target
    dist = turnsBetween source target
    -- turns required for fleet
    fleetTurns = fleetTurnsRemaining f
    -- fictive enemy fleet
    fictHostileFleet = Fleet 2 ships sid tid (dist+fleetTurns) (dist+fleetTurns)
    -- remove ships from planet
    source'' = emptyPlanet source

--
-- *** SCORE FUNCTIONS ***
--

-- defend score
-- indicates how worthly a planet is to defend
-- a planet with a negative score requires no defending (not hostile in the end, or never allied)
defendScore :: Planet -> [Planet] -> [Fleet] -> Double
defendScore p allPlanets fleets
        -- only defend planets that become hostile, and are allied at some point
        | not (isHostile p') || turnsUntilAllied < 0 = -1.0
        | otherwise = (1 / meanTurnDist) ** (1 / growthRate)
  where
    -- sort fleets according to turns remaining and owner
    fleets' = mergeAndSortFleets fleets
    -- filter fleets for planet under consideration
    fleets'' x = filter ((==) (planetId x) . fleetDestination) fleets'
    -- updated planet
    p' = simulateFleets p (fleets'' p) 0
    -- turns until planet becomes allied
    turnsUntilAllied = turnsUntil isAllied p (fleets'' p) 0
    -- updated version of all planets
    allPlanets' = map (\x -> simulateFleets x (fleets'' x) 0) allPlanets
    -- allied planets
    alliedPlanets' = filter ((/=) (planetId p) . planetId) $ filter isAllied allPlanets'
    -- mean distance from planet p to my planets
    meanTurnDist = meanInts $ map (fromIntegral . turnsBetween p) alliedPlanets'
    -- growth rate of planet
    growthRate = fromIntegral $ planetGrowthRate p

-- conquer score
-- indicates how interesting a planet is to conquer
conquerScore :: BotParameters -> Double -> [Budgets] -> [Planet] -> [Fleet] -> (Planet,[Order]) -> Double
conquerScore    _     _        []         _         _   _ = error "ERROR! No more own planets?"
conquerScore params risk budgetsAlliedPlanets allPlanets fleets (p,orders) = if not (isAllied p') && not (null orders)
							 -- planet still needs to be conquered
                                      		         then score
                               				 -- planet is already conquered by fleets in flight
                               				 else -1
  where
    -- conquer score
    score = failFactor * distsFactor * turnsFactor * shipsFactor
    -- different score factors
    	-- fail factor: reduce conquer score if planet can't be conquered
    failFactor = if useFailFactorInConquerScore params 
		   then (if isAllied p'' then 1.0 else failFactorConquerScore params)
		   else 1.0 
	-- distances factor: take mean distances to hostile and allied planets into account
    distsFactor = if useDistsFactorInConquerScore params
		   --then (sqrt meanDistToHostilePlanets) / (sqrt meanDistToAlliedPlanets)
		   -- formula below results in less draws, but also more losses => needs further tweaking?!?
		   -- ----> ParameterBotImproved2_20101112: 30/30 (2 wins | 4 losses | 24 draws) => wins: 6.7%
		   -- vs
		   -- ----> ParameterBotImproved2_20101112: 30/30 (8 wins | 19 losses | 3 draws) => wins: 26.7%
		   --then sqrt . max 0.0 . logBase 2 $ (/) (meanDistToHostilePlanets**2) 2*(meanDistToAlliedPlanets**2)
		   --then sqrt . logBase 2 . (+) 1.0 $ (/) (meanDistToHostilePlanets**2) 2*(meanDistToAlliedPlanets**2)
		   --then (sqrt minDistToHostilePlanets) / (sqrt minDistToAlliedPlanets)
		   then sqrt (meanDistToClosest3HostilePlanets / meanDistToAlliedPlanets)
		   else 1.0
        -- turns factor: take into account number of turns needed to conquer planet (and include growth rate in it)
    turnsFactor = if useTurnsFactorInConquerScore params
		    then (1 + sqrt(g))/(log(turnsNeeded) + (logBase 10 g) + 1)
		    else 1.0
	-- ships factor: take into account required number of ships to conquer planet, along with growth rate
    shipsFactor = if useShipsFactorInConquerScore params
		   then ((1 + 1/sqrt(shipsNeeded + 1)) ** (growthRate + 1)) - 1
		   else 1.0

    -- id for planet p
    pid = planetId p
    -- growth rate of planet p
    g = fromIntegral $ planetGrowthRate p
    -- fleets for planet p
    fleets' = filter ((==) pid . fleetDestination) $ mergeAndSortFleets fleets
    -- updated target planet
    p' = simulateFleets p fleets' 0
    -- fleets for fictive conquer orders
    orderFleets = map (fleetForOrder (p:map getPlanet budgetsAlliedPlanets)) orders
    -- determine number of turns needed to conquer the planet
    turnsNeeded = if null orders then 0.0 else fromIntegral . maximum $ map fleetTurnsRemaining orderFleets :: Double
    -- add order fleets
    fleets'' = mergeAndSortFleets $ fleets' ++ orderFleets
    -- simulate planet after planet all fleets
    p'' = simulateFleets p fleets'' 0
    -- adjust number of turns needed, depending on whether planet was conquered or not
    -- naive estimate: double turns needed if planet wasn't conquered yet
    turnsNeededAdj = if isAllied p'' then turnsNeeded else 2*turnsNeeded
    -- growth rate: large fast-growing planets are more interesting
    growthRate = fromIntegral $ planetGrowthRate p :: Double
    -- size correction: planets that require more ships to conquer are less interesting
    shipsNeeded = fromIntegral . sum $ map orderShips orders :: Double
    -- allied planets
    alliedPlanets = filter ((/=) pid . planetId) $ filter isAllied allPlanets
    -- hostile planets
    hostilePlanets = filter ((/=) pid . planetId) $ filter isHostile allPlanets
    -- mean distance (turns-wise) between target planet and hostile planets
    meanDistToHostilePlanets = if null hostilePlanets then 100 else mean $ map (distanceBetween p) hostilePlanets
    -- min distance (turns-wise) between target planet and hostile planets
    minDistToHostilePlanets = if null hostilePlanets then 100 else minimum $ map (distanceBetween p) hostilePlanets
    -- mean top 3 closest distances (turns-wise) between target planet and hostile planets
    meanDistToClosest3HostilePlanets = if null hostilePlanets then 100 else mean . take 3 . sort $ map (distanceBetween p) hostilePlanets
    -- mean distance (turns-wise) between target planet and allied planets
    meanDistToAlliedPlanets = if null alliedPlanets then 10 else mean $ map (distanceBetween p) alliedPlanets
    -- min distance (turns-wise) between target planet and allied planets
    minDistToAlliedPlanets = if null alliedPlanets then 10 else minimum $ map (distanceBetween p) alliedPlanets

attackScore :: [Planet] -> [Fleet] -> Planet -> Double
attackScore allPlanets fleets p 
	-- attacking allied or neutral planets makes no sense
	| isAllied p' || isNeutral p' || null alliedPlanets = -1.0
	-- prefer large isolated hostile planets close to an allied planet	
	-- | otherwise = ( meanHostileDist / minAlliedDist ) ** sqrt(g + 1)
	| otherwise = ( meanHostileDist / sqrt(minAlliedDist) ) ** sqrt(g + 1)
  where
    -- planet id
    pid = planetId p
    -- filtered, merged and sorted fleets
    fleets' = filter ((==) pid . fleetDestination) $ mergeAndSortFleets fleets
    -- updated planet
    p' = simulateFleets p fleets' 0
    -- growth
    g = fromIntegral $ planetGrowthRate p
    -- allied planets
    alliedPlanets = filter isAllied allPlanets
    -- distance to allied planets
    alliedDists = map (distanceBetween p) alliedPlanets
    -- distance to closest allied planet
    minAlliedDist = minimum alliedDists
    -- hostile planets
    hostilePlanets = filter ((/=) pid . planetId) $ filter isHostile allPlanets
    -- distance to hostile planets
    hostileDists = map (distanceBetween p) hostilePlanets
    -- mean distance to hostile planets
    -- if no other hostile planets are found, give this planet a high attack score
    meanHostileDist = if null hostilePlanets then 100 else mean hostileDists

--
-- *** STRATEGY FUNCTIONS ***
--

-- defendPlanet
-- defend a particular planet (if necessary)
defendPlanet :: Planet -> [Budgets] -> [Fleet] -> [Order]
defendPlanet p budgetAlliedPlanets fleets
        -- planet is (no longer) hostile in the end, or never allied => no orders
        | not (isHostile p') || turnsUntilAllied < 0 = []
        -- no candidate source planets => no orders
        | null sortedCandSourcePlanets' = []
        -- include defend orders, add corresponding fleet to list of fleets
        | otherwise = order : defendPlanet p budgetAlliedPlanets' (fleet:fleets)
  where
    -- planet id
    pid = planetId p
    -- filter fleets for planet p; merging/sorting is required, because fleet are being added
    fleets' = filter ((==) pid . fleetDestination) $ mergeAndSortFleets fleets
    -- updated planet
    p' = simulateFleets p fleets' 0
    -- turns until planet becomes allied
    turnsUntilAllied = turnsUntil isAllied p fleets' 0
    -- only consider planets that still have budget left (regular and updated budget)
    --budgetPlanets = filter ((>0) . getBudget) $ filter ((>0) . getRiskBudget) budgetAlliedPlanets
    budgetPlanets = filter ((>0) . getBudget) $ filter ((>0) . getUpdatedBudget) budgetAlliedPlanets
    -- candidate source planets: different pid, still budget available
    candSourcePlanets = filter ((/=) pid . planetId . getPlanet) budgetPlanets
    -- sorted candidate source planets
    sortedCandSourcePlanets = sortBy (comparing (distanceBetween p' . getPlanet)) candSourcePlanets
    sortedCandSourcePlanets' = if isNeutral p && turnsUntilAllied > turnsUntilHostile
				-- avoid weakening neutral planets for incoming enemy fleets
				then dropWhile (\x -> turnsBetween p (getPlanet x) < minTurnsRemHostile) sortedCandSourcePlanets
				else sortedCandSourcePlanets
    -- turns until planet becomes hostile
    turnsUntilHostile = turnsUntil isHostile p fleets' 0
    -- hostile fleets
    hostileFleets = filter isHostile fleets'
    -- minimum turns remaining of hostile fleets
    minTurnsRemHostile = if null hostileFleets then 0 else (minimum . map fleetTurnsRemaining) hostileFleets
    -- source planet: candidate source planet closest to target
    -- avoid weakening neutral planet for incoming hostile fleets
    -- FIXME: improve this, also take budget/distance into account?
    sourceBudgets = head sortedCandSourcePlanets'
    sourcePlanet = getPlanet sourceBudgets
    -- id for source planet
    sourcePid = planetId $ sourcePlanet
    -- distance from source planet to target planet
    dist = turnsBetween p sourcePlanet
    -- budget to spend
    budget = min (getBudget sourceBudgets) (getUpdatedBudget sourceBudgets) -- (getRiskBudget sourceBudgets)
    -- number of ships required to defend the planet
    reqShips = if dist < turnsUntilHostile
                 -- if fleet arrives before enemy, require future number of enemy ships
                 -- overestimate, because this includes enemy growth
                 then planetShips p' + 1
                 -- if fleet arrives after planet become hostile, include extra growth too
                 else planetShips p' + (dist - turnsUntilHostile) * (planetGrowthRate p) + 1
    -- ships
    ships = if (budget > 0) then min budget reqShips else error "defendPlanet: zero bduget?"
    -- order to defend
    order = sourcePid `seq` pid `seq` ships `seq` Order sourcePid pid ships
    -- update my planets
    budgetAlliedPlanets' = removeBudgetShips 0.0 budgetAlliedPlanets (sourcePid, ships)
    -- fleet matching the order
    fleet = fleetForOrder [sourcePlanet,p] order

-- defend strategy
-- defend allied planets under attack
defend :: BotParameters -> [Planet] -> [Budgets] -> [Fleet] -> [Order]
defend   _      []       _        _    = []
defend params (p:ps) budgetsAllPlanets fleets = if score >= threshold
                                          -- planet worth defending, so include defend orders
                                          then orders ++ defend params ps budgetsAllPlanets' fleets
                                          -- planet not worth defending, so just ignore it
                                          else defend params ps budgetsAllPlanets fleets
  where
    -- defend score threshold, negative scored planets should never be considered
    threshold = max 0.0 (defendScoreThreshold params)
    -- defend score for this planet
    score = defendScore p (map getPlanet budgetsAllPlanets) fleets
    -- allied planets (needs to be redetermined from budgetsAllPlanets)
    budgetAlliedPlanets = filter (isAllied . getPlanet) budgetsAllPlanets
    -- orders for defending planet
    orders = defendPlanet p budgetAlliedPlanets fleets
    -- adjust all planets by orders
    budgetsAllPlanets' = foldl (removeBudgetShips 0.0) budgetsAllPlanets $ map (\o -> (orderSource o, orderShips o)) orders

-- make orders to conquer planet
mkConquerOrders :: Double -> [Budgets] -> Planet -> [Planet] -> [Fleet] -> Int -> [Order]
-- no allied planets => no conquer orders
mkConquerOrders _ [] _ _ _ _ = []
-- only conquer planets not conquered already
mkConquerOrders risk budgetsAlliedPlanets p allPlanets fs turnCorr 
	-- don't conquer if there are no suited source planets
	| null candSourcePlanets = [] 
        -- don't conquer planet already conquered (or conquered enough)
	| isAllied p' && ((not $ isHostile p) || (isAllied p'')) = []
	-- only conquer neutral planets in one go
        | isNeutral p' && totBudget < planetShips p' + 1 = [] 
        -- only conquer hostile planets likely to remain conquered 
	| isHostile p' && totBudget < planetShips p' + max_growth + 1 = []
        -- don't weaken neutral planet for incoming hostile fleets, if no allied fleets are under way
        | isNeutral p && (not alliedFleetsUnderWay) && dist < minTurnsRemHostile = []
        -- | isNeutral p && isHostile p' && (not alliedFleetsUnderWay) && dist < hostileTurns = []
        | otherwise = order `seq` order : mkConquerOrders risk budgetsAlliedPlanets' p allPlanets (fleet:fs) turnCorr'
  where
        -- id for target planet
    targetPid = planetId p
        -- fleets for planet p only
    fs' = filter ((==) targetPid . fleetDestination) $ mergeAndSortFleets fs
        -- updated target planet
    p' = simulateFleets p fs' 0
    	-- fictive enemy fleets
    fictHostileFleets = allFictHostileFleets p allPlanets fs
	-- updated target planet after additional fictive hostile fleets
    p'' = simulateFleets p (mergeAndSortFleets $ fs' ++ fictHostileFleets) 0
        -- are there any allied fleets travelling to planet p?
    alliedFleetsUnderWay = not . null $ filter isAllied fs'
        -- turns until planet becomes hostile
    hostileTurns = turnsUntil isHostile p fs' 0
        -- hostile fleets
    hostileFleets = filter isHostile fs'
	-- minimum turns remaining for hostile fleets
    minTurnsRemHostile = if null hostileFleets then 0 else (minimum . map fleetTurnsRemaining) hostileFleets
        -- budget planets: planets that still have (risk) budget available
    budgetPlanets = filter ((>0) . getBudget) $ filter ((>0) . getRiskBudget) budgetsAlliedPlanets
        -- candidate source planets: different pid, still budget available
    candSourcePlanets = filter ((/=) targetPid . planetId . getPlanet) budgetPlanets
        -- total budget: sum of budgets of candidate source planets 
    totBudget = sum $ map (\x -> min (getBudget x) (getRiskBudget x)) candSourcePlanets
	-- sorted candidate source planets (by distance)
    sortedCandSourcePlanets = sortBy (comparing (distanceBetween p' . getPlanet)) candSourcePlanets
    	-- source planet: candidate source planet closest to target
    sourceBudgets = head sortedCandSourcePlanets
    sourcePlanet = getPlanet sourceBudgets
	-- maximum growth before furthest allied fleet reaches the planet
    --max_growth = if isNeutral p' then 0 else (planetGrowthRate p') * ((turnsBetween p' . lastOf5 . last $ sortedCandSourcePlanets) - turnCorr)
    	-- id for source planet
    sourcePid = planetId sourcePlanet
    	-- distance from source planet to target planet
    dist = turnsBetween p' sourcePlanet
    	-- growth of target planet by the time fleet gets there
    growth = if isNeutral p' then 0 else (dist - turnCorr) * (planetGrowthRate p')
	-- maximum distance between candidate source planet and target planet
    max_dist = turnsBetween p' (getPlanet . last $ sortedCandSourcePlanets)
    	-- max growth of target planet by the time furthest allied fleet gets there
    max_growth = if isNeutral p' then 0 else (max_dist - turnCorr) * (planetGrowthRate p')
    	-- budget is minimum of current budget and computed risk budget
    budget = min (getBudget sourceBudgets) (getRiskBudget sourceBudgets)
    	-- required number of ships to conquer the planet
    reqShips = if isNeutral p' then planetShips p' + 1 else max budget (planetShips p' + growth + 1)
    	-- number of ships to send
    ships = if (budget > 0) then min budget reqShips else error "mkConquerOrders: ERROR! Budget of zero!"
    	-- actual order
    order = sourcePid `seq` targetPid `seq` ships `seq` Order sourcePid targetPid ships
    	-- update my planets
    budgetsAlliedPlanets' = removeBudgetShips risk budgetsAlliedPlanets (sourcePid, ships)
    	-- fleet that corresponds with this order
    fleet = fleetForOrder [sourcePlanet,p'] order
    	-- new turn correction
    turnCorr' = dist

-- conquer strategy
-- conquer additional planets
conquer :: BotParameters -> Double -> [Planet] -> [Planet] -> [Fleet] -> [Order]
-- no allied planets => no conquer orders
conquer _ _ [] _ _ = []
-- no planets to conquer
conquer _ _ _ [] _ = []
-- conquer head planet (if score is positive), and adjust allied planets accordingly
conquer params risk alliedPlanets allPlanets fleets
        -- only conquer planets with positive score
	| score >= (conquerScoreThreshold params) = orders ++ conquer params risk alliedPlanets' allPlanets fleets'
        -- if first planet in sorted list is no longer interesting to consider, then neither are the others
	| otherwise = [] 
  where
    -- determine budgets for allied planets (using current version of all planets)
    budgetsAllied = map (detBudgets risk fleets allPlanets) alliedPlanets
    -- conquer orders
    ordersPerPlanet = map (\p -> mkConquerOrders risk budgetsAllied p allPlanets fleets 0) allPlanets
    -- conquer scores (2nd allPlanets argument needs to be original version !)
    scores = map (conquerScore params risk budgetsAllied allPlanets fleets) $ zip allPlanets ordersPerPlanet
    -- helper function: first element of triple
    fst3 (x,_,_) = x
    -- planet to conquer
    (score, targetPlanet, orders) = head . reverse . sortBy (comparing fst3) $ zip3 scores allPlanets ordersPerPlanet
    -- id of target planet
    tid = planetId targetPlanet
    -- fleets for orders
    orderFleets = map (fleetForOrder allPlanets) orders
    -- updated version of allied planets
    alliedPlanets' = foldl removeShips alliedPlanets $ map (\f -> (fleetSource f, fleetShips f)) orderFleets
    -- update fleets
    fleets' = fleets ++ orderFleets

-- reinforce a single planet
reinforcePlanet :: BotParameters -> Int -> Double -> [Planet] -> [Fleet] -> [Planet] -> Planet -> [Order] -> [Order]
-- no extra reinforcements if we ran out of allied planets
reinforcePlanet _ _ _ _ _ [] _ orders = orders
reinforcePlanet params i risk allPlanets fleets alliedPlanets targetPlanet pastOrders
	-- reached max. reinforcements per target planet
	| i >= maxReinforcementsPerPlanet params = []
	-- target planet has served as a source planet before
        | elem tid orderSids = []
	-- no (more) candidate source planets
	| null candSources = []
	-- last candidate source planet
	| (null . tail) alliedPlanets = [order]
	| otherwise = order : reinforcePlanet params (i+1) risk allPlanets fleets alliedPlanetsFiltered targetPlanet (order:pastOrders)
  where
    -- target planet id
    tid = planetId targetPlanet
    -- order sources
    orderSids = map orderSource pastOrders
    -- past reinforcement fleets in this turn
    reinforceFleets = map (fleetForOrder allPlanets) pastOrders
    -- adjusted allied planets
    alliedPlanets' = foldl removeShips alliedPlanets $ map (\f -> (fleetSource f, fleetShips f)) reinforceFleets
    -- budgets of allied planets
    budgetsAllied = map (detBudgets risk (fleets++reinforceFleets) allPlanets) alliedPlanets'
    -- size of reinforcement
    size p = if (reinforcementSize params > 0) then reinforcementSize params else planetGrowthRate p
    -- candidate source planets
    candSources = if useUpdatedBudgetForReinforcing params
		   then filter (\x -> (getUpdatedBudget x) > size (getPlanet x) && tid /= planetId (getPlanet x)) budgetsAllied
   	 	   else filter (\x -> (getRiskBudget x) > size (getPlanet x) && tid /= planetId (getPlanet x)) budgetsAllied
    -- first allied planet
    p = getPlanet $ head candSources
    -- source planet id
    sid = planetId p
    -- reiforcement order
    order = Order sid tid (size p)
    -- remove allied planet used as source
    alliedPlanetsFiltered = filter ((/=) sid . planetId) alliedPlanets

-- reinforce allied planets in front-line
reinforce :: BotParameters -> Double -> [Planet] -> [Fleet] -> [Order]
reinforce params risk allPlanets fleets 
	| null hostilePlanets' = []
	| otherwise = foldl (\os p -> (++) os $ reinforcePlanet params 0 risk allPlanets fleets sourcePlanets p os) [] selectedPlanetsToReinforce
  where
    -- allied planets
    alliedPlanets = filter isAllied allPlanets
    -- merged and sorted fleets
    fleets' = mergeAndSortFleets fleets
    -- merged, sorted and filtered fleets for a particular planet
    fleets'' p = filter ((==) (planetId p) . fleetDestination) fleets'
    -- all planets, after processing fleets
    allPlanets' = map (\p -> simulateFleets p (fleets'' p) 0) allPlanets
    -- allied planets
    alliedPlanets' = filter isAllied allPlanets'
    -- hostile planets
    hostilePlanets' = filter isHostile allPlanets'
    -- mean distances to hostile planets
    -- FIXME: find a better way to determine frontline planets !
    --alliedDistances = map (\p -> mean $ map (distanceBetween p) hostilePlanets') alliedPlanets
    alliedDistances = map (\p -> minimum $ map (distanceBetween p) hostilePlanets') alliedPlanets
    -- mean distances to hostile planets
    --alliedDistances' = map (\p -> mean $ map (distanceBetween p) hostilePlanets') alliedPlanets'
    alliedDistances' = map (\p -> minimum $ map (distanceBetween p) hostilePlanets') alliedPlanets'
    -- allied planets sorted by distances, farest first
    sourcePlanets = (reverse . map snd . sortBy (comparing fst) . zip alliedDistances) alliedPlanets
    -- allied planets sorted by distances, closest first
    planetsToReinforce = (map snd . sortBy (comparing fst) . zip alliedDistances') alliedPlanets'
    -- selection of planets to reinforce: all or first N
    selectedPlanetsToReinforce = if reinforceFirstN params /= 0 then take (reinforceFirstN params) planetsToReinforce else planetsToReinforce
    -- selected source planets: don't use planets that are considered for reinforcing
    selectedSourcePlanets = foldl (\ps p -> filter ((/=) (planetId p) . planetId) ps) sourcePlanets selectedPlanetsToReinforce
    

-- attack a single planet
attackPlanet :: Double -> [Budgets] -> [Planet] -> [Fleet] -> Planet -> Int -> [Order]
attackPlanet risk budgetsAllied allPlanets fleets p turnCorr
	-- no candidate source planets
	| null candSourcePlanets = []
	-- planet conquered by current fleets, and can't be conquered by fictive hostile fleets
	| not (isHostile p') && not (isHostile p && isHostile p''') = []
	-- insufficient budget
	-- | totBudget < planetShips p'' + max_growth + 1 = []
	| totBudget < planetShips p'' + max_growth + 1 = []
 	-- don't weaken neutral planet for incoming hostile fleets
 	| (isNeutral p && (not alliedFleetsUnderWay) && dist < minTurnsRemHostile) = []
 	-- | (isNeutral p && isHostile p' && (not alliedFleetsUnderWay) && (dist < turnsUntilHostile)) = []
	-- else, create order and continue
	| otherwise = order : attackPlanet risk budgetsAllied' allPlanets (f:fleets) p turnCorr'
  where
 	-- planet id
    pid = planetId p
	-- filtered, merged and sorted fleets
    fleets' = filter ((==) pid . fleetDestination) $ mergeAndSortFleets fleets
    	-- updated planet
    p' = simulateFleets p fleets' 0
    	-- fictive enemy fleets
    fictHostileFleets = allFictHostileFleets p allPlanets fleets
	-- updated target planet after additional fictive hostile fleets
    p''' = simulateFleets p (mergeAndSortFleets $ fleets' ++ fictHostileFleets) 0
     	-- reference distance for fictive hostile fleet probabilities
    refDist = fromIntegral . last . take 3 . sort . map fleetTurnsRemaining $ mergeAndSortFleets fictHostileFleets
    --refDist = (**2) . fromIntegral . head . sort $ map fleetTurnsRemaining fictHostileFleets
	-- probabilities of hostile fleet
    probHostileFleets =  map (min 1.0 . (/) refDist . fromIntegral . fleetTurnsRemaining) fictHostileFleets
    	-- fictive hostile fleets after applying probabilities
    fictHostileFleets' = map (\(p,f) -> f { fleetShips = ceiling (p * (fromIntegral $ fleetShips f)) } ) $ zip probHostileFleets fictHostileFleets
	-- updated target planet after additional fictive hostile fleets
    p'' = simulateFleets p (mergeAndSortFleets $ fleets' ++ fictHostileFleets') 0
        -- are there any allied fleets travelling to planet p?
    alliedFleetsUnderWay = not . null $ filter isAllied fleets'
        -- turns until planet becomes hostile
    turnsUntilHostile = turnsUntil isHostile p fleets' 0
        -- hostile fleets
    hostileFleets = filter isHostile fleets'
	-- minimum turns remaining of hostile fleets
    minTurnsRemHostile = if null hostileFleets then 0 else (minimum . map fleetTurnsRemaining) hostileFleets
        -- total budget: sum of budgets of candidate source planets 
    totBudget = sum $ map (\x -> min (getBudget x) (getRiskBudget x)) candSourcePlanets
        -- budget planets: planets that still have (risk) budget available
    budgetPlanets = filter ((>0) . getBudget) $ filter ((>0) . getRiskBudget) budgetsAllied
        -- candidate source planets: different pid, still budget available
    candSourcePlanets = filter ((/=) pid . planetId . getPlanet) budgetPlanets
	-- sorted candidate source planets
    sortedCandSourcePlanets = sortBy (comparing (distanceBetween p' . getPlanet)) candSourcePlanets
	-- source planet, with budgets
    sourceBudgets = head sortedCandSourcePlanets
    sourcePlanet = getPlanet sourceBudgets
	-- distance between source planet and target planet
    dist = turnsBetween sourcePlanet p
    	-- growth
    growth = planetGrowthRate p * (dist - turnCorr)
	-- distance between source planet and target planet
    max_dist = turnsBetween (getPlanet . last $ sortedCandSourcePlanets) p
    	-- growth
    max_growth = planetGrowthRate p * (max_dist - turnCorr)
    	-- budget is minimum of current budget and computed risk budget
    budget = min (getBudget sourceBudgets) (getRiskBudget sourceBudgets)
        -- number of required ships
    reqShips = if isNeutral p then planetShips p'' + 1 else max budget (planetShips p'' + growth + 1)
    	-- number of ships to send
    ships = if (budget > 0) then min budget reqShips else error "createOrder: ERROR! Budget of zero!"
	-- actual order
    order = Order (planetId sourcePlanet) pid ships
    	-- update my planets
    budgetsAllied' = removeBudgetShips risk budgetsAllied (planetId sourcePlanet, orderShips order)
    	-- fleet that corresponds with this order
    f = fleetForOrder [sourcePlanet,p'] order
        -- new turn correction
    turnCorr' = turnsBetween sourcePlanet p
 
-- attack strategy
-- attack possibly harmful hostile planets
attack :: BotParameters -> Double -> [Planet] -> [Planet] -> [Fleet] -> [Order]
attack params risk alliedPlanets allPlanets fleets 
	| null scores || null hostilePlanets = []
	-- only attack planets worth attacking
	| targetScore > (attackScoreThreshold params) = if not (null attackOrders)
							 then attackOrders ++ attack params risk alliedPlanets' allPlanets (attackFleets++fleets')
							 else []
  	-- if top target planet is not worth it, then neither is the rest
	| otherwise = []
  where
    -- determine budgets for allied planets (using current version of all planets)
    budgetsAllied = map (detBudgets risk fleets allPlanets) alliedPlanets
    -- merge and sorted fleets
    fleets' = mergeAndSortFleets fleets
    -- attack scores for hostile planets
    scores = map (attackScore allPlanets fleets') allPlanets
    -- hostile planets
    hostilePlanets = filter (isHostile . snd) $ zip scores allPlanets -- only current hostile planets
    --hostilePlanets = filter((>0) . fst) $ zip scores allPlanets -- also include future hostile planets
    -- hostile planets sorted by attack score
    sortedHostilePlanets = reverse . sortBy (comparing fst) $ hostilePlanets
    -- determine target planet by attack score
    (targetScore, targetPlanet) = head sortedHostilePlanets
    -- id of target planet
    tid = planetId targetPlanet
    -- attack orders
    attackOrders = attackPlanet risk budgetsAllied allPlanets fleets' targetPlanet 0
    -- attack fleets
    attackFleets = map (fleetForOrder allPlanets) attackOrders
    -- updated version of allied planets
    alliedPlanets' = foldl removeShips alliedPlanets $ map (\f -> (fleetSource f, fleetShips f)) attackFleets

-- overall strategy: 
-- (i)  conquer additional planets
-- (ii) reinforce allied planets in front-line
-- (iii)   defend allied planets
-- (iv)  counter hostile moves
-- (v) attack hostile planets
doTurn :: GameState -> [Order]    
doTurn state = defendOrders ++ counterOrders ++ attackOrders ++ conquerOrders ++ reinforceOrders
  where
    -- 
    -- *** FUNCTION CALLS ***
    --
    -- orders for conquering additional planets (pass current version of all planets, to determine conservative budgets)
    conquerOrders = if enableConquer params then conquer params risk alliedPlanets allPlanets fleets else []
    -- orders for defending allied planets
    defendOrders = if enableDefend params then defend params allPlanetsDefendSorted budgetsAllPlanetsAfterConquer fleetsAfterConquer else []
    -- orders that reinforce allied planets
    reinforceOrders = if enableReinforce params then reinforce params risk allPlanetsAfterDefend fleetsAfterDefend else []
    -- orders for countering hostile moves
    counterOrders = []
    -- orders for attacking hostile planets
    attackOrders = if enableAttack params then attack params risk alliedPlanetsAfterReinforce allPlanetsAfterReinforce fleetsAfterReinforce else []
    
    --
    -- *** FUNCTION ARGUMENTS ***
    --
    -- all planets
    allPlanets = planets state
    -- allied planets (current version)
    alliedPlanets = filter isAllied allPlanets
    -- hostile planets (current version)
    hostilePlanets = filter isHostile allPlanets
    -- fleets: merged and sorted
    fleets = mergeAndSortFleets $ gameStateFleets state
    -- number of ships on my planets and in my fleets
    alliedShips = sum $ (map planetShips alliedPlanets) ++ (map fleetShips $ filter isAllied fleets)
    -- number of ships on enemy planets and in enemy fleets
    hostileShips = sum $ (map planetShips hostilePlanets) ++ (map fleetShips $ filter isHostile fleets)
    -- risk
    currRisk = detRisk params (production state) (alliedShips, hostileShips)
    -- all planets, updated
    allPlanets' = map (\p -> simulateFleets p (filter ((==) (planetId p) . fleetDestination) fleets) 0) allPlanets
    -- allied planets, updated
    alliedPlanets' = filter isAllied allPlanets'
    -- allied planets, updated
    hostilePlanets' = filter isHostile allPlanets'
    -- risk, determined by future state
    futureRisk = detRisk params (sum $ map planetGrowthRate alliedPlanets', sum $ map planetGrowthRate hostilePlanets') (sum $ map planetShips alliedPlanets', sum $ map planetShips hostilePlanets')
    -- actually used risk
    risk = if useFutureRisk params then futureRisk else currRisk
    -- win factor
    winFactor = if useFutureWinFactorConquerScoreThreshold params
		 then detFutureWinFactor allPlanets fleets
		 else detWinFactor (production state) (alliedShips, hostileShips)
    -- bot parameters
    --  1111_0.0__P-0.686__1.0_1_1.0__0.0__0.0__0.25__0.741__1.0__1.0_0_0.8_1011_1__4__4_0
    --  fitness of 0.5620 @ selected_bots2 and newmaps
    params = (BotParameters 
		-- ** mode controls **
		True -- conquer
		True -- reinforce
		True -- defend
		True -- attack
		-- ** score thresholds **
                0.0 --0.6 -- defend
		(min 0.686 winFactor) -- conquer
		1.0 -- attack
    		-- ** risk parameters **
    		False -- use future risk
		1.0 -- safety factor for using lowest risk (how muc do we need to be ahead?), 1.0: no risk when we're ahead or equal
    		0.0 -- 0.1 -- lowest risk
		0.0 -- low risk
    		0.25 -- some risk
    		0.741 -- moderate risk
    		1.0 -- high risk
    		1.0 -- 10.0 -- full risk
		-- ** conquer parameters **
		False -- use fail factor in conquer score
		0.8 -- fail factor
		True -- use distances factor in conquer score
		False -- use turns factor in conquer score
		True -- use ships factor in conquer score
		True -- use future win factor (or not)
		-- ** reinforcement parameters **
		1 -- reinforcement size (0: planet growth)
		4 -- max reinforcements per planet
		4 -- number of planets to reinforce (0: all)
		False -- use updated budget (if false, use risk budget)
             )

    --
    -- *** UPDATED STUFF ***
    --
    -- conquer fleets
    conquerFleets = map (fleetForOrder allPlanets) conquerOrders
    -- all planets after conquering
    allPlanetsAfterConquer = foldl removeShips allPlanets $ map (\o -> (orderSource o, orderShips o)) conquerOrders
    -- new set of fleets (original + conquer)
    fleetsAfterConquer = fleets ++ conquerFleets

    -- allied planets, sorted by importance for defending
    allPlanetsDefendSorted = sortBy (comparing planetGrowthRate) allPlanetsAfterConquer
    -- budgets for all planets
    budgetsAllPlanetsAfterConquer = map (detBudgets 0.0 fleetsAfterConquer allPlanetsAfterConquer) allPlanetsAfterConquer

    -- construct list of defend fleets
    defendFleets = map (fleetForOrder allPlanetsAfterConquer) defendOrders
    -- all planets after defending
    allPlanetsAfterDefend = foldl removeShips allPlanetsAfterConquer $ map (\o -> (orderSource o, orderShips o)) defendOrders
    -- new set of fleets (original + defend)
    fleetsAfterDefend = fleetsAfterConquer ++ defendFleets

    -- reinforce fleets
    reinforceFleets = map (fleetForOrder allPlanetsAfterDefend) reinforceOrders
    -- all planets after conquering
    allPlanetsAfterReinforce = foldl removeShips allPlanetsAfterDefend $ map (\o -> (orderSource o, orderShips o)) reinforceOrders
    -- allied planets after defending
    alliedPlanetsAfterReinforce = filter isAllied allPlanetsAfterReinforce
    -- hostile planets after defending
    hostilePlanetsAfterReinforce = filter isHostile allPlanetsAfterReinforce
    -- new set of fleets (original + conquer)
    fleetsAfterReinforce = fleetsAfterDefend ++ reinforceFleets
   
    -- production of allied planets
    productionAllied = sum $ map planetGrowthRate alliedPlanetsAfterReinforce
    -- production of hostile planets
    productionHostile = sum $ map planetGrowthRate hostilePlanetsAfterReinforce
    -- number of ships on my planets and in my fleets
    alliedShips' = sum $ (map planetShips alliedPlanetsAfterReinforce) ++ (map fleetShips $ filter isAllied fleetsAfterReinforce)
    -- number of ships on enemy planets and in enemy fleets
    hostileShips' = sum $ (map planetShips hostilePlanetsAfterReinforce) ++ (map fleetShips $ filter isHostile fleetsAfterReinforce)

    -- attack fleets
    attackFleets = map (fleetForOrder allPlanetsAfterReinforce) attackOrders
    -- all planets after defending
    allPlanetsAfterAttack = foldl removeShips allPlanetsAfterReinforce $ map (\o -> (orderSource o, orderShips o)) attackOrders
    -- allied planets after defending
    alliedPlanetsAfterAttack = filter isAllied allPlanetsAfterAttack
    -- new set of fleets (original + defend + attack)
    fleetsAfterAttack = fleetsAfterReinforce ++ attackFleets

--
-- *** MAIN ***
--
main :: IO ()
main = bot doTurn
--main = debugBot doTurn

