Minesweeper with ECS
After some time in chats of game development communities like Love2D, raylib and itch.io I had heard tons of how great an ECS(Entity Component System architecture) is, how better than the usual OOP ones. After reading that multiple time I decided to give it a try and check if the advantages were real or if it was only preaching about their preferences.
Since minesweeper was a simple game I decided to remake my love2D minesweeper clone with an ECS architecture by using the tutorial Love2D | Entity Component System as starting point.
What is an ECS?
Entity Component System architecture is a pattern mostly used for games. It follows the composition over inheritance principle by defining each object in the game as an entity(player, ground, bullets, etc). This pattern has three main parts:
- Entity: Is a general purpose object, has a unique id and a list of components.
- Component: The data for one aspect of the object.
- System: Performs the logic globally to the entities which have the same components as the system.
ECS in minesweeper
After following the tutorial and having the base structure and logic for the entity, component and system of the ECS, the next step was starting to implement my game with it.
The initial point was identifying what data I used to make the initial minesweeper implementation and creating the main components from that, so I created the components:
- Point - has x and y fields for coordinates.
- Rect - has a width and height fields to know the size to draw the rects in the map.
- Block - has the state of the block (not visible without mine, not visible with flag, visible without mine, visible with mine or visible with pressed mine) , a boolean to check if the block has a mine and a count of mines in the neighborhood.
- Input - has a timer to create a minimum delay between clicks.
For each grid of the map an entity with each of the components was created. So we only have one entity with all the components (ehh not much composition in this case).
After starting trying to implement the systems, some way to communicate between them was missing, so I created an event module where the systems can register to listen for specific events and any system can fire an event. The received event has a table to pass any data. With all this ready I implemented the game logic with the following systems:
- Rect click detection system - This system will check for all the entities that have a point, rect and input components and check if the entity was clicked. If the entity is clicked an “entity_pressed” event is fired.
- Block detection system - This system will listen for “entity_pressed” events, check if the pressed entity has a block component and if it has, make the block pass to the wanted state. If a mine is pressed the “mine_pressed” event is fired, if no mine is pressed the “block_opened” event is fired.
- Block render system - This system will check for all entities with a point, a rect and a block component and draw a rect in their place according to their block visibility state, the point coordinates and the rect sizes..
- Block expanding system - This system will listen for “block_opened” events and check if the entity received with the event has a block component. If it has, it will check if the block has no mines in its neighborhood and if it has no mines, it will display all the blocks in its neighborhood, it will also fire a “block_opened” event to each of the neighbors that also doens’t have mines in its neighborhood.
- End game checking system - This system will listen for the “mine_pressed”“ event to trigger the defeat state and will check for the "block_opened”“ event to trigger the check which validates if the player has won.
A bit of the initialization logic of the entities stayed in main.lua but could also be factored into a specific system if needed.
This was a funny experiment, initially the ECS architecture was a bit different. But after testing it I found some advantages:
- With this separation of components and systems it was much easier to find reusable parts I can apply in multiple games.
- It’s really simple to test new logic, you just need to add a system with your logic, you select what component groups you want it to apply and you can test it. No need to dive in classes of logic, or changing other parts of the game.
- If something weird is found, it is also simple to disable a system and the game will work anyway.
- Some parts like reaching a specific block in the map turned to be somewhat harder, but when that happens some coupling between the stuff would be done, so better decisions were obtained by this unconsciously.
The same UI and logic was obtained with this implementation as we can see in the next image.
If you want to checkout the code of the project check the repository.