HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Zalera calculator

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
calculatorzalerastackoverflow

Problem

I recently started replaying Final Fantasy XII. One of its optional bosses, Zalera, has, among others, the following mechanics:

His attack sequence is Lv. 2 Sleep, Lv. 3 Disable, Lv. 4 Break and Lv. 5 Reverse, affecting characters whose levels are divisible by 2, 3, 4 or 5, respectively, and then Prime Lv. Death, which affects characters whose levels are a prime number.

Character levels not affected by any of these spells are levels 49, 77, and 91. Levels (under 100) susceptible to Prime Lv. Death are: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, and 97.

To calculate which levels were affected by what attack, I wrote a calculator. It takes 1 to 6 numbers between 1 and 99 inclusive and reports what will affect the player characters with those numbers. Since there's at maximum 6 player characters under the player's control, I limited the amount to a full party size of 6.
Code:

```
from argparse import ArgumentParser

class ZaleraCalculator:
def __init__(self, vars):
self.vars = vars
self.vulnerable = {
2: "Sleep",
3: "Disable",
4: "Break",
5: "Reverse"
}

def isDivisibleByN(self, x, n):
if x % n == 0:
print("Divisible by {}, vulnerable to {}."
.format(n, self.vulnerable[n]))

def isPrime(self, x):
# Primality test, works good enough for values this low
if 2 in [x, pow(2, x, x)]:
print("Prime, vulnerable to Death.")

def generateReport(self):
for i in self.vars:
print("\nCharacter with Lvl: {}".format(i))
if i 99:
print("Arguments not between 1 and 99 inclusive are illegal.")
else:
if i 6:
print(
"Too many arguments were provided. Guest characters are irrelevant.")
elif not args.vars:
print("No arguments were provided.")
else:
calc = ZaleraCalculator(a

Solution

I think that using a class here is overkill. You don't need to maintain any state, you just need to analyze the values provided by the user. Moreover, primality or divisability testing have almost nothing to do with this class, they are just tools it uses and should not be methods.

So I would write these functions, one of them using a constant:

SPELLS = {
    2: "Sleep",
    3: "Disable",
    4: "Break",
    5: "Reverse",
}

def usable_spells(level):
    for spell_level, effect in SPELLS.iteritems():
        if level % spell_level == 0:
            yield spell_level, effect

def is_prime(x):
    # Primality test, works good enough for values this low
    return 2 in [x, pow(2, x, x)]

def generate_report(character_levels, zalera_level=40):
    for level in character_levels:
        print "\nCharacter with Lvl:", level
        if level < zalera_level:
            print "Character level lower than Zalera's level,", zalera_level
        for spell in usable_spells(level):
            print "Divisible by {}, vulnerable to {}.".format(*spell)
        if is_prime(level):
            print "Prime, vulnerable to Death."


I changed a couple of things:

  • used Python's naming conventions (snake_case);



  • removed some calls to format because you can get the same result using plain old print;



  • iterated through the spells using a loop rather than manually enumerating all the values (you can force the top-down ordering using sorted(SPELLS.iteritems(), reverse=True);



  • added Zalera's level as a parameter with a default value, to name it;



  • removed the check for level correctness as we can ask argparse to do it for us.



Now let's look at how to build a better command-line parser.

First off, nargs can take the value '+' to indicate that at least one value is required. Second, instead of printing the error yourself (to stdout!), you can tell argparse that you encountered an error analyzing its results: just call error() on your parser object.

Lastly, you may want to make use of the choices parameter on add_argument as it allow you to pass an iterable of values that will be considered acceptable by argparse; the rest being invalid. In the end, I would write:

def parse_command_line():
    parser = ArgumentParser(description='FFXII Zalera calculator')
    parser.add_argument(
        "levels",
        type=int,
        help="One or more character levels, excluding guest characters.",
        nargs="+",
        choices=xrange(1, 100),
        metavar="CHARACTER_LEVEL")
    args = parser.parse_args()

    if len(args.levels) > 6:
        parser.error(
            "Too many arguments were provided. "
            "Guest characters are irrelevant.")
    return args.levels

if __name__ == "__main__":
    generate_report(parse_command_line())


The metavar parameter is here to avoid that argparse prints the whole set of choices on the help message. Also note the way to break too long strings literal: as long as there is a line continuation (implicit such as inside parenthesis or explicit using \ at the end of a line) consecutive string literals will be considered the same string.

Code Snippets

SPELLS = {
    2: "Sleep",
    3: "Disable",
    4: "Break",
    5: "Reverse",
}


def usable_spells(level):
    for spell_level, effect in SPELLS.iteritems():
        if level % spell_level == 0:
            yield spell_level, effect


def is_prime(x):
    # Primality test, works good enough for values this low
    return 2 in [x, pow(2, x, x)]


def generate_report(character_levels, zalera_level=40):
    for level in character_levels:
        print "\nCharacter with Lvl:", level
        if level < zalera_level:
            print "Character level lower than Zalera's level,", zalera_level
        for spell in usable_spells(level):
            print "Divisible by {}, vulnerable to {}.".format(*spell)
        if is_prime(level):
            print "Prime, vulnerable to Death."
def parse_command_line():
    parser = ArgumentParser(description='FFXII Zalera calculator')
    parser.add_argument(
        "levels",
        type=int,
        help="One or more character levels, excluding guest characters.",
        nargs="+",
        choices=xrange(1, 100),
        metavar="CHARACTER_LEVEL")
    args = parser.parse_args()

    if len(args.levels) > 6:
        parser.error(
            "Too many arguments were provided. "
            "Guest characters are irrelevant.")
    return args.levels


if __name__ == "__main__":
    generate_report(parse_command_line())

Context

StackExchange Code Review Q#146417, answer score: 7

Revisions (0)

No revisions yet.