Recently, I have developed a rogue-like game with chess mechanics. In this game, you explore randomly generated dungeons, fight enemies with moves from the game of chess, and discover hundreds of items, spells and units. In this article, I highlight the most interesting aspects of game design and implementation I went through during this epic journey.
Overview of the game
Welcome to the Pawngeon, available as a mobile application on the Google Play, is a turn-based roguelike game in which the player tries to escape a dungeon, the Pawngeon, while beating enemies in the process.
A roguelike is a type of video game genre that is characterized by random level generation, turn-based gameplay, permadeath (meaning that when your character dies, you lose all progress and have to start from the beginning), and often features procedural generation of gameplay elements such as loot, enemies, and terrain. What’s interesting with such gameplay, is that you restart each game from scratch, and discover different boosts and items during each run. And Welcome to the Pawngeon features many items and spells, with potential synergies making your character powerful: teleportation spell, invocation spells, armors, amulets, or the mysterious Devil’s Rose.
This difficulty is increasing as you progress towards the floors, until you reach the final boss, a bit like in the excellent The Biding of Isaac. Let’s have a deeper look into the game mechanics, and their implementation.
The game is implemented using Object-Oriented Programming (OOP), and is articulated around the following main classes:
- Tile, the base cell of the game which can be empty or containing a wall, an unit or an object;
- Room, a 10x10 grid of tiles representing the current room;
- Floor, a graph of rooms;
- Controller, managing the moves of the player and the bot.
Unit is a class, inheriting from Tile, representing characters, either allies or monsters, or even the player. Each unit has an inventory, health points (HP) and magic points (MP) and possible movements.
For example, a Knight would have the following attributes:
- Inventory: [<empty list>]
- HP: 1/1
- MP: 1/1
- Moves: [(1, 2), (2, 1), (1, -2), (2, -1), (-1, 2), (-2, 1), (-1, -2), (-2, -1)]
Note that the moves are couples of values, representing relative movements on X and Y axis:
In addition to the classical chess pieces, there are monsters that feature their own unique movements, such as bats and skeletons:
Items are extremely important in the game, as they give abilities and powers to the units possessing them. It’s another class inheriting from Tile. There are three types of items:
- Instant use, such has HP hearts ❤️ or MP hearts 💙, which effect and disappear when stepped on;
- Usable, such as potions 🧪 and scrolls 📑, which can be stored in the inventory and used a single time;
- Wearable, such as sword 🗡️ and rings 💍, which can be equipped and give a permanent bonus. Spells also come from wearable items, such as the Crown of Death which gives the possibility to cast the Death Spell.
Let’s take the Pegasus Boots as an example.
When wearing the Pegasus boots, you move like a rook. Note that this is true for all instance of the class Unit, not only for the player. Thus, a pawn or a skeleton can wear the boots and move like a rook until they’re defeated.
Spells are defined by a cost in MP, a range and an effect. For instance, the Corruption Spell cost 5 MP, has a range of 2, and produce the effect of changing the team (black or white) of the unit targeted: an enemy becomes ally, an ally becomes enemy. Another example is the Duplication Spell, which you can use to duplicate your character or any other unit. The duplication include the inventory, opening crazy possibilities.
If different items equipped give the same spell, then its cost in MP is reduced by 1. The cost can never be negative, however it can decrease to 0, meaning that you have a complete mastery of the spell and are able to use it at each turn for free, making you a powerful sorcerer!
The controller manages the movements of the player and the bot controlling enemies. To select its next move, the bot uses a classical Minimax algorithm. It is a heuristic-based algorithm, meaning that it requires a function that evaluate the state of the board — returning a high value if the position is at the advantage of the player, and a low value if the position is at the advantage of the monsters. The heuristic chosen here simply attributes a numerical value to each piece (a pawn is worth 1, a knight is worth 3, etc.), positive for the player and negative for the monsters, and adds up the total value of the room. To push the bot to attack the player when possible, each health point of the player is given a value of 25, as shown in the formula:
The minimax algorithm then works by recursively evaluating each possible move and its outcomes. It assumes that the player will make the move that is most detrimental to the bot, and selects the move that has the highest minimum payoff among all possible moves. The search is quite limited, in order to keep the bot quite weak; this way, the difficulty comes from the number of enemies.
The player, on its side, can obviously chose its actions by touching the screen. The interface is made intuitive by highlighting the selected pieces and possible actions. When the room is cleared, meaning there are no enemies, the game is no longer turn-based and the player can move automatically to any tile by touching it. For this behaviour, a path-finding algorithm is needed — I simply implemented a Breadth-First Search (BFS).
Random generation of the Dungeon
The randomness of the dungeons mainly comes from the randomness of each individual room, and from the placement of these rooms, constituing floors.
There are different types of room: battle rooms, bonus item rooms, taverns, spirit deal rooms and boss rooms. When a room is initialized, different elements are placed, with probabilities depending on the current floor:
- 🎲 Enemies (what enemies? how much?);
- 🎲️ Walls, organized in different random patterns;
- 🎲️ Decoration;
- 🎲 Chest and items;
- 🎲️ Loot obtained when clearing the room from all enemies.
This leads to endless possibilities of different rooms.
The algorithm for generating a floor is quite simple:
- Intialize a list of rooms, depending on the floor, for instance rooms = [empty, battle, battle, battle, special room, battle, bonus item room, boss room]. The main rules are that the floor should have one special room (tavern or spirit deal), one bonus item room, one secret room and one boss room. The first room is empty and all others are battle rooms.
- Place the first room.
- Select the next room in the list, randomly select a direction between “top”, “bottom”, “left” and “right”, and place the selected room next to the previous room placed, in the given direction. If another room is already there, select another directions (if there are already rooms in all directions, move the attention to an adjacent room).
- Repeat step 3 until all rooms are placed.
Graphical user interface
Welcome to the Pawngeon is implemented using Python Kivy. Kivy is a free, open-source Python framework for building cross-platform user interfaces. It allows developers to create apps that can run on multiple platforms including Windows, macOS, Linux, iOS, and Android, using a single codebase.
Each tile (ground, door, monster, items, etc.) is illustrated by a squared image: a 240x240 PNG file. This way it is easy to render the room, and to add new content to the game.
To give intuition to the player, the same color is used for the items, the text or the hearts thats concerns the same concept:
- 🔴 Red for HP and damage;
- 🔵 Blue for MP;
- 🟢 Green for range;
- 🟠 Orange for gold and items.
The color of the ground also indicates the type of room:
All musics are 8-bit rendition of classical music pieces. The OST is available on YouTube.
Developing a game is very fun and rewarding journey. As developers work on a game, they must constantly use their problem-solving skills to overcome challenges and roadblocks that arise during development. This may involve debugging code, optimizing performance, or finding ways to improve the user experience.
Outside from the development and the object-oriented programming, the prior step of designing the game mechanics, the interactions between objects, the rules and the goals, is also an incredibly interesting process. What are innovative game designs you can think of?
If you’d like to try out this game on your Android phone, and to play with the mechanics described in this article, click here.
Thanks for reading!