Learn how to create the turn-based combat system found in games like Pokémon, Final Fantasy, and Undertale. Raspberry Pi’s Rik Cross shows you how.
In the late 1970s, high school student Richard Garriott made a little game called Akalabeth. Programmed in Applesoft BASIC, it helped set the template for the role-playing genre on computers. Even today, turn-based combat is still a common sight in games, with this autumn’s Pokémon Sword and Shield revolving around a battle system which sees opponents take turns to plan and execute attacks or defensive moves.
The turn-based combat system in this article is text-only, and works by allowing players to choose to defend against or attack their opponent in turn. The battle ends when only one player has some health remaining.
Player taking part in the battle is added to the static
players list as it’s created. Players have a
health value (initially set to
100) and a Boolean
defending value (initially set to
False) to indicate whether a player is using their shield. Players also have an
inputmethod attribute, which is the function used for getting player input for making various choices in the game. This function is passed to the object when created, and means that we can have human players that give their input through the keyboard, as well as computer players that make choices (in our case simply by making a random choice between the available options).
A base A
ction class specifies an action
owner and an
opponent, as well as an
execute() method which has no effect on the game. Subclasses of the base class override this execute() method to specify the effect the action has on the
owner and/or the
opponent of the action. As a basic example, two actions have been created:
Defend, which sets the owner’s
defending attribute to
Attack, which sets the owner’s
defending attribute to
False, and lowers the opponent’s
health by a random amount depending on whether or not they are defending.
Players take turns to choose a single action to perform in the battle, starting with the human ‘Hero’ player. The
choose_action() method is used to decide what to do next (in this case either attack or defend), as well as an opponent if the player has chosen to attack. A player can only be selected as an opponent if they have a
health value greater than 0, and are therefore still in the game. This
choose_action() method returns an
Action, which is then executed using its
execute() method. A few
time.sleep() commands have also been thrown in here to ramp up the suspense!
After each player has had their turn, a check is done to make sure that at least two players still have a
health value greater than 0, and therefore that the battle can continue. If so, the static
get_next_player() method finds the next player still in the game to take their turn in the battle, otherwise, the game ends and the winner is announced.
Our example battle can be easily extended in lots of interesting ways. The AI for choosing an action could also be made more sophisticated, by looking at opponents’
defending attributes before choosing an action. You could also give each action a ‘cost’, and give players a number of action ‘points’ per turn. Chosen actions would be added to a list, until all of the points have been used. These actions would then be executed one after the other, before moving on to the next player’s turn.
You can read more features like this one in Wireframe issue 28, available now at Tesco, WHSmith, all good independent UK newsagents, and the Raspberry Pi Store, Cambridge.
Or you can buy Wireframe directly from Raspberry Pi Press — delivery is available worldwide. And if you’d like a handy digital version of the magazine, you can also download issue 28 for free in PDF format.