178 lines
8.3 KiB
Haskell
178 lines
8.3 KiB
Haskell
{-# LANGUAGE ViewPatterns, LambdaCase, DataKinds, GADTs #-}
|
|
|
|
import System.IO
|
|
import Control.Applicative
|
|
import Control.Arrow (first, second)
|
|
import Control.Monad
|
|
import Data.Either
|
|
import Data.Foldable
|
|
import Data.Function
|
|
import Data.IORef
|
|
import Data.List
|
|
import Data.Maybe
|
|
|
|
import qualified Data.Map as M
|
|
|
|
type EntityId = Int
|
|
type PlayerId = Int
|
|
type FactoryId = Int
|
|
|
|
data Factory = Factory { factoryId :: EntityId
|
|
, factoryOwner :: PlayerId
|
|
, factoryCyborgs :: Int
|
|
, factoryProduction :: Int
|
|
, factoryDelay :: Int
|
|
} deriving (Eq, Show)
|
|
|
|
data Troop = Troop { troopId :: EntityId
|
|
, troopOwner :: PlayerId
|
|
, troopOrigin :: FactoryId
|
|
, troopTarget :: FactoryId
|
|
, troopCyborgs :: Int
|
|
, troopTurnsLeft :: Int
|
|
} deriving (Eq, Show)
|
|
|
|
data Bomb = Bomb { bombId :: EntityId
|
|
, bombSender :: PlayerId
|
|
, bombOrigin :: FactoryId
|
|
, bombTarget :: FactoryId
|
|
, bombDelay :: Int
|
|
} deriving (Eq, Show)
|
|
|
|
data Action = Move FactoryId FactoryId Int
|
|
| SendBomb FactoryId FactoryId
|
|
| Increase FactoryId
|
|
| Wait
|
|
deriving (Eq, Show)
|
|
|
|
actionString :: Action -> String
|
|
actionString (Move src dst cnt) = unwords ["MOVE", show src, show dst, show cnt]
|
|
actionString (SendBomb src dst) = unwords ["BOMB", show src, show dst]
|
|
actionString (Increase fid) = unwords ["INC", show fid]
|
|
actionString Wait = "WAIT"
|
|
|
|
thisPlayer, neutralPlayer, opponent :: PlayerId
|
|
thisPlayer = 1
|
|
neutralPlayer = 0
|
|
opponent = -1
|
|
|
|
main :: IO ()
|
|
main = do
|
|
hSetBuffering stdout NoBuffering -- DO NOT REMOVE
|
|
|
|
factoryCount <- readLn :: IO Int -- the number of factories
|
|
linkCount <- readLn :: IO Int -- the number of links between factories
|
|
|
|
links <- fmap (M.fromList . concat) $ replicateM linkCount $ do
|
|
[f1, f2, dist] <- map read . words <$> getLine
|
|
pure ([((f1, f2), dist), ((f2, f1), dist)] :: [((FactoryId, FactoryId), Int)])
|
|
|
|
bombsLeftRef <- newIORef 2
|
|
|
|
forever $ do
|
|
bombsLeft <- readIORef bombsLeftRef
|
|
entityCount <- readLn :: IO Int -- the number of entities (e.g. factories and troops)
|
|
|
|
(partitionEithers -> (factoryList, partitionEithers -> (troopList, bombList))) <- replicateM entityCount $ do
|
|
((read -> entityId):entityType:(map read -> [a1,a2,a3,a4,a5])) <- words <$> getLine
|
|
pure $ case entityType of
|
|
"FACTORY" -> Left $ Factory entityId a1 a2 a3 a4
|
|
"TROOP" -> Right . Left $ Troop entityId a1 a2 a3 a4 a5
|
|
"BOMB" -> Right . Right $ Bomb entityId a1 a2 a3 a4
|
|
|
|
let factories = M.fromList [ (factoryId f, f) | f <- factoryList ]
|
|
let myFactories = filter (\f -> factoryOwner f == thisPlayer) factoryList
|
|
let inTransitTo =
|
|
let transitMap = M.fromList
|
|
[ (to, (mine, theirs))
|
|
| let myTroops = filter (\t -> troopOwner t == thisPlayer) troopList
|
|
, let theirTroops = filter (\t -> troopOwner t /= thisPlayer) troopList
|
|
, to <- map factoryId factoryList
|
|
, let mine = sum $ map troopCyborgs $ filter (\t -> troopTarget t == to) myTroops
|
|
, let theirs = sum $ map troopCyborgs $ filter (\t -> troopTarget t == to) theirTroops
|
|
]
|
|
in \dst -> fromMaybe (0, 0) $ M.lookup dst transitMap
|
|
|
|
let myTotalProduction = sum $ map factoryProduction myFactories
|
|
|
|
let distributeScores src = do
|
|
dst <- myFactories
|
|
guard $ factoryId dst /= factoryId src
|
|
let (srcDefending, srcAttacking) = inTransitTo (factoryId src)
|
|
let (dstDefending, dstAttacking) = inTransitTo (factoryId dst)
|
|
let srcTotal = factoryCyborgs src - srcAttacking
|
|
let dstTotal = factoryCyborgs dst + dstDefending - dstAttacking
|
|
guard $ 2 * srcTotal >= 3 * dstTotal
|
|
let amount = (factoryCyborgs src + 1) `div` 3
|
|
guard $ amount > 0
|
|
dist <- toList $ M.lookup (factoryId src, factoryId dst) links
|
|
let threshold = (1.0 - saturate 20.0 (fromIntegral (factoryCyborgs src)))
|
|
* (fromIntegral (factoryProduction src + 1) / 4.0)
|
|
* 0.5
|
|
let score = saturate 10.0 (fromIntegral (srcTotal - dstTotal))
|
|
* saturate 20.0 (fromIntegral (srcTotal - amount))
|
|
* (1.0 - 0.25 * saturate 20.0 (fromIntegral dstTotal))
|
|
guard $ score >= threshold
|
|
pure (score, [Move (factoryId src) (factoryId dst) amount])
|
|
|
|
let attackScores src = do
|
|
guard $ factoryCyborgs src >= 5 * factoryProduction src
|
|
dst <- filter (\f -> factoryOwner f /= thisPlayer) factoryList
|
|
dist <- toList $ M.lookup (factoryId src, factoryId dst) links
|
|
let (mineDefending, theirsAttacking) = inTransitTo (factoryId src)
|
|
let (mineAttacking, theirsDefending) = inTransitTo (factoryId dst)
|
|
let myTotal = factoryCyborgs src - theirsAttacking
|
|
let theirTotal = factoryCyborgs dst + theirsDefending - mineAttacking
|
|
let theirEstimate
|
|
| factoryOwner dst == neutralPlayer = theirTotal
|
|
| otherwise = theirTotal + factoryProduction dst * (1 + dist)
|
|
guard $ 2 * myTotal >= 3 * theirTotal
|
|
let amount = theirEstimate + 1
|
|
guard $ amount > 0
|
|
let threshold = (1.0 - saturate 20.0 (fromIntegral (factoryCyborgs src)))
|
|
* (fromIntegral (factoryProduction src + 1) / 4.0)
|
|
* saturate 6.0 (fromIntegral myTotalProduction)
|
|
* 0.5
|
|
let score = fromIntegral (factoryProduction dst + 8) / 10.0
|
|
* saturate 10.0 (fromIntegral (myTotal - amount))
|
|
* (40.0 / (39.0 + fromIntegral dist))
|
|
guard $ score >= threshold
|
|
pure (score, [Move (factoryId src) (factoryId dst) amount])
|
|
|
|
let increaseScores src = do
|
|
guard $ factoryProduction src < 3
|
|
guard $ factoryCyborgs src >= 10
|
|
let score = saturate 10.0 $ fromIntegral myTotalProduction
|
|
pure (score, [Increase (factoryId src)])
|
|
|
|
let factoryActions src = concatMap snd $ best 1 $ concatMap ($ src) $
|
|
[ distributeScores, attackScores, increaseScores ]
|
|
|
|
let actions = concatMap factoryActions myFactories
|
|
|
|
let bombActions = concatMap snd $ best 1 $ do
|
|
guard $ bombsLeft > 0 && not (null myFactories)
|
|
dst <- filter (\f -> factoryOwner f == opponent) factoryList
|
|
guard $ factoryCyborgs dst >= 50 + 50 * (2 - bombsLeft)
|
|
let src = snd $ minimumBy (compare `on` fst) $
|
|
[ (dist, s) | s <- myFactories
|
|
, dist <- toList $ M.lookup (factoryId s, factoryId dst) links
|
|
]
|
|
let score = saturate 100.0 $ fromIntegral $ factoryCyborgs dst
|
|
pure (score, [SendBomb (factoryId src) (factoryId dst)])
|
|
|
|
let actions' = actions ++ bombActions
|
|
case actions' of
|
|
[] -> putStrLn $ actionString Wait
|
|
xs -> putStrLn $ intercalate "; " $ map actionString xs
|
|
|
|
let isSendBomb act = case act of { SendBomb _ _ -> True; _ -> False }
|
|
modifyIORef bombsLeftRef (subtract $ length $ filter isSendBomb actions')
|
|
|
|
saturate :: Double -> Double -> Double
|
|
saturate hl x = if x < 0.0 then 0.0 else 1.0 - (2.0 ** (-x / hl))
|
|
|
|
best :: Ord a => Int -> [(a, b)] -> [(a, b)]
|
|
best n = take n . sortBy (flip compare `on` fst)
|
|
|