From 507ad5aa9c3a4375d51d21f47adb88b7c9f2e108 Mon Sep 17 00:00:00 2001 From: sabadev Date: Sun, 31 Jan 2021 21:44:49 -0500 Subject: [PATCH] Refactored enemy generation code to use the state monad for added clarity. --- avoidance.cabal | 5 ++++- package.yaml | 1 + src/Game.hs | 53 +++++++++++++++++++++++++++++++------------------ 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/avoidance.cabal b/avoidance.cabal index db5a136..c36a7e0 100644 --- a/avoidance.cabal +++ b/avoidance.cabal @@ -4,7 +4,7 @@ cabal-version: 1.12 -- -- see: https://github.com/sol/hpack -- --- hash: 87a537766b62d585df8459c87acf35954734419e1001087d8ee7a31a18ef7b8e +-- hash: 520f87668084a72f8c390a727f65ee5f23fa26348e0bad127fae867e456ba625 name: avoidance version: 0.1.0.0 @@ -35,6 +35,7 @@ library build-depends: ansi-terminal-game ==1.0.0.0 , base >=4.7 && <5 + , mtl ==2.2.2 , random ==1.1 default-language: Haskell2010 @@ -49,6 +50,7 @@ executable avoidance ansi-terminal-game ==1.0.0.0 , avoidance , base >=4.7 && <5 + , mtl ==2.2.2 , random ==1.1 default-language: Haskell2010 @@ -64,5 +66,6 @@ test-suite avoidance-test ansi-terminal-game ==1.0.0.0 , avoidance , base >=4.7 && <5 + , mtl ==2.2.2 , random ==1.1 default-language: Haskell2010 diff --git a/package.yaml b/package.yaml index ca22d30..5baf5ad 100644 --- a/package.yaml +++ b/package.yaml @@ -22,6 +22,7 @@ description: Please see the README on GitHub at = 4.7 && < 5 +- mtl == 2.2.2 - random == 1.1 library: diff --git a/src/Game.hs b/src/Game.hs index 9b745c3..cc4595f 100644 --- a/src/Game.hs +++ b/src/Game.hs @@ -4,8 +4,11 @@ import Data.Bifunctor (bimap) import Data.Char (toUpper) import Data.Maybe (listToMaybe) import System.Random (Random(..)) +import qualified Control.Monad.State as S import qualified Terminal.Game as G +type RandomState = S.State G.StdGen + data Screen = TitleScreen | HelpScreen | GameScreen deriving (Eq) data Direction = U | D | L | R | Stop deriving (Eq, Bounded, Enum) @@ -113,29 +116,41 @@ handleEnemies state = do let oldEnemies = stateEnemy state let randomGen1 = stateRandomGen state let maxEnemies = stateDifficulty state - let (enemiesToCreate, randomGen2) = G.getRandom (0, maxEnemies - length oldEnemies) randomGen1 - let (newRandomEnemies, randomGen3) = createEnemies enemiesToCreate (oldEnemies, randomGen2) - let updatedEnemies = moveEnemies newRandomEnemies - state { stateEnemy = updatedEnemies, stateRandomGen = randomGen3 } + let (newEnemies, randomGen2) = flip S.runState (stateRandomGen state) $ randomRange (0, maxEnemies - length oldEnemies) >>= flip createEnemies oldEnemies + let updatedEnemies = moveEnemies newEnemies + state { stateEnemy = updatedEnemies, stateRandomGen = randomGen2 } moveEnemies :: [Character Enemy] -> [Character Enemy] moveEnemies = filter (not . isOutOfBounds) . fmap (\character -> moveCharacter (entityDirection character) character) -createEnemies :: Int -> ([Character Enemy], G.StdGen) -> ([Character Enemy], G.StdGen) -createEnemies 0 (enemies, randomGen) = (enemies, randomGen) -createEnemies enemiesToCreate (enemies, randomGen) = let (enemy, newRandomGen) = createEnemy randomGen in createEnemies (enemiesToCreate - 1) (enemy : enemies, newRandomGen) - -createEnemy :: G.StdGen -> (Character Enemy, G.StdGen) -createEnemy randomGen = do - let (randomDirection, randomGen1) = random randomGen - let (position, randomGen2) = enemyStartPosition randomGen1 randomDirection - (Character { entityCoords = position, entityDirection = randomDirection }, randomGen2) - -enemyStartPosition :: G.StdGen -> Direction -> (G.Coords, G.StdGen) -enemyStartPosition randomGen U = let (column, newRandomGen) = G.getRandom (fst topLeftBoundary, fst bottomRightBoundary) randomGen in ((snd bottomRightBoundary, column), newRandomGen) -enemyStartPosition randomGen D = let (column, newRandomGen) = G.getRandom (fst topLeftBoundary, fst bottomRightBoundary) randomGen in ((snd topLeftBoundary, column), newRandomGen) -enemyStartPosition randomGen L = let (row, newRandomGen) = G.getRandom (snd topLeftBoundary, snd bottomRightBoundary) randomGen in ((row, fst bottomRightBoundary), newRandomGen) -enemyStartPosition randomGen R = let (row, newRandomGen) = G.getRandom (snd topLeftBoundary, snd bottomRightBoundary) randomGen in ((row, fst topLeftBoundary), newRandomGen) +createEnemies :: Int -> [Character Enemy] -> RandomState [Character Enemy] +createEnemies 0 enemies = return enemies +createEnemies enemiesToCreate enemies = createEnemy >>= createEnemies (enemiesToCreate - 1) . flip (:) enemies + +createEnemy :: RandomState (Character Enemy) +createEnemy = do + randomDirection <- randomResult + position <- enemyStartPosition randomDirection + return $ Character { entityCoords = position, entityDirection = randomDirection } + +randomAction :: (Random a) => (G.StdGen -> (a, G.StdGen)) -> RandomState a +randomAction action = do + randomGen <- S.get + let (result, randomGenNew) = action randomGen + S.put randomGenNew + return result + +randomResult :: (Random a) => RandomState a +randomResult = randomAction random + +randomRange :: (Random a) => (a, a) -> RandomState a +randomRange bounds = randomAction $ G.getRandom bounds + +enemyStartPosition :: Direction -> RandomState G.Coords +enemyStartPosition U = randomRange (fst topLeftBoundary, fst bottomRightBoundary) >>= \column -> return (snd bottomRightBoundary, column) +enemyStartPosition D = randomRange (fst topLeftBoundary, fst bottomRightBoundary) >>= \column -> return (snd topLeftBoundary, column) +enemyStartPosition L = randomRange (snd topLeftBoundary, snd bottomRightBoundary) >>= \row -> return (row, fst bottomRightBoundary) +enemyStartPosition R = randomRange (snd topLeftBoundary, snd bottomRightBoundary) >>= \row -> return (row, fst topLeftBoundary) handleKeyPress :: State -> Char -> State handleKeyPress state@(State { stateScreen = TitleScreen }) 'Q' = state { stateIsQuitting = True } -- 2.20.1