dev #25
					 18 changed files with 289 additions and 103 deletions
				
			
		|  | @ -58,6 +58,8 @@ These are the keybinds *in* the game. All other keybinds in the menus should be | ||||||
| | Move down      | `Arrow Down`  | `s`         | | | Move down      | `Arrow Down`  | `s`         | | ||||||
| | Move right     | `Arrow Right` | `d`         | | | Move right     | `Arrow Right` | `d`         | | ||||||
| | Show inventory | `i`           |             | | | Show inventory | `i`           |             | | ||||||
|  | | Restart level  | `r`           |             | | ||||||
|  | | Quit game      | `Esc`         |             | | ||||||
| 
 | 
 | ||||||
| ### Example playthrough | ### Example playthrough | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,4 +15,4 @@ levels: [ | ||||||
| 
 | 
 | ||||||
|         entities: [] |         entities: [] | ||||||
|     } |     } | ||||||
| ] | ] | ||||||
|  | @ -19,8 +19,8 @@ levels: [ | ||||||
|         items: [ |         items: [ | ||||||
|             { |             { | ||||||
|                 id: "key", |                 id: "key", | ||||||
|                 x: 0, |                 x: 1, | ||||||
|                 y: 1, |                 y: 2, | ||||||
|                 name: "Sleutel", |                 name: "Sleutel", | ||||||
|                 description: "Deze sleutel kan een deur openen", |                 description: "Deze sleutel kan een deur openen", | ||||||
|                 useTimes: 1, |                 useTimes: 1, | ||||||
|  | @ -35,8 +35,8 @@ levels: [ | ||||||
|         entities: [ |         entities: [ | ||||||
|             { |             { | ||||||
|                 id: "door", |                 id: "door", | ||||||
|                 x: 0, |                 x: 1, | ||||||
|                 y: 3, |                 y: 4, | ||||||
|                 name: "Deur", |                 name: "Deur", | ||||||
|                 description: "Deze deur kan geopend worden met een sleutel", |                 description: "Deze deur kan geopend worden met een sleutel", | ||||||
|                 direction: up, |                 direction: up, | ||||||
|  | @ -48,4 +48,4 @@ levels: [ | ||||||
|             } |             } | ||||||
|         ] |         ] | ||||||
|     } |     } | ||||||
| ] | ] | ||||||
|  | @ -76,4 +76,4 @@ levels: [ | ||||||
|             } |             } | ||||||
|         ] |         ] | ||||||
|     } |     } | ||||||
| ] | ] | ||||||
|  | @ -131,4 +131,4 @@ levels: [ | ||||||
|             } |             } | ||||||
|         ] |         ] | ||||||
|     } |     } | ||||||
| ] | ] | ||||||
|  | @ -1,9 +1,6 @@ | ||||||
| # Dit gehele bestand is een Block | player: { | ||||||
| 
 |  | ||||||
| # Dit is een entry: key + value |  | ||||||
| player: { # Value is hier een block |  | ||||||
|     hp: 50, |     hp: 50, | ||||||
|     inventory: [ # BlockList |     inventory: [ | ||||||
|         { |         { | ||||||
|             id: "dagger", |             id: "dagger", | ||||||
|             x: 0, |             x: 0, | ||||||
|  | @ -18,7 +15,6 @@ player: { # Value is hier een block | ||||||
|     ] |     ] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| # Dit is een entry |  | ||||||
| levels: [ | levels: [ | ||||||
|     { |     { | ||||||
|         layout: { |         layout: { | ||||||
|  | @ -135,4 +131,4 @@ levels: [ | ||||||
|             } |             } | ||||||
|         ] |         ] | ||||||
|     } |     } | ||||||
| ] | ] | ||||||
|  | @ -9,7 +9,7 @@ import RPGEngine.Config ( bgColor, winDimensions, winOffsets ) | ||||||
| import RPGEngine.Render ( initWindow, render ) | import RPGEngine.Render ( initWindow, render ) | ||||||
| import RPGEngine.Input ( handleAllInput ) | import RPGEngine.Input ( handleAllInput ) | ||||||
| import RPGEngine.Input.Playing ( checkPlaying, spawnPlayer ) | import RPGEngine.Input.Playing ( checkPlaying, spawnPlayer ) | ||||||
| import RPGEngine.Data (Game (..), State (..), Layout, Level (..), Physical (..)) | import RPGEngine.Data (Game (..), State (..), Layout, Level (..), Physical (..), Entity(..), Direction(..), Player(..)) | ||||||
| import RPGEngine.Data.Default (defaultLevel, defaultPlayer) | import RPGEngine.Data.Default (defaultLevel, defaultPlayer) | ||||||
| 
 | 
 | ||||||
| import Graphics.Gloss ( play ) | import Graphics.Gloss ( play ) | ||||||
|  | @ -27,22 +27,15 @@ playRPGEngine title fps = do | ||||||
| -- TODO revert this | -- TODO revert this | ||||||
| -- Initialize the game | -- Initialize the game | ||||||
| initGame :: Game | initGame :: Game | ||||||
| -- initGame = Game {  | initGame = Game { state = Menu } | ||||||
| --     state   = Menu{ base = StateBase{  | -- initGame = Game{ state = initState } | ||||||
| --         renderer = renderMenu,  | --     where initState = Playing{ | ||||||
| --         inputHandler = handleInputMenu | --         levels = [defaultLevel, otherLevel], | ||||||
| --     }} | --         count = 0, | ||||||
| -- } | --         level  = defaultLevel, | ||||||
| initGame = Game{ | --         player = spawnPlayer defaultLevel defaultPlayer, | ||||||
|     state = initState | --         restart = initState | ||||||
| } | --     } | ||||||
|     where initState = Playing{ |  | ||||||
|         levels = [defaultLevel, otherLevel], |  | ||||||
|         count = 0, |  | ||||||
|         level  = defaultLevel, |  | ||||||
|         player = spawnPlayer defaultLevel defaultPlayer, |  | ||||||
|         restart = initState |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
| -- TODO remove this | -- TODO remove this | ||||||
| otherLayout :: Layout | otherLayout :: Layout | ||||||
|  | @ -50,6 +43,8 @@ otherLayout = [ | ||||||
|     [Blocked, Blocked, Blocked], |     [Blocked, Blocked, Blocked], | ||||||
|     [Blocked, Entrance, Blocked], |     [Blocked, Entrance, Blocked], | ||||||
|     [Blocked, Walkable, Blocked], |     [Blocked, Walkable, Blocked], | ||||||
|  |     [Blocked, Walkable, Blocked], | ||||||
|  |     [Blocked, Walkable, Blocked], | ||||||
|     [Blocked, Exit, Blocked], |     [Blocked, Exit, Blocked], | ||||||
|     [Blocked, Blocked, Blocked] |     [Blocked, Blocked, Blocked] | ||||||
|     ] |     ] | ||||||
|  | @ -69,12 +64,30 @@ otherLevel = Level { | ||||||
|         (1, 2, Walkable), |         (1, 2, Walkable), | ||||||
|         (2, 2, Blocked), |         (2, 2, Blocked), | ||||||
|         (0, 3, Blocked), |         (0, 3, Blocked), | ||||||
|         (1, 3, Exit), |         (1, 3, Walkable), | ||||||
|         (2, 3, Blocked), |         (2, 3, Blocked), | ||||||
|         (0, 4, Blocked), |         (0, 4, Blocked), | ||||||
|         (1, 4, Blocked), |         (1, 4, Walkable), | ||||||
|         (2, 4, Blocked) |         (2, 4, Blocked), | ||||||
|  |         (0, 5, Blocked), | ||||||
|  |         (1, 5, Exit), | ||||||
|  |         (2, 5, Blocked), | ||||||
|  |         (0, 6, Blocked), | ||||||
|  |         (1, 6, Blocked), | ||||||
|  |         (2, 6, Blocked) | ||||||
|     ], |     ], | ||||||
|     items    = [], |     items    = [], | ||||||
|     entities = [] |     entities = [ | ||||||
|  |         Entity{ | ||||||
|  |             entityId = "door", | ||||||
|  |             entityX  = 1, | ||||||
|  |             entityY  = 3, | ||||||
|  |             entityName = "Epic door", | ||||||
|  |             entityDescription = "epic description", | ||||||
|  |             entityActions = [], | ||||||
|  |             entityValue = Nothing, | ||||||
|  |             entityHp = Nothing, | ||||||
|  |             direction = North | ||||||
|  |         } | ||||||
|  |         ] | ||||||
| } | } | ||||||
|  | @ -12,7 +12,7 @@ import RPGEngine.Render.Core ( Renderer ) | ||||||
| -- A game is the base data container. | -- A game is the base data container. | ||||||
| data Game = Game { | data Game = Game { | ||||||
|     state  :: State |     state  :: State | ||||||
| } | } deriving (Eq, Show) | ||||||
| 
 | 
 | ||||||
| ------------------------------- State -------------------------------- | ------------------------------- State -------------------------------- | ||||||
| 
 | 
 | ||||||
|  | @ -33,6 +33,7 @@ data State = Menu | ||||||
|            | Win |            | Win | ||||||
|            -- Lost a level |            -- Lost a level | ||||||
|            | Lose           { restart   :: State        } |            | Lose           { restart   :: State        } | ||||||
|  |            deriving (Eq, Show) | ||||||
| 
 | 
 | ||||||
| ------------------------------- Level -------------------------------- | ------------------------------- Level -------------------------------- | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -64,9 +64,30 @@ defaultLevel = Level { | ||||||
| 
 | 
 | ||||||
| defaultPlayer :: Player | defaultPlayer :: Player | ||||||
| defaultPlayer = Player { | defaultPlayer = Player { | ||||||
|     playerHp  = Prelude.Nothing, -- Compares to infinity |     -- playerHp      = Prelude.Nothing, -- Compares to infinity | ||||||
|     inventory = [], |     playerHp      = Just 50, | ||||||
|     position  = (0, 0) |     inventory     = [ Item{ | ||||||
|  |         itemId = "key", | ||||||
|  |         itemX  = 0, | ||||||
|  |         itemY  = 0, | ||||||
|  |         itemName  = "Epic key", | ||||||
|  |         itemDescription = "MyKey", | ||||||
|  |         itemActions = [], | ||||||
|  |         itemValue = Nothing, | ||||||
|  |         useTimes = Nothing | ||||||
|  |         }, Item{ | ||||||
|  |         itemId = "dagger", | ||||||
|  |         itemX  = 0, | ||||||
|  |         itemY  = 0, | ||||||
|  |         itemName  = "My dagger", | ||||||
|  |         itemDescription = "dagger", | ||||||
|  |         itemActions = [], | ||||||
|  |         itemValue = Nothing, | ||||||
|  |         useTimes = Nothing | ||||||
|  |         }], | ||||||
|  |     position      = (0, 0), | ||||||
|  |     showInventory = False, | ||||||
|  |     showHp        = True | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| defaultSelector :: ListSelector | defaultSelector :: ListSelector | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ type InputHandler a = Event -> (a -> a) | ||||||
| data ListSelector = ListSelector { | data ListSelector = ListSelector { | ||||||
|     selection :: Int, |     selection :: Int, | ||||||
|     selected  :: Bool |     selected  :: Bool | ||||||
| } | } deriving (Eq, Show) | ||||||
| 
 | 
 | ||||||
| ------------------------------ Exported ------------------------------ | ------------------------------ Exported ------------------------------ | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ module RPGEngine.Input.Playing | ||||||
| ( handleInputPlaying | ( handleInputPlaying | ||||||
| , checkPlaying | , checkPlaying | ||||||
| , spawnPlayer | , spawnPlayer | ||||||
|  | , putCoords | ||||||
| ) where | ) where | ||||||
| 
 | 
 | ||||||
| import RPGEngine.Input.Core (InputHandler, handle, handleKey, composeInputHandlers) | import RPGEngine.Input.Core (InputHandler, handle, handleKey, composeInputHandlers) | ||||||
|  | @ -32,6 +33,8 @@ handleInputPlaying = composeInputHandlers [ | ||||||
|     handleKey (Char 's') Down $ movePlayer South, |     handleKey (Char 's') Down $ movePlayer South, | ||||||
|     handleKey (Char 'a') Down $ movePlayer West, |     handleKey (Char 'a') Down $ movePlayer West, | ||||||
| 
 | 
 | ||||||
|  |     handleKey (Char 'r') Down restartGame, | ||||||
|  | 
 | ||||||
|     handleKey (Char 'i') Down $ toggleInventoryShown True, |     handleKey (Char 'i') Down $ toggleInventoryShown True, | ||||||
|     handleKey (Char 'i') Up   $ toggleInventoryShown False |     handleKey (Char 'i') Up   $ toggleInventoryShown False | ||||||
|     ] |     ] | ||||||
|  | @ -58,13 +61,15 @@ pauseGame g@Game{ state = playing@Playing{} } = pausedGame | ||||||
|     where pausedGame  = g{ state = Paused playing } |     where pausedGame  = g{ state = Paused playing } | ||||||
| pauseGame g = g | pauseGame g = g | ||||||
| 
 | 
 | ||||||
|  | restartGame :: Game -> Game | ||||||
|  | restartGame g@Game{ state = playing@Playing{ restart = restarted } } = g{ state = restarted } | ||||||
|  | 
 | ||||||
| -- Go to next level if there is a next level, otherwise, initialize win state. | -- Go to next level if there is a next level, otherwise, initialize win state. | ||||||
| goToNextLevel :: State -> State | goToNextLevel :: State -> State | ||||||
| goToNextLevel s@Playing{ levels = levels, level = current, count = count, player = player } = nextState | goToNextLevel s@Playing{ levels = levels, level = current, count = count, player = player } = nextState | ||||||
|     where -- Either the next level or winState |     where nextState | (count + 1) < length levels = nextLevelState | ||||||
|           nextState | (count + 1) < length levels = nextLevelState |  | ||||||
|                     | otherwise                   = Win |                     | otherwise                   = Win | ||||||
|           nextLevelState = s{ level = nextLevel, count = count + 1, player = movedPlayer } |           nextLevelState = s{ level = nextLevel, count = count + 1, player = movedPlayer, restart = nextLevelState } | ||||||
|           nextLevel      = levels !! (count + 1) |           nextLevel      = levels !! (count + 1) | ||||||
|           movedPlayer    = spawnPlayer nextLevel player |           movedPlayer    = spawnPlayer nextLevel player | ||||||
| goToNextLevel s = s | goToNextLevel s = s | ||||||
|  |  | ||||||
|  | @ -6,13 +6,11 @@ import RPGEngine.Data ( Game ) | ||||||
| import RPGEngine.Parse.StructureToGame ( structureToGame ) | import RPGEngine.Parse.StructureToGame ( structureToGame ) | ||||||
| import GHC.IO (unsafePerformIO) | import GHC.IO (unsafePerformIO) | ||||||
| import Text.Parsec.String (parseFromFile) | import Text.Parsec.String (parseFromFile) | ||||||
| import RPGEngine.Parse.TextToStructure (structure) | import RPGEngine.Parse.TextToStructure ( gameFile ) | ||||||
| 
 | 
 | ||||||
| ------------------------------ Exported ------------------------------ | ------------------------------ Exported ------------------------------ | ||||||
| 
 | 
 | ||||||
| parse :: FilePath -> Game | parse :: FilePath -> Game | ||||||
| parse filename = structureToGame struct | parse filename = structureToGame struct | ||||||
|     where (Right struct) = unsafePerformIO io |     where (Right struct) = unsafePerformIO io | ||||||
|           io             = parseFromFile structure filename |           io             = parseFromFile gameFile filename | ||||||
| 
 |  | ||||||
| tempParse = parseFromFile |  | ||||||
|  | @ -10,26 +10,25 @@ import RPGEngine.Data | ||||||
|              entityActions, entityValue, entityHp, direction), |              entityActions, entityValue, entityHp, direction), | ||||||
|       Item(itemId, itemX, itemY, itemName, itemDescription, itemValue, |       Item(itemId, itemX, itemY, itemName, itemDescription, itemValue, | ||||||
|            itemActions, useTimes), |            itemActions, useTimes), | ||||||
|       Level(layout, items, entities), |       Level(layout, items, entities, index), | ||||||
|       Game (..), State (..) ) |       Game (..), State (..) ) | ||||||
| import RPGEngine.Parse.TextToStructure | import RPGEngine.Parse.TextToStructure | ||||||
|     ( Value(Infinite, Action, Layout, String, Direction, Integer), |     ( Value(Infinite, Action, Layout, String, Direction, Integer), | ||||||
|       Key(Tag, ConditionList), |       Key(Tag, ConditionList), | ||||||
|       Structure(..) ) |       Structure(..) ) | ||||||
| import RPGEngine.Data.Default (defaultPlayer, defaultLevel, defaultItem, defaultEntity) | import RPGEngine.Data.Default (defaultPlayer, defaultLevel, defaultItem, defaultEntity) | ||||||
|  | import RPGEngine.Input.Playing (putCoords, spawnPlayer) | ||||||
| 
 | 
 | ||||||
| ------------------------------ Exported ------------------------------ | ------------------------------ Exported ------------------------------ | ||||||
| 
 | 
 | ||||||
| structureToGame :: Structure -> Game | structureToGame :: [Structure] -> Game | ||||||
| -- structureToGame [Entry(Tag "player") playerBlock, Entry(Tag "levels") levelsBlock] = game | structureToGame [Entry (Tag "player") playerBlock, Entry (Tag "levels") levelsBlock] = game | ||||||
| structureToGame (Entry (Tag "player") playerBlock) = game |     where game         = Game newState | ||||||
|     where game         = Game{ state = newState } |           newState     = Playing newLevels 0 currentLevel newPlayer newState | ||||||
|           newState     = Playing{ levels = newLevels, level = currentLevel, player = newPlayer, restart = newState } |           newLevels    = structureToLevels levelsBlock | ||||||
|           -- newLevels    = structureToLevels levelsBlock |           currentLevel = head newLevels | ||||||
|           -- currentLevel = head newLevels |           newPlayer    = spawnPlayer currentLevel $ structureToPlayer playerBlock | ||||||
|           newLevels    = [defaultLevel] | structureToGame _ = Game Menu | ||||||
|           currentLevel = defaultLevel |  | ||||||
|           newPlayer    = structureToPlayer playerBlock |  | ||||||
| 
 | 
 | ||||||
| ------------------------------- Player ------------------------------- | ------------------------------- Player ------------------------------- | ||||||
| 
 | 
 | ||||||
|  | @ -60,7 +59,9 @@ structureToLevels (Block struct) = structureToLevel <$> struct | ||||||
| structureToLevels _              = [defaultLevel] | structureToLevels _              = [defaultLevel] | ||||||
| 
 | 
 | ||||||
| structureToLevel :: Structure -> Level | structureToLevel :: Structure -> Level | ||||||
| structureToLevel (Block entries) = structureToLevel' entries defaultLevel | structureToLevel (Block entries) = indexIsSet | ||||||
|  |   where indexIsSet = level{ index = putCoords level } | ||||||
|  |         level      = structureToLevel' entries defaultLevel | ||||||
| structureToLevel _               = defaultLevel | structureToLevel _               = defaultLevel | ||||||
| 
 | 
 | ||||||
| structureToLevel' :: [Structure] -> Level -> Level | structureToLevel' :: [Structure] -> Level -> Level | ||||||
|  |  | ||||||
|  | @ -18,9 +18,13 @@ import Text.Parsec | ||||||
|       notFollowedBy, |       notFollowedBy, | ||||||
|       sepBy, |       sepBy, | ||||||
|       many, |       many, | ||||||
|       try ) |       try, spaces, endOfLine ) | ||||||
| import qualified Text.Parsec as P ( string ) | import qualified Text.Parsec as P ( string ) | ||||||
| import Text.Parsec.String ( Parser ) | import Text.Parsec.String ( Parser ) | ||||||
|  | import Text.Parsec.Combinator (lookAhead) | ||||||
|  | 
 | ||||||
|  | gameFile :: Parser [Structure] | ||||||
|  | gameFile = try $ do many1 $ ignoreWS structure | ||||||
| 
 | 
 | ||||||
| -------------------------- StructureElement -------------------------- | -------------------------- StructureElement -------------------------- | ||||||
| 
 | 
 | ||||||
|  | @ -111,7 +115,7 @@ data Value = String String | ||||||
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ||||||
| 
 | 
 | ||||||
| value :: Parser Value | value :: Parser Value | ||||||
| value = choice [layout, string, integer, infinite, action, direction] | value = choice [layout, string, integer, infinite, direction, action] | ||||||
| 
 | 
 | ||||||
| string :: Parser Value | string :: Parser Value | ||||||
| string = try $ String <$> between (char '\"') (char '\"') reading | string = try $ String <$> between (char '\"') (char '\"') reading | ||||||
|  | @ -149,7 +153,7 @@ direction = try $ do | ||||||
|         ignoreWS $ P.string "left", |         ignoreWS $ P.string "left", | ||||||
|         ignoreWS $ P.string "right" |         ignoreWS $ P.string "right" | ||||||
|         ] |         ] | ||||||
|     notFollowedBy alphaNum |     -- lookAhead $ char ',' | ||||||
|     return $ Direction $ make value |     return $ Direction $ make value | ||||||
|     where make "up"    = North |     where make "up"    = North | ||||||
|           make "right" = East |           make "right" = East | ||||||
|  | @ -160,15 +164,12 @@ direction = try $ do | ||||||
| layout :: Parser Value | layout :: Parser Value | ||||||
| layout = try $ do | layout = try $ do | ||||||
|     open <- ignoreWS $ oneOf openingBrackets |     open <- ignoreWS $ oneOf openingBrackets | ||||||
|     ignoreWS $ char '|' |  | ||||||
|     list <- ignoreWS $ ignoreWS strip `sepBy` ignoreWS (char '|') |  | ||||||
|     let closing = getMatchingClosingBracket open |     let closing = getMatchingClosingBracket open | ||||||
|     ignoreWS $ char closing |     value <- many1 strip <* ignoreWS (char closing) | ||||||
|     return $ Layout list |     return $ Layout value | ||||||
| 
 | 
 | ||||||
| strip :: Parser Strip | strip :: Parser Strip | ||||||
| strip = try $ do | strip = try $ do ignoreWS (char '|') *> ignoreWS (physical `sepBy` char ' ') | ||||||
|     physical `sepBy` char ' ' |  | ||||||
| 
 | 
 | ||||||
| physical :: Parser Physical | physical :: Parser Physical | ||||||
| physical = try $ do | physical = try $ do | ||||||
|  |  | ||||||
|  | @ -55,7 +55,11 @@ test-suite rpg-engine-test | ||||||
|   main-is: Spec.hs |   main-is: Spec.hs | ||||||
|   hs-source-dirs: test |   hs-source-dirs: test | ||||||
|   default-language: Haskell2010 |   default-language: Haskell2010 | ||||||
|   build-depends: base >=4.7 && <5, hspec <= 2.10.6, hspec-discover, rpg-engine |   build-depends:  | ||||||
|  |     base >=4.7 && <5, | ||||||
|  |     rpg-engine, | ||||||
|  |     hspec <= 2.10.6, hspec-discover, | ||||||
|  |     parsec >= 3.1.15.1 | ||||||
|   other-modules:  |   other-modules:  | ||||||
|     Parser.GameSpec |     Parser.GameSpec | ||||||
|     Parser.StructureSpec |     Parser.StructureSpec | ||||||
|  | @ -6,24 +6,50 @@ import RPGEngine.Data | ||||||
| import RPGEngine.Parse.Core | import RPGEngine.Parse.Core | ||||||
| import RPGEngine.Parse.TextToStructure | import RPGEngine.Parse.TextToStructure | ||||||
| import RPGEngine.Parse.StructureToGame | import RPGEngine.Parse.StructureToGame | ||||||
|  | import RPGEngine.Parse.TextToStructure (gameFile) | ||||||
| 
 | 
 | ||||||
| spec :: Spec | spec :: Spec | ||||||
| spec = do | spec = do | ||||||
|     describe "Game" $ do |     describe "Game" $ do | ||||||
|         it "TODO: Simple game" $ do |         -- TODO There is a weird bug that caused this to go in an infinite loop. Fix later. | ||||||
|             pending |         xit "Simple game" $ do | ||||||
|         it "TODO: More complex game" $ do |             let input   = "player: {\n    hp: 50,\n    inventory: []\n}\n\nlevels: [\n    {\n        layout: {\n            | * * * * * *\n            | * s . . e *\n            | * * * * * *\n        },\n        \n        items: [],\n\n        entities: []\n\n\n  }\n]" | ||||||
|             pending |                 correct = Game { | ||||||
|         it "TODO: Game with multiple levels" $ do |                     state = Playing { | ||||||
|             pending |                         levels = [], | ||||||
|  |                         count = 0, | ||||||
|  |                         level = Level { | ||||||
|  |                             RPGEngine.Data.layout = [], | ||||||
|  |                             index = [], | ||||||
|  |                             items = [], | ||||||
|  |                             entities = [] | ||||||
|  |                         }, | ||||||
|  |                         player = Player { | ||||||
|  |                             playerHp = Just 50, | ||||||
|  |                             inventory = [], | ||||||
|  |                             position = (0, 0), | ||||||
|  |                             showHp = True, | ||||||
|  |                             showInventory = False | ||||||
|  |                         }, | ||||||
|  |                         restart = Menu | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 (Right struct) = parseWith gameFile input | ||||||
|  |             structureToGame struct `shouldBe` correct | ||||||
|  |         it "More complex game" $ do | ||||||
|  |             pendingWith "fix parsing first" | ||||||
|  |         it "Game with multiple levels" $ do | ||||||
|  |             pendingWith "fix parsing first" | ||||||
|      |      | ||||||
|     describe "Player" $ do |     describe "Player" $ do | ||||||
|         it "cannot die" $ do |         it "cannot die" $ do | ||||||
|             let input   = "player: { hp: infinite, inventory: [] }" |             let input   = "player: { hp: infinite, inventory: [] }" | ||||||
|                 correct = Player { |                 correct = Player { | ||||||
|                     playerHp  = Prelude.Nothing, |                     playerHp      = Prelude.Nothing, | ||||||
|                     inventory = [], |                     inventory     = [], | ||||||
|                     position  = (0, 0) |                     position      = (0, 0), | ||||||
|  |                     showHp        = True, | ||||||
|  |                     showInventory = False | ||||||
|                 } |                 } | ||||||
|                 Right (Entry (Tag "player") struct) = parseWith structure input |                 Right (Entry (Tag "player") struct) = parseWith structure input | ||||||
|             structureToPlayer struct `shouldBe` correct |             structureToPlayer struct `shouldBe` correct | ||||||
|  | @ -31,9 +57,11 @@ spec = do | ||||||
|         it "without inventory" $ do |         it "without inventory" $ do | ||||||
|             let input   = "player: { hp: 50, inventory: [] }" |             let input   = "player: { hp: 50, inventory: [] }" | ||||||
|                 correct = Player { |                 correct = Player { | ||||||
|                     playerHp  = Just 50, |                     playerHp      = Just 50, | ||||||
|                     inventory = [], |                     inventory     = [], | ||||||
|                     position  = (0, 0) |                     position      = (0, 0), | ||||||
|  |                     showHp        = True, | ||||||
|  |                     showInventory = False | ||||||
|                 } |                 } | ||||||
|                 Right (Entry (Tag "player") struct) = parseWith structure input |                 Right (Entry (Tag "player") struct) = parseWith structure input | ||||||
|             structureToPlayer struct `shouldBe` correct |             structureToPlayer struct `shouldBe` correct | ||||||
|  | @ -54,14 +82,12 @@ spec = do | ||||||
|                             useTimes = Prelude.Nothing |                             useTimes = Prelude.Nothing | ||||||
|                         } |                         } | ||||||
|                     ], |                     ], | ||||||
|                     position  = (0, 0) |                     position      = (0, 0), | ||||||
|  |                     showHp        = True, | ||||||
|  |                     showInventory = False | ||||||
|                 } |                 } | ||||||
|                 Right (Entry (Tag "player") struct) = parseWith structure input |                 Right (Entry (Tag "player") struct) = parseWith structure input | ||||||
|             structureToPlayer struct `shouldBe` correct |             structureToPlayer struct `shouldBe` correct | ||||||
|      |  | ||||||
|     describe "Layout" $ do |  | ||||||
|         it "simple" $ do |  | ||||||
|             pending |  | ||||||
| 
 | 
 | ||||||
|     describe "Items" $ do |     describe "Items" $ do | ||||||
|         it "simple" $ do |         it "simple" $ do | ||||||
|  | @ -117,23 +143,40 @@ spec = do | ||||||
|             structureToActions struct `shouldBe` correct |             structureToActions struct `shouldBe` correct | ||||||
| 
 | 
 | ||||||
|     describe "Entities" $ do |     describe "Entities" $ do | ||||||
|         it "TODO: Simple entity" $ do |         it "Simple entity" $ do | ||||||
|             pending |             pendingWith "fix parsing first" | ||||||
|      |      | ||||||
|     describe "Level" $ do |     describe "Level" $ do | ||||||
|         it "Simple layout" $ do |         it "Simple layout" $ do | ||||||
|             let input   = "{ layout: { | * * * * * * \n| * s . . e *\n| * * * * * * }, items: [], entities: [] }" |             let input   = "{ layout: { | * * * * * *\n| * s . . e *\n| * * * * * *\n }, items: [], entities: [] }" | ||||||
|                 correct = Level { |                 correct = Level { | ||||||
|                     RPGEngine.Data.layout = [ |                     RPGEngine.Data.layout = [ | ||||||
|                         [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], |                         [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], | ||||||
|                         [Blocked, Entrance, Walkable, Walkable, Exit, Blocked], |                         [Blocked, Entrance, Walkable, Walkable, Exit, Blocked], | ||||||
|                         [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] |                         [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] | ||||||
|                     ], |                     ], | ||||||
|  |                     index    = [ | ||||||
|  |                         (0, 0, Blocked), | ||||||
|  |                         (1, 0, Blocked), | ||||||
|  |                         (2, 0, Blocked), | ||||||
|  |                         (3, 0, Blocked), | ||||||
|  |                         (4, 0, Blocked), | ||||||
|  |                         (5, 0, Blocked), | ||||||
|  |                         (0, 1, Blocked), | ||||||
|  |                         (1, 1, Entrance), | ||||||
|  |                         (2, 1, Walkable), | ||||||
|  |                         (3, 1, Walkable), | ||||||
|  |                         (4, 1, Exit), | ||||||
|  |                         (5, 1, Blocked), | ||||||
|  |                         (0, 2, Blocked), | ||||||
|  |                         (1, 2, Blocked), | ||||||
|  |                         (2, 2, Blocked), | ||||||
|  |                         (3, 2, Blocked), | ||||||
|  |                         (4, 2, Blocked), | ||||||
|  |                         (5, 2, Blocked) | ||||||
|  |                     ], | ||||||
|                     items    = [], |                     items    = [], | ||||||
|                     entities = [] |                     entities = [] | ||||||
|                 } |                 } | ||||||
|                 Right struct = parseWith structure input |                 Right struct = parseWith structure input | ||||||
|             structureToLevel struct `shouldBe` correct |             structureToLevel struct `shouldBe` correct | ||||||
|              |  | ||||||
|         it "TODO: Complex layout" $ do |  | ||||||
|             pending |  | ||||||
|  | @ -5,6 +5,8 @@ import Test.Hspec | ||||||
| import RPGEngine.Data | import RPGEngine.Data | ||||||
| import RPGEngine.Parse.Core | import RPGEngine.Parse.Core | ||||||
| import RPGEngine.Parse.TextToStructure | import RPGEngine.Parse.TextToStructure | ||||||
|  | import Text.Parsec.String (parseFromFile) | ||||||
|  | import GHC.IO (unsafePerformIO) | ||||||
| 
 | 
 | ||||||
| spec :: Spec | spec :: Spec | ||||||
| spec = do | spec = do | ||||||
|  | @ -68,7 +70,7 @@ spec = do | ||||||
|                     ]], "") |                     ]], "") | ||||||
|             parseWithRest structure input `shouldBe` correct |             parseWithRest structure input `shouldBe` correct | ||||||
| 
 | 
 | ||||||
|             let input   = "entities: [ { id: \"door\", x: 4, y: 1, name:\"Secret door\", description: \"This secret door can only be opened with a key\", direction: left , actions: { [inventoryContains(key)] useItem(key), [] leave() } } ]" |             let input   = "entities: [ { id: \"door\", x: 4, y: 1, name:\"Secret door\", description: \"This secret door can only be opened with a key\", direction: left, actions: { [inventoryContains(key)] useItem(key), [] leave() } } ]" | ||||||
|                 correct = Right (Entry (Tag "entities") $ Block [ Block [ |                 correct = Right (Entry (Tag "entities") $ Block [ Block [ | ||||||
|                     Entry (Tag "id")   $ Regular $ String "door", |                     Entry (Tag "id")   $ Regular $ String "door", | ||||||
|                     Entry (Tag "x")    $ Regular $ Integer 4, |                     Entry (Tag "x")    $ Regular $ Integer 4, | ||||||
|  | @ -83,6 +85,17 @@ spec = do | ||||||
|                     ]], "") |                     ]], "") | ||||||
|             parseWithRest structure input `shouldBe` correct |             parseWithRest structure input `shouldBe` correct | ||||||
|          |          | ||||||
|  |         it "combines actions and direction" $ do | ||||||
|  |             let input   = "entities: [ { direction: left, actions: { [inventoryContains(key)] useItem(key), [] leave() } } ]" | ||||||
|  |                 correct = Right (Entry (Tag "entities") $ Block [ Block [ | ||||||
|  |                     Entry (Tag "direction") $ Regular $ Direction West, | ||||||
|  |                     Entry (Tag "actions") $ Block [ | ||||||
|  |                         Entry (ConditionList [InventoryContains "key"]) $ Regular $ Action $ UseItem "key", | ||||||
|  |                         Entry (ConditionList []) $ Regular $ Action Leave | ||||||
|  |                     ] | ||||||
|  |                     ]], "") | ||||||
|  |             parseWithRest structure input `shouldBe` correct | ||||||
|  |          | ||||||
|         it "can parse entries" $ do |         it "can parse entries" $ do | ||||||
|             let input   = "id: \"dagger\"" |             let input   = "id: \"dagger\"" | ||||||
|                 correct = Right $ Entry (Tag "id") $ Regular $ String "dagger" |                 correct = Right $ Entry (Tag "id") $ Regular $ String "dagger" | ||||||
|  | @ -252,22 +265,21 @@ spec = do | ||||||
|             parseWith RPGEngine.Parse.TextToStructure.direction input `shouldBe` correct |             parseWith RPGEngine.Parse.TextToStructure.direction input `shouldBe` correct | ||||||
|          |          | ||||||
|         it "can parse layouts" $ do |         it "can parse layouts" $ do | ||||||
|             let input   = "| * * * * * * * *\n| * s . . . . e *\n| * * * * * * * *" |             let input   = "{ | * * * * * * * *\n | * s . . . . e *\n | * * * * * * * *\n }" | ||||||
|                 correct = Right $ Layout [ |                 correct = Right $ Layout [ | ||||||
|                     [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], |                     [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], | ||||||
|                     [Blocked, Entrance, Walkable, Walkable, Walkable, Walkable, Exit, Blocked], |                     [Blocked, Entrance, Walkable, Walkable, Walkable, Walkable, Exit, Blocked], | ||||||
|                     [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] |                     [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] | ||||||
|                     ] |                     ] | ||||||
|             parseWith RPGEngine.Parse.TextToStructure.layout input `shouldBe` correct |             parseWith value input `shouldBe` correct | ||||||
| 
 | 
 | ||||||
|             let input   = "{ |* * * * * * * *|* s . . . . e *|* * * * * * * * }" |             let input   = "layout: { | * * * * * * * *\n | * s . . . . e *\n | * * * * * * * *\n }" | ||||||
|                 -- correct = Right $ Entry (Tag "layout") $ Regular $ Layout [ |                 correct = Right $ Entry (Tag "layout") $ Regular $ Layout [ | ||||||
|                 correct = Right $ Layout [ |  | ||||||
|                     [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], |                     [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], | ||||||
|                     [Blocked, Entrance, Walkable, Walkable, Walkable, Walkable, Exit, Blocked], |                     [Blocked, Entrance, Walkable, Walkable, Walkable, Walkable, Exit, Blocked], | ||||||
|                     [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] |                     [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] | ||||||
|                     ] |                     ] | ||||||
|             parseWith RPGEngine.Parse.TextToStructure.value input `shouldBe` correct |             parseWith structure input `shouldBe` correct | ||||||
|      |      | ||||||
|     describe "Brackets" $ do |     describe "Brackets" $ do | ||||||
|         it "matches closing <" $ do |         it "matches closing <" $ do | ||||||
|  | @ -289,3 +301,75 @@ spec = do | ||||||
|             let input   = '[' |             let input   = '[' | ||||||
|                 correct = ']' |                 correct = ']' | ||||||
|             getMatchingClosingBracket input `shouldBe` correct |             getMatchingClosingBracket input `shouldBe` correct | ||||||
|  | 
 | ||||||
|  |     describe "Full game file" $ do | ||||||
|  |         it "single level" $ do | ||||||
|  |             let input   = "player: {\n    hp: 50,\n    inventory: []\n}\n\nlevels: [\n    {\n        layout: {\n            | * * * * * *\n            | * s . . e *\n            | * * * * * *\n        },\n        \n        items: [],\n\n        entities: []\n\n\n  }\n]" | ||||||
|  |                 correct = Right [ | ||||||
|  |                     Entry (Tag "player") $ Block [ | ||||||
|  |                         Entry (Tag "hp") $ Regular $ Integer 50, | ||||||
|  |                         Entry (Tag "inventory") $ Block [] | ||||||
|  |                     ], | ||||||
|  |                     Entry (Tag "levels") $ Block [ Block [ | ||||||
|  |                         Entry (Tag "layout") $ Regular $ Layout [ | ||||||
|  |                             [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], | ||||||
|  |                             [Blocked, Entrance, Walkable, Walkable, Exit, Blocked], | ||||||
|  |                             [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] | ||||||
|  |                             ], | ||||||
|  |                         Entry (Tag "items") $ Block [], | ||||||
|  |                         Entry (Tag "entities") $ Block [] | ||||||
|  |                     ]] | ||||||
|  |                     ] | ||||||
|  |             parseWith gameFile input `shouldBe` correct | ||||||
|  |          | ||||||
|  |         it "two levels" $ do | ||||||
|  |             let input   = "player: {\n    hp: 50,\n    inventory: []\n}\n\nlevels: [\n    {\n        layout: {\n            | * * * * * *\n            | * s . . e *\n            | * * * * * *\n        },\n        \n        items: [],\n\n        entities: []\n    },\n    {\n        layout: {\n            | * * *\n            | * e *\n            | * . *\n            | * . *\n            | * . *\n            | * . *\n            | * s *\n            | * * *\n        },\n\n        items: [],\n\n        entities: []\n    }\n]" | ||||||
|  |                 correct = Right [ | ||||||
|  |                     Entry (Tag "player") $ Block [ | ||||||
|  |                         Entry (Tag "hp") $ Regular $ Integer 50, | ||||||
|  |                         Entry (Tag "inventory") $ Block [] | ||||||
|  |                     ], | ||||||
|  |                     Entry (Tag "levels") $ Block [ | ||||||
|  |                         Block [ | ||||||
|  |                             Entry (Tag "layout") $ Regular $ Layout [ | ||||||
|  |                                 [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], | ||||||
|  |                                 [Blocked, Entrance, Walkable, Walkable, Exit, Blocked], | ||||||
|  |                                 [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] | ||||||
|  |                                 ], | ||||||
|  |                             Entry (Tag "items") $ Block [], | ||||||
|  |                             Entry (Tag "entities") $ Block [] | ||||||
|  |                         ], Block [ | ||||||
|  |                             Entry (Tag "layout") $ Regular $ Layout [ | ||||||
|  |                                 [Blocked,Blocked,Blocked], | ||||||
|  |                                 [Blocked,Exit,Blocked], | ||||||
|  |                                 [Blocked,Walkable,Blocked], | ||||||
|  |                                 [Blocked,Walkable,Blocked], | ||||||
|  |                                 [Blocked,Walkable,Blocked], | ||||||
|  |                                 [Blocked,Walkable,Blocked], | ||||||
|  |                                 [Blocked,Entrance,Blocked], | ||||||
|  |                                 [Blocked,Blocked,Blocked] | ||||||
|  |                                 ], | ||||||
|  |                             Entry (Tag "items") $ Block [], | ||||||
|  |                             Entry (Tag "entities") $ Block [] | ||||||
|  |                         ] | ||||||
|  |                         ] | ||||||
|  |                     ] | ||||||
|  |             parseWith gameFile input `shouldBe` correct | ||||||
|  |          | ||||||
|  |         it "from file" $ do | ||||||
|  |             let correct = Right [ | ||||||
|  |                     Entry (Tag "player") $ Block [ | ||||||
|  |                         Entry (Tag "hp") $ Regular $ Integer 50, | ||||||
|  |                         Entry (Tag "inventory") $ Block [] | ||||||
|  |                     ], | ||||||
|  |                     Entry (Tag "levels") $ Block [ Block [ | ||||||
|  |                         Entry (Tag "layout") $ Regular $ Layout [ | ||||||
|  |                             [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], | ||||||
|  |                             [Blocked, Entrance, Walkable, Walkable, Exit, Blocked], | ||||||
|  |                             [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked] | ||||||
|  |                             ], | ||||||
|  |                         Entry (Tag "items") $ Block [], | ||||||
|  |                         Entry (Tag "entities") $ Block [] | ||||||
|  |                     ]] | ||||||
|  |                     ] | ||||||
|  |             unsafePerformIO (parseFromFile gameFile "levels/level1.txt") `shouldBe` correct | ||||||
							
								
								
									
										19
									
								
								test/Spec.hs
									
										
									
									
									
								
							
							
						
						
									
										19
									
								
								test/Spec.hs
									
										
									
									
									
								
							|  | @ -1 +1,18 @@ | ||||||
| {-# OPTIONS_GHC -F -pgmF hspec-discover #-} | {-# OPTIONS_GHC -F -pgmF hspec-discover #-} | ||||||
|  | 
 | ||||||
|  | -------------------------- How to use Hspec -------------------------- | ||||||
|  | 
 | ||||||
|  | -- If a test has not yet been written: | ||||||
|  | -- Use `pending` or `pendingWith`. | ||||||
|  | --     it "Description" $ do | ||||||
|  | --       pendingWith "Reason" | ||||||
|  | 
 | ||||||
|  | -- Temporarily disable running a test: | ||||||
|  | -- Replace `it` with `xit` | ||||||
|  | --     xit "Description" $ do ... | ||||||
|  | 
 | ||||||
|  | -- Temporarily only run a specific test: | ||||||
|  | -- Put `focus` in front. | ||||||
|  | --      it "Description" $ do ... | ||||||
|  | -- becomes | ||||||
|  | --      focus $ it "Description" $ do ... | ||||||
		Reference in a new issue