packages feed

sc2hs-0.1.0.0: src/Network/SC2/Agent.hs

{-#LANGUAGE NoMonadFailDesugaring #-} -- FIXME
{-#LANGUAGE TypeApplications, TemplateHaskell, NamedFieldPuns, TypeSynonymInstances, FlexibleInstances, ScopedTypeVariables#-}
{-#LANGUAGE GADTs, RankNTypes, GeneralisedNewtypeDeriving, PatternSynonyms, DataKinds, ConstraintKinds, FlexibleContexts, TypeOperators,OverloadedLabels, PartialTypeSignatures #-}
module Network.SC2.Agent where

import Control.Monad.Freer
import Control.Effects.Logging
import Data.OpenUnion ((:++:))
import Control.Monad.Freer.Reader
import Data.Traversable
import Control.Lens
import Data.Functor.Identity
import Network.SC2.LowLevel.Requests as LLR
import Control.Monad.Freer.State
import Data.Word
import Network.SC2.Constants.Entities
import qualified Network.SC2.Constants.Abilities as A
import qualified Network.SC2.Constants.Units as U
import qualified Proto.S2clientprotocol.Sc2api as S
import qualified Proto.S2clientprotocol.Raw as S
import Network.SC2.LowLevel.Types 
import Control.Monad.Freer.TH
import Lens.Labels (HasLens')
import Network.SC2.LowLevel.Protocol  as LLP (SC2Control, syncRequest, Split, unsafeRequest, unsafeResponse, getStatus)
import Data.ProtoLens (defMessage)
import qualified Data.Text as T
import Network.SC2.LowLevel.Convert
import Data.Maybe
import Data.Coerce

data AgentUnitKnowledge = UnitKnowledge { -- TODO Use an entity component system, or at least...
    --TODO Turn this into a map from PlayerID -> UnitContainer or some other associative structure
    --TODO or just a damn Reader and use local
    ownUnits :: UnitContainer
    , enemyUnits :: UnitContainer
    , alliedUnits :: UnitContainer
    , neutralUnits :: UnitContainer
    , allUnits :: UnitContainer
} deriving (Eq, Show)
defUnitKnowledge :: AgentUnitKnowledge
defUnitKnowledge = UnitKnowledge{ownUnits = [], enemyUnits = [], alliedUnits = [], neutralUnits =[], allUnits = []}
type UnitFilter = Unit -> Bool

instance Eq UnitKnowledgeFilter where
    (==) a b= True
instance Show UnitKnowledgeFilter where
    show a = ""
data WorldState = WorldState {
    gameInfo :: GameInfoResponse,
    knownUnits :: AgentUnitKnowledge
} deriving (Eq, Show)


type UnitKnowledgeFilter = (AgentUnitKnowledge -> UnitContainer)
data Agent a where
    UpdateObservations :: Agent ()
    ViewPlayerResources :: Agent PlayerResources
    ViewMapInfo :: Agent MapInfo
    ViewUnits ::  Agent AgentUnitKnowledge
    OrderUnits :: forall a. Orderable a=> a -> Order -> Agent ()
    SendChat :: T.Text -> ChatChannel -> Agent ()
    ViewWorldState :: Agent WorldState
    Step :: Agent ()
    StepN :: Word -> Agent ()
    GetStatus :: Agent S.Status

    
$(makeEffect ''Agent)
type SC2Agent r = Member Agent r 
type SC2AgentEffects = Agent
type SC2AgentSupportEffects = '[Split, Logging]
runSC2Agent :: (Members (SC2AgentSupportEffects) r) =>  WorldState -> Eff (Agent ': r) ~> Eff ( SC2Control ': r)
runSC2Agent initialState = evalState initialState . reinterpret2 act where
    act :: (Members (SC2AgentSupportEffects) r) => Agent  ~> Eff (State WorldState ': SC2Control ': r)
    act (Network.SC2.Agent.Step) = do --ugh
        result <- syncRequest (LLR.Step) --ugh
        case result of 
            Right () -> return ()
            Left e -> logError ("runSC2Agent Step: " ++ show e)
    act (StepN i) = do
        result <- syncRequest (StepMany i)
        case result of
            Right () -> return ()
            Left e -> logError ("runSC2Agent StepN: " ++ show e)
    act GetStatus = LLP.getStatus
        
    act UpdateObservations = do
        Right obs <- syncRequest $ ((defMessage & #observation .~ defMessage) :: S.Request) -- FIXME
        currState <- get @WorldState
        put $ processObservations currState (obs ^. #observation . #observation)
    
    act ViewUnits= do
        state <- get
        return (knownUnits state)
    act ViewWorldState = get
    act (OrderUnits units order) = do -- TODO: This is terribly hacky. Make Action a Requestable!
        let aruc = (defMessage:: S.ActionRawUnitCommand) & #abilityId .~ (A.toInt $ abilityID order) & setTarget (target order)  & #unitTags .~ (toTags units)
        let ar = (defMessage :: S.ActionRaw) & #unitCommand .~ aruc
        let a = (defMessage :: S.RequestAction) & #actions .~ [(defMessage :: S.Action) & #actionRaw .~ ar]
        _ <- syncRequest ((defMessage & #action .~ a)      :: S.Request)
        return ()
        where
            setTarget (TargetPoint p) m = m & #targetWorldSpacePos .~ convertTo p
            setTarget (TargetUnit u) m = m & #targetUnitTag .~ coerce u





enemyStartLocations :: Eff (Agent ': r) [Point]
enemyStartLocations = do
    state <- viewWorldState
    return $ (startLocations . fromJust . startRaw . gameInfo) state
processObservations ::  WorldState -> S.Observation -> WorldState
processObservations state obs = state {knownUnits = knownUnits'} where
    knownUnits' = UnitKnowledge {ownUnits, enemyUnits, alliedUnits, neutralUnits, allUnits}
    allUnits = fmap (fromJust . convertFrom) (obs ^. #rawData . #units)
    ownUnits = filter (\u -> alliance u == S.Self) allUnits
    enemyUnits = filter (\u -> alliance u == S.Enemy) allUnits
    alliedUnits = filter (\u -> alliance u == S.Ally) allUnits
    neutralUnits = filter (\u -> alliance u == S.Neutral) allUnits



--TODO: Turn this into a lens (is it a zoom?) over arbitrary UnitContainer

selectFromAllUnits :: SC2Agent r=>  UnitFilter -> Eff r UnitContainer
selectFromAllUnits f= do
    unitsKnown <- viewUnits
    return (filter f (allUnits unitsKnown))
    

units :: SC2Agent r=> Eff  r UnitContainer
units = do
    all <- viewUnits
    return (ownUnits all)



workers :: SC2Agent r =>  Eff r UnitContainer
workers = do
    un <- units
    return (filter isWorker un)
    


isWorker' :: UnitType -> Bool
isWorker' u = u == U.Probe || u == U.SCV || u == U.Drone
isWorker :: Unit -> Bool
isWorker u = isWorker' (unitType u)

viewUnitsOfPlayer :: SC2Agent r => PlayerID -> Eff (r) UnitContainer
viewUnitsOfPlayer player = selectFromAllUnits (\u -> owner u == player)


initialiseWithGameInfo :: GameInfoResponse -> WorldState
initialiseWithGameInfo gi = WorldState {gameInfo = gi, knownUnits = defUnitKnowledge}


attack :: (Member Agent r, Orderable a, Targetable b) => a -> b -> Eff r ()
attack units target = do
    send $ OrderUnits units order
    where
        order = Order A.Attack (asTarget target) False