Overview

Elemental Monsters is a simple monster battler using a five element rock, papers, scissors inspired system. The elements currently in use are:

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
There are five elemental types with "major" and "minor" type advantages.
The Monsters

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.

Code Overview

The code for Elemental Monsters is broken up into three main files:

The main program is primarily responsible for handling of player interaction. This includes:

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

The monster class wraps everything about a monster in a single place. This includes:

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 Player Class

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:

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.

  1. The player is asked to enter their name.
  2. If a save game with that name exists, the player is provided a number of choices (load, overwrite, enter a new name).
  3. If the player chooses to continue with an existing save, it is loaded and returned to the main program.
  4. 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.
  5. 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: