{-# LANGUAGE ViewPatterns #-} import System.IO import Control.Applicative import Control.Arrow (first, second, (>>>)) import Control.Monad import Control.Monad.Writer import Data.Either import Data.Foldable import Data.Function import Data.IORef import Data.List import Data.Maybe import Data.Monoid 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 , bombOwner :: PlayerId , bombOrigin :: FactoryId , bombTarget :: FactoryId , bombTurnsLeft :: Int } deriving (Eq, Show) data Action = Move FactoryId FactoryId Int | SendBomb FactoryId FactoryId | Increase FactoryId | Wait deriving (Eq, Show) class HasEntityId a where entityId :: a -> EntityId instance HasEntityId Factory where entityId = factoryId instance HasEntityId Troop where entityId = troopId instance HasEntityId Bomb where entityId = bombId class HasOwner a where owner :: a -> PlayerId instance HasOwner Factory where owner = factoryOwner instance HasOwner Troop where owner = troopOwner instance HasOwner Bomb where owner = bombOwner class HasCyborgs a where cyborgs :: a -> Int instance HasCyborgs Factory where cyborgs = factoryCyborgs instance HasCyborgs Troop where cyborgs = troopCyborgs class HasOrigin a where origin :: a -> FactoryId instance HasOrigin Troop where origin = troopOrigin instance HasOrigin Bomb where origin = bombOrigin class HasTarget a where target :: a -> FactoryId instance HasTarget Troop where target = troopTarget instance HasTarget Bomb where target = bombTarget class HasTurnsLeft a where turnsLeft :: a -> Int instance HasTurnsLeft Troop where turnsLeft = troopTurnsLeft instance HasTurnsLeft Bomb where turnsLeft = bombTurnsLeft data GameState = GameState { gsLinks :: M.Map (FactoryId, FactoryId) Int , gsFactories :: [Factory] , gsTroops :: [Troop] , gsBombs :: [Bomb] , gsBombsLeft :: Int , gsNextEntity :: EntityId , gsTurnCount :: Int } deriving (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 (not used) 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)]) turnCountRef <- newIORef 1 bombsLeftRef <- newIORef 2 forever $ do entityCount <- readLn :: IO Int (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 nextEntity = (+1) $ maximum $ 0 : map entityId factoryList ++ map entityId troopList ++ map entityId bombList turnCount <- readIORef turnCountRef bombsLeft <- readIORef bombsLeftRef let chosenActions = chooseActions $ GameState { gsLinks = links , gsFactories = factoryList , gsTroops = troopList , gsBombs = bombList , gsNextEntity = nextEntity , gsTurnCount = turnCount , gsBombsLeft = bombsLeft } case chosenActions 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 chosenActions) modifyIORef turnCountRef (+1) awareness, foresight :: Int awareness = 10 -- number of actions to consider foresight = 30 -- number of moves to look ahead chooseActions :: GameState -> [Action] chooseActions gs = loop [] (scoreGame (simulate [] foresight gs)) (allFactoryActions ++ bombActions) where scoreActions as = scoreGame $ simulate [(thisPlayer, as)] foresight gs loop cs sc as = case best awareness $ map (\a -> (scoreActions (cs ++ [a]), a)) as of (sc',a):rs | sc' > sc -> loop (cs ++ [a]) sc' (map snd rs) _ -> cs myFactories = filter (\f -> owner f == thisPlayer) (gsFactories gs) transitMap = M.fromList [ (to, (mine, theirs)) | let (myTroops, theirTroops) = partition (\t -> owner t == thisPlayer) (gsTroops gs) , to <- map entityId (gsFactories gs) , let mine = sum [ cyborgs t | t <- myTroops, target t == to ] , let theirs = sum [ cyborgs t | t <- theirTroops, target t == to ] ] inTransitTo dst = fromMaybe (0, 0) $ M.lookup dst transitMap distributeScores src = do dst <- myFactories guard $ entityId dst /= entityId src let (srcDefending, srcAttacking) = inTransitTo (entityId src) let (dstDefending, dstAttacking) = inTransitTo (entityId dst) let srcTotal = cyborgs src - srcAttacking let dstTotal = cyborgs dst + dstDefending - dstAttacking let amount = (cyborgs src + 1) `div` 3 guard $ amount > 0 && amount <= cyborgs src dist <- toList $ M.lookup (entityId src, entityId dst) (gsLinks gs) pure $ Move (entityId src) (entityId dst) amount attackScores src = do dst <- filter (\f -> owner f /= thisPlayer) (gsFactories gs) dist <- toList $ M.lookup (entityId src, entityId dst) (gsLinks gs) let (mineDefending, theirsAttacking) = inTransitTo (entityId src) let (mineAttacking, theirsDefending) = inTransitTo (entityId dst) let myTotal = cyborgs src - theirsAttacking let theirTotal = cyborgs dst + theirsDefending - mineAttacking let theirEstimate | owner dst == neutralPlayer = theirTotal | otherwise = theirTotal + factoryProduction dst * (1 + dist) let amount = theirEstimate + 1 guard $ amount > 0 && amount <= cyborgs src pure $ Move (entityId src) (entityId dst) amount increaseScores src = do guard $ factoryProduction src < 3 guard $ cyborgs src >= 10 pure $ Increase (entityId src) factoryActions src = foldr mplus mempty $ map ($ src) $ [ distributeScores, attackScores, increaseScores ] allFactoryActions = concatMap factoryActions myFactories bombActions = do guard $ (gsBombsLeft gs) > 0 && not (null myFactories) guard $ (gsBombsLeft gs) > 1 || (gsTurnCount gs) > 50 dst <- filter (\f -> owner f == opponent) (gsFactories gs) --guard $ cyborgs dst >= 50 + 50 * (2 - (gsBombsLeft gs)) let src = snd $ minimumBy (compare `on` fst) $ [ (dist, s) | s <- myFactories , dist <- toList $ M.lookup (entityId s, entityId dst) (gsLinks gs) ] pure $ SendBomb (entityId src) (entityId dst) best :: Ord a => Int -> [(a, b)] -> [(a, b)] best n = take n . sortBy (flip compare `on` fst) gameTurn :: [(PlayerId, [Action])] -> GameState -> GameState gameTurn orders = moveTroops >>> moveBombs >>> processOrders orders >>> produceCyborgs >>> solveBattles >>> explodeBombs simulate :: [(PlayerId, [Action])] -> Int -> GameState -> GameState simulate orders n gs | n > 0 = simulate [] (n-1) (gameTurn orders gs) | otherwise = gs scoreGame :: GameState -> Double scoreGame gs = (fromIntegral myCyborgs + 10 * fromIntegral myProduction + 10 * fromIntegral myFactories) / (fromIntegral oppCyborgs + 10 * fromIntegral oppProduction + 10 * fromIntegral oppFactories + 1) where myFactories = length $ filter (\f -> owner f == thisPlayer) (gsFactories gs) oppFactories = length $ filter (\f -> owner f == opponent) (gsFactories gs) ((Sum myCyborgs, Sum myProduction), (Sum oppCyborgs, Sum oppProduction)) = execWriter $ do forM_ (gsFactories gs) $ \f -> do when (owner f == thisPlayer) $ tell ((Sum (cyborgs f), Sum (factoryProduction f)), mempty) when (owner f == opponent) $ tell (mempty, (Sum (cyborgs f), Sum (factoryProduction f))) forM_ (gsTroops gs) $ \t -> do when (owner t == thisPlayer) $ tell ((Sum (cyborgs t), mempty), mempty) when (owner t == opponent) $ tell (mempty, (Sum (cyborgs t), mempty)) moveTroops, moveBombs, produceCyborgs, solveBattles, explodeBombs :: GameState -> GameState moveTroops gs0 = gs0 { gsTroops = map (\t -> t { troopTurnsLeft = turnsLeft t - 1 }) (gsTroops gs0) } moveBombs gs0 = gs0 { gsBombs = map (\b -> b { bombTurnsLeft = turnsLeft b - 1 }) (gsBombs gs0) } processOrders :: [(PlayerId, [Action])] -> GameState -> GameState processOrders orders gs0 = foldl processOrder gs0 orders where processOrder gs (p, as) = foldl (processAction p) gs as processAction p gs a = checkBalances $ case a of Move from to amt -> processMove p from to amt gs SendBomb from to -> processBomb p from to gs Increase factory -> processIncrease p factory gs Wait -> gs processMove p from to amt gs = gs { gsFactories = map (\f -> if entityId f /= from then f else f { factoryCyborgs = max 0 (cyborgs f - amt) }) (gsFactories gs) , gsTroops = gsTroops gs ++ [Troop { troopId = gsNextEntity gs , troopOwner = p , troopOrigin = from , troopTarget = to , troopCyborgs = min amt (sum $ map (\f -> if entityId f == from then cyborgs f else 0) (gsFactories gs)) , troopTurnsLeft = fromJust $ M.lookup (from, to) (gsLinks gs) }] , gsNextEntity = gsNextEntity gs + 1 } processBomb p from to gs = gs { gsBombs = gsBombs gs ++ [Bomb { bombId = gsNextEntity gs , bombOwner = p , bombOrigin = from , bombTarget = to , bombTurnsLeft = fromJust $ M.lookup (from, to) (gsLinks gs) }] , gsNextEntity = gsNextEntity gs + 1 } processIncrease p factory gs = gs { gsFactories = map (\f -> if entityId f /= factory || cyborgs f < 10 then f else f { factoryCyborgs = cyborgs f - 10 , factoryProduction = min 3 (factoryProduction f + 1) }) (gsFactories gs) } checkBalances gs | all (\f -> cyborgs f >= 0) (gsFactories gs) = gs | otherwise = error "Cyborg count is negative!" produceCyborgs gs = gs { gsFactories = map produce (gsFactories gs) } where produce f | factoryDelay f > 0 = f { factoryDelay = factoryDelay f - 1 } | otherwise = f { factoryCyborgs = cyborgs f + factoryProduction f } solveBattles gs = gs { gsFactories = map solveFactory (gsFactories gs) , gsTroops = troopsInTransit } where (troopsArriving, troopsInTransit) = partition (\t -> turnsLeft t <= 0) (gsTroops gs) solveFactory f | attacking > defending = f { factoryOwner = attackingPlayer , factoryCyborgs = attacking - defending } | otherwise = f { factoryCyborgs = defending - attacking } where troopsArrivingHere = filter (\t -> target t == entityId f) troopsArriving defending = cyborgs f + sum [ cyborgs t | t <- troopsArrivingHere, owner t == owner f ] attacking = sum [ cyborgs t | t <- troopsArrivingHere, owner t /= owner f ] attackingPlayer = if owner f == opponent then thisPlayer else opponent explodeBombs gs = gs { gsFactories = foldl explodeBomb (gsFactories gs) bombsArrived , gsBombs = bombsInTransit } where (bombsArrived, bombsInTransit) = partition (\b -> turnsLeft b <= 0) (gsBombs gs) explodeBomb fs b = flip map fs $ \f -> if entityId f /= target b then f else f { factoryCyborgs = max 0 (cyborgs f - max 10 (cyborgs f `div` 2)) , factoryDelay = 5 }