Select Git revision
Ahmed Elghazali authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
game.py 32.30 KiB
#!/usr/bin/python3
from map import rooms
from player import *
from items import *
from gameparser import *
from enemies import *
from item_dist import *
from enemies_dist import populate_enemies
from minigame import MiniGame
from minigame_map import game_map, game_info
import random
import os
from time import sleep
import time
import sys
def add_article(articleless):
'''
A primative function for taking a string without an article at the start and adding an indefinite article
'''
if articleless[:1] in ['a', 'e', 'i', 'o', 'u']:
return "an " + articleless
else:
return "a " + articleless
def slow_print(s):
for letter in s:
sys.stdout.write(letter)
sys.stdout.flush()
time.sleep(0.03)
def clear():
# posix is os name for Linux or mac
if os.name == 'posix':
os.system('clear')
# else screen will be cleared for windows
else:
os.system('cls')
def display_inventory():
clear()
while True:
print("Weapon equipped:", player.get_equipped_name("weapon"))
print("Defense equipped:", player.get_equipped_name("defense"))
print("Magic item equipped:", player.get_equipped_name("magic"))
print("")
if len(player.inventory) > 0:
print("You have ", end = "")
for x in range(len(player.inventory)-1):
final_name = add_article(player.inventory[x].name)
print(final_name, end = ", ")
print(add_article(player.inventory[len(player.inventory)-1].name), end = ".")
print("\n")
print("The commands you can use are:")
for item in player.inventory:
print("INSPECT", item.id.upper(), "to inspect", item.name)
for category in player.equipped:
item = player.equipped[category]
if item == None: continue
print("INSPECT", item.id.upper(), "to inspect", item.name)
for item in player.inventory:
if item.usable == True:
print("USE", item.id.upper(), "to use", item.name)
for item in player.inventory:
if item.equippable == True:
print("EQUIP", item.id.upper(), "to equip", item.name)
for category in player.equipped:
item = player.equipped[category]
if item == None: continue
print("UNEQUIP", item.id.upper(), "to unequip", item.name)
print("EXIT to exit the inventory sub-menu.")
user_input = input(">")
user_input = normalise_input(user_input)
clear()
if user_input[0] == "inspect":
if len(user_input) == 1:
print("You must specify something to inspect.")
else:
player.inspect_item(user_input[1])
if user_input[0] == "use":
if len(user_input) == 1:
print("You must specify something to use.")
else:
# find item
item_to_use = None
for item in player.inventory:
if item.id == user_input[1]:
item_to_use = item
break
if item_to_use == None:
print("Could not find that item to use.")
elif item_to_use.usable == True:
item.on_interaction(player)
else:
print("Could not use that item.")
if user_input[0] == "equip":
if len(user_input) == 1:
print("You must specify something to equip.")
else:
player.equip(user_input[1])
if user_input[0] == "unequip":
if len(user_input) == 1:
print("You must specify something to unequip.")
else:
player.unequip(user_input[1])
if user_input[0] == "exit":
break
def display_map():
# display mini-map - uncleared rooms do not display names, just question marks
clear()
print("You inspect your map, here is what the relevant part says.")
print("Question marks represent rooms you haven't cleared yet.\n\n")
start_point = player.current_room
information = {
"current": {
"exists": True,
"name": start_point.name,
}
}
# gather information about rooms we need to print
for direction in ["north", "east", "south", "west"]:
if direction in start_point.exits:
exists = True
else:
exists = False
information[direction] = {
"exists": exists,
}
if exists == True:
room_directed_to_id = start_point.exits[direction]
room = rooms[room_directed_to_id]
if room.cleared == True:
information[direction]["name"] = room.name
else:
information[direction]["name"] = "?????"
# print the information
north_name_offset = 0
north_arrow_offset = len(information["current"]["name"]) // 2
if information["west"]["exists"] == True and information["north"]["exists"] == True:
north_name_offset = 8 + len(information["west"]["name"]) + len(information["current"]["name"]) // 2 - len(information["north"]["name"]) // 2
north_arrow_offset = 8 + len(information["west"]["name"]) + len(information["current"]["name"]) // 2
if information["north"]["exists"] == True:
print(" " * north_name_offset + information["north"]["name"])
print(" " * north_arrow_offset + "^")
for i in range(2):
print(" " * north_arrow_offset + "|")
middle_print_line = ""
if information["west"]["exists"] == True:
middle_print_line = middle_print_line + information["west"]["name"] + " <----- "
middle_print_line += information["current"]["name"]
if information["east"]["exists"] == True:
middle_print_line = middle_print_line + " -----> " + information["east"]["name"]
print(middle_print_line)
south_name_offset = 0
south_arrow_offset = len(information["current"]["name"]) // 2
if information["west"]["exists"] == True and information["south"]["exists"] == True:
south_name_offset = 8 + len(information["west"]["name"]) + len(information["current"]["name"]) // 2 - len(information["south"]["name"]) // 2
south_arrow_offset = 8 + len(information["west"]["name"]) + len(information["current"]["name"]) // 2
if information["south"]["exists"] == True:
for i in range(2):
print(" " * south_arrow_offset + "|")
print(" " * south_arrow_offset + "v")
print(" " * south_name_offset + information["south"]["name"])
input("\n\n> Press any key to continue.")
def display_help():
clear()
menus = {
"items": {
"label": "items",
"lines": [
"While travelling around the world you will encounter items. Items can be picked up and stored in your inventory.",
"To see the items you have picked up, you can open the inventory sub-menu. You can also drop items you don't want...",
"anymore. In the inventory menu you will also see your equipped items - some items can be equipped to certain slots...",
"to help you during battle. Some items can also be used in the inventory sub-menu."
]
},
"battle": {
"label": "battle information",
"lines": [
"When you encounter an enemy, you will begin a battle with them. Battle is based off a turn-based system. You will get...",
"a chance to attack, then the enemy will attack. The damage your attack does is based off your weapon, its wobble and attack base stats and...",
"the enemy's defensive stats. Wobble is the percentage variation around the weapon's base attack stat. The enemy's damage is based off...",
"its base attack stats, your defensive base stat and defensive items. You can choose from a number of moves, each with different benefits and...",
"drawbacks. Based off your base luck and magic item you have a chance of getting a critical hit which increases damage or missing entirely and...",
"doing no damage. The enemy has the same mechanics, but based just off their base luck stat. Each attack costs your equipment...",
"durability - when durability of an item reaches zero, it will break."
],
}
}
while True:
print("\nWhich help sub-menu do you wish to display?")
print("You can use the following commands:")
for menu in menus:
menu_data = menus[menu]
print(menu.upper() + " to display " + menu_data["label"] + " menu.")
print("EXIT to exit the help sub-menu.")
user_input = input(">")
user_input = normalise_input(user_input)
if user_input[0] == "exit":
clear()
break
for menu in menus:
if user_input[0] == menu:
clear()
to_display = menus[menu]
for line in to_display["lines"]:
print(line)
def display_stats():
'''
Display stats is designed to show the player their stats on screen after entering a command
'''
# Calculate the leveling up bar
clear()
global player
bar_width = 30
xp = player.xp
required = player.level * 10
percentage = (xp / required) * bar_width
required_bar = " ["
for i in range(bar_width + 1):
if i < percentage:
required_bar += "#"
else:
required_bar += "-"
required_bar += f"] {xp}/{required}"
# Display the player's stats
print(f"These are your current stats:\nHealth: {player.hp}/{player.base_hp}\nXP:\n{required_bar}")
equipped_weapon = player.equipped['weapon']
if equipped_weapon is not None:
print(f"Attack: {player.base_attack} (+{equipped_weapon.attack} from {equipped_weapon.name}, wobble {equipped_weapon.wobble * 100}%)")
else:
print(f'Attack {player.base_attack}')
equipped_defense = player.equipped['defense']
if equipped_defense is not None:
print(f'Defense: {player.base_defense} (-{equipped_defense.defense * 100}% of attack from {equipped_defense.name}, wobble {equipped_defense.wobble * 100}%)')
else:
print(f'Defense: {player.base_defense}')
equipped_magic = player.equipped['magic']
if equipped_magic is not None:
print(f'Luck: {player.base_luck} (+{equipped_magic.magic} from {equipped_magic.name})')
else:
print(f'Luck: {player.base_luck}')
input("Press ENTER to continue...\n")
clear()
def combat(enemy):
clear()
print("You have encountered a level " + str(enemy.level), str(enemy.name) + "!")
while True:
print(enemy.name + " has " + str(enemy.hp) + " hp remaining.")
print("")
print("You have " + str(player.hp) + " hp remaining.\n")
print("What would you like to do?")
print("The commands you can use are:")
print("ATTACK to perform a normal attack.")
print("ATTACK STRONG to perform a strong attack with a chance to miss.")
print("ATTACK QUICK to perform a quick attack.")
print("INVENTORY to view your inventory, to switch weapons, inspect weapons, use items etc.")
print("STATS to see your current stats.")
user_input = input(">")
user_input = normalise_input(user_input)
clear()
if user_input[0] == "inventory" or user_input[0] == "inv":
display_inventory()
elif user_input[0] == "attack":
# normal attack
if len(user_input) == 1:
print("You attacked using a normal attack!")
missed = player.check_attack_missed()
if missed == True:
print("Your attack missed. You did 0 damage!")
else:
attack_damage, critical = player.get_damage()
damage_to_do = int(max(enemy.apply_defense(attack_damage), 0))
if critical == True:
print("Critical hit! Your attack did " + str(damage_to_do) + " damage!")
else:
print("Your attack did " + str(damage_to_do) + " damage!")
enemy.take_damage(damage_to_do)
took_dura = player.take_attack_wear(2)
if took_dura: print("Your attack spent 2 durability!")
# strong attack does more damage, but costs more durability, and has higher chance of missing
elif len(user_input) > 1 and user_input[1] == "strong":
print("You attacked using a strong attack!")
missed = player.check_attack_missed(1.25)
if missed == True:
print("Your attack missed. You did 0 damage!")
else:
attack_damage, critical = player.get_damage(1.25)
damage_to_do = int(max(enemy.apply_defense(attack_damage), 0))
if critical == True:
print("Critical hit! Your attack did " + str(damage_to_do) + " damage!")
else:
print("Your attack did " + str(damage_to_do) + " damage!")
enemy.take_damage(damage_to_do)
took_dura = player.take_attack_wear(3)
if took_dura: print("Your attack spent 3 durability!")
# quick attack does a series of smaller attacks
elif len(user_input) > 1 and user_input[1] == "quick":
print("You attacked using a series of quick attacks!")
for i in range(3):
missed = player.check_attack_missed(0.3)
if missed == True:
print("Your attack missed. You did 0 damage!")
else:
attack_damage, critical = player.get_damage(0.3)
damage_to_do = int(max(enemy.apply_defense(attack_damage), 0))
if critical == True:
print("Critical hit! Your attack did " + str(damage_to_do) + " damage!")
else:
print("Your attack did " + str(damage_to_do) + " damage!")
enemy.take_damage(damage_to_do)
took_dura = player.take_attack_wear(1)
if took_dura: print("Your attack spent 1 durability!")
else:
print("Could not perform that type of attack.")
# check if you killed enemy
if enemy.alive == False:
print("You defeated the " + enemy.name + "!\n")
player.gain_xp(enemy.level * 5)
break
# now calculate the enemies attack
print("\nThe " + enemy.name + " will now attack.")
enemy_attack_damage, critical = enemy.get_attack()
damage_to_do = int(max(player.apply_defense(enemy_attack_damage), 0))
if critical == True:
print("Critical hit! The enemy did " + str(damage_to_do) + " damage!")
else:
print("The enemy did " + str(damage_to_do) + " damage!")
player.take_defense_wear(damage_to_do // 10)
player.take_damage(damage_to_do)
# check if you died
if player.alive == False:
print("You died!\n")
break
elif user_input[0] == "stats":
display_stats()
else:
print("Could not recognise that command.")
def boss_battle():
game = MiniGame(game_map, game_info, random.randint(3, 6), int(player.level*2))
won = game.game_loop() # Should return true if they won and false if they lost
player.fought_boss = True
if not won:
player.alive = False
def list_of_items(items):
if len(items) > 0:
print("'", end = "")
for x in range(len(items)-1):
print(items[x]["name"], end = ", ")
print(items[len(items)-1]["name"], end = "'")
else:
return ''
def print_room_items(room):
if len(room.items) > 0:
print("There is ", end = "")
for x in range(len(room.items)-1):
final_name = add_article(room.items[x].name)
print(final_name, end = ", ")
print(add_article(room.items[len(room.items)-1].name), end = " ")
print("here.\n")
def print_inventory_items(items):
if len(items) > 0:
print("You have ", end = "")
for x in range(len(items)-1):
final_name = add_article(items[x].name)
print(final_name, end = ", ")
print(add_article(items[len(items)-1].name), end = ".")
print("\n")
def print_room(room):
print("")
print(room.name)
print(room.description + "\n")
print_room_items(room)
def exit_leads_to(exits, direction):
return rooms[exits[direction]].name
def print_exit(direction, leads_to):
print("GO " + direction.upper() + " to " + leads_to + ".")
def print_menu(exits, room_items, inv_items):
print("The commands you can use are:")
# Iterate over available exits
for direction in exits:
# Print the exit name and where it leads to
print_exit(direction, exit_leads_to(exits, direction))
for item in room_items:
if item.can_pickup is True:
final_name = add_article(item.name)
print("TAKE", item.id.upper(), "to take", final_name + ".")
elif item.on_interaction is not None:
# In this case, it's an item we must interact with in the game space as it cannot be picked up
print(f"INTERACT {item.id.upper()} {item.on_interaction_description}.")
for item in inv_items:
if item.can_place is True:
print("DROP", item.id.upper(), "to drop your", item.name + ".")
print("INVENTORY to view your inventory")
print("STATS to see your player stats")
print("MAP to view the map")
print("HELP for help information")
def execute_go(direction):
possible_exits = player.current_room.exits
if direction in possible_exits:
new_room_name = possible_exits[direction]
if new_room_name == "boss_room":
if player.level >= 5:
boss_battle()
else:
print("Your not strong enough to enter, you must reach level 5")
else:
new_room = rooms[new_room_name]
print("You are moving to", new_room.name)
player.current_room = new_room
else:
print("You cannot go there.")
def check_conflicts(space):
'''
Pass a list (of items) to this function and it will check if the list
has any conflicting IDS (preventing effective usage of items)
space : list
'''
checked_items = {} # {class: [items]}
for item in space:
if type(item) in checked_items.keys():
# There is a conflict
checked_items[type(item)].append(item)
else:
checked_items[type(item)] = [item]
# Now, check through the identified items and see if they need unique names
for item_list in checked_items.values():
if len(item_list) > 1:
# Iter through items in array, giving them unique names
counter = 0
for item in item_list:
# We don't need to make the change if it's already been made
if item.conflict is False:
counter += 1
item.conflict = True
item.id += f"_{counter}"
else:
for item in item_list:
if item.conflict is True:
item.id = item.id[:-2] # I'm assuming there's not going to be more than 9 conflicting items in an area...
item.conflict = False
def execute_take(item_id):
for item in player.current_room.items:
if item.id == item_id and item.can_pickup is True:
player.inventory.append(item)
player.current_room.items.remove(item)
check_conflicts(player.inventory)
check_conflicts(player.current_room.items)
return
print("You cannot take that")
def execute_drop(item_id):
for item in player.inventory:
if item.id == item_id and item.can_place is True:
player.inventory.remove(item)
player.current_room.items.append(item)
check_conflicts(player.current_room.items)
check_conflicts(player.inventory)
return
print("You cannot drop that")
def execute_interact(item_id):
for item in player.current_room.items:
if item.id == item_id and item.on_interaction is not None and not item.can_pickup:
before_inv = player.inventory
# We need to check the item can't be picked up as then the on_interation is dedicated to use inside the inv
item.on_interaction(player)
# There may be a conflict
if before_inv != player.inventory:
check_conflicts(player.inventory)
return
print("You cannot interact with that")
def execute_command(command):
"""This function takes a command (a list of words as returned by
normalise_input) and, depending on the type of action (the first word of
the command: "go", "take", or "drop"), executes either execute_go,
execute_take, or execute_drop, supplying the second word as the argument.
"""
if len(command) == 0:
return
if command[0] == "go":
if len(command) > 1:
execute_go(command[1])
else:
print("Go where?")
elif command[0] == "take":
if len(command) > 1:
execute_take(command[1])
else:
print("Take what?")
elif command[0] == "drop":
if len(command) > 1:
execute_drop(command[1])
else:
print("Drop what?")
elif command[0] == "inventory" or command[0] == "inv":
display_inventory()
elif command[0] == "map":
display_map()
elif command[0] == "help":
display_help()
elif command[0] == "interact":
if len(command) > 1:
execute_interact(command[1])
else:
print("Interact with what?")
elif command[0] == "stats":
display_stats()
else:
print("This makes no sense.")
def menu(exits, room_items, inv_items):
# Display menu
print_menu(exits, room_items, inv_items)
# Read player's input
user_input = input("> ")
# Normalise the input
normalised_user_input = normalise_input(user_input)
return normalised_user_input
def move(exits, direction):
# Next room to go to
return rooms[exits[direction]]
def menu_beg():
slow_print("What would you like to do?\n[1]Start Game!\n[2]Quit\n>")
menu_choice = input()
choice = menu_choice.lower()
if choice == '1':
clear()
sleep(1)
loadingbar()
elif choice == '2':
sleep(1)
response = False
while not response:
clear()
slow_print("Are you sure you want to quit? y/n\n>")
confirm = input()
if confirm.lower() == 'y':
quit()
elif confirm.lower() == 'n':
clear()
response = True
return menu_beg()
else:
slow_print("Invalid reply...")
def loadingbar():
print("Loading the game...")
for i in range(26):
sleep(random.uniform(0.05, 0.2))
bar = ('□' * i) + '-' * (25 - i)
print(f'\r[{bar}]', end='')
print()
print('-=!LOADED!=-')
slow_print("> Press enter to begin the game.")
press = input()
# This is the entry point of our program
def main():
global player
clear()
slow_print("Welcome to...""\n")
print("""
████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
█░░░░░░░░░░░░░░█░░░░░░█████████░░░░░░░░░░░░░░█░░░░░░░░░░░░░░█░░░░░░░░░░░░░░░░███░░░░░░░░░░░░░░░░░░█░░░░░░░░░░░░░░█░░░░░░██████████░░░░░░█░░░░░░░░░░░░░░█
█░░▄▀▄▀▄▀▄▀▄▀░░█░░▄▀░░█████████░░▄▀▄▀▄▀▄▀▄▀░░█░░▄▀▄▀▄▀▄▀▄▀░░█░░▄▀▄▀▄▀▄▀▄▀▄▀░░███░░▄▀▄▀▄▀▄▀▄▀▄▀▄▀░░█░░▄▀▄▀▄▀▄▀▄▀░░█░░▄▀░░░░░░░░░░██░░▄▀░░█░░▄▀▄▀▄▀▄▀▄▀░░█
█░░▄▀░░░░░░▄▀░░█░░▄▀░░█████████░░░░░░▄▀░░░░░░█░░▄▀░░░░░░░░░░█░░▄▀░░░░░░░░▄▀░░███░░░░░░░░░░░░▄▀▄▀░░█░░▄▀░░░░░░▄▀░░█░░▄▀▄▀▄▀▄▀▄▀░░██░░▄▀░░█░░▄▀░░░░░░░░░░█
█░░▄▀░░██░░▄▀░░█░░▄▀░░█████████████░░▄▀░░█████░░▄▀░░█████████░░▄▀░░████░░▄▀░░███████████░░░░▄▀░░░░█░░▄▀░░██░░▄▀░░█░░▄▀░░░░░░▄▀░░██░░▄▀░░█░░▄▀░░█████████
█░░▄▀░░░░░░▄▀░░█░░▄▀░░█████████████░░▄▀░░█████░░▄▀░░░░░░░░░░█░░▄▀░░░░░░░░▄▀░░█████████░░░░▄▀░░░░███░░▄▀░░██░░▄▀░░█░░▄▀░░██░░▄▀░░██░░▄▀░░█░░▄▀░░░░░░░░░░█
█░░▄▀▄▀▄▀▄▀▄▀░░█░░▄▀░░█████████████░░▄▀░░█████░░▄▀▄▀▄▀▄▀▄▀░░█░░▄▀▄▀▄▀▄▀▄▀▄▀░░███████░░░░▄▀░░░░█████░░▄▀░░██░░▄▀░░█░░▄▀░░██░░▄▀░░██░░▄▀░░█░░▄▀▄▀▄▀▄▀▄▀░░█
█░░▄▀░░░░░░▄▀░░█░░▄▀░░█████████████░░▄▀░░█████░░▄▀░░░░░░░░░░█░░▄▀░░░░░░▄▀░░░░█████░░░░▄▀░░░░███████░░▄▀░░██░░▄▀░░█░░▄▀░░██░░▄▀░░██░░▄▀░░█░░▄▀░░░░░░░░░░█
█░░▄▀░░██░░▄▀░░█░░▄▀░░█████████████░░▄▀░░█████░░▄▀░░█████████░░▄▀░░██░░▄▀░░█████░░░░▄▀░░░░█████████░░▄▀░░██░░▄▀░░█░░▄▀░░██░░▄▀░░░░░░▄▀░░█░░▄▀░░█████████
█░░▄▀░░██░░▄▀░░█░░▄▀░░░░░░░░░░█████░░▄▀░░█████░░▄▀░░░░░░░░░░█░░▄▀░░██░░▄▀░░░░░░█░░▄▀▄▀░░░░░░░░░░░░█░░▄▀░░░░░░▄▀░░█░░▄▀░░██░░▄▀▄▀▄▀▄▀▄▀░░█░░▄▀░░░░░░░░░░█
█░░▄▀░░██░░▄▀░░█░░▄▀▄▀▄▀▄▀▄▀░░█████░░▄▀░░█████░░▄▀▄▀▄▀▄▀▄▀░░█░░▄▀░░██░░▄▀▄▀▄▀░░█░░▄▀▄▀▄▀▄▀▄▀▄▀▄▀░░█░░▄▀▄▀▄▀▄▀▄▀░░█░░▄▀░░██░░░░░░░░░░▄▀░░█░░▄▀▄▀▄▀▄▀▄▀░░█
█░░░░░░██░░░░░░█░░░░░░░░░░░░░░█████░░░░░░█████░░░░░░░░░░░░░░█░░░░░░██░░░░░░░░░░█░░░░░░░░░░░░░░░░░░█░░░░░░░░░░░░░░█░░░░░░██████████░░░░░░█░░░░░░░░░░░░░░█
████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████""")
press = input()
while True:
clear()
menu_beg()
sleep(1)
clear()
while True:
clear()
populate_enemies()
populate_rooms()
player.fought_boss = False
print("You suddenly wake up in an ancient abandoned manner house. Every window boarded, every door to the real world locked...")
print("The old house finds itself inhabited by a plethora of strange monsters, of which many seem violent. It is up to you...")
print("to trek forward and find a way out with what little you have - avoiding death as if it were around every corner.")
print("\nIn order to win, you must level up to lvl 5 and then navigate to the final boss room which contains the only passage to the real world. This room is located at the end of the long, dark underground passage... You may find this fight easier the higher level you are")
input("> Press enter to begin the game.")
# Main game loop
while True:
clear()
# Display game status (room description, inventory etc.)
print_room(player.current_room)
if player.current_room.cleared == False:
# filler, for now
for enemy in player.current_room.enemies:
combat(enemy)
if player.alive == False:
break
if player.alive == False:
break
else:
player.current_room.cleared = True
# Show the menu with possible actions and ask the player
command = menu(player.current_room.exits, player.current_room.items, player.inventory)
# Execute the player's command
execute_command(command)
time.sleep(1) # If the command was invalid or feedback is given based on this command then we want them
# to being able to view it before clearing the screen on the next game loop
if player.fought_boss == True:
break
# player death message
if player.alive == False:
print("Game over. You did not manage to beat the game. Try again.")
time.sleep(0.5)
input("> Press enter to play again.")
else:
print("You beat the game, well done!!")
time.sleep(0.5)
input("> Press enter to play again.")
# Reset the player var
player = Player()
if __name__ == "__main__":
main()