Before I started with the implementation, I split the project into multiple modules. Each module is handling some aspect of the game. When I wanted to make 2 modules dependent on each other, I had to split each module into interface and implementation. Implementation from one module was depending on the interface from the other module. This is called the dependency inversion principle.
View – Everything that the user can see and interact with is located in this module. Game board, buttons, dialogs or head-up display. The View is displaying the state of the Model. If we replace this module with another one, which only supports the console input/output and implements View interface, the game will still work (but It will be a lot harder to play). If we want to find some information about the player, the View needs to access the Controller module to get this info. This is the standard Model-View-Controller pattern.
Controller – It Is responsible for updating the View, when the game state changes. While the game is waiting for the player input, the Controller calls the View. In this case, the modal dialog or buttons are displayed for the player to respond. Although the Controller has an access to the Model, it can only read its values and cannot change them.
Model – I split the Model into 2 logical parts. There is the Model-Read module. Modules that have access to this module can read the state of the model, but they cannot change any variables. Most of the methods here have prefix ‘get’ or ‘is’ (getHealth, isReady,…). Model-Write module offers methods that can change the state of the model. It extends the module Model-Read so it has access to all read methods. Typical methods here have ‘set’ prefix (setHealth, setReady,…).
Command Queue – During the game, commands are added to Command Queue. Commands are blocks of code executed at once. When the time comes for a command to be executed, it receives an input. Output from the previous command is an input for the current command. When there are no more commands in the queue, the game stops. There are commands that alter the model, fire events or update the View. There are also commands for converting output, so it matches the input required by the next command. There is a command that can skip other commands in the queue and jump to a specified one.
Service – Only this module has an access to Command Queue and puts commands to it. Commands are not executed immediately after they are added to the queue. Service method usually contains multiple commands. Good example for one method from this module is performAction method. When executed, it will add 2 commands to the queue: SelectAction and ExecuteAction.
Action – This module has the most dependencies. It contains various Actions. Service is building commands using these elementary Actions. The Action module does not depend on the Service or the Command Queue, so Action cannot add itself to the command queue. Only Service can. Actions can fire events, update model, request input from the user or update the view. They can also register new event listeners. Example of Action is GainHealth action that will first update the model and change the current health of the player. Then this action calls the View via Controller. The View will then display some animation of the red heart coming to the user HUD. This Action wrapped in Command will be finished, and next the Command can be executed.
Event Queue & Event Listeners – Actions can fire various events. When the player moves, performs some action or when the certain phase of the game starts, an event is fired and added to the event queue. Various components can listen to these events and react to them by adding or skipping some Commands in the command queue. For example, when the player is attacked and should lose 3 health tokens, an event is thrown (LoseHealthEvent). If he has Bandages, there is a listener registered on the Event Queue that is waiting for LoseHealthEvent to be fired. If the player decides to use Bandages, he will discard them, but will lose only 1 health.
In this game, there are lots of asynchronous data transfers. When we are waiting for the user input, command output or animation end, we are dealing with asynchronous transmissions. One way how to handle them is to use callbacks in method arguments. But there is even a better approach. Using ReactiveX Java library (RxJava), we can return Observable object. We can subscribe to it, and when the asynchronous method is finished, our subscription is executed.
In this example we have async method
that waits for user to select how many players will play this game.
We can subscribe to response
getNumberOfPlayers().subscribe( returnValue -> model.setPlayers(returnValue))
When the user selects a number of players, our model will be updated.
Another advantage is that we can merge multiple observable responses into one, or we can chain multiple async methods together.
The code is much more readable using ReactiveX observable pattern.
Command is usually not added alone to the queue, but with 2 adjacent commands. They are called BeforeX and AfterX (BeforeLoseHealth/AfterLoseHealth). Their only purpose is to fire event. Listeners can react to this event and they can alter the input for the main command (LoseHealth) or add new commands before the main command. If necessary, main command can be skipped by the event listener.
In the example below, the player was attacked and is about to lose 3 health tokens. Lucky for him, he owns bandages. They can prevent the player from losing up to 2 health tokens, but they must be discarded afterwards. When the bandages listener is invoked by LoseHealth event, user must decide if he wants to use bandages. If he does, the bandages are discarded, the model is updated and the user lose only 1 health token instead of 3.
When I was writing the code for this game, I was using Java 8 syntax and new features like lambda expressions, method references and streams. Sadly, I did not realize that Android version 7 and less does not support Java 8. This version of Java is only supported by the latest Android version - Oreo. As I didn’t want to be limited by this, I had to find a way around it.
It is fortunate that there is the Retrolambda maven plugin. Retrolambda lets you run Java 8 code with lambda expressions, method references and try-with-resources statements on Java 7, 6 or 5. Retrolambda cannot deal with streams and new Java 8 API, so I had to manually rewrite all streams to the old syntax equivalent.
A very nice pattern (that I did not use) is the Entity-component-system. We can decouple entities from their properties with it.
Every entity can have one or more properties or attributes. These can be added or removed at runtime. The System is a place where these entities are and their components are registered. We can filter entities by their components and create entity systems that operate only on entities with the given components.
Example of entities: Player, Monster, Token, Button.
Example of components: Visible, Movable, Touchable, Drawable.
We can create a matrix and connect entities only with components they need.
Currently I am implementing the logic for all the assets that the user can obtain during the game. There are 72 assets now, but the number will rise. I expect this game to be finished at the end of year 2018. I don’t know yet if I will be able to publish it to Google Play Store or Steam, because I copied the game mechanics, texts and items. I am pretty sure that it is not 100% legal. The Game has some bugs and the UI is not polished, but if you would like to try it, you can download it here:
If you have any questions, address them to firstname.lastname@example.org
Until then, Happy Playing!