This is my first leap into Python and I had a lot of fun writing this - it is not complete yet.
I'm interested in adding a perk system next, instead of generically increasing stats I'd like the player to be able to choose specific perks. General feedback and criticism is welcomed!
"""A Python3 'game' that generates enemies that scale according to the players specific attributes and level. Further features to be implemented. Wait for the screens to refresh before typing in new commands - unless you like losing."""
import random
import os
import time
class clearScreen(): # Simply so I don't have to type os.system('cls||clear')
# Progammers are lazy, right?
def clearTheScreen():
try:
os.system('cls||clear')
except:
pass
class Character():
def __init__(self, health, mana, defence, attack, level, experience, criticalChance, gold):
self.health = health
self.mana = mana
self.defence = defence
self.attack = attack
self.level = level
self.experience = experience
self.criticalChance = criticalChance
self.gold = gold
class Mage(Character):
def __init__(self):
super().__init__(health = 150,
mana = 100,
defence = 3,
attack = 16, # 12, 35 for debug
level = 1,
experience = 0,
criticalChance = 5, # 5 or 50 for testing
gold = 0)
name = input("What is your name? ").upper()
maxHealth = 150
maxMana = 100
statUpPoints = 0 # Basically level up points
inTheBattle = False # Checks battle state for enemy scaling
playerClass = "MAGE" # To determine which UI to print
class enemyWarrior(Character):
def __init__(self):
super().__init__(health = 1,
mana = 0,
defence = 0,
attack = 0,
level = 1,
experience = 0,
criticalChance = 0,
gold = 0)
name = random.choice(["Skeleton Warrior", "Warrior of the Light", "Summoned Warrior"])
maxHealth = 1
maxMana = -20 # Stats can be increased or reduced to change the scale
class enemyMage(Character):
def __init__(self):
super().__init__(health = 1,
mana = 50,
defence = -2,
attack = 5,
level = 1,
experience = 0,
criticalChance = 0,
gold = 0)
name = random.choice(["Ethereal Mage of Frost", "Wisp of Lightning", "Dark Wizard"])
def choosePlayer(): # Will expand when functionality is more complete
choosePlayerCommand = input("Mage(1), 2, 3 ")
if choosePlayerCommand == "1":
player = Mage()
return player
else:
player = Mage()
return player
def enemySpawner():
if player.playerClass == "MAGE":
if player.level < 10:
enemy = enemyWarrior()
return enemy
elif player.level < 20:
enemy = enemyMage()
return enemy
def levelUp():
global experienceNeeded, menuBool # Eek globals, are these necesarry or could I deal with them in a different way?
experienceNeeded = [] # Holds the sequence of numbers that determine how much xp is needed to level up
menuBool = False # Important for printing proper menus
maxLevel = 60; # Max Level Achievable by a Character
a = 0; # Initialization
b = 1; # Initialization
# Determines how much experience is needed for each level
for c in range(0 , maxLevel):
c = a + b;
a = b;
b = c;
experienceNeeded.append(c)
# ^ Fibonacci, [1,1,2,3,5,8,13,21,34,55,89, etc...]
while player.experience > experienceNeeded[player.level]:
# ^ Checks if player has leveled up
if player.level % 5 == 0: # Every 5 levels
player.criticalChance += 2 # add 2% every 5 levels to critical chance, needs balancing
player.level += 1 # Add one to players level
player.health = player.maxHealth # Reset players health
player.mana = player.maxMana # Reset players mana
player.statUpPoints += 1 # Give 1 stat point per level up
menuBool = True # Used to disengage the regular interface
player.inTheBattle = False
userInterface.printPerkMenu() # Opens up the Perk Menu
class scale(): # Scale those enemies, needs some balancing
def scaleEnemies():
# Quickly reset the stats so enemy scaling remains constant, I'm not sure how else to tackle this
enemy.level = 0
enemy.maxHealth = 0
enemy.maxMana = 0
enemy.attack = 0
enemy.defence = 0
enemy.experience = 0
enemy.level += round(player.level + random.choice(range(1,5)))
#enemy.level += round(abs((player.attack + player.defence) / 7) + (enemy.health / 25) + random.choice(range(1,3)))
enemy.maxHealth += round(enemy.level * 10) + random.choice(range(35, 100))
enemy.health = enemy.maxHealth
enemy.maxMana += round(enemy.level * 5) + random.choice(range(25, 70))
enemy.mana = enemy.maxMana
enemy.attack += round((player.level + enemy.level) + (player.defence / 5) + random.choice(range(0,enemy.level)) + 2)
enemy.defence += round((player.level + enemy.level) + (player.attack / 5) + random.choice(range(0,enemy.level)) + 2)
enemy.experience += round(enemy.level * 50) + random.choice(range(player.level, player.maxHealth))
#enemy.gold += round((enemy.level * 2) + random.choice(range(enemy.level, 35)))
enemy.criticalChance += round(2 + (enemy.level * 0.10))
player.inTheBattle = True
class userInterface(): # Our user interface is just text
def printUI():
print("\n",
"[", player.name, "the", player.playerClass, "]", "\n",
"Level: ", player.level, "| Experience: {}/{}".format(player.experience,experienceNeeded[player.level]), "\n",
"Health: ", player.health, "/", player.maxHealth, "\n",
"Mana: ", player.mana, "/", player.maxMana, "\n",
"Defence: ", player.defence, "\n",
"Attack: ", player.attack, "\n",
"Critical Chance: ",player.criticalChance, "%", "\n")
print(
"[", enemy.name, "]", "\n",
"Difficulty: ", enemy.level, "\n",
"Health: ", enemy.health, "/", enemy.maxHealth, "\n",
"Mana: ", enemy.mana, "/", enemy.maxMana, "\n",
"Defence: ", enemy.defence, "\n",
"Attack: ", enemy.attack, "\n",
"Critical Chance: ",enemy.criticalChance, "%", "\n",
"Experience: ",enemy.experience, "\n")
def printPerkMenu(): # Menu for leveling up our stats
statUp = (player.level + 1)
statUpHealth = 25 + (player.level * 2)
statUpMana = 10 + (player.level * 2)
clearScreen.clearTheScreen() # Clear the screen!
print("Level Up! Choose a Stat to Increase:", "\n",
"Attack + {} [1]".format(statUp), "\n",
"Defence + {} [2]".format(statUp), "\n",
"Health + {} [3]".format(statUpHealth), "\n",
"Mana + {} [4]".format(statUpMana), "\n")
while player.statUpPoints > 0:
if player.statUpPoints == 0:
menuBool = False
# ^ When no longer leveling up, display regular UI
command = input("Level {}! >> ".format(player.level))
if command == "1":
player.attack += statUp
player.statUpPoints -= 1
elif command == "2":
player.defence += statUp
player.statUpPoints -= 1
elif command == "3":
player.health += statUpHealth
player.maxHealth += statUpHealth
player.statUpPoints -= 1
elif command == "4":
player.mana += statUpMana
player.maxMana += statUpMana
player.statUpPoints -= 1
time.sleep(0.2)
def playerAttackCommands(): # Temporary, will make seperate command functionality for each different player you can choose and will likely move everything except for the print statements outside of the userInterface class.
damageDealtByPlayer = [0] # Weird way to store damage for printing
playerCriticalHit = player.attack * 2 # Determines critical damage
playerCriticalHitBool = False # Weird way to check for criticals
playerHeal = round((player.level * player.maxHealth) / 20)
playerHealCost = round((player.level * player.maxHealth) / 50)
if player.playerClass == "MAGE":
print("\n", "*"*40, "\n",
"1.) Attack using your staffs power, has chance to critical.", "\n",
"2.) Attack with magic for high damage! (Costs 25 Mana)", "\n",
"3.) Heal yourself for {} health. (Costs {} Mana)".format(playerHeal, playerHealCost), "\n",
"4.) Choice 4", "\n",
"5.) Run Away!", "\n")
chooseAttack = input(">> ")
if chooseAttack == "1":
damageDealtByPlayer[0] = round(player.attack - (0.25 * enemy.defence))
if damageDealtByPlayer[0] > 0: # Checks if player was able to bypass enemies armor, if so determine if they critically hit
if random.choice(range(100)) <= player.criticalChance:
playerCriticalHitBool = True # Used for printing mostly
damageDealtByPlayer[0] = (1 + playerCriticalHit) - (0.25 * enemy.defence) # Calculates critical hit
enemy.health -= damageDealtByPlayer[0] # Subtracts hit from enemies health points
else:
enemy.health -= damageDealtByPlayer[0] # If player did not critically hit, calculate damage normally
else:
damageDealtByPlayer[0] = 1
enemy.health -= 1
if chooseAttack == "2": # Magic attack, needs balancing
if player.mana >= 25:
player.mana -= 25
damageDealtByPlayer[0] = round(player.attack * 1.75) - (0.25 * enemy.defence)
if damageDealtByPlayer[0] > 0:
enemy.health -= damageDealtByPlayer[0]
else:
damageDealtByPlayer[0] = 1
enemy.health -= 1
else:
damageDealtByPlayer[0] = "NOMANA"
if chooseAttack == "3": # Healing needs to be balanced
if player.mana >= playerHealCost:
player.mana -= playerHealCost
player.health += playerHeal
damageDealtByPlayer[0] = "HEAL"
else:
damageDealtByPlayer[0] = "NOMANA"
if chooseAttack == "4":
time.sleep(1)
print("Debug")
if chooseAttack == "5":
player.health -= enemy.attack / 1.5
damageDealtByPlayer[0] = "RUN"
enemy.experience = 0
enemy.health = 0
try: # Clear the screen then prints damage dealt by player, will not always be under a try statement... this is temporary
clearScreen.clearTheScreen()
print("Calculating....")
time.sleep(0.3)
if damageDealtByPlayer[0] == "HEAL":
print("You heal yourself for {} hit points.".format(playerHeal))
if damageDealtByPlayer[0] == "NOMANA":
print("Not enough mana!")
if damageDealtByPlayer[0] == "RUN":
print("You run away but take {} damage in return.".format(enemy.attack / 1.5))
else:
if playerCriticalHitBool == True:
print("You've Critically Hit For {} Damage".format(damageDealtByPlayer[0]))
playerCriticalHitBool = False
else:
print("You've Hit For {} Damage".format(damageDealtByPlayer[0]))
time.sleep(0.8)
damageDealtByPlayer[0] = 0 # Reset damage so it does not stack
except ValueError:
pass
def enemyAttackCommands(): # 'AI' for attacks dealt by enemies, this will also be seperated from the userInterface class except for the print statements
enemyCriticalHit = 2 * enemy.attack
enemyCriticalHitBool = False
damageDealtByEnemy = [0]
enemyHeal = round((enemy.level * enemy.maxHealth) / 20)
enemyHealCost = round((enemy.level * enemy.maxHealth) / 55)
damageDealtByEnemy[0] = round(enemy.attack - (0.25 * enemy.defence))
if enemy.health < (0.40 * enemy.maxHealth) and enemy.mana >= enemyHealCost:
enemy.mana -= enemyHealCost
enemy.health += enemyHeal
damageDealtByEnemy[0] = "HEAL"
elif damageDealtByEnemy[0] > 0:
if random.choice(range(100)) <= enemy.criticalChance:
enemyCriticalHitBool = True
damageDealtByEnemy[0] = 1 + enemyCriticalHit
player.health -= damageDealtByEnemy[0]
else:
player.health -= damageDealtByEnemy[0]
else:
damageDealtByEnemy[0] = 1
player.health -= damageDealtByEnemy[0]
try: # Will not always be a try statement, this is temporary
if damageDealtByEnemy[0] == "HEAL":
print("The enemy heals itself for {} hit points.".format(enemyHeal))
if enemyCriticalHitBool == True:
print("You've Been Critically Hit For {} Damage".format(damageDealtByEnemy[0]))
enemyCriticalHitBool = False
else:
print("You've Been Hit For {} Damage".format(damageDealtByEnemy[0]))
time.sleep(1)
damageDealtByEnemy[0] = 0
except:
pass
player = choosePlayer() # Initialize player
enemy = enemySpawner() # Initialize enemy
# Game Loop
while True:
if player.health > player.maxHealth: # players health cannot exceed maximum
player.health = player.maxHealth
if player.mana > player.maxMana: # Players mana cannot exceed maximum
player.mana = player.maxMana
if enemy.health > enemy.maxHealth: # Enemies health cannot exceed maximum
enemy.health = enemy.maxHealth
if enemy.mana > enemy.maxMana: # Enemies mana cannot exceed maximum
enemy.mana = enemy.maxMana
if enemy.health <= 0: # Check if enemy has died
print("You've slain the {} and gained {} experience!".format(enemy.name,enemy.experience))
player.inTheBattle = False
player.experience += enemy.experience # Add experience
#player.gold += enemy.gold # Gold is useless as of right now
enemy = enemySpawner() # Spawn new enemy
time.sleep(3) # Pause screen for ~ 3 seconds
if player.health <= 0: # Check if player has died
break # DEAD
if player.inTheBattle == False: # Check if player is battling an enemy otherwise the enemy will constantly be respawned with different stats and never lose health.
scale.scaleEnemies() # Class for scaling enemies stats and levels
levelUp() # Used for some calculations and pre-battle prep
if menuBool == False: # If leveling up, don't bother printing anything else
userInterface.printUI() # Prints user interface
if player.health > 0: # Check if player is still alive
userInterface.playerAttackCommands() # Start combat for player
if enemy.health > 0: # Check if enemy is alive
userInterface.enemyAttackCommands() # Starts enemy battle 'AI'