Refactored enemy generation code to use the state monad for added clarity.
authorsabadev <saba@sabadev.xyz>
Mon, 1 Feb 2021 02:44:49 +0000 (21:44 -0500)
committersabadev <saba@sabadev.xyz>
Mon, 1 Feb 2021 02:44:49 +0000 (21:44 -0500)
avoidance.cabal
package.yaml
src/Game.hs

index db5a136..c36a7e0 100644 (file)
@@ -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
index ca22d30..5baf5ad 100644 (file)
@@ -22,6 +22,7 @@ description:         Please see the README on GitHub at <https://github.com/gith
 dependencies:
 - ansi-terminal-game == 1.0.0.0
 - base >= 4.7 && < 5
+- mtl == 2.2.2
 - random == 1.1
 
 library:
index 9b745c3..cc4595f 100644 (file)
@@ -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 }