Files
codingame/code_royal/src/Main.purs
2020-04-11 17:17:27 +02:00

274 lines
8.9 KiB
Plaintext

module Main where
-- mines only on side
-- build giants
import Prelude
import Control.Monad.State (State, gets, modify_, runState)
import Control.MonadZero (guard)
import Data.Array (any, filter, foldl, head, length, sortBy)
import Data.Maybe (Maybe(..), fromJust)
import Data.Tuple (fst, snd)
import Effect (Effect)
import Effect.Console (error, log)
import GameInput (Minion, Site, SiteInfo, ProtoSite, parseInitInput, parseInput)
import Lib (dist)
import Partial.Unsafe (unsafePartial)
type GameState =
{ gold :: Int
, numSites :: Int
, touchedSite :: Int
, sites :: Array Site
, units :: Array Minion
, leftSide :: Boolean
}
main :: Effect Unit
main = do
initInput <- parseInitInput
error $ show initInput
loop initInput.numSites initInput.sites Nothing
loop :: Int -> Array SiteInfo -> Maybe GameState -> Effect Unit
loop numSites siteInfo gameState = do
input <- parseInput numSites
-- do we start on the left side of the map?
let leftSide' = case gameState of
Just gs -> gs.leftSide
Nothing -> (queen input.units).x < 500
let gameState' =
{ gold: input.gold
, numSites
, touchedSite: input.touchedSite
, sites: combinedSites input.sites
, units: input.units
, leftSide: leftSide'
}
let res = runState loop' gameState'
let state = snd res
let val = fst res
log $ val
loop state.numSites (toSiteInfo <$> state.sites) (Just state)
where
-- combine sites with siteInfo and old state
combinedSites :: Array ProtoSite -> Array Site
combinedSites sites = do
protoS <- sites
infoS <- siteInfo
guard $ protoS.id == infoS.id
let prevSite = case gameState of
Just gs -> head $ filter (\s -> s.id == infoS.id) gs.sites
Nothing -> Nothing
let mineLvl = case prevSite of
Just site -> site.mineLvl
Nothing -> 0
pure { id: protoS.id
, gold: protoS.gold
, maxMineSize: protoS.maxMineSize
, structureType: protoS.structureType
, owner: protoS.owner
, param1: protoS.param1
, param2: protoS.param2
, x: infoS.x
, y: infoS.y
, radius: infoS.radius
, mineLvl
}
loop' :: State GameState String
loop' = do
ba <- buildAll
ta <- trainAll
pure $ ba <> "\n" <> ta
buildAll :: State GameState String
buildAll = do
sites <- gets _.sites
units <- gets _.units
let buildingsCnt = length $ friendlySites sites
let minesCnt = length $ friendlyMines sites
if minesCnt < 3
then buildMines
else if buildingsCnt < 6
then case head $ nearFreeSites (queen units) sites of
Just site -> do
let typ = if not $ hasArcherBarrack sites then 1
else if not $ hasKnightsBarrack sites then 0
else 2
pure $ build site typ
Nothing -> avoid
else if buildingsCnt < 9
then buildTowers
else avoid
buildTowers :: State GameState String
buildTowers = do
leftSide <- gets _.leftSide
sites <- gets _.sites
case head $ nearFreeSites (corner leftSide) sites of
Just site -> pure $ "BUILD " <> show site.id <> " TOWER"
Nothing -> avoid
buildMines :: State GameState String
buildMines = do
units <- gets _.units
sites <- gets _.sites
case head $ nearNonEmptyMines (queen units) sites of
Just site -> do
touched <- gets _.touchedSite
if touched == -1 || touched /= site.id
then pure unit
else modify_ (\s -> s { sites = map (incMineLvl site.id) s.sites })
pure $ "BUILD " <> show site.id <> " MINE"
Nothing -> avoid
where
incMineLvl :: Int -> Site -> Site
incMineLvl sId site
| sId == site.id = site { mineLvl = site.mineLvl + 1 }
| otherwise = site
avoid :: State GameState String
avoid = do
sites <- gets _.sites
nEnemy <- nearestEnemy
case nEnemy of
Just enemy -> do
let site = unsafePartial $ fromJust $ head $
sortBy (\s1 s2 -> compare (dist enemy s2) (dist enemy s1)) (friendlySites sites)
pure $ moveToPos site
Nothing -> pure $ "MOVE 0 0"
-- nearest non-queen enemy
nearestEnemy :: State GameState (Maybe Minion)
nearestEnemy = do
units <- gets _.units
pure $ head $ filter (\u -> isEnemy u) units
trainAll :: State GameState String
trainAll = do
gold <- gets _.gold
sites <- gets _.sites
units <- gets _.units
let ownArchers = ownMinions units
let barrack =if gold > 140
then
if length ownArchers < 4 && length (enemyKnights units) /= 0
then archerBarrack sites
else if length (enemyTowers sites) > 2
then giantBarrack sites
else
knightBarrack sites
else []
pure $ foldl siteToIds "TRAIN" barrack
where
siteToIds acc site = acc <> " " <> show site.id
knightBarrack sites = case head $ knightBarracks sites of
Just barrack -> [barrack]
Nothing -> []
archerBarrack sites = case head $ archerBarracks sites of
Just barrack -> [barrack]
Nothing -> []
giantBarrack sites = case head $ giantBarracks sites of
Just barrack -> [barrack]
Nothing -> []
build :: forall e. { id :: Int | e } -> Int -> String
build s typ = "BUILD " <> show s.id <> " BARRACKS-" <> t
where t | typ == 0 = "KNIGHT"
| typ == 1 = "ARCHER"
| otherwise = "GIANT"
queen :: Array Minion -> Minion
queen units = unsafePartial $ fromJust $ head $ filter (\u -> u.unitType == -1 && u.owner == 0) units
enemyQueen :: Array Minion -> Minion
enemyQueen units = unsafePartial $ fromJust $ head $ filter (\u -> u.unitType == -1 && u.owner == 1) units
ownMinions :: Array Minion -> Array Minion
ownMinions = filter isOwn
enemyKnights :: Array Minion -> Array Minion
enemyKnights = filter isEnemy <<< filter isKnight
freeSites :: Array Site -> Array Site
freeSites = filter (\s -> s.owner == -1)
friendlySites :: Array Site -> Array Site
friendlySites = filter (\s -> s.owner == 0)
friendlyMines :: Array Site -> Array Site
friendlyMines sites = filter (\s -> s.structureType == 0) $ friendlySites sites
enemyTowers :: Array Site -> Array Site
enemyTowers = filter isEnemy <<< filter isTower
nearSites :: forall a. { x :: Int, y :: Int | a } -> Array Site -> Array Site
nearSites minion sites = sortBy (compareSiteDist minion) sites
nearFreeSites :: forall a. { x :: Int, y :: Int | a } -> Array Site -> Array Site
nearFreeSites minion sites = sortBy (compareSiteDist minion) (freeSites sites)
nearNonEmptyMines :: forall x. { x :: Int, y :: Int | x } -> Array Site -> Array Site
nearNonEmptyMines minion sites = filter (\s -> s.gold > 20 && s.mineLvl < 5 && s.owner /= 1) $ nearSites minion sites
hasKnightsBarrack :: Array Site -> Boolean
hasKnightsBarrack sites = any (\s -> s.param2 == 0) (friendlySites sites)
hasArcherBarrack :: Array Site -> Boolean
hasArcherBarrack sites = any (\s -> s.param2 == 1) (friendlySites sites)
hasGiantsBarrack :: Array Site -> Boolean
hasGiantsBarrack sites = any (\s -> s.param2 == 2) (friendlySites sites)
knightBarracks :: Array Site -> Array Site
knightBarracks sites = filter (\s -> s.param2 == 0) (friendlySites sites)
archerBarracks :: Array Site -> Array Site
archerBarracks sites = filter (\s -> s.param2 == 1) (friendlySites sites)
giantBarracks :: Array Site -> Array Site
giantBarracks sites = filter (\s -> s.param2 == 2) (friendlySites sites)
toSiteInfo :: Site -> SiteInfo
toSiteInfo s = { id: s.id, x: s.x, y: s.y, radius: s.radius }
compareSiteDist :: forall x. { x :: Int, y :: Int | x } -> Site -> Site -> Ordering
compareSiteDist u s1 s2 = compare (dist s1 u) (dist s2 u)
corner :: Boolean -> { x :: Int, y :: Int }
corner leftSide = if leftSide then { x: 0, y: 0 } else { x: 1920, y: 1000 }
isOwn :: forall a. { owner :: Int | a } -> Boolean
isOwn = owner 0
isEnemy :: forall a. { owner :: Int | a } -> Boolean
isEnemy = owner 1
owner :: Int -> forall a. { owner :: Int | a } -> Boolean
owner oId r = r.owner == oId
isKnight :: Minion -> Boolean
isKnight minion = minion.unitType == 0
isArcher :: Minion -> Boolean
isArcher minion = minion.unitType == 1
isGiant :: Minion -> Boolean
isGiant minion = minion.unitType == 2
isTower :: forall a. { structureType :: Int | a } -> Boolean
isTower s = s.structureType == 1
barracks :: Array Site -> Array Site
barracks sites = filter (\b -> b.structureType == 2) sites
moveToPos :: forall e. { x :: Int, y :: Int | e } -> String
moveToPos p = "MOVE " <> show p.x <> " " <> show p.y