content: -- | Ways for the client to use AI to produce server requests, based on
  -- the client's view of the game state.
  module Game.LambdaHack.Client.AI
    ( queryAI
  #ifdef EXPOSE_INTERNAL
      -- * Internal operations
    , pickActorAndAction
  #endif
    ) where
  
  import Prelude ()
  
  import Game.LambdaHack.Core.Prelude
  
  import qualified Data.EnumMap.Strict as EM
  
  import Game.LambdaHack.Client.AI.PickActionM
  import Game.LambdaHack.Client.AI.PickActorM
  import Game.LambdaHack.Client.MonadClient
  import Game.LambdaHack.Client.Request
  import Game.LambdaHack.Client.State
  import Game.LambdaHack.Common.Actor
  import Game.LambdaHack.Common.ActorState
  import Game.LambdaHack.Common.Faction
  import Game.LambdaHack.Common.MonadStateRead
  import Game.LambdaHack.Common.Point
  import Game.LambdaHack.Common.State
  import Game.LambdaHack.Common.Time
  import Game.LambdaHack.Common.Types
  
  -- | Handle the move of an actor under AI control (regardless if the whole
  -- faction is under human or computer control).
  queryAI :: MonadClient m => ActorId -> m RequestAI
  queryAI aid = do
    -- @sleader@ may be different from @gleader@ due to @restoreLeaderFromRun@,
    -- but only leaders may change faction leader, so we fix that beforehand:
    body <- getsState $ getActorBody aid
    foeAssocs <- getsState $ foeRegularAssocs (bfid body) (blid body)
    friendAssocs <- getsState $ friendRegularAssocs (bfid body) (blid body)
    mleader <- getsState $ gleader . (EM.! bfid body) . sfactionD
    mleaderCli <- getsClient sleader
    unless (Just aid == mleader || mleader == mleaderCli) $
      -- @aid@ is not the leader, so he can't change leader later on,
      -- so we match the leaders here
      modifyClient $ \cli -> cli {_sleader = mleader}
    (aidToMove, treq, oldFlee) <- pickActorAndAction foeAssocs friendAssocs
                                                     Nothing aid
    let tryAgain = do
          -- Leader waits; a waste; try once to pick a yet different leader
          -- or at least a non-waiting action. Undo state changes in @pickAction@:
          modifyClient $ \cli -> cli
            { _sleader = mleader
            , sfleeD = EM.alter (const oldFlee) aidToMove $ sfleeD cli }
          (a, t, _) <- pickActorAndAction foeAssocs friendAssocs
                                          (Just aidToMove) aid
          return (a, t)
    (aidToMove2, treq2) <-
      if mleader /= Just aid
      then return (aidToMove, treq)
      else case treq of
        ReqWait -> tryAgain
        ReqYell -> tryAgain
        _ -> return (aidToMove, treq)
    return ( ReqAITimed treq2
           , if aidToMove2 /= aid then Just aidToMove2 else Nothing )
  
  -- | Pick an actor to move and an action for him to perform, given an optional
  -- previous candidate actor and action and the server-proposed actor.
  pickActorAndAction :: MonadClient m
                     => [(ActorId, Actor)] -> [(ActorId, Actor)]
                     -> Maybe ActorId -> ActorId
                     -> m (ActorId, RequestTimed, Maybe (Point, Time))
  pickActorAndAction foeAssocs friendAssocs maid aid = do
    mleader <- getsClient sleader
    aidToMove <-
      if mleader == Just aid
      then pickActorToMove foeAssocs friendAssocs maid
      else do
        setTargetFromDoctrines foeAssocs friendAssocs aid
        return aid
    oldFlee <- getsClient $ EM.lookup aidToMove . sfleeD
    -- Trying harder (@retry@) whenever no better leader found and so at least
    -- a non-waiting action should be found.
    -- If a new leader found, there is hope (but we don't check)
    -- that he gets a non-waiting action without any desperate measures.
    let retry = Just aidToMove == maid
    treq <- pickAction foeAssocs friendAssocs aidToMove retry
    return (aidToMove, treq, oldFlee)
  