From b1455b4bf5105200c893d56c06670e1613f6b548 Mon Sep 17 00:00:00 2001 From: sabadev Date: Sun, 31 Jan 2021 16:35:04 -0500 Subject: [PATCH] Added random enemies. --- avoidance.cabal | 5 ++++- package.yaml | 1 + src/Game.hs | 47 +++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/avoidance.cabal b/avoidance.cabal index 5050e3a..db5a136 100644 --- a/avoidance.cabal +++ b/avoidance.cabal @@ -4,7 +4,7 @@ cabal-version: 1.12 -- -- see: https://github.com/sol/hpack -- --- hash: a487f145932021e6d83c8b3989b615bddbb0c6e27ff93006bc87b13e7840fbcd +-- hash: 87a537766b62d585df8459c87acf35954734419e1001087d8ee7a31a18ef7b8e 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 + , random ==1.1 default-language: Haskell2010 executable avoidance @@ -48,6 +49,7 @@ executable avoidance ansi-terminal-game ==1.0.0.0 , avoidance , base >=4.7 && <5 + , random ==1.1 default-language: Haskell2010 test-suite avoidance-test @@ -62,4 +64,5 @@ test-suite avoidance-test ansi-terminal-game ==1.0.0.0 , avoidance , base >=4.7 && <5 + , random ==1.1 default-language: Haskell2010 diff --git a/package.yaml b/package.yaml index 060fde8..ca22d30 100644 --- a/package.yaml +++ b/package.yaml @@ -22,6 +22,7 @@ description: Please see the README on GitHub at = 4.7 && < 5 +- random == 1.1 library: source-dirs: src diff --git a/src/Game.hs b/src/Game.hs index ada0342..21e3050 100644 --- a/src/Game.hs +++ b/src/Game.hs @@ -2,11 +2,16 @@ module Game (gameSettings, G.getStdGen, G.playGame) where import Data.Bifunctor (bimap) import Data.Char (toUpper) +import System.Random (Random(..)) import qualified Terminal.Game as G data Screen = TitleScreen | HelpScreen | GameScreen deriving (Eq) -data Direction = U | D | L | R | Stop deriving (Eq) +data Direction = U | D | L | R | Stop deriving (Eq, Bounded, Enum) + +instance Random Direction where + random g = randomR (U, R) g + randomR (lower, upper) g = case randomR (fromEnum lower, fromEnum upper) g of (r, g') -> (toEnum r, g') data Player data Box @@ -91,15 +96,45 @@ handleEvent state G.Tick = handleTick state handleTick :: State -> State handleTick state@(State { stateScreen = GameScreen }) = do + let stateAfterEnemies = handleEnemies state let oldPlayer = statePlayer state let player = moveCharacter (entityDirection oldPlayer) oldPlayer - let box = handleCollision player (stateBox state) - if isOutOfBounds box then state { stateScreen = TitleScreen } - else state { statePlayer = player, stateBox = box, stateScore = stateScore state + 1 } + let box = handleCollision player (stateBox stateAfterEnemies) + if isOutOfBounds box then stateAfterEnemies { stateScreen = TitleScreen } + else stateAfterEnemies { statePlayer = player, stateBox = box, stateScore = stateScore stateAfterEnemies + 1 } handleTick state = state +handleEnemies :: State -> State +handleEnemies state = do + let oldEnemies = stateEnemy state + let randomGen1 = stateRandomGen state + let maxEnemies = 5 + 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 } + +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) + handleKeyPress :: State -> Char -> State -handleKeyPress state@(State { stateScreen = TitleScreen }) 'Q' = state { stateIsQuitting = True } +handleKeyPress state@(State { stateScreen = TitleScreen }) 'Q' = state { stateIsQuitting = True } handleKeyPress state@(State { stateScreen = TitleScreen }) 'P' = state { statePlayer = initPlayer, stateBox = initBox, stateEnemy = initEnemies, stateScreen = GameScreen, stateScore = 0 } handleKeyPress state@(State { stateScreen = TitleScreen }) 'H' = state { stateScreen = HelpScreen } handleKeyPress state@(State { stateScreen = HelpScreen }) _ = state { stateScreen = TitleScreen } @@ -169,5 +204,5 @@ handleCollision (Character { entityCoords = coords, entityDirection = direction | willCollide direction coords (entityCoords box) = moveCharacter direction box | otherwise = box -isOutOfBounds :: Character Box -> Bool +isOutOfBounds :: Character t -> Bool isOutOfBounds (Character { entityCoords = (row, column) }) = row == fst topLeftBoundary || column == snd topLeftBoundary || row == snd bottomRightBoundary || column == fst bottomRightBoundary -- 2.20.1