    -- | 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 :: forall (m :: * -> *). MonadClient m => ActorId -> m RequestAI
    queryAI ActorId
    aid = do
      -- @sleader@ may be different from @gleader@ due to @restoreLeaderFromRun@,
      -- but only leaders may change faction leader, so we fix that beforehand:
      body <- (State -> Actor) -> m Actor
    forall a. (State -> a) -> m a
    forall (m :: * -> *) a. MonadStateRead m => (State -> a) -> m a
    getsState ((State -> Actor) -> m Actor) -> (State -> Actor) -> m Actor
    forall a b. (a -> b) -> a -> b
    $ ActorId -> State -> Actor
    getActorBody ActorId
    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 $ \StateClient
    cli -> StateClient
    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@:
            (StateClient -> StateClient) -> m ()
    forall (m :: * -> *).
    MonadClient m =>
    (StateClient -> StateClient) -> m ()
    modifyClient ((StateClient -> StateClient) -> m ())
    -> (StateClient -> StateClient) -> m ()
    forall a b. (a -> b) -> a -> b
    $ \StateClient
    cli -> StateClient
    cli
              { _sleader = mleader
              , sfleeD = EM.alter (const oldFlee) aidToMove $ sfleeD cli }
            (a, t, _) <- [(ActorId, Actor)]
    -> [(ActorId, Actor)]
    -> Maybe ActorId
    -> ActorId
    -> m (ActorId, RequestTimed, Maybe (Point, Time))
    forall (m :: * -> *).
    MonadClient m =>
    [(ActorId, Actor)]
    -> [(ActorId, Actor)]
    -> Maybe ActorId
    -> ActorId
    -> m (ActorId, RequestTimed, Maybe (Point, Time))
    pickActorAndAction [(ActorId, Actor)]
    foeAssocs [(ActorId, Actor)]
    friendAssocs
                                            (ActorId -> Maybe ActorId
    forall a. a -> Maybe a
    Just ActorId
    aidToMove) ActorId
    aid
            return (a, t)
      (aidToMove2, treq2) <-
        if mleader /= Just aid
        then return (aidToMove, treq)
        else case treq of
          RequestTimed
    ReqWait -> m (ActorId, RequestTimed)
    tryAgain
          RequestTimed
    ReqYell -> m (ActorId, RequestTimed)
    tryAgain
          RequestTimed
    _ -> (ActorId, RequestTimed) -> m (ActorId, RequestTimed)
    forall a. a -> m a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (ActorId
    aidToMove, RequestTimed
    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 :: forall (m :: * -> *).
    MonadClient m =>
    [(ActorId, Actor)]
    -> [(ActorId, Actor)]
    -> Maybe ActorId
    -> ActorId
    -> m (ActorId, RequestTimed, Maybe (Point, Time))
    pickActorAndAction [(ActorId, Actor)]
    foeAssocs [(ActorId, Actor)]
    friendAssocs Maybe ActorId
    maid ActorId
    aid = do
      mleader <- (StateClient -> Maybe ActorId) -> m (Maybe ActorId)
    forall a. (StateClient -> a) -> m a
    forall (m :: * -> *) a.
    MonadClientRead m =>
    (StateClient -> a) -> m a
    getsClient StateClient -> Maybe ActorId
    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 = ActorId -> Maybe ActorId
    forall a. a -> Maybe a
    Just ActorId
    aidToMove Maybe ActorId -> Maybe ActorId -> Bool
    forall a. Eq a => a -> a -> Bool
    == Maybe ActorId
    maid
      treq <- pickAction foeAssocs friendAssocs aidToMove retry
      return (aidToMove, treq, oldFlee)