10
\$\begingroup\$

I decided to work on putting together an Arena-style (very basic) text-based RPG as a way to help myself learn Python. Unfortunately, after about 1,000 lines of pieced-together code, I realize that I've been doing myself a *dis*service by reenforcing poor practices, and by going line-by-line, my inefficiency is multiplying exponentially.

I don't honestly know where to start. I'm sure there are libraries, shortcuts, and greatly more efficient methods than what I'm using. I'm okay with starting over (there are errors not worth finding, anyway), but I would hugely appreciate anyone knowledgeable looking over my code, giving me a few of the bigger pointers for how to clean up my current methods, and where to look for new ones. A quick skim, I'm sure, would suffice. I also want to keep it mostly text and basic, so I'm not looking for something like PyGame, just straightforward stuff. (For extra, I plan on utilizing my final code to create a "leaderboard" of randomly-generated characters that fight each other, so pointers in that direction help). I had to cut out many of the very long lists for post length, but it gives you an idea.

import random
import math


class character:

    def __init__(self):

        # There are hundreds of these... cut down for post length
        self.STR = 75
        self.DEX = 75
        self.CON = 75
        self.AGI = 75
        self.INT = 75
        self.WIS = 75
        self.statPoints = 75
        self.AP = 0
        self.XP = 0
        self.LVL = 1
        self.name = ''
        self.xpToLVL = 500
        self.fightsWon = 0
        self.XPSinceLVL = 0
        self.dmgmitPercent = 0
        self.mindmg = 0
        self.maxdmg = 0
        self.mindmgmit = 0
        self.maxdmgmit = 0
        self.minrobeSP = 0
        self.maxrobeSP = 0
        self.WarBuff = 0
        self.poisonCounter = 0
        self.dmg = 0
        self.armorType = 0 
        self.shieldEquipped = 0
        self.visitedPTrainer = 0
        self.visitedCTrainer = 0
        self.visitedArena = 0


def fightRound(hero, enemy, skillPick):

    heavyB = 0
    defS = 0
    flank = 0
    poison = 0
    heal = 0
    shock = 0
    fireB = 0
    counterA = 0
    resil = 0
    shockstun = 0
    monkstun = 0
    rejuv = 0

    if hero.curClass == 1 and skillPick == 1:
        heavyB = 1
    elif hero.curClass == 1 and skillPick == 2:
        defS = 1
    elif hero.curClass == 1 and skillPick == 3:
        hero.WarBuff += 1
    # ETC, cut for post length

    EheavyB = 0
    EdefS = 0
    Eflank = 0
    Epoison = 0
    Eheal = 0
    Eshock = 0
    EfireB = 0
    EcounterA = 0
    Eresil = 0
    Eshockstun = 0
    Emonkstun = 0
    Erejuv = 0
    EskillPick = 0

    if enemy.curClass == 1:
        if hero.HP < ((enemy.CON * 5 * enemy.LVL) / 10):
            EskillPick = 1
        elif enemy.HP > (enemy.CON * 5 * enemy.LVL) / 2 and hero.HP > (hero.CON * 5 * hero.LVL) / 2:
            if random.randint(1, 100) < 66:
                EskillPick = 2
        elif random.randint(1, 100) < 51:
            EskillPick = 2
        else:
            EskillPick = 1
    if enemy.curClass == 2:
        if hero.HP > (hero.CON * 5 * hero.LVL) / 2 and enemy.HP > (enemy.HP * 5 * enemy.LVL) / 2:
            if random.randint(1, 100) < 80:
                EskillPick = 1
        elif random.randint(1, 100) < 40:
            EskillPick = 2
        else:
            EskillPick = 1
    #ETC, cut down for post length

    if enemy.curClass == 1 and EskillPick == 1:
        EheavyB = 1
        print 'Your enemy lunges forward...'
    elif enemy.curClass == 1 and EskillPick == 2:
        EdefS = 1
        print 'Your enemy pulls back...'
    elif enemy.curClass == 1 and EskillPick == 3:
        enemy.WarBuff += 1
    elif enemy.curClass == 2 and EskillPick == 1:
        Eflank = 1
        print 'Your enemy tries to flank you...'
    elif enemy.curClass == 2 and EskillPick == 2:
        Epoison = 1
        print 'Your enemy coats his daggers...'
    # ETC, cut for post length

    hero.STR = float(hero.STR)
    hero.DEX = float(hero.DEX)
    hero.CON = float(hero.CON)
    hero.INT = float(hero.INT)
    hero.WIS = float(hero.WIS)
    #hero.DEX = float(hero.DEX)

    heroMissed = 0
    enemyMissed = 0

    enemy.STR = float(enemy.STR)
    enemy.DEX = float(enemy.DEX)
    enemy.CON = float(enemy.CON)
    enemy.INT = float(enemy.INT)
    enemy.WIS = float(enemy.WIS)

    hero.dodge = float(hero.dodge)
    hero.block = float(hero.block)
    enemy.dodge = float(enemy.dodge)
    enemy.block = float(enemy.block)
    hero.crit = float(hero.crit)
    enemy.crit = float(enemy.crit)

    if hero.weaponType == 0:
        hero.swing = hero.AGI * 3
    elif hero.weaponType == 1:
        hero.swing = hero.AGI * 2
    elif hero.weaponType > 1:
        hero.swing = hero.AGI

    heroAttacks = int(math.floor(hero.swing / 100))
    heroExtraAttack = hero.swing - (heroAttacks * 100)
    if random.randint(1, 100) <= heroExtraAttack:
        heroAttacks += 1
    if heroAttacks < 1:
        heroMissed = 1

    if enemy.weaponType == 0:
        enemy.swing = enemy.AGI * 3
    elif enemy.weaponType == 1:
        enemy.swing = enemy.AGI * 2
    elif enemy.weaponType > 1:
        enemy.swing = enemy.AGI

    enemyAttacks = int(math.floor(enemy.swing / 100))
    enemyExtraAttack = enemy.swing - (enemyAttacks * 100)
    if random.randint(1, 100) <= enemyExtraAttack:
        enemyAttacks += 1
    if enemyAttacks < 1:
        enemyMissed = 1

    # print '*DEBUG*: \# of hero attacks:',heroAttacks
    # print '*DEBUG*: \# of enemy attacks:',enemyAttacks

    # Just until I get items in...
    if hero.weaponType == 0:
        hero.mindmg = hero.LVL * 10
        hero.maxdmg = hero.LVL * 10
    elif hero.weaponType == 1:
        hero.mindmg = hero.LVL * 5
        hero.maxdmg = hero.LVL * 25
    #ETC, cut...
    hero.dodge = hero.AGI / 20 + hero.MonP * 0.4
    if hero.armorType == 2:
        hero.dodge = hero.dodge / 4
    elif hero.armorType == 1:
        hero.dodge = hero.dodge / 2
    hero.dodge = float(hero.dodge)

    hero.crit = hero.AGI / 20 + hero.RogP * 0.4
    hero.crit = float(hero.crit)

    hero.dmgmitPercent = float(hero.STR / 20 / 100)

    if hero.armorType == 2:
        hero.mindmgmit = hero.LVL * 10
        hero.maxdmgmit = hero.LVL * 15
    elif hero.armorType == 1:
        hero.mindmgmit = hero.LVL * 3
        hero.maxdmgmit = hero.LVL * 6
    elif hero.armorType == 3:
        hero.minrobeSP = hero.LVL * 1
        hero.maxrobeSP = hero.LVL * 10

    enemy.dodge = enemy.AGI / 20 + enemy.MonP * 0.4
    if enemy.armorType == 2:
        enemy.dodge = enemy.dodge / 4
    elif enemy.armorType == 1:
        enemy.dodge = enemy.dodge / 2
    enemy.dodge = float(enemy.dodge)

    enemy.crit = enemy.AGI / 20 + enemy.RogP * 0.4
    enemy.crit = float(enemy.crit)

    enemy.dmgmitPercent = float(enemy.STR / 20 / 100)

    # haven't done this yet
    if hero.shieldEquipped == 1:
        hero.block = hero.STR / 20
    else:
        hero.block = 0

    if hero.weaponType == 4:
        hero.SP = random.randint(hero.mindmg, hero.maxdmg)
    elif hero.weaponType != 4:
        hero.SP = hero.LVL * 10
    if hero.armorType == 3:
        hero.SP += random.randint(hero.minrobeSP, hero.maxrobeSP)
    hero.SP *= hero.INT / 100

    if enemy.weaponType == 4:
        enemy.SP = random.randint(enemy.mindmg, enemy.maxdmg)
    elif enemy.weaponType != 4:
        enemy.SP = enemy.LVL * 10
    if enemy.armorType == 3:
        enemy.SP += random.randint(enemy.minrobeSP, enemy.maxrobeSP)
    enemy.SP *= enemy.INT / 100

    if enemy.curClass == 3 or enemy.curClass == 4:
        enemyMissed = 0
        enemyAttacks = 1

    if enemy.curClass == 3 or enemy.curClass == 4:
        enemyMissed = 0
        enemyAttacks = 1

    if heroMissed == 1:
        print 'You missed!'

    while heroAttacks > 0:

        herodmg = float(random.randint(hero.mindmg, hero.maxdmg))
        enemymit = float(random.randint(enemy.mindmgmit, enemy.maxdmgmit))

        heroagility = float(hero.AGI)
        if hero.weaponType == 0:
            herodmg *= hero.CON / 100
            enemymit = enemymit / (heroagility / 33)
        elif hero.weaponType == 1:
            herodmg *= hero.DEX / 100
            enemymit = enemymit / (heroagility / 50)
        elif hero.weaponType == 2:
            herodmg *= hero.STR / 100
            enemymit = enemymit / (heroagility / 100)
        elif hero.weaponType == 3:
            herodmg *= hero.STR / 67
            enemymit = enemymit / (heroagility / 100)
        elif hero.weaponType == 4:
            herodmg *= hero.INT / 100
            enemymit = 0
        if enemymit < 0:
            enemymit = 0

        enemydodgeroll = enemy.dodge * 100
        enemyblockroll = enemy.block * 100
        herocritroll = hero.crit * 100

        enemydodged = 0
        enemyblocked = 0
        herocritted = 0

        if shock == 1:
            herocritroll = 0
            enemydodgeroll = 0
            enemyblockroll = 0
            enemymit = 0
            herodmg = hero.SP
            shockroll = (500 + hero.WizA1 * 5)
            if random.randint(1, 10000) <= shockroll:
                enemy.paralyzed = 1
                shockstun = 1

        # ETC, cut...

        if defS == 1:
            herodmg = float(herodmg)
            herodmg *= (0.75 - (enemy.WarA2 * .0025))
            herodmg = int(herodmg)

        herodmg = int(herodmg)

        if rejuv == 1:
            herodmg = 0
            heroAttacks = 0
            print 'You close your eyes and inhale deeply.'
        elif heal == 1:
            herodmg = 0
            heroAttacks = 0
            heroheal = float(hero.SP * (1.1 + (hero.HeaA1 * 0.009)))
            heroheal = int(math.ceil(heroheal))
            print 'You heal', heroheal, 'damage!'
            hero.HP += heroheal
        elif hero.paralyzed == 1:
            print 'You struggle but can\'t move!'
            herodmg = 0
            hero.paralyzed = 0
        # ETC, cut down for post length
        elif herocritted == 1:
            print 'You crit for', herodmg, 'damage!'
        else:
            print 'You strike your enemy for', herodmg, 'damage.'

        enemy.HP -= herodmg

        heroAttacks -= 1

    if enemyMissed == 1:
        print 'Your enemy missed!'

    while enemyAttacks > 0:

        # print 'average enemy dmg is',float((enemy.mindmg+enemy.maxdmg)/2)
        enemydmg = float(random.randint(enemy.mindmg, enemy.maxdmg))
        heromit = float(random.randint(hero.mindmgmit, hero.maxdmgmit))

        enemyagility = float(enemy.AGI)
        if enemy.weaponType == 0:
            enemydmg *= enemy.CON / 100
            heromit = heromit / (enemyagility / 33)
        elif enemy.weaponType == 1:
            enemydmg *= enemy.DEX / 100
            heromit = heromit / (enemyagility / 50)
        elif enemy.weaponType == 2:
            enemydmg *= enemy.STR / 100
            heromit = heromit / (enemyagility / 100)
        elif enemy.weaponType == 3:
            enemydmg *= enemy.STR / 67
            heromit = heromit / (enemyagility / 100)
        elif enemy.weaponType == 4:
            enemydmg *= enemy.INT / 100
            heromit = 0
        if heromit < 0:
            heromit = 0
        # print 'Your mitigation is',heromit
        herododgeroll = hero.dodge * 100
        heroblockroll = hero.block * 100
        enemycritroll = enemy.crit * 100

        enemycritted = 0
        herododged = 0
        heroblocked = 0

        if Eshock == 1:
            herododgeroll = 0
            heroblockroll = 0
            enemycritroll = 0
            heromit = 0
            enemydmg = enemy.SP
            Eshockroll = (500 + enemy.WizA1 * 5)
            if random.randint(1, 10000) <= Eshockroll:
                hero.paralyzed = 1
                Eshockstun = 1

        if EfireB == 1:
            enemycritroll = 0
            heromit = 0
            enemydmg = enemy.SP
            fireBburn = int(
                float(math.ceil(enemydmg * (0.1 + enemy.WizA2 * 0.004))))
            if fireBburn > hero.fireBburn:
                hero.fireBburn = fireBburn
            hero.fireBCount = 5

        if Eresil == 1:
            herododgeroll = 0
            hero.dmgmitPercent += (
                hero.dodge / 100) * (0.5 + .005 * hero.MonA2)

        if random.randint(1, 10000) <= enemycritroll:
            enemydmg *= enemy.DEX / 50
            enemycritted = 1
            if flank == 1:
                heromit *= (0.9 - (hero.RogA1 * 0.009))
                herododgeroll *= (0.9 - (hero.RogA1 * 0.009))
                heroblockroll *= (0.9 - (hero.RogA1 * 0.009))
            if Eflank == 1:
                heromit *= (0.9 - (enemy.RogA1 * 0.009))
                herododgeroll *= (0.9 - (enemy.RogA1 * 0.009))
                heroblockroll *= (0.9 - (enemy.RogA1 * 0.009))

        # ETC

        if enemy.stunned == 1:
            if enemydmg - enemy.stunvalue >= 1:
                enemydmg = enemydmg - enemy.stunvalue
                enemy.stunvalue = 0
                enemy.stunned = 0
                print 'He stammers a bit, then',
            elif enemydmg - enemy.stunvalue < 1:
                enemy.stunvalue = enemy.stunvalue - enemydmg
                enemydmg = 0

        enemydmg = int(enemydmg)

        if Erejuv == 1:
            enemydmg = 0
            enemyAttacks = 0
            Erejuv = 0
        elif Eheal == 1:
            enemydmg = 0
            enemyAttacks = 0
            enemyheal = float(enemy.SP * (1.1 + (enemy.HeaA1 * 0.009)))
            enemyheal = int(math.ceil(enemyheal))
            print 'Your enemy heals', enemyheal, 'damage!'
            enemy.HP += enemyheal
        elif enemy.paralyzed == 1:
            print 'Your enemy only twitches.'
            enemydmg = 0
            enemy.paralyzed = 0
        # ETC...
        elif enemycritted == 1:
            print 'Your enemy crits you for', enemydmg, 'damage!'
        else:
            print 'Your enemy strikes you for', enemydmg, 'damage.'

        hero.HP -= enemydmg

        enemyAttacks -= 1



hero = character()


def fight(hero, enemy):

    createEnemy(enemy)
    hero.HP = hero.CON * 5 * hero.LVL
    enemy.HP = enemy.CON * 5 * enemy.LVL
    hero.WarBuff = 0
    hero.RogBuff = 0
    hero.HeaBuff = 0
    hero.WizBuff = 0
    hero.MonBuff = 0
    hero.rejuvCounter = 0
    hero.poisonCounter = 0
    hero.stunned = 0
    hero.fireBburn = 0
    hero.fireBCount = 0
    hero.stunvalue = 0
    hero.paralyzed = 0

    while hero.HP > 0 and enemy.HP > 0:
        errorcheckFight = 1
        while errorcheckFight == 1:
            print('What would you like to do?'),
            if hero.curClass == 1:
                skillPick = input(
                    ' [0] Attack  |  [1] Heavy Blow  |  [2] Defensive Strike  |  [3] Buff: Training')
            elif hero.curClass == 2:
                skillPick = input(
                    ' [0] Attack  |  [1] Flank  |  [2] Poison  |  [3] Buff: Feinting')
            # ETC...
            if skillPick > 3:
                print
                print 'Please select a proper response, from inside the [brackets].'
                print
            else:
                errorcheckFight = 0
        print
        fightRound(hero, enemy, skillPick)

        if enemy.poisonCounter > 0:
            print 'Your enemy was Poisoned for', enemy.poisonCounter * hero.RogA2 * 5, 'damage.'
            enemy.HP -= enemy.poisonCounter * hero.RogA2 * 5
        if hero.rejuvCounter > 0:
            herorejuv = int(
                float(math.ceil((hero.SP) * (.3 + (hero.HeaA2 * .003)))))
            print 'You rejuvenated', herorejuv, 'damage.'
            hero.HP += herorejuv
            hero.rejuvCounter -= 1
        if enemy.fireBCount > 0:
            print 'Your enemy burns for', enemy.fireBburn, 'damage.'
            enemy.HP -= enemy.fireBburn
            enemy.fireBCount -= 0
            if enemy.fireBCount < 1:
                enemy.fireBburn = 0

        if hero.poisonCounter > 0:
            print 'You were Poisoned for', hero.poisonCounter * enemy.RogA2 * 5, 'damage.'
            hero.HP -= hero.poisonCounter * enemy.RogA2 * 5
        if enemy.rejuvCounter > 0:
            enemyrejuv = int(
                float(math.ceil((enemy.SP) * (.3 + (enemy.HeaA2 * .003)))))
            print 'Your enemy rejuvenated', enemyrejuv, 'damage.'
            enemy.HP += enemyrejuv
            enemy.rejuvCounter -= 1
        if hero.fireBCount > 0:
            print 'You burn for', hero.fireBburn, 'damage.'
            hero.HP -= hero.fireBburn
            hero.fireBCount -= 0
            if hero.fireBCount < 1:
                hero.fireBburn = 0

        print
        print 'HERO HP:', hero.HP, ' |------|  ENEMY HP:', enemy.HP
        print

    if enemy.HP <= 0:
        hero.fightsWon += 1
        print 'You Won!'
        print 'You gain 100 XP and', int(float(hero.WIS / 75) * 100), 'AP.'
        hero.XP += 100
        hero.XPSinceLVL += 100
        hero.AP += int(float(hero.WIS / 75) * 100)
        hero.APSinceLVL += int(float(hero.WIS / 75) * 100)
    elif hero.HP <= 0:
        hero.fightsLost += 1
        print 'You Lost...'

    hero.xpToLVL = (hero.LVL * 100 + 500) - hero.XPSinceLVL
    if hero.xpToLVL <= 0:
        hero.LVL += 1
        hero.XPSinceLVL = 0
        hero.statPoints += 5
        print
        print 'You have gained a level!  You are now level,', str(hero.LVL) + '!'
    classList = ['', 'Warrior', 'Rogue', 'Healer', 'Wizard', 'Monk']
    classLevels = [0, hero.WarLVL, hero.RogLVL,
                   hero.HeaLVL, hero.WizLVL, hero.MonLVL]
    if (classLevels[hero.curClass] * 100 + 500) - (hero.APSinceLVL) <= 0:
        hero.APSinceLVL = 0
        classLevels[hero.curClass] += 1
        print 'You have gained a class level!  You are now a level', classLevels[hero.curClass], classList[hero.curClass] + '!'
    print
    print hero.xpToLVL, 'experience left for level', (hero.LVL + 1), 'and', int((classLevels[hero.curClass] * 100 + 500) - (hero.APSinceLVL)), 'left for', classList[hero.curClass], 'level', str(classLevels[hero.curClass]) + '.'
    # print '*DEBUG*: Fights Won:',hero.fightsWon,'   Fights
    # Lost:',hero.fightsLost,'    Level:',hero.LVL
    main()


def createEnemy(enemy):
    #enemy = character()

    enemy.LVL = hero.LVL

    enemyClass = random.randint(1, 5)
    enemy.curClass = enemyClass
    enemy.statPoints += (enemy.LVL - 1) * 3
    if enemy.curClass == 1:
        enemy.STR += int(enemy.statPoints * 0.50)
        enemy.CON += int(enemy.statPoints * 0.30)
        enemy.AGI += int(enemy.statPoints * 0.20)
        enemy.WarP += int(enemy.LVL * 0.70)
        enemy.WarA1 += int(enemy.LVL * 0.60)
        enemy.WarA2 += int(enemy.LVL * 0.60)
        enemy.mindmgmit = enemy.LVL * 5
        enemy.maxdmgmit = enemy.LVL * 10
        enemy.mindmg = enemy.LVL * 10
        enemy.maxdmg = enemy.LVL * 50
        wartype = random.randint(1, 2)
        if wartype == 1:
            enemy.weaponType = 2
            enemy.shieldEquipped = 1
            enemy.mindmgmit += enemy.LVL * 2
            enemy.maxdmgmit += enemy.LVL * 5
        else:
            enemy.weaponType = 3
            enemy.shieldEquipped = 0
    elif enemy.curClass == 2:
        enemy.DEX += int(enemy.statPoints * 0.50)
        enemy.AGI += int(enemy.statPoints * 0.30)
        enemy.CON += int(enemy.statPoints * 0.20)
        enemy.RogP += int(enemy.LVL * 0.60)
        enemy.RogA1 += int(enemy.LVL * 0.50)
        enemy.RogA2 += int(enemy.LVL * 0.50)
        enemy.mindmgmit = enemy.LVL * 3
        enemy.maxdmgmit = enemy.LVL * 6
        enemy.mindmg = enemy.LVL * 5
        enemy.maxdmg = enemy.LVL * 25
        enemy.weaponType = 1
        enemy.armorType = 1
        # ETC, cut for post length


def classPick(hero):

    classList = ['Warrior', 'Rogue', 'Healer', 'Wizard', 'Monk']
    classLevels = [hero.WarLVL, hero.RogLVL,
                   hero.HeaLVL, hero.WizLVL, hero.MonLVL]
    print '                            -------'
    print '---------------------------| ARENA |----------------------------'
    print '                            -------'
    print
    charname = raw_input('Please enter your hero\'s name:')
    hero.name = charname
    print
    print 'Please pick a starting class:'
    classAnswered = 0
    while classAnswered == 0:
        classNumcounter = 0
        print
        while classNumcounter < 5:
            print '[' + str(classNumcounter + 1) + ']:', classList[classNumcounter]
            classNumcounter += 1
        print '[H]elp for more information.'
        classAnswer = raw_input('Please enter [1-5] or [H]')
        if classAnswer == 'H':
            # HAVE FUN HERE -- DON'T FORGET!!
            print
            print '----------------------------------------------------------------'
            print 'THE WARRIOR:'
            print 'The warrior is a heavily armored fighter using a sword,'
            print 'Versed in offensive and defensive tactics.'
            print 'This class is straightforward and harty, suited for beginners.'

            # ETC cut for post length

            print '----------------------------------------------------------------'
            print
        else:
            classAnswer = int(classAnswer)
            if classAnswer == 1:
                hero.WarLVL += 1
                hero.WarA1 += 1
                hero.WarA2 += 1
                hero.WarB += 1
                hero.WarP += 1
                hero.WarE += 1
                hero.weaponType = 3
                hero.armorType = 2
                hero.curClass = 1
            elif classAnswer == 2:
                hero.RogLVL += 1
                hero.RogA1 += 1
                hero.RogA2 += 1
                hero.RogB += 1
                hero.RogP += 1
                hero.RogE += 1
                hero.weaponType = 1
                hero.armorType = 1
                hero.curClass = 2
                # ETC, cut for post length

            classAnswered = 1
    classAnswer -= 1
    classList = ['Warrior', 'Rogue', 'Healer', 'Wizard', 'Monk']
    classLevels = [hero.WarLVL, hero.RogLVL,
                   hero.HeaLVL, hero.WizLVL, hero.MonLVL]
    print
    print 'Welcome to the Arena,', hero.name + ', the level', classLevels[classAnswer], classList[classAnswer] + '!'
    print
    print 'Please visit your [P]ersonal trainer before stepping into'
    print 'The Arena itself.'


def personalTrainer(hero):
    statCounter = 0
    while hero.visitedPTrainer == 0:
        print
        print 'Ah,', hero.name + ', come in, come in!'
        #ETC, cut for post length

        print
        hero.visitedPTrainer = 1

    statsList = ['STR', 'DEX', 'CON', 'AGI', 'INT', 'WIS']
    statsValues = [hero.STR, hero.DEX, hero.CON, hero.AGI, hero.INT, hero.WIS]
    print '------------------------'
    print 'Your current Stats:'
    while statCounter < 6:
        print '[' + str(statCounter + 1) + ']', statsList[statCounter] + ':', int(statsValues[statCounter])
        statCounter += 1
    print 'You have', hero.statPoints, 'points left.'
    print '------------------------'
    statPick = raw_input(
        'Train [1]-[6]; [V]iew detailed stats; [L]eave; [H]elp')
    if str.upper(statPick) == 'L':
        main()
    elif str.upper(statPick) == 'V':
        print '(Definitely not yet implemented...)'
        personalTrainer(hero)
    elif str.upper(statPick) == 'H':
        # more tedium
        print
        print '----------------------------------------------------------------'
        print 'STRENGTH:'
        print 'Strength determines your damage with swords and staves: (STR)%'
        print 'And your raw damage mitigation. ((STR-100)/10)%: 200STR = -10%'
        print 'It is especially important for Warriors and Healers.'
        print
        print 'DEXTERITY:'
        # ETC, cut for post length

        print '----------------------------------------------------------------'
        print
        personalTrainer(hero)
    else:
        if hero.statPoints <= 0:
            print 'You need more experience to train further.'
            personalTrainer(hero)
        statPick = int(statPick)
    if statPick == 1:
        STRadd = input(
            'How many points would you like to add to Strength? [0]-[' + str(hero.statPoints) + ']')
        if STRadd > hero.statPoints:
            STRadd = 0
            print 'You haven\'t gained enough experience to train so much!'
        hero.STR += STRadd
        hero.statPoints -= STRadd
        personalTrainer(hero)
    elif statPick == 2:
        DEXadd = input(
            'How many points would you like to add to Dexterity? [0]-[' + str(hero.statPoints) + ']')
        if DEXadd > hero.statPoints:
            DEXadd = 0
            print 'You haven\'t gained enough experience to train so much!'
        hero.DEX += DEXadd
        hero.statPoints -= DEXadd
        personalTrainer(hero)

    # Etc - cut for post length

    else:
        print 'Please pick something from inside the [brackets].'
        personalTrainer(hero)


def main():

    if hero.WarLVL == 0 and hero.RogLVL == 0 and hero.HeaLVL == 0 and hero.WizLVL == 0 and hero.MonLVL == 0:
        classPick(hero)

    print
    print 'What would you like to do?:'
    locationPick = raw_input(
        '[F]ight in the Arena; [C]lass Trainer; [P]ersonal Trainer; [H]elp')
    if str.upper(locationPick) == 'F':
        enemy = character()
        fight(hero, enemy)
    # ETC...


main()
\$\endgroup\$
2
  • \$\begingroup\$ If someone wants to point me to the proper location to use, I would be happy to post the uncut version and link it. The post here limits me to 30k characters, despite the scrollbar. \$\endgroup\$
    – Xodarap777
    Commented Feb 9, 2014 at 2:45
  • \$\begingroup\$ You might want to edit your off-site code based on the answer[s] you receive, then post the new code as a follow-up question. If there's something in a specific answer you don't understand (e.g. 'refactoring' or 'classes'), then ask about that answer by posting a comment to the answer. \$\endgroup\$
    – ChrisW
    Commented Feb 9, 2014 at 23:03

1 Answer 1

7
\$\begingroup\$

The primary problem I see is that your functions try to do everything from start to finish. For example fightRound knows all about the different kinds of weapons and armor and chances of critical hits and what effect these all have on the damage. It would benefit greatly being separated into multiple items that handle individual aspects of this. Consider a conductor of an orchestra: he doesn't tell violinist to pull the bow or the trumpet player when to breathe; the conductor just decides when the notes must be played.

You can make fightRound more like a conductor through something called refactoring. It's a process of finding similar code, turning it into reusable blocks, and using that new block without changing the meaning of your code. By giving that reusable block a name, it can become the master of its primary purpose (playing a trumpet note), and let the caller focus on conducting.

In your code you can find many good candidates for refactoring by looking for if/elif trees. Since I'm not seeing all of your code, I may guess the wrong pattern to simplify. But on the whole this approach should help reduce the amount of code, and increase the focus within each block. But watch out for any cases where my advice is wrong because I didn't have the full picture, and see if you can figure out how to tweak it into something that helps.

Refactoring into functions

Let's start with a simple refactoring. Here's some code from early on in fightRound:

if hero.weaponType == 0:
    hero.swing = hero.AGI * 3
elif hero.weaponType == 1:
    hero.swing = hero.AGI * 2
elif hero.weaponType > 1:
    hero.swing = hero.AGI

[...]

if enemy.weaponType == 0:
    enemy.swing = enemy.AGI * 3
elif enemy.weaponType == 1:
    enemy.swing = enemy.AGI * 2
elif enemy.weaponType > 1:
    enemy.swing = enemy.AGI

These two blocks are the same, except the first one references hero everywhere and the second references enemy. As a first step, I would like to see this become something like:

hero.swing = getWeaponSwing(hero)
enemy.swing = getWeaponSwing(enemy)

where getWeaponSwing(char) was a simple implementation of that common code.

Similarly the interleaved code that calculates attacks and misses is also repeated. This could become its own function, or perhaps merged with the suggested getWeaponSwing and given a more inclusive name.

Refactoring into data

A little further down, just past the comment # Just until I get items in..., there is an abbreviated if/elif tree that sets mindmg and maxdmg per the type of weapon. Let's consider what refactoring can do here. If the overall structure looks the same for all the omitted weaponType values, consider a data-driven refactoring:

# up above, probably globally:
WeaponDamageCoef = [
    (10, 10), # level 0
    (5, 25),  # level 1
    ... ]

# back in fightRound
coef = WeaponDamageCoef[hero.weaponType]
hero.mindmg = coef[0] * hero.LVL
hero.maxdmg = coef[1] * hero.LVL

# or, a more advanced way
hero.mindmg, hero.maxdmg = [coef * hero.LVL for coef in WeaponDamageCoef[hero.weaponType]]

Refactoring into classes

It's possible that the above scenario was simplified. Maybe it's not always a coefficient you can look up by weapon type, or it's not always multiplied by the level. If you need further customization, you can make multiple weapon classes that offer the same interface. Then store an instance of the weapon on your hero and enemy, and let the weapon figure out its thing. Here's a roughed out example of that:

class Weapon(object):
    def getDamageRange(self, char):
        return 10 * char.LVL, 10 * char.LVL

class MagicWeapon(object):     # or perhaps inherit from Weapon, or some shared base
    def getDamageRange(self, char):
        return 5 * char.LVL + 3 * char.INT, 7 * char.LVL + 8 * char.INT
...
hero.weapon = MagicWeapon()
...
hero.mindmg, hero.maxdmg = hero.weapon.getDamageRange(hero)

Next steps

Look for as many instances of repeating code as you can find, and try to refactor them into helpers. After you do this, the code should become smaller and easier to manage. You may find that after the first level of refactoring, other similarities start to become apparent and you can do further higher level refactorings.

There are a lot of other opportunities to improve this code. Refactoring won't fix them all. My hope is that once you reduce the quantity of code through good factoring, it will be easier to address the opportunities that remain.

\$\endgroup\$
2
  • \$\begingroup\$ I've seen mentions of "refactoring libraries" or modules for python. Is there anything that suggests or hints at refactoring in a proper or pythonian way? It seems like the kind of thing, oddly enough, that a python script could chew up (un-refactored) and spit out (refactored), at least as a starting point. It seems daunting to me at this point, even if I am starting over. \$\endgroup\$
    – Xodarap777
    Commented Mar 19, 2014 at 7:13
  • 1
    \$\begingroup\$ Proper refactoring seems enough of an art that seems like asking for a "proper" cubist rendition of a photograph. Refactoring is daunting, as it's work that really only pays off if it easier to review and reveal defects or easier to add more functionality. Doing it well takes a lot of practice; try it in small pieces, changing things in small steps as you constantly verify the program still does what you want after each step. \$\endgroup\$ Commented Mar 19, 2014 at 16:31

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.