Brittbot

Upgrading from Data Center Tech to Software Engineer

A piece of barren land infected by evil creatures like wolves, bats, ghouls, mummies, wizards, and skeletons.

The Infected Land: A CLI Game I Wrote In Python

I recently created, and updated a new technical project that’s available to download from this GitHub repo: The Infected Land. In this post I will discuss the following topics about this web application:

  1. Why I created it
  2. The code running it
  3. My future plans for the game

Why I Created The Infected Land

The main reason I created this game because I wanted to practice my Python skills after taking various refresher courses on YouTube. (You can read why I refreshed my skills here.) I wanted to practice both Object Oriented Programming (OOP) and taking in user input. Creating a game seemed the easiest, and most fun, option for me. Plus, it gives me the chance to write a story and some character development since I had to put writing to the side to focus on programming.

The Game’s Storyline

The Infected Land is a Command Line Interface (CLI) game where you (as the Hero) must defeat the Villain’s evil creations to cleanse the infection harming the land and its residents.

There are three battles in the game:

  1. Beast battle
  2. Non-beast battle
  3. Human battle

As the player progresses through the game an malevolent voice of the VILLAIN appears. The player learns more and more about the VILLAIN, and even about Hero.

There is also a chance after ever battle for the Hero to restore a portion of their health, and improve their battle gear by choosing a more powerful sword or better armor.

The final battle of the game pits the Hero against the VILLAIN. If the Hero succeeds then the land will heal from its infection for good, and the residents can live in peace!

The Code Running The Infected Land

Classes

There’s quite a bit of code running the game. First, I’ll review the two Classes: Hero and Villain.

Both classes have a similar Constructor function setup:

class Hero:
    def __init__(self, name: str, class_type: str, race_type: str,         weapon_type: str, weapon_damage: int, armor_type: str, armor_defense: int, health: int)
class Villain:
    def __init__(self, name: str, class_type: str, race_type: str, beast_type: str, weapon_type: str, weapon_damage: int,              armor_type: str, armor_defense: int, health: int)

The major difference between the two Classes is that the Hero class’ “name” parameter can be modified by user input. If the user doesn’t provide a name, I created some code to give the player a default name of “HERO.”

The Hero class contains the methods that takes in the user’s input to fight the evil creature in each battle. Here’s a snippet of the code for the Beast battle, which is the first battle in the game:

def hero_attack_beast(self) -> None:
        """Takes input from the player to attack the beast Villain.
        :return: None
        """
        random_num = random.randint(0, 1)
        print()
        print(center_text("-" * 80))
        print(center_text(f"{hero.name}, choose your action: (A) Attack or (B) Block."))
        print(center_text("-" * 80))
        print()
        user_input = input("Enter your choice here: ").capitalize()
        try:
            if user_input == "A":
                if random_num == 0:
                    print()
                    print(center_text(f"!! {hero.name} slashes at the {random_beast.name} with their sword !!"))
                    attack_sleep_print()
                    print()
                    print(center_text(f"-- {random_beast.name} fails to dodge the attack --"))
                    attack_sleep_print()
                    print()
                    print(center_text(f"!! {self.weapon_type} does {self.weapon_damage - random_beast.armor_defense} points of damage !!"))
                    random_beast.health -= (self.weapon_damage - random_beast.armor_defense)
                    print()

The user’s input is connected to a random number to determine if their attack or block is successful. I wrapped the user’s input in a “Try/Except” clause to deal with incorrect input. If the user does provide incorrect input, a message appears on a screen and the battle function appears again.

As for the Villains, their attacks are automated and connected to a random number to determine if their attack was successful. Here’s a snippet of that code:

def beast_attack_hero(self) -> None:
        """Attacks the Hero with a random beast.
        :return: None
        """
        from hero import hero  # Placing the import statement here to avoid a circular import error
        random_num = random.randint(0, 1)
        print(center_text("*" * 80))
        print(center_text(f"!! {self.name} attacks {hero.name} with its {self.weapon_type} !!"))
        print(center_text("*" * 80))
        attack_sleep_print()
        if random_num == 0:
            print()
            print(center_text(f"-- {hero.name} fails to block the attack with their shield --"))
            attack_sleep_print()
            print()
            print(center_text(f"!! {self.weapon_type} does {self.weapon_damage - hero.armor_defense} points of damage !!"))
            hero.health -= (self.weapon_damage - hero.armor_defense)

The battle runs as so:

  1. The random Villain for the battle attacks the Hero first
  2. Then the Hero gets their turn to attack or block.
    • If the Hero chooses to block and it’s successful, that stuns the Villain and the Hero gets to attack again
  3. Then the random Villain attacks.
  4. The battle continues to run like this until either the Hero or the Villain dies.

Text Formatting Tools

Since this is a CLI game good text formatting is vital to a good player experience. I chose to center the text on the screen, except user input, because it looked better to me. However, I ran into a challenge at first because each player may have a different default size for their Terminal window. With the v0.1 version of the game I used the built-in “.center()” function. While that was kinda successful, the manual padding was too cumbersome.

While building out the v0.2 version of the game I went back to Google to find better techniques on how to center the text in Python. This time I hit the jacket with the Generative AI response because it provided this bit of code:

def center_text(text, width=None) -> None:
    """Centers the displayed text in the terminal window.

    :param text: The text displayed on the terminal window.
    :param width: The width of the end user's terminal window.
    :return: None
    """
    if width is None:
        width = os.get_terminal_size().columns
    return text.center(width)

I added the comments, but basically the code uses the “os.get_terminal_size” function to get the number of columns of the terminal window. Thus, it centers the text depending on the width. I tried this out with multiple types of column sizes and it works well!

I created other functions to delay the amount of time the text appears on screen. That way it gives the player enough time to read and follow the story.

Wow, this post is getting long. To see the full code running this game please visit the repo here.

My Future Plans For This Game

After releasing v0.2 of the game I’m pretty much done for what I wanted to accomplish with the game. I don’t see myself adding any additional content to it. Instead, I’ll probably make a new game like one of those old school CLI dungeon crawlers.