DRTS Bot Implementation

implementation

#1

Just the current bot code, for kartezjusz2009.
I had a look into the repository, and found the BotBehavior module. I guess this contains the complete bot.

module BotBehavior exposing (BotKind(..), botFromBotKind, bot20171217, getNextHarderOrHardestKind, getNextEasierOrEasiestKind)

import Game.Game as Game exposing (PlayerId, NodeId, EdgeDirection, UnitId, Node, AliveUnit)
import Game.Control
import AllDict
import Dict


type BotKind = Easy | Normal | Hard

type alias BotParameter =
  { sendUnitAmountFactor : Float
  , sendUnitAbsoluteThresholdFactor : Float
  , sendUnitRelativeThresholdFactor : Float
  }

getNextHarderOrHardestKind : BotKind -> BotKind
getNextHarderOrHardestKind kind =
  case kind of
  Easy -> Normal
  Normal -> Hard
  Hard -> Hard

getNextEasierOrEasiestKind : BotKind -> BotKind
getNextEasierOrEasiestKind kind =
  case kind of
  Easy -> Easy
  Normal -> Easy
  Hard -> Normal

botFromBotKind : BotKind -> PlayerId -> Game.Model -> List Game.Control.FromPlayerMsgToGame
botFromBotKind botKind =
  case botKind of
  Easy ->
    bot20180916 { sendUnitAmountFactor = 0.5, sendUnitAbsoluteThresholdFactor = 1.8, sendUnitRelativeThresholdFactor = 1.8 }
  Normal ->
    bot20180916 { sendUnitAmountFactor = 0.8, sendUnitAbsoluteThresholdFactor = 1.2, sendUnitRelativeThresholdFactor = 1.2 }
  Hard -> bot20171217

original2017BotParameter : BotParameter
original2017BotParameter =
  { sendUnitAmountFactor = 1, sendUnitAbsoluteThresholdFactor = 1, sendUnitRelativeThresholdFactor = 1 }

bot20171217 : PlayerId -> Game.Model -> List Game.Control.FromPlayerMsgToGame
bot20171217 = bot20180916 original2017BotParameter

bot20180916 : BotParameter -> PlayerId -> Game.Model -> List Game.Control.FromPlayerMsgToGame
bot20180916 botParameter botPlayerId game =
  game |> Game.dictUnitSubsetFromOwnerId botPlayerId
  |> Dict.toList
  |> List.filterMap (bot20180916UnitBehavior botParameter game)

bot20180916UnitBehavior : BotParameter -> Game.Model -> (UnitId, AliveUnit) -> Maybe Game.Control.FromPlayerMsgToGame
bot20180916UnitBehavior botParameter game (unitId, unit) =
  case unit.location of
  Game.OnEdge _ _ -> Nothing
  Game.OnNode locationNodeId ->
    let
      shouldStayOnNode =
        case game.dictNode |> Dict.get locationNodeId of
        Nothing -> False
        Just locationNode -> locationNode.occupationProgress < 1 && 0 < locationNode.occupationSpeedBase

      outEdges =
        game.dictEdge |> AllDict.keys
        |> List.filter (\edgeDirection -> edgeDirection.origNodeId == locationNodeId)

      destinationCandidates =
        outEdges
        |> List.filterMap (\edgeDirection ->
          case game.dictNode |> Dict.get edgeDirection.destNodeId of
          Nothing -> Nothing
          Just destNode -> Just (edgeDirection, destNode))

      destinationCandidatePriorityAndAmountToMove : (EdgeDirection, Node) -> Maybe (Float, Float)
      destinationCandidatePriorityAndAmountToMove (routeEdgeDirection, destNode) =
        let
          unitsOnEdgeAndDestination =
            game |> Game.getAliveUnits
            |> Dict.filter (\_ unit ->
              case unit.location of
              Game.OnNode unitLocationNodeId -> unitLocationNodeId == routeEdgeDirection.destNodeId
              Game.OnEdge unitLocationEdgeDirection _ -> unitLocationEdgeDirection == routeEdgeDirection)

          ownUnitsOnEdgeAndDestination =
            unitsOnEdgeAndDestination
            |> Dict.filter (\_ otherUnit -> unit.ownerPlayerId == otherUnit.ownerPlayerId)

          ownPowerOnTheWayAndAtDestination =
            ownUnitsOnEdgeAndDestination
            |> aggregatedPowerFromDictUnit

          enemyPowerOnTheWayAndAtDestination =
            ownUnitsOnEdgeAndDestination
            |> Dict.diff unitsOnEdgeAndDestination
            |> aggregatedPowerFromDictUnit

          amountToMove =
            (unit.power - (ownPowerOnTheWayAndAtDestination - enemyPowerOnTheWayAndAtDestination * 1.4)) * 0.6 *
            botParameter.sendUnitAmountFactor
        in
          if amountToMove < 0.7 * botParameter.sendUnitAbsoluteThresholdFactor ||
            unit.power < (enemyPowerOnTheWayAndAtDestination - ownPowerOnTheWayAndAtDestination) / 2 *
            botParameter.sendUnitRelativeThresholdFactor
          then Nothing
          else Just (-ownPowerOnTheWayAndAtDestination, amountToMove)

      destinationCandidatesOrderedAndFilteredWithAmountToMove =
        destinationCandidates
        |> List.filterMap (\candidate ->
          case destinationCandidatePriorityAndAmountToMove candidate of
          Nothing -> Nothing
          Just priorityAndAmountToMove -> Just (candidate, priorityAndAmountToMove))
        |> List.sortBy (Tuple.second >> Tuple.first)
        |> List.map (\(destination, (_, amountToMove)) -> (destination, amountToMove))
        |> List.reverse
    in
      case destinationCandidatesOrderedAndFilteredWithAmountToMove |> List.head of
      Nothing -> Nothing
      Just ((edgeDirection, destNode), amountToMove) ->
        if unit.path /= [] || shouldStayOnNode || unit.power < 1
        then Nothing
        else
          { unitId = unitId
          , path = [ edgeDirection.destNodeId ]
          , portionToSendRelative = ((amountToMove / unit.power) |> min 1)
          } |> Game.Control.SendUnitPortionOnPath |> Just

aggregatedPowerFromDictUnit : Dict.Dict UnitId AliveUnit -> Float
aggregatedPowerFromDictUnit = Dict.values >> (List.map .power) >> List.sum


#2

Types from the Game.Game module:

module Game.Game exposing (..)

import AllDict
import Vector2 exposing (..)
import Dict
import Dict.Extra
import Set


type alias Model =
  { dictNode : Dict.Dict NodeId Node
  , dictEdge : EdgeDict EdgeProperties
  , dictUnit : Dict.Dict UnitId AliveOrDeadUnit
  , progressedAmountMilli : Int
  , result : Maybe GameResult
  }

type alias EdgeDict value = AllDict.AllDict EdgeDirection value (Int, Int)

type alias Node =
  { location : Vector2.Float2
  , production : Float
  , occupationSpeedBase : Float
  , occupantPlayerId : PlayerId
  , occupationProgress : Float
  }

type alias EdgeDirection =
  { origNodeId : NodeId
  , destNodeId : NodeId
  }

type alias EdgeProperties =
  { travelDuration : Float
  }

type UnitLocation
  = OnNode NodeId
  | OnEdge EdgeDirection Float

type alias UnitWithGenericPower power =
  { ownerPlayerId : PlayerId
  , location : UnitLocation
  , power : power
  , path : UnitPath
  }

type alias AliveOrDeadUnit = UnitWithGenericPower UnitPower

type alias AliveUnit = UnitWithGenericPower Float

type UnitPower
  = Alive Float
  | Dead Int

type alias GameResult =
  { winnerPlayersIds : Set.Set PlayerId
  }

type alias NodeId = Int

type alias PlayerId = Int

type alias UnitId = Int

type alias UnitPath = List NodeId