Authors: Christine Song, Albert Yip
What is the problem?
hachi is an endless side scroller game where you play as a dog trying to find its way home. Along your journey, you must avoid obstacles and temptations.
We represent the dog and obstacles/temptations as objects. If the objects collide, the game will end. Objects will progressively move down the screen faster. The dog (player) will avoid obstacles/temptations by running (by default) or jumping.
There will also be a scoring system where points are accumulated based on the passage of time (correlating to the speed of the falling objects).
What is the something extra?
Power ups the dog can collect and enhance abilities (eg. higher jump height, slower and invincibility) will be implemented as the something extra.
What did we learn from doing this?
We have learned to use the FunGen library to implement our game as well as render sprites.
Additionally, trying to keep the functionalities in every class separate while leaving the functions robust was a challenge.
Functional programming is very suitable for creating game abstractions since the game state is just another function waiting to be called. Utilizing IO, or more generally monads, we were able to empower our game design by taking advantage of the typing system in a functional paradigm. We found it easier to manage states of the incoming obstacles/power ups given the nature of their immutability. Movement and collision were straightforward to implement as it follows standard game design logic. We cycle the obstacles/power ups from right to left, prompting the user to left click and jump. If a user hits an obstacle then the game ends. Otherwise, they may continue on and run into power ups. With the scope of this project, we implemented a jump power up and invincibility power up which does not allow stacking. The jump power up significantly increases your maximum jump height, allowing the user to avoid obstacles much easier. The invincibility power up grants 'phasing' abilities. The user will pass through obstacles. Note, these power ups last for a short duration of time. When the duration has ended, the user must collect another power up to retain the effects. Additionally, we keep track of a user score calculated on how much time has passed. As the difficulty increases, the score also increases (meaning the speed of the objects increase).
On the contrary, we found it difficult to work with FunGen and integrate their game state persistence (eg. GameAttribute) with our own code. For example, there were numerous places we had to use `getGameAttribute` and `setGameAttribute` as side effects, rather than returning and transforming some value. Utilizing IO proved difficult as we had to constantly escape IO to perform any operations. Additionally, we ran into trouble with IO and IOGame. Some operations would return IO and other operations would return IOGame. This meant that the functions would not work properly as we cannot change monad types within a do clause. To mitigate these problems, we had to structure our game such that most computations returning IO would be lifted into IOGame. This meant a lot of our functions were impure and would return empty values wrapped with IOGame. Thus, our integration with FunGen may be considered somewhat imperative. In conclusion, it is very feasible to develop games in a functional setting, but caution must be taken to avoid following anti-patterns. It can be easy to think and write code in an OO manner (as traditional). Keeping this in mind, you can write robust and functional game code with the caveat that it is easier to extend and refactor in the future.