dev #25
					 9 changed files with 504 additions and 12 deletions
				
			
		
							
								
								
									
										249
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										249
									
								
								README.md
									
										
									
									
									
								
							|  | @ -1,3 +1,250 @@ | |||
| # RPG-Engine | ||||
| 
 | ||||
| Schrijf een game-engine voor een rollenspel | ||||
| Schrijf een game-engine voor een rollenspel | ||||
| 
 | ||||
| https://pixel-poem.itch.io/dungeon-assetpuck | ||||
| https://kyrise.itch.io/kyrises-free-16x16-rpg-icon-pack | ||||
| 
 | ||||
| # RPG Engine requirements | ||||
| 
 | ||||
| ## Functional requirements | ||||
| 
 | ||||
| - [ ] Parsing of engine configuration file to game object | ||||
| - [ ] Rendering of all game objects (Levels, objects, entities, ...) | ||||
| - [ ] A start menu with the possibility of selecting a level | ||||
| - [ ] An end screen that shows wether or not a player won | ||||
| - [ ] Support for built-in engine functions | ||||
| 
 | ||||
| - [ ] Player can move around in grid-world. | ||||
| - [ ] Player can pick up objects. | ||||
| - [ ] Player can use objects. | ||||
| - [ ] Player can loose and gain health points. | ||||
| - [ ] Player can interact with other entities (fight enimies, open doors, ...). | ||||
| - [ ] Player can go to the next level. | ||||
| 
 | ||||
| ## Not-functional requirements | ||||
| 
 | ||||
| - [ ] Use Parsing. | ||||
| - [ ] Use at least one (1) monad transformer. | ||||
| - [ ] Write good and plenty of documentation.:w | ||||
| 
 | ||||
| - [ ] Write tests (for example, using HSpec). | ||||
| 
 | ||||
| --- | ||||
| 
 | ||||
| # Plaats om dingen neer te jotten | ||||
| 
 | ||||
| ``` | ||||
| Play <--- HandleInput | ||||
|  | | ||||
|  | | ||||
|  v | ||||
| Level <--- LoadLevel <--- Parse | ||||
|  | | ||||
|  | | ||||
|  v | ||||
| RenderLevel | ||||
| ``` | ||||
| 
 | ||||
| - [ ] State paradigma gebruiken om van startscherm naar playscherm naar pause scherm naar endscherm te gaan | ||||
| 
 | ||||
| Nuttige links:  | ||||
| 
 | ||||
| - https://jakewheat.github.io/intro_to_parsing/ | ||||
| 
 | ||||
| ``` | ||||
| Jarne — Today at 22:44 | ||||
| Da kan hoor en had da eerst, me gloss eeft geen goede text dus... | ||||
| ListDirectory, en er was ook een fuctie takeBaseName | ||||
| ``` | ||||
| 
 | ||||
| # RPG-Engine Documentation | ||||
| 
 | ||||
| ## Playing the game | ||||
| 
 | ||||
| TODO | ||||
| 
 | ||||
| - Input commands etc | ||||
| - An example playthrough | ||||
| 
 | ||||
| ## Writing your own stages | ||||
| 
 | ||||
| A stage description file, conventionally named `<stage_name>.txt` is a file with a JSON-like format. It is used to describe | ||||
|  everything inside a single stage of your game, including anything related to the player, the levels your game contains | ||||
|  and what happens in that level. It is essentially the raw representation of the initial state of a single game. | ||||
| 
 | ||||
| > Note: At the moment, every game has a single stage description file. Chaining several files together is not possible yet. | ||||
| 
 | ||||
| A stage description file consists of several elements. | ||||
| 
 | ||||
| | Element         | Short description                                                                                         | | ||||
| | --------------- | --------------------------------------------------------------------------------------------------------- | | ||||
| | `Block`         | optionally surrounded by `{ ... }`, consists of several `Entry`'s, optionally separated by commas `,`     | | ||||
| | `Entry`         | is a `Key` - `Value` pair, optionally separated by a colon `:`                                            | | ||||
| | `Key`           | is a unique, predefined `String` describing `Value`                                                       | | ||||
| | `Value`         | is either a `Block` or a `BlockList` or a traditional value, such as `String` or `Int`                    | | ||||
| | `BlockList`     | is a number of `Block`'s, surrounded by `[ ... ]`, separated by commas, can be empty                      | | ||||
| 
 | ||||
| We'll look at the following example to explain these concepts. | ||||
| 
 | ||||
| ```javascript | ||||
| player: { | ||||
|     hp: 50, | ||||
|     inventory: [ | ||||
|         { | ||||
|             id: "dagger", | ||||
|             x: 0, | ||||
|             y: 0, | ||||
|             name: "Dagger", | ||||
|             description: "Basic dagger you found somewhere", | ||||
|             useTimes: infinite, | ||||
|             value: 10, | ||||
| 
 | ||||
|             actions: {} | ||||
|         } | ||||
|     ] | ||||
| } | ||||
| 
 | ||||
| levels: [ | ||||
|     { | ||||
|         layout: { | ||||
|             | * * * * * * | ||||
|             | * s . . e * | ||||
|             | * * * * * * | ||||
|         }, | ||||
|         items: [], | ||||
|         entities: [] | ||||
|     }, | ||||
|     { | ||||
|         layout: { | ||||
|             | * * * * * * * * | ||||
|             | * s . . . . e * | ||||
|             | * * * * * * * * | ||||
|         }, | ||||
|         items: [ | ||||
|             { | ||||
|                 id: "key", | ||||
|                 x: 3, | ||||
|                 y: 1, | ||||
|                 name: "Doorkey", | ||||
|                 description: "Unlocks a secret door", | ||||
|                 useTimes: 1, | ||||
|                 value: 0, | ||||
|                 actions: { | ||||
|                     [not(inventoryFull())] retrieveItem(key), | ||||
|                     [] leave() | ||||
|                 } | ||||
|             } | ||||
|         ], | ||||
|         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() | ||||
|                 } | ||||
|             } | ||||
|         ] | ||||
|     } | ||||
| ] | ||||
| ``` | ||||
| 
 | ||||
| This stage description file consists of a single `Block`. A stage description file always does. This top level `Block` | ||||
|  contains two `Value`s `player` and `levels`, not separated by commas.  | ||||
| 
 | ||||
| `player` describes a `Block` that represents the player of the game. Its `Entry`s are `hp` (a traditional value) and | ||||
|  `inventory` (a `BlockList` of several other `Block`s). They are both separated by commas this time. It is possible for | ||||
|  the inventory to be an empty list `[]`. | ||||
| 
 | ||||
| `levels` is a `BlockList` that contains all the information to construct your game. | ||||
| 
 | ||||
| ### `layout` syntax | ||||
| 
 | ||||
| If `Key`  has the value `layout`, `Value` is none of the types discussed so far. Instead `Layout` is specifically made | ||||
|  to describe the layout of a level. This object is surrounded by `{ ... }` and consists of multiple lines, starting with | ||||
|  a vertical line `|` and several characters of the following: | ||||
| 
 | ||||
| - `x` is an empty tile a.k.a. void. | ||||
| - `.` is a tile walkable by the player. | ||||
| - `*` is a tile not walkable by the player. | ||||
| - `s` is the starting position of the player. | ||||
| - `e` is the exit. | ||||
| 
 | ||||
| All characters are interspersed with spaces. | ||||
| 
 | ||||
| ### `actions` syntax | ||||
| 
 | ||||
| If `Key` has the value `actions`, the following changes are important for its `Value`, which in this case is a `Block` | ||||
|  with zero or more `Entry`s like so: | ||||
| 
 | ||||
| - `Key` has type `ConditionList`. | ||||
|    | ||||
|   A `ConditionList` consists of several `Condition`s, surrounded by `[ ... ]`, separated by commas. A `ConditionList` | ||||
|    can be empty. If so, the conditional is always fulfilled. | ||||
| 
 | ||||
|   A `Condition` is one of the following: | ||||
|    | ||||
|   - `inventoryFull()`: the players inventory is full. | ||||
|   - `inventoryContains(objectId)`: the players inventory contains an object with id `objectId`. | ||||
|   - `not(condition)`: logical negation of `condition`. | ||||
| 
 | ||||
| - `Value` is an `Action`. | ||||
| 
 | ||||
|   An `Action` is one of the following: | ||||
| 
 | ||||
|   - `leave()` | ||||
|   - `retrieveItem(objectId)` | ||||
|   - `useItem(objectId)` | ||||
|   - `decreaseHp(entityId, objectId)` | ||||
|   - `increasePlayerHp(objectId)` | ||||
| 
 | ||||
| ### Back to the example | ||||
| 
 | ||||
| If we look at the example, all the objects are | ||||
| 
 | ||||
| ``` | ||||
| >Block< | ||||
|     Entry = Key ('player') + >Block< | ||||
|         Entry = Key ('hp') + Value (50) | ||||
|         Entry = Key ('inventory') + >BlockList< | ||||
|             length = 1 | ||||
|             Block | ||||
|                 Entry = Key ('id') + Value ("dagger") | ||||
|                 ... <several traditional entries like this> | ||||
|                 Entry = Key ('actions') + empty Block | ||||
|     Entry = Key ('levels') + >BlockList< | ||||
|         length = 2 | ||||
|         >Block< | ||||
|             Entry = Key ('layout') + Layout | ||||
|                 <multiple lines that describe the layout> | ||||
|             Entry = Key ('items') + empty BlockList | ||||
|             Entry = Key ('entities') + empty BlockList | ||||
|         >Block< | ||||
|             Entry = Key ('layout') + Layout | ||||
|                 <multiple lines that describe the layout> | ||||
|             Entry = Key ('items') + >BlockList< | ||||
|                 length = 1 | ||||
|                 >Block< | ||||
|                     Entry = Key ('id') + Value ("key") | ||||
|                     ... <several traditional entries like this> | ||||
|                     Entry = Key ('actions') + >Block< | ||||
|                         Entry = >ConditionList< + Action ('retrieveItem(key)') | ||||
|                             length = 1 | ||||
|                             Condition ('not(inventoryFull())'))  | ||||
|                         Entry = empty ConditionList + Action ('leave()') | ||||
|             Entry = Key ('entities') + >BlockList< | ||||
|                 length = 1 | ||||
|                 >Block< | ||||
|                     Entry = Key ('id') + Value ("door") | ||||
|                     ... <several traditional entries like this> | ||||
|                     Entry = Key ('actions') + >Block< | ||||
|                         Entry = >ConditionList< + Action ('useItem(key)') | ||||
|                             length = 1 | ||||
|                             Condition ('inventoryContains(key)') | ||||
|                         Entry = empty ConditionList + Action ('leave()') | ||||
| ``` | ||||
							
								
								
									
										132
									
								
								lib/control/Parse.hs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								lib/control/Parse.hs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,132 @@ | |||
| module Parse where | ||||
| 
 | ||||
| -- TODO Maak wrapper module | ||||
| -- TODO This module should not be used by anything except for wrapper module and tests | ||||
| 
 | ||||
| import Game | ||||
| import Player | ||||
| import Text.Parsec | ||||
| import Text.Parsec.Char | ||||
| import Text.Parsec.String | ||||
| import Data.List | ||||
| import Data.Maybe | ||||
| import Text.Parsec.Error (Message(UnExpect)) | ||||
| 
 | ||||
| -- TODO parseFromFile gebruiken | ||||
| 
 | ||||
| -- Parser type | ||||
| -- type Parser = Parsec String () | ||||
| 
 | ||||
| -- A wrapper, which takes a parser and some input and returns a  | ||||
| -- parsed output. | ||||
| parseWith :: Parser a -> String -> Either ParseError a | ||||
| parseWith parser = parse parser "" | ||||
| 
 | ||||
| ignoreWS :: Parser a -> Parser a | ||||
| ignoreWS parser = spaces >> parser | ||||
| 
 | ||||
| -- Also return anything that has not yet been parsed | ||||
| parseWithRest :: Parser a -> String -> Either ParseError (a, String) | ||||
| --                     fmap (,) over Parser monad and apply to rest | ||||
| parseWithRest parser = parse ((,) <$> parser <*> rest) "" | ||||
|     where rest = manyTill anyToken eof | ||||
| 
 | ||||
| parseToGame :: Game | ||||
| parseToGame = undefined | ||||
| 
 | ||||
| -- Info in between brackets, '(..)', '[..]', '{..}' or '<..>' | ||||
| data Brackets a = Brackets a | ||||
|               deriving (Eq, Show) | ||||
| 
 | ||||
| parseToPlayer :: Player | ||||
| parseToPlayer = undefined | ||||
| 
 | ||||
| -- any words separated by whitespace | ||||
| parseWord :: Parser String | ||||
| parseWord = do many alphaNum | ||||
| 
 | ||||
| -- TODO Expand to allow different kinds of brackets, also see Brackets data type. | ||||
| -- TODO Check if brackets match order. | ||||
| -- TODO Allow nested brackets. | ||||
| brackets :: Parser (Brackets String) | ||||
| brackets = do | ||||
|     ignoreWS $ char '(' | ||||
|     e <- ignoreWS $ many1 alphaNum | ||||
|     ignoreWS $ char ')' | ||||
|     return $ Brackets e | ||||
| 
 | ||||
| ------------------------ | ||||
| 
 | ||||
| data Value = String String | ||||
|            | Integer Int | ||||
|            | Infinite | ||||
|            deriving (Show, Eq) | ||||
| 
 | ||||
| -- See documentation for more details, only a short description is | ||||
| --provided here. | ||||
| data StructureElement = Block [StructureElement] | ||||
|                       | Entry String StructureElement-- Key + Value | ||||
|                       | Regular Value -- Regular value, Integer or String or Infinite | ||||
|                       | ConditionList [StructureElement] | ||||
|                       -- TODO | ||||
|                       | Condition -- inventoryFull() etc. | ||||
|                       -- TODO | ||||
|                       | Action -- leave(), useItem(objectId) etc. | ||||
|                       deriving (Show, Eq) | ||||
| 
 | ||||
| -- TODO Add ConditionList and Action | ||||
| structureElement :: Parser StructureElement | ||||
| structureElement = choice [block, regular] | ||||
| 
 | ||||
| -- A Block is a list of Entry s | ||||
| block :: Parser StructureElement | ||||
| block = do | ||||
|     ignoreWS $ char '{' | ||||
|     list <- ignoreWS $ many1 entry | ||||
|     ignoreWS $ char '}' | ||||
|     return $ Block list | ||||
| 
 | ||||
| entry :: Parser StructureElement | ||||
| entry = do | ||||
|     key <- ignoreWS $ many1 alphaNum | ||||
|     ignoreWS $ char ':' | ||||
|     value <- ignoreWS structureElement -- TODO Is this the correct one to use? | ||||
|     return $ Entry key value | ||||
| 
 | ||||
| regular :: Parser StructureElement | ||||
| regular = do | ||||
|     value <- ignoreWS $ choice [integer, valueString, infinite] | ||||
|     return $ Regular value | ||||
| 
 | ||||
| integer :: Parser Value | ||||
| integer = do | ||||
|     value <- ignoreWS $ many1 digit | ||||
|     return $ Integer (read value :: Int) | ||||
| 
 | ||||
| valueString :: Parser Value | ||||
| valueString = do | ||||
|     ignoreWS $ char '"' | ||||
|     value <- ignoreWS $ many1 (noneOf ['"']) | ||||
|     ignoreWS $ char '"' | ||||
|     return $ String value | ||||
| 
 | ||||
| infinite :: Parser Value | ||||
| infinite = do | ||||
|     ignoreWS $ string "infinite" | ||||
|     notFollowedBy alphaNum | ||||
|     return Infinite | ||||
| 
 | ||||
| conditionList :: Parser StructureElement | ||||
| conditionList = do | ||||
|     ignoreWS $ char '[' | ||||
|     list <- ignoreWS $ many1 condition | ||||
|     ignoreWS $ char ']' | ||||
|     return $ ConditionList list | ||||
| 
 | ||||
| -- TODO | ||||
| condition :: Parser StructureElement | ||||
| condition = undefined | ||||
| 
 | ||||
| -- TODO YOU ARE HERE | ||||
| action :: Parser StructureElement | ||||
| action = undefined | ||||
|  | @ -8,11 +8,12 @@ library | |||
|   hs-source-dirs: lib, lib/control, lib/data, lib/render | ||||
|   build-depends: | ||||
|     base >= 4.7 && <5, | ||||
|     gloss >= 1.11 && < 1.14, gloss-juicy >= 0.2.3 | ||||
|     gloss >= 1.11 && < 1.14, gloss-juicy >= 0.2.3, | ||||
|     parsec >= 3.1.15.1 | ||||
|   exposed-modules: | ||||
|     RPGEngine, | ||||
|     -- Control | ||||
|     Input, InputHandling, | ||||
|     Input, InputHandling, Parse, | ||||
|     -- Data | ||||
|     Game, Internals, Player, State, | ||||
|     -- Render | ||||
|  | @ -26,7 +27,11 @@ executable rpg-engine | |||
| 
 | ||||
| test-suite rpg-engine-test | ||||
|   type: exitcode-stdio-1.0 | ||||
|   main-is: RPG-Engine-Test.hs | ||||
|   main-is: RPGEngineSpec.hs | ||||
|   hs-source-dirs: test | ||||
|   default-language: Haskell2010 | ||||
|   build-depends: base >=4.7 && <5, hspec <= 2.10.6, rpg-engine | ||||
|   build-depends: base >=4.7 && <5, hspec <= 2.10.6, hspec-discover, rpg-engine | ||||
|   other-modules:  | ||||
|     InteractionSpec,  | ||||
|     -- Parsing | ||||
|     ParsedToGameSpec, ParserSpec | ||||
|  |  | |||
|  | @ -42,6 +42,7 @@ extra-deps: | |||
| # | ||||
| # extra-deps: [] | ||||
| - gloss-juicy-0.2.3@sha256:0c3bca95237cbf91f8b3b1936a0661f1e0457acd80502276d54d6c5210f88b25,1618 | ||||
| - parsec-3.1.15.1@sha256:8c7a36aaadff12a38817fc3c4ff6c87e3352cffd1a58df640de7ed7a97ad8fa3,4601 | ||||
| 
 | ||||
| # Override default flag values for local packages and extra-deps | ||||
| # flags: {} | ||||
|  |  | |||
							
								
								
									
										9
									
								
								test/InteractionSpec.hs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								test/InteractionSpec.hs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| module InteractionSpec where | ||||
| 
 | ||||
| import Test.Hspec | ||||
| 
 | ||||
| spec :: Spec | ||||
| spec = do | ||||
|     describe "Player with Inventory" $ do | ||||
|         it "TODO: Simple test" $ do | ||||
|             pending | ||||
							
								
								
									
										52
									
								
								test/ParsedToGameSpec.hs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								test/ParsedToGameSpec.hs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | |||
| module ParsedToGameSpec where | ||||
| 
 | ||||
| import Test.Hspec | ||||
| import Parse | ||||
| 
 | ||||
| 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 | ||||
|      | ||||
|     describe "Player" $ do | ||||
|         it "TODO: Simple player" $ do | ||||
|             pending | ||||
|      | ||||
|     describe "Inventory" $ do | ||||
|         it "TODO: Empty inventory" $ do | ||||
|             pending | ||||
|         it "TODO: Singleton inventory" $ do | ||||
|             pending | ||||
|         it "TODO: Filled inventory" $ do | ||||
|             pending | ||||
|      | ||||
|     describe "Items" $ do | ||||
|         it "TODO: Simple item" $ do | ||||
|             pending | ||||
|             -- Check id | ||||
|             -- Check x | ||||
|             -- Check y | ||||
|             -- Check name | ||||
|             -- Check description | ||||
|             -- Check useTimes | ||||
|             -- Check value | ||||
|             -- Check actions | ||||
|      | ||||
|     describe "Actions" $ do | ||||
|         it "TODO: Simple action" $ do | ||||
|             pending | ||||
|      | ||||
|     describe "Entities" $ do | ||||
|         it "TODO: Simple entity" $ do | ||||
|             pending | ||||
|      | ||||
|     describe "Level" $ do | ||||
|         it "TODO: Simple layout" $ do | ||||
|             pending | ||||
|         it "TODO: Complex layout" $ do | ||||
|             pending | ||||
							
								
								
									
										52
									
								
								test/ParserSpec.hs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								test/ParserSpec.hs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | |||
| module ParserSpec where | ||||
| 
 | ||||
| import Test.Hspec | ||||
| import Parse | ||||
| import Data.Either | ||||
| 
 | ||||
| spec :: Spec | ||||
| spec = do | ||||
|     describe "Basics of entries" $ do | ||||
|         it "can parse integers" $ do | ||||
|             let correct = Right $ Regular $ Integer 1 | ||||
|             correct `shouldBe` parseWith regular "1" | ||||
|         it "can parse string" $ do | ||||
|             let input   = "dit is een string" | ||||
|                 correct = Right $ Regular $ String input | ||||
|             correct `shouldBe` parseWith regular ("\"" ++ input ++ "\"") | ||||
|         it "can parse infinite" $ do | ||||
|             let correct = Right $ Regular Infinite | ||||
|             correct `shouldBe` parseWith regular "infinite" | ||||
| 
 | ||||
|             let wrong = Right $ Regular Infinite | ||||
|             wrong `shouldNotBe` parseWith regular "infinitee" | ||||
|          | ||||
|         it "can parse entries" $ do | ||||
|             let input   = "id : \"dagger\"" | ||||
|                 correct = Right $ Entry "id" $ Regular $ String "dagger" | ||||
|             correct `shouldBe` parseWith entry input | ||||
| 
 | ||||
|             let input   = "x: 0" | ||||
|                 correct = Right $ Entry "x" $ Regular $ Integer 0 | ||||
|             correct `shouldBe` parseWith entry input | ||||
|          | ||||
|             let input   = "useTimes: infinite" | ||||
|                 correct = Right $ Entry "useTimes" $ Regular Infinite | ||||
|             correct `shouldBe` parseWith entry input | ||||
|      | ||||
|     describe "Special kinds" $ do | ||||
|         it "can parse actions" $ do | ||||
|             let input = "actions: {}" | ||||
|                 correct = Right $ Entry "actions" $ Regular Infinite -- TODO Change this | ||||
|             correct `shouldBe` parseWith action input | ||||
|          | ||||
|         it "can parse conditions" $ do | ||||
|             pending | ||||
|          | ||||
|         it "can parse layouts" $ do | ||||
|             pending | ||||
|      | ||||
|     describe "Lists and blocks" $ do | ||||
|         it "can parse entities" $ do | ||||
|             pending | ||||
|          | ||||
|  | @ -1,7 +0,0 @@ | |||
| import Test.Hspec | ||||
| 
 | ||||
| main :: IO() | ||||
| main = hspec $ do | ||||
|     describe "Dummy category" $ do | ||||
|         it "Dummy test" $ do | ||||
|             0 `shouldBe` 0 | ||||
							
								
								
									
										1
									
								
								test/RPGEngineSpec.hs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/RPGEngineSpec.hs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| {-# OPTIONS_GHC -F -pgmF hspec-discover #-} | ||||
		Reference in a new issue