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 right     | `Arrow Right` | `d`         | | ||||
| | Show inventory | `i`           |             | | ||||
| | Restart level  | `r`           |             | | ||||
| | Quit game      | `Esc`         |             | | ||||
| 
 | ||||
| ### Example playthrough | ||||
| 
 | ||||
|  |  | |||
|  | @ -19,8 +19,8 @@ levels: [ | |||
|         items: [ | ||||
|             { | ||||
|                 id: "key", | ||||
|                 x: 0, | ||||
|                 y: 1, | ||||
|                 x: 1, | ||||
|                 y: 2, | ||||
|                 name: "Sleutel", | ||||
|                 description: "Deze sleutel kan een deur openen", | ||||
|                 useTimes: 1, | ||||
|  | @ -35,8 +35,8 @@ levels: [ | |||
|         entities: [ | ||||
|             { | ||||
|                 id: "door", | ||||
|                 x: 0, | ||||
|                 y: 3, | ||||
|                 x: 1, | ||||
|                 y: 4, | ||||
|                 name: "Deur", | ||||
|                 description: "Deze deur kan geopend worden met een sleutel", | ||||
|                 direction: up, | ||||
|  |  | |||
|  | @ -1,9 +1,6 @@ | |||
| # Dit gehele bestand is een Block | ||||
| 
 | ||||
| # Dit is een entry: key + value | ||||
| player: { # Value is hier een block | ||||
| player: { | ||||
|     hp: 50, | ||||
|     inventory: [ # BlockList | ||||
|     inventory: [ | ||||
|         { | ||||
|             id: "dagger", | ||||
|             x: 0, | ||||
|  | @ -18,7 +15,6 @@ player: { # Value is hier een block | |||
|     ] | ||||
| } | ||||
| 
 | ||||
| # Dit is een entry | ||||
| levels: [ | ||||
|     { | ||||
|         layout: { | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ import RPGEngine.Config ( bgColor, winDimensions, winOffsets ) | |||
| import RPGEngine.Render ( initWindow, render ) | ||||
| import RPGEngine.Input ( handleAllInput ) | ||||
| 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 Graphics.Gloss ( play ) | ||||
|  | @ -27,22 +27,15 @@ playRPGEngine title fps = do | |||
| -- TODO revert this | ||||
| -- Initialize the game | ||||
| initGame :: Game | ||||
| -- initGame = Game {  | ||||
| --     state   = Menu{ base = StateBase{  | ||||
| --         renderer = renderMenu,  | ||||
| --         inputHandler = handleInputMenu | ||||
| --     }} | ||||
| initGame = Game { state = Menu } | ||||
| -- initGame = Game{ state = initState } | ||||
| --     where initState = Playing{ | ||||
| --         levels = [defaultLevel, otherLevel], | ||||
| --         count = 0, | ||||
| --         level  = defaultLevel, | ||||
| --         player = spawnPlayer defaultLevel defaultPlayer, | ||||
| --         restart = initState | ||||
| --     } | ||||
| initGame = Game{ | ||||
|     state = initState | ||||
| } | ||||
|     where initState = Playing{ | ||||
|         levels = [defaultLevel, otherLevel], | ||||
|         count = 0, | ||||
|         level  = defaultLevel, | ||||
|         player = spawnPlayer defaultLevel defaultPlayer, | ||||
|         restart = initState | ||||
|     } | ||||
| 
 | ||||
| -- TODO remove this | ||||
| otherLayout :: Layout | ||||
|  | @ -50,6 +43,8 @@ otherLayout = [ | |||
|     [Blocked, Blocked, Blocked], | ||||
|     [Blocked, Entrance, Blocked], | ||||
|     [Blocked, Walkable, Blocked], | ||||
|     [Blocked, Walkable, Blocked], | ||||
|     [Blocked, Walkable, Blocked], | ||||
|     [Blocked, Exit, Blocked], | ||||
|     [Blocked, Blocked, Blocked] | ||||
|     ] | ||||
|  | @ -69,12 +64,30 @@ otherLevel = Level { | |||
|         (1, 2, Walkable), | ||||
|         (2, 2, Blocked), | ||||
|         (0, 3, Blocked), | ||||
|         (1, 3, Exit), | ||||
|         (1, 3, Walkable), | ||||
|         (2, 3, Blocked), | ||||
|         (0, 4, Blocked), | ||||
|         (1, 4, Blocked), | ||||
|         (2, 4, Blocked) | ||||
|         (1, 4, Walkable), | ||||
|         (2, 4, Blocked), | ||||
|         (0, 5, Blocked), | ||||
|         (1, 5, Exit), | ||||
|         (2, 5, Blocked), | ||||
|         (0, 6, Blocked), | ||||
|         (1, 6, Blocked), | ||||
|         (2, 6, Blocked) | ||||
|     ], | ||||
|     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. | ||||
| data Game = Game { | ||||
|     state  :: State | ||||
| } | ||||
| } deriving (Eq, Show) | ||||
| 
 | ||||
| ------------------------------- State -------------------------------- | ||||
| 
 | ||||
|  | @ -33,6 +33,7 @@ data State = Menu | |||
|            | Win | ||||
|            -- Lost a level | ||||
|            | Lose           { restart   :: State        } | ||||
|            deriving (Eq, Show) | ||||
| 
 | ||||
| ------------------------------- Level -------------------------------- | ||||
| 
 | ||||
|  |  | |||
|  | @ -64,9 +64,30 @@ defaultLevel = Level { | |||
| 
 | ||||
| defaultPlayer :: Player | ||||
| defaultPlayer = Player { | ||||
|     playerHp  = Prelude.Nothing, -- Compares to infinity | ||||
|     inventory = [], | ||||
|     position  = (0, 0) | ||||
|     -- playerHp      = Prelude.Nothing, -- Compares to infinity | ||||
|     playerHp      = Just 50, | ||||
|     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 | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ type InputHandler a = Event -> (a -> a) | |||
| data ListSelector = ListSelector { | ||||
|     selection :: Int, | ||||
|     selected  :: Bool | ||||
| } | ||||
| } deriving (Eq, Show) | ||||
| 
 | ||||
| ------------------------------ Exported ------------------------------ | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ module RPGEngine.Input.Playing | |||
| ( handleInputPlaying | ||||
| , checkPlaying | ||||
| , spawnPlayer | ||||
| , putCoords | ||||
| ) where | ||||
| 
 | ||||
| import RPGEngine.Input.Core (InputHandler, handle, handleKey, composeInputHandlers) | ||||
|  | @ -32,6 +33,8 @@ handleInputPlaying = composeInputHandlers [ | |||
|     handleKey (Char 's') Down $ movePlayer South, | ||||
|     handleKey (Char 'a') Down $ movePlayer West, | ||||
| 
 | ||||
|     handleKey (Char 'r') Down restartGame, | ||||
| 
 | ||||
|     handleKey (Char 'i') Down $ toggleInventoryShown True, | ||||
|     handleKey (Char 'i') Up   $ toggleInventoryShown False | ||||
|     ] | ||||
|  | @ -58,13 +61,15 @@ pauseGame g@Game{ state = playing@Playing{} } = pausedGame | |||
|     where pausedGame  = g{ state = Paused playing } | ||||
| 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. | ||||
| goToNextLevel :: State -> State | ||||
| goToNextLevel s@Playing{ levels = levels, level = current, count = count, player = player } = nextState | ||||
|     where -- Either the next level or winState | ||||
|           nextState | (count + 1) < length levels = nextLevelState | ||||
|     where nextState | (count + 1) < length levels = nextLevelState | ||||
|                     | 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) | ||||
|           movedPlayer    = spawnPlayer nextLevel player | ||||
| goToNextLevel s = s | ||||
|  |  | |||
|  | @ -6,13 +6,11 @@ import RPGEngine.Data ( Game ) | |||
| import RPGEngine.Parse.StructureToGame ( structureToGame ) | ||||
| import GHC.IO (unsafePerformIO) | ||||
| import Text.Parsec.String (parseFromFile) | ||||
| import RPGEngine.Parse.TextToStructure (structure) | ||||
| import RPGEngine.Parse.TextToStructure ( gameFile ) | ||||
| 
 | ||||
| ------------------------------ Exported ------------------------------ | ||||
| 
 | ||||
| parse :: FilePath -> Game | ||||
| parse filename = structureToGame struct | ||||
|     where (Right struct) = unsafePerformIO io | ||||
|           io             = parseFromFile structure filename | ||||
| 
 | ||||
| tempParse = parseFromFile | ||||
|           io             = parseFromFile gameFile filename | ||||
|  | @ -10,26 +10,25 @@ import RPGEngine.Data | |||
|              entityActions, entityValue, entityHp, direction), | ||||
|       Item(itemId, itemX, itemY, itemName, itemDescription, itemValue, | ||||
|            itemActions, useTimes), | ||||
|       Level(layout, items, entities), | ||||
|       Level(layout, items, entities, index), | ||||
|       Game (..), State (..) ) | ||||
| import RPGEngine.Parse.TextToStructure | ||||
|     ( Value(Infinite, Action, Layout, String, Direction, Integer), | ||||
|       Key(Tag, ConditionList), | ||||
|       Structure(..) ) | ||||
| import RPGEngine.Data.Default (defaultPlayer, defaultLevel, defaultItem, defaultEntity) | ||||
| import RPGEngine.Input.Playing (putCoords, spawnPlayer) | ||||
| 
 | ||||
| ------------------------------ Exported ------------------------------ | ||||
| 
 | ||||
| structureToGame :: Structure -> Game | ||||
| -- structureToGame [Entry(Tag "player") playerBlock, Entry(Tag "levels") levelsBlock] = game | ||||
| structureToGame (Entry (Tag "player") playerBlock) = game | ||||
|     where game         = Game{ state = newState } | ||||
|           newState     = Playing{ levels = newLevels, level = currentLevel, player = newPlayer, restart = newState } | ||||
|           -- newLevels    = structureToLevels levelsBlock | ||||
|           -- currentLevel = head newLevels | ||||
|           newLevels    = [defaultLevel] | ||||
|           currentLevel = defaultLevel | ||||
|           newPlayer    = structureToPlayer playerBlock | ||||
| structureToGame :: [Structure] -> Game | ||||
| structureToGame [Entry (Tag "player") playerBlock, Entry (Tag "levels") levelsBlock] = game | ||||
|     where game         = Game newState | ||||
|           newState     = Playing newLevels 0 currentLevel newPlayer newState | ||||
|           newLevels    = structureToLevels levelsBlock | ||||
|           currentLevel = head newLevels | ||||
|           newPlayer    = spawnPlayer currentLevel $ structureToPlayer playerBlock | ||||
| structureToGame _ = Game Menu | ||||
| 
 | ||||
| ------------------------------- Player ------------------------------- | ||||
| 
 | ||||
|  | @ -60,7 +59,9 @@ structureToLevels (Block struct) = structureToLevel <$> struct | |||
| structureToLevels _              = [defaultLevel] | ||||
| 
 | ||||
| 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' :: [Structure] -> Level -> Level | ||||
|  |  | |||
|  | @ -18,9 +18,13 @@ import Text.Parsec | |||
|       notFollowedBy, | ||||
|       sepBy, | ||||
|       many, | ||||
|       try ) | ||||
|       try, spaces, endOfLine ) | ||||
| import qualified Text.Parsec as P ( string ) | ||||
| import Text.Parsec.String ( Parser ) | ||||
| import Text.Parsec.Combinator (lookAhead) | ||||
| 
 | ||||
| gameFile :: Parser [Structure] | ||||
| gameFile = try $ do many1 $ ignoreWS structure | ||||
| 
 | ||||
| -------------------------- StructureElement -------------------------- | ||||
| 
 | ||||
|  | @ -111,7 +115,7 @@ data Value = String String | |||
| ---------------------------------------------------------------------- | ||||
| 
 | ||||
| value :: Parser Value | ||||
| value = choice [layout, string, integer, infinite, action, direction] | ||||
| value = choice [layout, string, integer, infinite, direction, action] | ||||
| 
 | ||||
| string :: Parser Value | ||||
| string = try $ String <$> between (char '\"') (char '\"') reading | ||||
|  | @ -149,7 +153,7 @@ direction = try $ do | |||
|         ignoreWS $ P.string "left", | ||||
|         ignoreWS $ P.string "right" | ||||
|         ] | ||||
|     notFollowedBy alphaNum | ||||
|     -- lookAhead $ char ',' | ||||
|     return $ Direction $ make value | ||||
|     where make "up"    = North | ||||
|           make "right" = East | ||||
|  | @ -160,15 +164,12 @@ direction = try $ do | |||
| layout :: Parser Value | ||||
| layout = try $ do | ||||
|     open <- ignoreWS $ oneOf openingBrackets | ||||
|     ignoreWS $ char '|' | ||||
|     list <- ignoreWS $ ignoreWS strip `sepBy` ignoreWS (char '|') | ||||
|     let closing = getMatchingClosingBracket open | ||||
|     ignoreWS $ char closing | ||||
|     return $ Layout list | ||||
|     value <- many1 strip <* ignoreWS (char closing) | ||||
|     return $ Layout value | ||||
| 
 | ||||
| strip :: Parser Strip | ||||
| strip = try $ do | ||||
|     physical `sepBy` char ' ' | ||||
| strip = try $ do ignoreWS (char '|') *> ignoreWS (physical `sepBy` char ' ') | ||||
| 
 | ||||
| physical :: Parser Physical | ||||
| physical = try $ do | ||||
|  |  | |||
|  | @ -55,7 +55,11 @@ test-suite rpg-engine-test | |||
|   main-is: Spec.hs | ||||
|   hs-source-dirs: test | ||||
|   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:  | ||||
|     Parser.GameSpec | ||||
|     Parser.StructureSpec | ||||
|  | @ -6,16 +6,40 @@ import RPGEngine.Data | |||
| import RPGEngine.Parse.Core | ||||
| import RPGEngine.Parse.TextToStructure | ||||
| import RPGEngine.Parse.StructureToGame | ||||
| import RPGEngine.Parse.TextToStructure (gameFile) | ||||
| 
 | ||||
| spec :: Spec | ||||
| spec = do | ||||
|     describe "Game" $ do | ||||
|         it "TODO: Simple game" $ do | ||||
|             pending | ||||
|         it "TODO: More complex game" $ do | ||||
|             pending | ||||
|         it "TODO: Game with multiple levels" $ do | ||||
|             pending | ||||
|         -- TODO There is a weird bug that caused this to go in an infinite loop. Fix later. | ||||
|         xit "Simple 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]" | ||||
|                 correct = Game { | ||||
|                     state = Playing { | ||||
|                         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 | ||||
|         it "cannot die" $ do | ||||
|  | @ -23,7 +47,9 @@ spec = do | |||
|                 correct = Player { | ||||
|                     playerHp      = Prelude.Nothing, | ||||
|                     inventory     = [], | ||||
|                     position  = (0, 0) | ||||
|                     position      = (0, 0), | ||||
|                     showHp        = True, | ||||
|                     showInventory = False | ||||
|                 } | ||||
|                 Right (Entry (Tag "player") struct) = parseWith structure input | ||||
|             structureToPlayer struct `shouldBe` correct | ||||
|  | @ -33,7 +59,9 @@ spec = do | |||
|                 correct = Player { | ||||
|                     playerHp      = Just 50, | ||||
|                     inventory     = [], | ||||
|                     position  = (0, 0) | ||||
|                     position      = (0, 0), | ||||
|                     showHp        = True, | ||||
|                     showInventory = False | ||||
|                 } | ||||
|                 Right (Entry (Tag "player") struct) = parseWith structure input | ||||
|             structureToPlayer struct `shouldBe` correct | ||||
|  | @ -54,15 +82,13 @@ spec = do | |||
|                             useTimes = Prelude.Nothing | ||||
|                         } | ||||
|                     ], | ||||
|                     position  = (0, 0) | ||||
|                     position      = (0, 0), | ||||
|                     showHp        = True, | ||||
|                     showInventory = False | ||||
|                 } | ||||
|                 Right (Entry (Tag "player") struct) = parseWith structure input | ||||
|             structureToPlayer struct `shouldBe` correct | ||||
| 
 | ||||
|     describe "Layout" $ do | ||||
|         it "simple" $ do | ||||
|             pending | ||||
| 
 | ||||
|     describe "Items" $ do | ||||
|         it "simple" $ do | ||||
|             let input   = "{ id: \"dagger\", x: 0, y: 0, name: \"Dagger\", description: \"Basic dagger you found somewhere\", useTimes: infinite, value: 10, actions: {} }" | ||||
|  | @ -117,23 +143,40 @@ spec = do | |||
|             structureToActions struct `shouldBe` correct | ||||
| 
 | ||||
|     describe "Entities" $ do | ||||
|         it "TODO: Simple entity" $ do | ||||
|             pending | ||||
|         it "Simple entity" $ do | ||||
|             pendingWith "fix parsing first" | ||||
|      | ||||
|     describe "Level" $ 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 { | ||||
|                     RPGEngine.Data.layout = [ | ||||
|                         [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], | ||||
|                         [Blocked, Entrance, Walkable, Walkable, Exit, 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    = [], | ||||
|                     entities = [] | ||||
|                 } | ||||
|                 Right struct = parseWith structure input | ||||
|             structureToLevel struct `shouldBe` correct | ||||
|              | ||||
|         it "TODO: Complex layout" $ do | ||||
|             pending | ||||
|  | @ -5,6 +5,8 @@ import Test.Hspec | |||
| import RPGEngine.Data | ||||
| import RPGEngine.Parse.Core | ||||
| import RPGEngine.Parse.TextToStructure | ||||
| import Text.Parsec.String (parseFromFile) | ||||
| import GHC.IO (unsafePerformIO) | ||||
| 
 | ||||
| spec :: Spec | ||||
| spec = do | ||||
|  | @ -68,7 +70,7 @@ spec = do | |||
|                     ]], "") | ||||
|             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 [ | ||||
|                     Entry (Tag "id")   $ Regular $ String "door", | ||||
|                     Entry (Tag "x")    $ Regular $ Integer 4, | ||||
|  | @ -83,6 +85,17 @@ spec = do | |||
|                     ]], "") | ||||
|             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 | ||||
|             let input   = "id: \"dagger\"" | ||||
|                 correct = Right $ Entry (Tag "id") $ Regular $ String "dagger" | ||||
|  | @ -252,22 +265,21 @@ spec = do | |||
|             parseWith RPGEngine.Parse.TextToStructure.direction input `shouldBe` correct | ||||
|          | ||||
|         it "can parse layouts" $ do | ||||
|             let input   = "| * * * * * * * *\n| * s . . . . e *\n| * * * * * * * *" | ||||
|             let input   = "{ | * * * * * * * *\n | * s . . . . e *\n | * * * * * * * *\n }" | ||||
|                 correct = Right $ Layout [ | ||||
|                     [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], | ||||
|                     [Blocked, Entrance, Walkable, Walkable, Walkable, Walkable, Exit, 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 *|* * * * * * * * }" | ||||
|                 -- correct = Right $ Entry (Tag "layout") $ Regular $ Layout [ | ||||
|                 correct = Right $ Layout [ | ||||
|             let input   = "layout: { | * * * * * * * *\n | * s . . . . e *\n | * * * * * * * *\n }" | ||||
|                 correct = Right $ Entry (Tag "layout") $ Regular $ Layout [ | ||||
|                     [Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked, Blocked], | ||||
|                     [Blocked, Entrance, Walkable, Walkable, Walkable, Walkable, Exit, 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 | ||||
|         it "matches closing <" $ do | ||||
|  | @ -289,3 +301,75 @@ spec = do | |||
|             let input   = '[' | ||||
|                 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 | ||||
							
								
								
									
										17
									
								
								test/Spec.hs
									
										
									
									
									
								
							
							
						
						
									
										17
									
								
								test/Spec.hs
									
										
									
									
									
								
							|  | @ -1 +1,18 @@ | |||
| {-# 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