From 11bafb6f6001108875b92df3a964af7674506d19 Mon Sep 17 00:00:00 2001 From: weiss Date: Fri, 10 Apr 2020 21:18:06 +0200 Subject: [PATCH] use State Monad for GameState --- code_royal/src/Main.purs | 165 ++++++++++++++++++------------ code_royal/src/ffi/GameInput.purs | 25 ++--- 2 files changed, 111 insertions(+), 79 deletions(-) diff --git a/code_royal/src/Main.purs b/code_royal/src/Main.purs index b2195c7..549fbbf 100644 --- a/code_royal/src/Main.purs +++ b/code_royal/src/Main.purs @@ -2,17 +2,27 @@ module Main where import Prelude +import Control.Monad.State (State, gets, runState) import Control.MonadZero (guard) -import Data.Array (any, filter, foldl, head, length, sortBy, (!!)) -import Data.JSDate (now, getTime) +import Data.Array (any, filter, foldl, head, length, sortBy) +-- import Data.JSDate (now, getTime) import Data.Maybe (Maybe(..), fromJust) +import Data.Tuple (fst, snd) import Effect (Effect) import Effect.Console (error, log) -import Effect.Random (randomInt) import GameInput (Minion, Site, SiteInfo, ProtoSite, parseInitInput, parseInput) import Lib (dist) import Partial.Unsafe (unsafePartial) +type GameState = + { gold :: Int + , numSites :: Int + , touchedSite :: Maybe Int + , sites :: Array Site + , units :: Array Minion + , leftSide :: Boolean + } + main :: Effect Unit main = do initInput <- parseInitInput @@ -31,8 +41,22 @@ loop numSites siteInfo leftSide = do let touchedSite = if input.touchedSite == -1 then Nothing else Just input.touchedSite + + let gameState = + { gold: input.gold + , numSites + , touchedSite + , sites: combinedSites input.sites + , units: input.units + , leftSide: leftSide' + } - loop' numSites input.gold touchedSite (combinedSites input.sites) input.units leftSide' + let res = runState loop' gameState + let state = snd res + let val = fst res + log $ val + loop state.numSites (toSiteInfo <$> state.sites) (Just state.leftSide) + where -- queenPos :: Array Minion -> { x :: Int, y :: Int } -- queenPos minions = queen minions @@ -53,82 +77,86 @@ loop numSites siteInfo leftSide = do , x: infoS.x , y: infoS.y , radius: infoS.radius + , mineLvl: -1 } -loop' :: Int -> Int -> Maybe Int -> Array Site -> Array Minion -> Boolean -> Effect Unit -loop' numSites gold touchedSite sites units leftSide = do - n <- now - let t0 = getTime n +loop' :: State GameState String +loop' = do + ba <- buildAll + ta <- trainAll + pure $ ba <> "\n" <> ta - log buildAll - t <- trainAll gold sites - log $ t - - n' <- now - let t1 = getTime n - -- error $ show t0 <> "Execution time: " <> (show $ t1 - t0) - loop numSites (toSiteInfo <$> sites) (Just leftSide) - where - buildAll :: String - buildAll = - if (length $ friendlySites sites) == 0 - then buildTowers - else if (length $ friendlyMines sites) < 3 - then buildMines - else if (length $ friendlySites sites) > 5 && (length $ friendlySites sites) < 11 - then buildTowers - else if (length $ friendlySites sites) <= 5 - then case head $ nearSites (queen units) sites of - Just site -> do - let typ = if hasKnightsBarrack sites then 1 else 0 - build site typ - Nothing -> avoid - else avoid - - buildTowers :: String - buildTowers = case head $ nearSites (corner leftSide) sites of - Just site -> "BUILD " <> show site.id <> " TOWER" - Nothing -> avoid - - buildMines :: String - buildMines = case head $ nearNonEmptyMines (queen units) sites of - Just site -> "BUILD " <> show site.id <> " MINE" +buildAll :: State GameState String +buildAll = do + sites <- gets _.sites + units <- gets _.units + if (length $ friendlySites sites) == 0 + then buildTowers + else if (length $ friendlyMines sites) < 3 + then buildMines + else if (length $ friendlySites sites) > 5 && (length $ friendlySites sites) < 11 + then buildTowers + else if (length $ friendlySites sites) <= 5 + then case head $ nearFreeSites (queen units) sites of + Just site -> do + let typ = if hasKnightsBarrack sites then 1 else 0 + pure $ build site typ Nothing -> avoid + else avoid - avoid :: String - avoid = case nearestEnemy of - Just enemy -> "MOVE " <> show (site enemy).x <> " " <> show (site enemy).y - Nothing -> "MOVE 0 0" - where site enemy = unsafePartial $ fromJust $ head $ - sortBy (\s1 s2 -> compare (dist enemy s2) (dist enemy s1)) (friendlySites sites) +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 - -- nearest non-queen enemy - nearestEnemy :: Maybe Minion - nearestEnemy = head $ filter (\u -> u.unitType /= -1 && isEnemy u) units +buildMines :: State GameState String +buildMines = do + units <- gets _.units + sites <- gets _.sites + case head $ nearNonEmptyMines (queen units) sites of + Just site -> + pure $ "BUILD " <> show site.id <> " MINE" + Nothing -> avoid --- TODO: make pure -trainAll :: Int -> Array Site -> Effect String -trainAll gold sites = do - randBarrack <- randomBarrack - choose <- randomInt 1 100 +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 $ "MOVE " <> show site.x <> " " <> show site.y + Nothing -> pure $ "MOVE 0 0" + +-- nearest non-queen enemy +nearestEnemy :: State GameState (Maybe Minion) +nearestEnemy = do + units <- gets _.units + pure $ head $ filter (\u -> u.unitType /= -1 && isEnemy u) units + +trainAll :: State GameState String +trainAll = do + gold <- gets _.gold + sites <- gets _.sites + + -- choose <- randomInt 1 100 + let choose = -1 -- TODO!!! let barrack = if gold > 140 - then if choose < 42 then knightBarrack else archerBarrack + then if choose < 42 then knightBarrack sites else archerBarrack sites else [] pure $ foldl siteToIds "TRAIN" barrack where siteToIds acc site = acc <> " " <> show site.id - knightBarrack = case head $ knightBarracks sites of + knightBarrack sites = case head $ knightBarracks sites of Just barrack -> [barrack] Nothing -> [] - archerBarrack = case head $ archerBarracks sites of + archerBarrack sites = case head $ archerBarracks sites of Just barrack -> [barrack] Nothing -> [] - randomBarrack = do - let ownBarracks = filter isOwn $ barracks sites - rand <- randomInt 0 $ length ownBarracks - case ownBarracks !! rand of - Just barrack -> pure [barrack] - Nothing -> pure [] queen :: Array Minion -> Minion queen units = unsafePartial $ fromJust $ head $ filter (\u -> u.unitType == -1 && u.owner == 0) units @@ -145,11 +173,14 @@ friendlySites = filter (\s -> s.owner == 0) friendlyMines :: Array Site -> Array Site friendlyMines sites = filter (\s -> s.structureType == 0) $ friendlySites sites -nearSites :: forall x. { x :: Int, y :: Int | x } -> Array Site -> Array Site -nearSites unit sites = sortBy (compareSiteDist unit) (freeSites sites) +nearSites :: forall a. { x :: Int, y :: Int | a } -> Array Site -> Array Site +nearSites unit sites = sortBy (compareSiteDist unit) sites + +nearFreeSites :: forall a. { x :: Int, y :: Int | a } -> Array Site -> Array Site +nearFreeSites unit sites = sortBy (compareSiteDist unit) (freeSites sites) nearNonEmptyMines :: forall x. { x :: Int, y :: Int | x } -> Array Site -> Array Site -nearNonEmptyMines unit sites = filter (\s -> s.gold > 0) $ nearSites unit sites +nearNonEmptyMines unit sites = filter (\s -> s.gold > 0 && s.mineLvl < 5 && s.owner /= 1) $ nearSites unit sites hasKnightsBarrack :: Array Site -> Boolean hasKnightsBarrack sites = any (\s -> s.param2 == 0) (friendlySites sites) diff --git a/code_royal/src/ffi/GameInput.purs b/code_royal/src/ffi/GameInput.purs index b8f22fa..95154e4 100644 --- a/code_royal/src/ffi/GameInput.purs +++ b/code_royal/src/ffi/GameInput.purs @@ -23,12 +23,12 @@ type SiteInfo = type ProtoSite = { id :: Int - , gold :: Int -- The total number of gold remaining to be mined from this site (-1 if unknown) - , maxMineSize :: Int -- The maximum rate that a mine can extract gold from this site (-1 if unknown) - , structureType :: Int -- -1 No structure, 0 Goldmine, 1 Tower, 2 Barracks - , owner :: Int -- -1 No structure, 0 friendly, 1 enemy - , param1 :: Int -- -1 No structure, else turns till training - , param2 :: Int -- -1 No structure, barracks: 0 knight 1 archer + , gold :: Int + , maxMineSize :: Int + , structureType :: Int + , owner :: Int + , param1 :: Int + , param2 :: Int } type Site = @@ -36,12 +36,13 @@ type Site = , x :: Int , y :: Int , radius :: Int - , gold :: Int - , maxMineSize :: Int - , structureType :: Int - , owner :: Int - , param1 :: Int - , param2 :: Int + , gold :: Int -- The total number of gold remaining to be mined from this site (-1 if unknown) + , maxMineSize :: Int -- The maximum rate that a mine can extract gold from this site (-1 if unknown) + , structureType :: Int -- -1 No structure, 0 Goldmine, 1 Tower, 2 Barracks + , owner :: Int -- -1 No structure, 0 friendly, 1 enemy + , param1 :: Int -- -1 No structure, else turns till training + , param2 :: Int -- -1 No structure, barracks: 0 knight 1 archer + , mineLvl :: Int -- -1 whatever, otherwise curr. mine lvl } type Minion =