#11 Write report
1
.gitignore
vendored
|
@ -12,3 +12,4 @@ extra/
|
||||||
*.dll
|
*.dll
|
||||||
|
|
||||||
stack.yaml.lock
|
stack.yaml.lock
|
||||||
|
.vscode/settings.json
|
||||||
|
|
3
.vscode/tasks.json
vendored
|
@ -49,9 +49,10 @@
|
||||||
"args": [
|
"args": [
|
||||||
"-s",
|
"-s",
|
||||||
"-o", "verslag.pdf",
|
"-o", "verslag.pdf",
|
||||||
"-f", "markdown+smart+header_attributes+yaml_metadata_block+auto_identifiers",
|
"-f", "markdown+smart+header_attributes+yaml_metadata_block+auto_identifiers+implicit_figures",
|
||||||
"--pdf-engine", "lualatex",
|
"--pdf-engine", "lualatex",
|
||||||
"--template", "eisvogel",
|
"--template", "eisvogel",
|
||||||
|
"--dpi=300",
|
||||||
"header.yaml",
|
"header.yaml",
|
||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
|
|
305
README.md
|
@ -1,40 +1,9 @@
|
||||||
<!--
|
<!--
|
||||||
## 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
|
|
||||||
|
|
||||||
- [x] 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 enemies, open doors, ...).
|
|
||||||
- [ ] Player can go to the next level.
|
|
||||||
|
|
||||||
## Not-functional requirements
|
|
||||||
|
|
||||||
- [x] Use Parsing.
|
|
||||||
- [ ] Use at least one (1) monad transformer.
|
|
||||||
- [ ] Write good and plenty of documentation.:w
|
|
||||||
|
|
||||||
- [x] Write tests (for example, using HSpec).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Nuttige links:
|
Nuttige links:
|
||||||
|
|
||||||
- https://jakewheat.github.io/intro_to_parsing/
|
- https://jakewheat.github.io/intro_to_parsing/
|
||||||
|
|
||||||
```
|
Config files cannot end with blank line
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<div style="page-break-after: always;"></div>
|
<div style="page-break-after: always;"></div>
|
||||||
-->
|
-->
|
||||||
|
@ -45,40 +14,193 @@ RPG-Engine is a game engine for playing and creating your own RPG games.
|
||||||
|
|
||||||
If you are interested in the development side of things, [development notes can be found here](#Development-notes).
|
If you are interested in the development side of things, [development notes can be found here](#Development-notes).
|
||||||
|
|
||||||
This README serves as both documentation and project report, so excuse the details that might not be important for the average user.
|
This README serves as both documentation and project report, so excuse the details that might not be important for the average reader.
|
||||||
|
|
||||||
## Playing the game
|
## Playing the game
|
||||||
|
|
||||||
These are the keybinds *in* the game. All other keybinds in the menus should be straightforward.
|
These are the keybinds while *in* game. All other keybinds in menus etc. should be straightforward.
|
||||||
|
|
||||||
| Action | Primary | Secondary |
|
| Action | Primary | Secondary |
|
||||||
| -------------- | ------------- | ----------- |
|
| -------------- | ------------- | ------------ |
|
||||||
| Move up | `Arrow Up` | `w` |
|
| Move up | `Arrow Up` | `w` |
|
||||||
| Move left | `Arrow Left` | `a` |
|
| Move left | `Arrow Left` | `a` |
|
||||||
| Move down | `Arrow Down` | `s` |
|
| Move down | `Arrow Down` | `s` |
|
||||||
| Move right | `Arrow Right` | `d` |
|
| Move right | `Arrow Right` | `d` |
|
||||||
| Interaction | `Space` | `f` |
|
| Interaction | `Space` | `f`, `Enter` |
|
||||||
| Show inventory | `i` | `Tab` |
|
| Show inventory | `i` | `Tab` |
|
||||||
| Restart level | `r` | |
|
| Restart level | `r` | |
|
||||||
| Quit game | `Esc` | |
|
| Quit game | `Esc` | |
|
||||||
|
|
||||||
### Example playthrough
|
### Example playthrough
|
||||||
|
|
||||||
TODO
|
{ width=600 }
|
||||||
|
{ width=600 }
|
||||||
|
{ width=600 }
|
||||||
|
{ width=600 }
|
||||||
|
{ width=600 }
|
||||||
|
{ width=600 }
|
||||||
|
{ width=600 }
|
||||||
|
{ width=600 }
|
||||||
|
{ width=600 }
|
||||||
|
{ width=600 }
|
||||||
|
{ width=600 }
|
||||||
|
{ width=600 }
|
||||||
|
{ width=600 }
|
||||||
|
{ width=600 }
|
||||||
|
{ width=600 }
|
||||||
|
|
||||||
- An example playthrough, with pictures and explanations
|
## Development notes
|
||||||
|
|
||||||
|
### Engine architecture
|
||||||
|
|
||||||
|
`RPGEngine` is the main module. It contains the `playRPGEngine` function which bootstraps the whole game. It is also
|
||||||
|
the game loop. From here, `RPGEngine` talks to its submodules.
|
||||||
|
|
||||||
|
These submodules are `Config`, `Data`, `Input`, `Parse` & `Render`. They are all responsible for their own part. However,
|
||||||
|
each of these submodules has their own submodules to divide the work. They are conveniently named after the state of
|
||||||
|
the game that they work with, e.g. the main menu has a module & when the game is playing is a different module. A special
|
||||||
|
one is `Core`, which is kind of like a library for every piece. It contains functions that are regularly used by the other modules.
|
||||||
|
|
||||||
|
- `Config`: Configuration values, should ultimately be moved into parsing from a file.
|
||||||
|
- `Data`: Data containers and accessors of information.
|
||||||
|
- `Input`: Anything that handles input or changes the state of the game.
|
||||||
|
- `Parse`: Parsing
|
||||||
|
- `Render`: Rendering of the game and everything below that.
|
||||||
|
|
||||||
|
#### Monads/Monad stack
|
||||||
|
|
||||||
|
Monads:
|
||||||
|
|
||||||
|
- Extensive use of `Maybe` for integers or infinity and `do` in parser implementation.
|
||||||
|
- `IO` to handle external information
|
||||||
|
- ...
|
||||||
|
|
||||||
|
Monad transformer: ??
|
||||||
|
|
||||||
|
I am afraid I did not write any monad transformers in this project. I think I could (and should) have focused more on
|
||||||
|
writing monads and monad transformers. In hindsight, I can see where I could and should have used them. I can think of
|
||||||
|
plenty of ways to make the current implementation simpler. This is unfortunate. However, I want to believe that my next
|
||||||
|
time writing a more complex Haskell program, I will remember using monad transformers. Sadly, I forgot this time.
|
||||||
|
|
||||||
|
An example of where I would use a monad transformer - in hindsight:
|
||||||
|
|
||||||
|
1. Interactions in game: something along the lines of ...
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
newtype StateT m a = StateT { runStateT :: m a }
|
||||||
|
|
||||||
|
instance Monad m => Monad (StateT m) where
|
||||||
|
return = lift . return
|
||||||
|
x >>= f = StateT $ do
|
||||||
|
v <- runStateT x
|
||||||
|
case v of
|
||||||
|
Playing level -> runStateT ( f level )
|
||||||
|
Paused continue -> runStateT ( continue >>= f )
|
||||||
|
-- etc
|
||||||
|
|
||||||
|
class MonadTransformer r where
|
||||||
|
lift :: Monad m => m a -> (r m) a
|
||||||
|
instance MonadTransformer StateT where
|
||||||
|
lift = StateT
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Interaction with the outside world should also be done with Monad(transformers) instead of using `unsafePerformIO`.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
Overall, only parsing is tested using Hspec. However, parsing is tested *thoroughly* and I am quite sure that there aren't
|
||||||
|
a lot of edge cases that I did not catch. This makes for a relaxing environment where you can quickly check if a change
|
||||||
|
you made breaks anything.
|
||||||
|
|
||||||
|
`Spec` is the main module. It does not contain any tests, but functions as the 'discover' module to find the other tests
|
||||||
|
in its folder.
|
||||||
|
|
||||||
|
`Parser.StructureSpec` tests functionality of `RPGEngine.Parse.TextToStructure`, `Parser.GameSpec` tests functionality
|
||||||
|
of `RPGEngine.Parse.StructureToGame`.
|
||||||
|
|
||||||
|
Known issues:
|
||||||
|
|
||||||
|
- [ ] Rendering is still not centered, I am sorry for those with small screens.
|
||||||
|
- [ ] Config files cannot end with an empty line. I could not get that to work and I decided that it was more important
|
||||||
|
to implement other functionality first. Unfortunately, I was not able to get back to it yet.
|
||||||
|
- [ ] The parser is unable to parse layouts with trailing whitespace.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Parsing was way harder than I initially expected. I believe over half my time on this project was spent trying to write the
|
||||||
|
parser. I am still not absolutely sure that it will work with *everything*, but it gets the job done at the moment. I don't
|
||||||
|
know if parsing into a structure before transforming the structure into a game was a good move. It might have saved me some
|
||||||
|
time if I did it straight to `Game`. I want to say that I have a parser-to-structure module now, but even so, there are some
|
||||||
|
links between `TextToStructure` and `Game` that make it almost useless to any other project (without changing anything).
|
||||||
|
|
||||||
|
Player-object interaction was easier than previous projects. I believe this is both because I am getting used to it by now
|
||||||
|
and because I spent a lot of time beforehand structuring everything. I also like to think that structuring the project is
|
||||||
|
what I did right. There is a clear hierarchy and you can find what you are looking for fairly easy, without having to search
|
||||||
|
for a function in file contents or having to scavenge multiple different files before finding what you want. However, I
|
||||||
|
absolutely wasted a lot of time restructuring the project multiple times, mostly because I was running into dependency cycles.
|
||||||
|
|
||||||
|
Overall, I believe the project was a success. I am proud of the end result. Though, please note my comments on monad transformers.
|
||||||
|
|
||||||
|
### Assets & dependencies
|
||||||
|
|
||||||
|
The following assets were used (and modified if specified):
|
||||||
|
|
||||||
|
- Kyrise's Free 16x16 RPG Icon Pack<sup>[[1]](#1)</sup>
|
||||||
|
|
||||||
|
- 2D Pixel Dungeon Asset Pack by Pixel_Poem<sup>[[2]](#2)</sup>
|
||||||
|
|
||||||
|
Every needed asset was taken and put into its own `.png`, instead of in the overview.
|
||||||
|
|
||||||
|
RPG-Engine makes use of the following libraries:
|
||||||
|
|
||||||
|
- [directory](https://hackage.haskell.org/package/directory) for listing levels in a directory
|
||||||
|
- [gloss](https://hackage.haskell.org/package/gloss) for game rendering
|
||||||
|
- [gloss-juicy](https://hackage.haskell.org/package/gloss-juicy) for rendering images
|
||||||
|
- [hspec](https://hackage.haskell.org/package/hspec) for testing
|
||||||
|
- [hspec-discover](https://hackage.haskell.org/package/hspec-discover) for allowing to split test files in multiple files
|
||||||
|
- [parsec](https://hackage.haskell.org/package/parsec) for parsing configuration files
|
||||||
|
|
||||||
<div style="page-break-after: always; visibility: hidden">\pagebreak</div>
|
<div style="page-break-after: always; visibility: hidden">\pagebreak</div>
|
||||||
|
|
||||||
## Writing your own stages
|
## References
|
||||||
|
|
||||||
A stage description file, conventionally named `<stage_name>.txt` is a file with a JSON-like format. It is used to describe
|
<a id="1">[1]</a> [Kyrise's Free 16x16 RPG Icon Pack](https://kyrise.itch.io/kyrises-free-16x16-rpg-icon-pack) © 2018
|
||||||
everything inside a single stage of your game, including anything related to the player, the levels your game contains
|
by [Kyrise](https://kyrise.itch.io/) is licensed under [CC BY 4.0](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)
|
||||||
|
|
||||||
|
<a id="2">[2]</a> [2D Pixel Dungeon Asset Pack](https://pixel-poem.itch.io/dungeon-assetpuck) by [Pixel_Poem](https://pixel-poem.itch.io/)
|
||||||
|
is not licensed
|
||||||
|
|
||||||
|
<div style="page-break-after: always; visibility: hidden">\pagebreak</div>
|
||||||
|
|
||||||
|
## Appendix A: Future development ideas
|
||||||
|
|
||||||
|
The following ideas could (or should) be implemented in the future of this project.
|
||||||
|
|
||||||
|
- [ ] **Entity system:** With en ES, you can implement moving entities and repeated input. It also resembles the typical
|
||||||
|
game loop more closely which can make it easier to implement other ideas in the future.
|
||||||
|
- [ ] **Game music:** Ambient game music and sound effects can improve the gaming experience I think.
|
||||||
|
- [ ] **Expand configuration file:** Implement the same methods for parsing stage description files to a configuration file,
|
||||||
|
containing keybinds, dimension sizes, even window titles, making this a truly customizable engine.
|
||||||
|
- [ ] **Camera follows player:** The camera should follow the player, making it always center. This allows for larger levels
|
||||||
|
increases the immersion of the game.
|
||||||
|
|
||||||
|
Changes in the backend:
|
||||||
|
|
||||||
|
- [ ] **Make inventory a state** At the moment, there is a boolean for inventory rendering. This should be turned into a state,
|
||||||
|
so it makes more sense to call it from other places as well.
|
||||||
|
- [ ] **Direction of entities** Change the rendering based on the direction of an entity.
|
||||||
|
- [ ] **Inventory with more details** The inventory should show more details of items, e.g. name, value, remaining use
|
||||||
|
times and description.
|
||||||
|
|
||||||
|
<div style="page-break-after: always; visibility: hidden">\pagebreak</div>
|
||||||
|
|
||||||
|
## Appendix B: Writing your own worlds
|
||||||
|
|
||||||
|
A world description file, conventionally named `<world_name_or_level_x>.txt` is a file with a JSON-like format. It is used to describe
|
||||||
|
everything inside a single world 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.
|
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 world description file consists of several elements.
|
||||||
|
|
||||||
A stage description file consists of several elements.
|
|
||||||
|
|
||||||
| Element | Short description |
|
| Element | Short description |
|
||||||
| --------------- | --------------------------------------------------------------------------------------------------------- |
|
| --------------- | --------------------------------------------------------------------------------------------------------- |
|
||||||
|
@ -159,7 +281,7 @@ levels: [
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
This stage description file consists of a single `Block`. A stage description file always does. This top level `Block`
|
This world description file consists of a single `Block`. A world description file always does. This top level `Block`
|
||||||
contains two `Value`s `player` and `levels`, not separated by commas.
|
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
|
`player` describes a `Block` that represents the player of the game. Its `Entry`s are `hp` (a traditional value) and
|
||||||
|
@ -252,79 +374,4 @@ If we look at the example, all the objects are
|
||||||
length = 1
|
length = 1
|
||||||
Condition ('inventoryContains(key)')
|
Condition ('inventoryContains(key)')
|
||||||
Entry = empty ConditionList + Action ('leave()')
|
Entry = empty ConditionList + Action ('leave()')
|
||||||
```
|
```
|
||||||
|
|
||||||
<div style="page-break-after: always; visibility: hidden">\pagebreak</div>
|
|
||||||
|
|
||||||
## Development notes
|
|
||||||
|
|
||||||
### Engine architecture
|
|
||||||
|
|
||||||
<mark>TODO</mark>
|
|
||||||
|
|
||||||
`RPGEngine` is the main module. It contains the `playRPGEngine` function which bootstraps the whole game. It is also
|
|
||||||
the game loop. From here, `RPGEngine` talks to its submodules.
|
|
||||||
|
|
||||||
These submodules are `Config`, `Data`, `Input`, `Parse` & `Render`. They are all responsible for their own part, either
|
|
||||||
containing the program configuration, data containers, everything needed to handle input, everything needed to parse a
|
|
||||||
source file & everything needed to render the game. However, each of these submodules has their own submodules to
|
|
||||||
divide the work. They are conveniently named after the state of the game that they work with, e.g. the main menu has a
|
|
||||||
module & when the game is playing is a different module. A special one is `Core`, which is kind of like a library for
|
|
||||||
every piece. It contains functions that are regularly used by the other modules.
|
|
||||||
|
|
||||||
#### Monads/Monad stack
|
|
||||||
|
|
||||||
<mark>TODO</mark>
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
<mark>TODO</mark>
|
|
||||||
|
|
||||||
### Assets & dependencies
|
|
||||||
|
|
||||||
The following assets were used (and modified if specified):
|
|
||||||
|
|
||||||
- Kyrise's Free 16x16 RPG Icon Pack<sup>[[1]](#1)</sup>
|
|
||||||
|
|
||||||
- 2D Pixel Dungeon Asset Pack by Pixel_Poem<sup>[[2]](#2)</sup>
|
|
||||||
|
|
||||||
Every needed asset was taken and put into its own `.png`, instead of in the overview.
|
|
||||||
|
|
||||||
RPG-Engine makes use of the following libraries:
|
|
||||||
|
|
||||||
- [directory](https://hackage.haskell.org/package/directory) for listing levels in a directory
|
|
||||||
- [gloss](https://hackage.haskell.org/package/gloss) for game rendering
|
|
||||||
- [gloss-juicy](https://hackage.haskell.org/package/gloss-juicy) for rendering images
|
|
||||||
- [hspec](https://hackage.haskell.org/package/hspec) for testing
|
|
||||||
- [hspec-discover](https://hackage.haskell.org/package/hspec-discover) for allowing to split test files in multiple files
|
|
||||||
- [parsec](https://hackage.haskell.org/package/parsec) for parsing configuration files
|
|
||||||
|
|
||||||
### Future development ideas
|
|
||||||
|
|
||||||
The following ideas could (or should) be implemented in the future of this project.
|
|
||||||
|
|
||||||
- [ ] **Entity system:** With en ES, you can implement moving entities and repeated input. It also resembles the typical
|
|
||||||
game loop more closely which can make it easier to implement other ideas in the future.
|
|
||||||
- [ ] **Game music:** Ambient game music and sound effects can improve the gaming experience I think.
|
|
||||||
- [ ] **Expand configuration file:** Implement the same methods for parsing stage description files to a configuration file,
|
|
||||||
containing keybinds, dimension sizes, even window titles, making this a truly customizable engine.
|
|
||||||
- [ ] **Camera follows player:** The camera should follow the player, making it always center. This allows for larger levels
|
|
||||||
increases the immersion of the game.
|
|
||||||
|
|
||||||
<div style="page-break-after: always; visibility: hidden">\pagebreak</div>
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
Parsing was way harder than I initially expected. About half of my time on this project was spent writing the parser.
|
|
||||||
|
|
||||||
<mark>TODO</mark>
|
|
||||||
|
|
||||||
<div style="page-break-after: always; visibility: hidden">\pagebreak</div>
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
<a id="1">[1]</a> [Kyrise's Free 16x16 RPG Icon Pack](https://kyrise.itch.io/kyrises-free-16x16-rpg-icon-pack) © 2018
|
|
||||||
by [Kyrise](https://kyrise.itch.io/) is licensed under [CC BY 4.0](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)
|
|
||||||
|
|
||||||
<a id="2">[2]</a> [2D Pixel Dungeon Asset Pack](https://pixel-poem.itch.io/dungeon-assetpuck) by [Pixel_Poem](https://pixel-poem.itch.io/)
|
|
||||||
is not licensed
|
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 905 B |
BIN
extra/walkthrough/5-1-01.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
extra/walkthrough/5-2-01.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
extra/walkthrough/5-2-02.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
extra/walkthrough/5-2-03.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
extra/walkthrough/5-2-04.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
extra/walkthrough/5-2-05.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
extra/walkthrough/5-2-06.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
extra/walkthrough/5-3-01.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
extra/walkthrough/5-3-02.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
extra/walkthrough/5-3-03.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
extra/walkthrough/5-3-04.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
extra/walkthrough/5-3-05.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
extra/walkthrough/5-3-06.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
extra/walkthrough/5-3-07.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
extra/walkthrough/5-3-08.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
extra/walkthrough/5-3-09.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
extra/walkthrough/selection.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
extra/walkthrough/you-win.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
144
levels/level5.txt
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
player: {
|
||||||
|
hp: 100,
|
||||||
|
inventory: [
|
||||||
|
{
|
||||||
|
id: "dagger",
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
name: "Swiss army knife",
|
||||||
|
description: "Your trustworthy army knife will never let you down",
|
||||||
|
useTimes: infinite,
|
||||||
|
value: 5,
|
||||||
|
actions: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "potion",
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
name: "Small healing potion",
|
||||||
|
description: "Will recover you from small injuries",
|
||||||
|
useTimes: 5,
|
||||||
|
value: 5,
|
||||||
|
actions: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
levels: [
|
||||||
|
{
|
||||||
|
layout: {
|
||||||
|
| * * * * * * *
|
||||||
|
| * s . . . e *
|
||||||
|
| * * * * * * *
|
||||||
|
},
|
||||||
|
items: [],
|
||||||
|
entities: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
layout: {
|
||||||
|
| x x * * * x x x x
|
||||||
|
| x x * . * x x x x
|
||||||
|
| * * * . * * * * *
|
||||||
|
| * s . . . . . e *
|
||||||
|
| * * * * * * * * *
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "key",
|
||||||
|
x: 3,
|
||||||
|
y: 3,
|
||||||
|
name: "Secret key",
|
||||||
|
description: "What if this key opens 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 door can only be opened with a secret key",
|
||||||
|
direction: left,
|
||||||
|
actions: {
|
||||||
|
[inventoryContains(key)] useItem(key),
|
||||||
|
[] leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
layout: {
|
||||||
|
| * * * * * * * * * * *
|
||||||
|
| * . . . . . . . . . *
|
||||||
|
| * * * * * * * * * . *
|
||||||
|
| * e . . . . . . . s *
|
||||||
|
| * * * * * * * * * . *
|
||||||
|
| x x x x x x x x * . *
|
||||||
|
| * * * * * * * * * . *
|
||||||
|
| * . . . . . . . . . *
|
||||||
|
| * * * * * * * * * * *
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "key",
|
||||||
|
x: 1,
|
||||||
|
y: 1,
|
||||||
|
name: "Key to sturdy door",
|
||||||
|
description: "You have proven worthy",
|
||||||
|
useTimes: 1,
|
||||||
|
value: 0,
|
||||||
|
actions: {
|
||||||
|
[not(inventoryFull())] retrieveItem(key),
|
||||||
|
[] leave()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "sword",
|
||||||
|
x: 1,
|
||||||
|
y: 7,
|
||||||
|
name: "Mighty sword",
|
||||||
|
description: "Slayer of evil",
|
||||||
|
useTimes: 3,
|
||||||
|
value: 100,
|
||||||
|
actions: {
|
||||||
|
[not(inventoryFull())] retrieveItem(sword),
|
||||||
|
[] leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
entities: [
|
||||||
|
{
|
||||||
|
id: "door",
|
||||||
|
x: 8,
|
||||||
|
y: 5,
|
||||||
|
name: "Sturdy door",
|
||||||
|
description: "I wonder what's behind it?",
|
||||||
|
direction: right,
|
||||||
|
actions: {
|
||||||
|
[inventoryContains(key)] useItem(key),
|
||||||
|
[] leave()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "devil",
|
||||||
|
x: 6,
|
||||||
|
y: 1,
|
||||||
|
name: "Evil powers",
|
||||||
|
description: "Certainly from hell",
|
||||||
|
hp: 55,
|
||||||
|
value: 10,
|
||||||
|
actions: {
|
||||||
|
[inventoryContains(dagger)] decreaseHp(devil, dagger),
|
||||||
|
[inventoryContains(sword)] decreaseHp(devil, sword),
|
||||||
|
[] leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -16,6 +16,7 @@ import Data.Foldable (find)
|
||||||
handleInputActionSelection :: InputHandler Game
|
handleInputActionSelection :: InputHandler Game
|
||||||
handleInputActionSelection = composeInputHandlers [
|
handleInputActionSelection = composeInputHandlers [
|
||||||
handleKey (SpecialKey KeySpace) Down selectAction,
|
handleKey (SpecialKey KeySpace) Down selectAction,
|
||||||
|
handleKey (SpecialKey KeyEnter) Down selectAction,
|
||||||
|
|
||||||
handleKey (SpecialKey KeyUp) Down $ moveSelector North,
|
handleKey (SpecialKey KeyUp) Down $ moveSelector North,
|
||||||
handleKey (SpecialKey KeyDown) Down $ moveSelector South
|
handleKey (SpecialKey KeyDown) Down $ moveSelector South
|
||||||
|
|
|
@ -36,6 +36,7 @@ handleInputPlaying = composeInputHandlers [
|
||||||
|
|
||||||
-- Interaction with entities and items
|
-- Interaction with entities and items
|
||||||
handleKey (SpecialKey KeySpace) Down checkForInteraction,
|
handleKey (SpecialKey KeySpace) Down checkForInteraction,
|
||||||
|
handleKey (SpecialKey KeyEnter) Down checkForInteraction,
|
||||||
handleKey (Char 'f') Down checkForInteraction,
|
handleKey (Char 'f') Down checkForInteraction,
|
||||||
|
|
||||||
handleKey (Char 'i') Down $ toggleInventoryShown True,
|
handleKey (Char 'i') Down $ toggleInventoryShown True,
|
||||||
|
|
|
@ -42,8 +42,7 @@ focusPlayer _ = id
|
||||||
renderLevel :: Renderer Level
|
renderLevel :: Renderer Level
|
||||||
renderLevel Level{ layout = l, items = i, entities = e } = level
|
renderLevel Level{ layout = l, items = i, entities = e } = level
|
||||||
where level = pictures [void, layout, items, entities]
|
where level = pictures [void, layout, items, entities]
|
||||||
-- void = createVoid
|
void = createVoid
|
||||||
void = blank
|
|
||||||
layout = renderLayout l
|
layout = renderLayout l
|
||||||
items = renderItems i
|
items = renderItems i
|
||||||
entities = renderEntities e
|
entities = renderEntities e
|
||||||
|
@ -92,12 +91,12 @@ renderInventory :: Player -> Picture
|
||||||
renderInventory Player{ showInventory = False } = blank
|
renderInventory Player{ showInventory = False } = blank
|
||||||
renderInventory Player{ inventory = list } = pictures [overlay, title, items]
|
renderInventory Player{ inventory = list } = pictures [overlay, title, items]
|
||||||
where title = translate 0 (offset (-1)) $ scale uizoom uizoom $ color white $ text "Inventory"
|
where title = translate 0 (offset (-1)) $ scale uizoom uizoom $ color white $ text "Inventory"
|
||||||
items = pictures $ map move $ zip [0::Int ..] (map (getRender . itemId) list)
|
items = pictures $ zipWith (curry move) [0::Int ..] (map (getRender . itemId) list)
|
||||||
move (i, pic) = translate 0 (offset i) pic
|
move (i, pic) = translate 0 (offset i) pic
|
||||||
offset i = negate (zoom * resolution * fromIntegral i)
|
offset i = negate (zoom * resolution * fromIntegral i)
|
||||||
|
|
||||||
withHealthBar :: HP -> Picture -> Picture
|
withHealthBar :: HP -> Picture -> Picture
|
||||||
withHealthBar (Nothing) renderedEntity = renderedEntity
|
withHealthBar Nothing renderedEntity = renderedEntity
|
||||||
withHealthBar (Just hp) renderedEntity = pictures [renderedEntity, positionedBar]
|
withHealthBar (Just hp) renderedEntity = pictures [renderedEntity, positionedBar]
|
||||||
where positionedBar = scale smaller smaller $ translate left up renderedBar
|
where positionedBar = scale smaller smaller $ translate left up renderedBar
|
||||||
renderedBar = pictures [heart, counter]
|
renderedBar = pictures [heart, counter]
|
||||||
|
|