4.4 Project
Getting to Know Functions
For this project, we will transform our game into one based on functions. Using functions allows us to bundle our logic into specific tasks, making our code more organized and reusable. We will create functions for generating characters, attacking enemies, and handling combat. This will help us to better structure our code and make it easier to read and maintain. We will also use type hinting to make our code more readable and to help us catch errors early. Type hinting allows us to specify the expected types of function parameters and return values, making our code more self-documenting and easier to understand. We will use TypedDict to define the structure of our character and enemy dictionaries, which will help us to ensure that we are using the correct types throughout our code. We will also use lists and dictionaries to store our enemies and locations, allowing us to easily access and modify them as needed. This will help us to keep our code organized and make it easier to add new enemies or locations in the future. Finally, we will use a simple combat system that allows
For this project, we will need to import some utilities. First we will need to import some types from the typing module to help us define our data structures. We will also need to import the random module to help us generate random numbers for our combat system. Finally, we will use the time module to add some delays in our game to make it more interactive.
from typing import TypedDict, List, Dict, Literal
import random, timeNow let’s create our first function. We will create a sleep() function that will pause the game for a brief period to make it appear as if the game is processing and actions are happening. We will use the time module’s sleep() function, which waits a specific amount of time (in seconds) based on the int passed in as a parameter. If we do not do this. the game will run too quickly, and the user will not be able to read the output. We will use this function throughout our code to create pauses between actions. We will place it after each print statement or input statement to give the user time to read the output. The sleep() function will take no parameters and will simply call time.sleep(0.5) to pause for half a second. By doing this, we can set the time between actions to a fixed value. If it is too long or too short we can easily adjust it in one place rather than changing every instance of time.sleep() in our code.
def sleep():
time.sleep(0.5)Next, let’s define our character using a TypedDict. We’ve already done this in the previous section, so you can copy that code here. While we are at it, let’s also define the strengths and weaknesses of our characters, using a dictionary to align strength to weakness.
class CharacterTypes(TypedDict):
name: str
health: int
attack_power: int
defense: int
strength: str
total_points: int = 20
strengths_weaknesses = {
"fire": "water",
"water": "earth",
"earth": "fire",
}
character: CharacterTypes = {
"name": "Hero",
"health": 90,
"attack_power": 10,
"defense": 5,
"strength": ""
}We will also create a list of enemies. We are creating two new enemies in this list for this project. We simply add a new dictionary for each enemy with the specified attributes. Similarly, we must add two new locations to the locations dictionary. Each location will have a name and an index that corresponds to the enemy in the enemies list. Finally, let’s create a gameplay_data dictionary to store the data about the gameplay, such as the enemies defeated, damage taken, and locations visited. This will help us keep track of the player’s progress throughout the game.
enemies: List[CharacterTypes] = [{
"name": "Goblin",
"health": 20,
"attack_power": 5,
"defense": 1,
"strength": "water"
}, {
"name": "Troll",
"health": 30,
"attack_power": 5,
"defense": 3,
"strength": "earth"
}, {
"name": "Dragon",
"health": 40,
"attack_power": 10,
"defense": 1,
"strength": "fire"
}]
locations: Dict[str, int] = {"forest": 0, "Bridge": 1, "Cave": 2}
class GameplayDataTypes(TypedDict):
enemies_defeated: List[str]
damage_taken: List[float]
damage_dealt: List[float]
locations_visited: List[str]
enemies_encountered: List[str]
gameplay_data = {
"enemies_defeated": [],
"damage_taken": [],
"damage_dealt": [],
"locations_visited": [],
"enemies_encountered": []
}
def generate_character(character: CharacterTypes, total_points: int) -> CharacterTypes:
for key in character:
if key == "name":
name = input("What is your Character's Name")
sleep()
character[key] = name
elif key == "strength":
while True:
strength = input(f"Choose a strength: \nOptions: {', '.join(strengths_weaknesses.keys())}")
sleep()
if strength.lower() not in strengths_weaknesses.keys():
print(f"{strength} is not {strengths_weaknesses.keys()}")
sleep()
continue
character[key] = strength.lower()
break
else:
while True:
item = input(
f"""Add points to your {key}.
You have {character[key]} {key} already.
You have {total_points} extra points available.
Enter an number:
"""
)
sleep()
try:
item = int(item)
if item > total_points:
raise ValueError(
f"{item} is greater than {total_points}"
)
character[key] += item
total_points -= item
break
except Exception as e:
print(f"{e}")
continue
print("Character Generated")
sleep()
for key in character:
print(f"{key} = {character[key]}")
sleep()
return character
def print_stats() -> None:
print("Here are your stats")
for key in gameplay_data:
print(f"{key}: {gameplay_data[key]}")
def game_over(enemy: str) -> None:
print(f"{character['name']} was defeated by {enemy}")
sleep()
print_stats()
exit(0)
def victory() -> None:
print(f"{character['name']} is Victorious!")
sleep()
print_stats()
def defeat_enemy(enemy:CharacterTypes) -> None:
print(f"{enemy['name']} was defeated.")
sleep()
gameplay_data['enemies_defeated'].append(enemy['name'])
def attack(attacker: CharacterTypes, defender: CharacterTypes, multiplier: float) -> None:
print(f"{attacker['name']} attacks {defender['name']}")
sleep()
attack_power = attacker["attack_power"] * multiplier
defender['health'] -= round(attack_power)
if attacker['name'] in [x['name'] for x in enemies]:
gameplay_data['damage_taken'].append(attack_power)
else:
gameplay_data['damage_dealt'].append(attack_power)
print(f"{attacker['name']} did {attack_power} damage to {defender['name']} ")
sleep()
options = ['attack', 'wait']
def enemy_action(character: CharacterTypes, enemy: CharacterTypes, multiplier: float):
enemy_choice = random.choice(options)
if enemy_choice == 'attack':
attack(attacker=enemy, defender=character, multiplier=multiplier)
else:
print(f"{enemy['name']} chooses to wait")
sleep()
def combat(location: str) -> None:
enemy: CharacterTypes = enemies[locations[location]]
name = character['name']
player_multiplier = 2.0 if character["strength"] == strengths_weaknesses[enemy["strength"]] else 1.0
if enemy["strength"] == strengths_weaknesses[character["strength"]]:
enemy_multiplier = 2.0
elif enemy["strength"] == character["strength"]:
enemy_multiplier = 0.5
else:
enemy_multiplier = 1.0
print(f"{name} ran into {enemy['name']}")
sleep()
while character["health"] > 0 and enemy["health"] > 0:
while True:
player_choice = input(f"Choose what you want to do: {' or '.join(options)}").lower()
if player_choice not in options:
print(f"{player_choice} not valid. Please choose again")
sleep()
continue
break
if player_choice == 'attack':
attack(attacker=character, defender=enemy, multiplier=player_multiplier)
if enemy['health'] <= 0:
continue
print(f"Now it's {enemy['name']}'s turn.")
sleep()
enemy_action(character=character, enemy=enemy, multiplier=enemy_multiplier)
else:
print(f"{character['name']} chooses to wait")
sleep()
print(f"Now it's {enemy['name']}'s turn.")
sleep()
enemy_action(character=character, enemy=enemy, multiplier=enemy_multiplier)
if character['health'] > 0:
defeat_enemy(enemy)
gameplay_data['locations_visited'].append(location)
print(f"Character health = {character['health']}")
sleep()
else:
game_over(enemy=enemy['name'])
character = generate_character(character=character, total_points=total_points)
for key in locations:
print("Choose a location to travel to")
sleep()
available_locations = [x for x in locations.keys() if x not in gameplay_data['locations_visited']]
while True:
loc = input(f"Choose an option: {' or '.join(available_locations)}")
sleep()
if loc not in available_locations:
print(f"{loc} not an option. Try again.")
sleep()
continue
break
combat(loc)
if character['health'] > 0:
victory()
print_stats()