Elemental Monsters is a simple monster battler using a five element rock, papers, scissors inspired system. The elements currently in use are:
- Water
- Fire
- Earth
- Wind
- Death
Each type has both a type which they have a "major" type advantage against, offering 25% additional damage, and a "minor" type advantage, offering 10% addition damage. Visually, those relationships look like the following:
![Elemental relationships in Elemental Monsters](static/types.png)
As stated already, monsters can be one of five types. The way that both the types and monsters have been implemented should allow for the easy addition of either. The types and damage modifiers are located in dmgmodifiers.csv and monster definitions are located in monsterdata.csv.
The current configuration includes five common monsters, one of each type, three uncommon monsters, and two rare monsters. These monsters were created to have better base stats as their rarity increases but this is not forced by the way the code is implemented. The monsters available in the current game are:
Name Element Rarity Strength Defense
Surf Crawler Water Common 6 4
Smouldering Creeper Fire Common 7 3
Terracotta Soldier Earth Common 4 6
Djinn Wind Common 5 5
Shadow Stalker Death Common 6 4
Arctic Hound Water Uncommon 8 6
Stone Golem Earth Uncommon 6 8
Eastern Drake Wind Uncommon 7 7
Cinder Abomination Fire Rare 11 7
Skeletal Wyvern Death Rare 10 8
Once the player's tamed monster levels beyond level one, the monsters which you will encounter can be from two levels below to one level above your currently "tamed" monster. That calculation is handled inside of the hunting function:
# Provide a random monster between -2 and +1 levels of the players
if player.monster.level == 1:
monster = Monster.random_monster()
else:
min_level = max(player.monster.level - 2, 1)
max_level = min(player.monster.level + 1, 10)
monster = Monster.random_monster(level=random.randint(min_level, max_level))
Both tamed and wild monsters can be a maximum of level ten. The experience required per level increases on a curve and is defined within the monster class.
Once a player defeats a monster in combat, they have the option to trade their current monster for the newly defeated monster. This will release their existing monster back to the "wild", however, and any experience gained prior to capturing the new monster will be forfeited.
The code for Elemental Monsters is broken up into three main files:
- The main program
- The monster class
- The player class
The main program is primarily responsible for handling of player interaction. This includes:
- Interacting with the player class
- Displaying of town menus
- Managing the hunting and combat loops
- Capturing and processing player input
- Calling the player class to create player saves at important checkpoints
While some of this funtionality, like handling player menus or adding multiple towns of different "tiers", could still use implementation, the program currently provides a complete player experience.
The Monster and Player classes will be gone into in more detail in future sections of this document.
The monster class wraps everything about a monster in a single place. This includes:
- Data loading
- Monster creation
- Random monster selection
- Attack success and damage
- Health removal and addition
- Experience awards and leveling
The monster instance itself stores a fair bit of information upon initialization:
def __init__(
self,
name: str,
element: str,
description: str = '',
tier: int = 1,
level: int = 1,
rarity: str = 'Common',
strength: int = 1,
defense: int = 1
):
# Basic information
self.name = name
self.description = description
self.element = element
self.rarity = rarity
self.tier = int(tier)
# Level and Experience
self.level = int(level)
self.experience = 0
self.experience_needed = self.get_experience_needed(int(level) + 1)
# Strength - Attack and Defense - Armor
self.strength = int(strength) + trunc(int(level) / 2)
self.defense = int(defense)
self.str_mod = trunc((self.strength - 10) / 2)
self.attack = self.BASE_DMG + self.str_mod
self.armor = self.BASE_DEFENSE + self.defense
# Hit Points
self.hp = sum(random.randint(self.MIN_HP_PER_LEVEL, self.MAX_HP_PER_LEVEL) for _ in range(self.level))
self.current_hp = self.hp
There are a number of important methods to understand within the monster class such as:
The final class to provide an overview of is the player class. Much like the monster class, the player class encompasses all the functionality required to manage the player. This includes:
- Player creation
- Initial player companion (monster) selection
- Save and load functionality
The actual player instance stores a small amount of information:
def __init__(self, name: str, monster: Monster):
self.created = datetime.now()
self.name = name
self.monster = monster
self.gold = 0
self.blessing = datetime.now()
self.savepath = Path(f'{self.save_dir}/{self.name}')
The meat of the work occurs within the interactive class method:
@classmethod
def interactive(cls):
"""Provide interactive constructor for player class"""
This class method provides interactive initialization of the player instance.
- The player is asked to enter their name.
- If a save game with that name exists, the player is provided a number of choices (load, overwrite, enter a new name).
- If the player chooses to continue with an existing save, it is loaded and returned to the main program.
- If no save exists or the player chooses to overwrite an existing save, the player is presented with three random common monster choices in select_monster.
- This new monster instance is attached to the new player instance and the game begins.
Finally, save and load functionality is provided by three methods within the player class: