4
\$\begingroup\$

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'
200_success
145k22 gold badges190 silver badges478 bronze badges
asked Jan 12, 2018 at 0:02
\$\endgroup\$
1
  • \$\begingroup\$ If player enters anything except 1-5, no action happens, but still the program will print "Calculating..." and "You've Hit For {} Damage" despite not actually using any default attack. \$\endgroup\$ Commented Jan 12, 2018 at 6:21

1 Answer 1

2
\$\begingroup\$

Some standard criticisms apply:

  1. Objects should manage their own data.

  2. User interface logic should not be part of model objects.

  3. For model objects, pass in data as parameters. Especially data from the user, like name.

With that out of the way, there are some things I think you might be fuzzy on:

  • Why is ClearScreen a class? Why isn't this simply a user interface function?

  • Everything has a name. But name isn't part of the Character class. Why not?

  • Do you realize that setting variables at "class level" makes them class variables, instead of instance variables? That is, when you do this:

    class enemyWarrior(Character):
     def __init__(self):
     ...
     name = random.choice(["Skeleton Warrior", "Warrior of the Light", "Summoned Warrior"])
    

    You are setting the name of all enemyWarrior objects to that value, rather than having each enemy warrior randomly pick a name. (This might be what you intend, I can't tell.)

  • For operations like choosePlayer ask yourself, "What am I doing? What data am I using? What is this a part of?" Specifically, choosePlayer seems like a user-interface method, since it interacts with the user. (You might break it into two methods, one for talking to the player and one for creating a Mage, but I don't think that's necessary at this point.)

  • The difficulty scaling is a good idea. But think about who should be doing that. It seems to me that might be a method of the enemy classes, with an input of the player, or the player's level. Or it might be a user interface function, or a function of the "game board" if there is such a thing.

answered Jan 12, 2018 at 1:27
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.