&

Screeps

PureScript

  • składnia podobna do Haskella
  • podzbiór rozszerzeń GHC, np. RankNTypes,
    FunctionalDependencies
  • kompiluje się do JavaScriptu
  • strict evaluation

Screeps w JavaScript

function(creep, myRooms) {
    var homeRoom = Game.rooms[creep.memory.homeRoom];
    if (creep.memory.fullcheck === undefined) creep.memory.fullcheck = false;
    if (creep.memory.fullcheck && creep.carry.energy == 0) {
        creep.memory.fullcheck = false;
    }
    if (!creep.memory.fullcheck && creep.carry.energy == creep.carryCapacity) {
        creep.memory.fullcheck = true;
    }
    if (creep.memory.fullcheck) {
        if (creep.upgradeController(homeRoom.controller) == ERR_NOT_IN_RANGE) {
            creep.moveTo(homeRoom.controller);
        }
        if (creep.pos.roomName != 'W3N58' && creep.pos.getRangeTo(homeRoom.controller) > 2) {
            creep.moveTo(homeRoom.controller);
        }
    } else {
        var myRoom = _.filterWithCache('Game.myRooms.' + creep.memory.homeRoom, Game.myRooms, {
            filter: {
                name: creep.memory.homeRoom
            }
        })[0];
        var energy = creep.pos.findInRange(FIND_DROPPED_RESOURCES, 3);
        if (energy.length != 0 && energy[0].resourceType == RESOURCE_ENERGY && energy[0].amount > 20) {
            if (creep.pickup(energy[0]) != OK) {
                creep.moveTo(energy[0]);
            }
        } else if (myRoom != undefined && myRoom.controllerLink != undefined && myRoom.controllerLink.energy > 0) {
            if (creep.withdraw(myRoom.controllerLink, RESOURCE_ENERGY) == ERR_NOT_IN_RANGE) {
                creep.moveTo(myRoom.controllerLink);
            }
        } else {
            var storage = homeRoom.storage;
            if (storage != undefined && storage.store[RESOURCE_ENERGY] > 50000) {
                if (creep.withdraw(storage, RESOURCE_ENERGY) == ERR_NOT_IN_RANGE) {
                    creep.moveTo(storage);
                } else {}
            } else {
                if (creep.memory.assignedNode != undefined) {
                    var myMiner = util.findMyMiner(creep);
                    util.collectEnergyFromMiner(creep, myMiner);
                } else {
                    energy = creep.pos.findClosestByRange(FIND_DROPPED_RESOURCES);
                    if (creep.pickup(energy) != OK) {
                        creep.moveTo(energy);
                    }
                }
            }
        }
    }
}

Screeps w PureScript

repeat do
  harvestEnergy
  transferEnergyToBase

Wolne monady

data Free f a
  = Pure a
  | Free (f (Free f a))

instance monadFree :: Functor f => Monad (Free f) where
  pure = Pure
  Pure a >>= f = f a
  Free m >>= f = Free ((>>= f) <$> m)

wrap :: forall f a. f (Free f a) -> Free f a
wrap = Free

resume :: forall f a. Functor f => Free f a -> Either (f (Free f a)) a
resume (Pure x) = Right x
resume (Free f) = Left f

liftF :: forall f a. Functor f => f a -> Free f a
liftF f = wrap $ pure <$> f

Plan

data PlanF a
  = HarvestEnergy a
  | TransferEnergyToBase a
  | Repeat (Plan Unit) a

instance functorPlanF :: Functor PlanF where
  map k f = case f of
    HarvestEnergy f'        -> HarvestEnergy $ k f'
    TransferEnergyToBase f' -> TransferEnergyToBase $ k f'
    Repeat x f'             -> Repeat x $ k f'

type Plan a = Free PlanF a

harvestEnergy :: Plan Unit
harvestEnergy = liftF $ HarvestEnergy unit

transferEnergyToBase :: Plan Unit
transferEnergyToBase = liftF $ TransferEnergyToBase unit

repeat :: Plan Unit -> Plan Unit
repeat block = liftF $ Repeat block unit

Flow

plan

memory

world

encodeJson

decodeJson

executePlan

Wykonywanie

executePlan :: Creep -> Plan Unit -> Exec (Plan Unit)
executePlan creep = resume >>> case _ of
  Left action -> peel action
  Right _ -> pure $ pure unit
  where
    peel action = case action of
      HarvestEnergy next ->
        if amtCarrying creep resource_energy < carryCapacity creep
          then do
            maybeSource <- findClosest find_sources
            case maybeSource of
              Just source -> do
                harvestSource creep source `orMoveTo` source
                stay
              Nothing -> throwError $ ErrorMessage "source not found"
          else transition next
      -- ...
      where
        stay = pure $ wrap action
        transition = executePlan creep
        -- ...

Kombinatory

  • repeat

...

Repeat

executePlan creep = -- ...
  where
    peel action = case action of
      Repeat block -> do
        block' <- executePlan creep block
        pure do
          block'
          plan
      -- ...
      where
        plan = wrap action
        -- ...

Demo

repeat do
  harvestEnergy
  transferEnergyToBase

Kombinatory

  • repeat

...

  • interrupt

+

Interrupt

executePlan creep = -- ...
  where
    peel action = case action of
      Interrupt interruptee interrupter next -> do
        interrupter' <- executePlan creep interrupter
        if isPure interrupter'
          then do
            interruptee' <- executePlan creep interruptee
            if isPure interruptee'
              then transition next
              else pure do
                liftF $ Interrupt interruptee' interrupter unit
                next
          else pure do
            interrupter'
            plan
      -- ...
      where
        plan = wrap action
        isPure = isRight <<< resume
        -- ...

Demo

repeat do
  harvestEnergy
  transferEnergyToBase `interrupt` build

Kombinatory

  • repeat

...

  • interrupt
  • interleave

+

+

Interleave

plan1 `interleave` plan2
do
  threadId <- fork plan2
  plan1
  kill threadId
do
  0 <- fork plan2
  plan1
  kill 0

Interleave

executePlan creep = -- ...
  where
    peel action = case action of
      Fork thread threadId next -> do
        isRunning <- hasThread threadId
        unless isRunning $
          addThread threadId thread
        transition next
      Join threadId next -> do
        isRunning <- hasThread threadId
        if isRunning
          then stay
          else transition next
      Kill threadId next -> do
        isRunning <- hasThread threadId
        when isRunning $
          removeThread threadId
        transition next
      -- ...
      where
        addThread threadId thread = -- ...
        hasThread threadId = -- ...
        removeThread threadId = -- ...
        -- ...

Demo

(repeat do
  harvestEnergy
  transferEnergyToBase `interrupt` build
)
  `interleave` repeat fight

Co dalej?

  • więcej akcji
  • optymalizacja
  • plany dla spawnów
  • metaplany dla zespołów creepów

PureScript & Screeps

By Piotr Kozakowski

PureScript & Screeps

  • 1,020