X-Git-Url: http://git.veekun.com/zzz-pokedex.git/blobdiff_plain/b6f30bf22db8ef0cfc13ed4fbbd1d88c4ec67e17..367f9d875282304de9c2390d5dbda410545226a2:/pokedex/formulae.py diff --git a/pokedex/formulae.py b/pokedex/formulae.py index 2059db7..29ab514 100644 --- a/pokedex/formulae.py +++ b/pokedex/formulae.py @@ -1,5 +1,22 @@ # encoding: utf8 """Faithful translations of calculations the games make.""" +from __future__ import division + +from itertools import izip + +def nCr(n, r): + """n-choose-r. + + Thanks for the "compact" solution go to: + http://stackoverflow.com/questions/2096573/counting-combinations-and-permutations-efficiently + """ + + return reduce( + lambda x, y: x * y[0] / y[1], + izip(xrange(n - r + 1, n + 1), + xrange(1, r + 1)), + 1) + def calculated_stat(base_stat, level, iv, effort): """Returns the calculated stat -- i.e. the value actually shown in the game @@ -19,3 +36,75 @@ def calculated_hp(base_hp, level, iv, effort): return 1 return (base_hp * 2 + iv + effort // 4) * level // 100 + 10 + level + +def earned_exp(base_exp, level): + """Returns the amount of EXP earned when defeating a Pokémon at the given + level. + """ + + return base_exp * level // 7 + +def capture_chance(percent_hp, capture_rate, + ball_bonus=1, status_bonus=1, heavy_modifier=0): + """Calculates the chance that a Pokémon will be caught, given its capture + rate and the percentage of HP it has remaining. + + Returns five values: the chance of a capture, then the chance of the ball + shaking three, two, one, or zero times. Each of these is a float such that + 0.0 <= n <= 1.0. Feel free to ignore all but the first. + """ + + if heavy_modifier: + # Only used by Heavy Ball. Changes the target's capture rate outright + capture_rate += heavy_modifier + if capture_rate <= 1: + capture_rate = 1 + + # This should really be integer math, right? But the formula uses FOURTH + # ROOTS in a moment, so it can't possibly be. It probably doesn't matter + # either way, so whatever; use regular ol' division. ball_bonus and + # status_bonus can be 1.5, anyway. + + # A slight math note: + # The formula is originally: (3 max - 2 curr) rate bonus / (3 max) + # I have reduced this to: (1 - 2/3 * pct) rate bonus + # My rationale is that this cannot possibly be integer math, so rounding is + # not a problem and commutation won't make a difference. It also + # simplifies the input considerably. + base_chance = (1 - 2/3 * percent_hp) * capture_rate \ + * ball_bonus * status_bonus + + shake_index = (base_chance / 255) ** 0.25 * (2**16 - 1) + + # Iff base_chance < 255, then shake_index < 65535. + # The game now picks four random uwords. However many of them are <= + # shake_index is the number of times the ball will shake. If all four are + # <= shake_index, the Pokémon is caught. + + # If shake_index >= 65535, all four randoms must be <= it, and the Pokémon + # will be caught. Skip hard math + if shake_index >= 65535: + return (1.0, 0.0, 0.0, 0.0, 0.0) + + # This brings up an interesting invariant: sum(return_value) == 1.0. + # Something is guaranteed to happen. + + # Alrighty. Here's some probability. + # The chance that a single random number will be <= shake_index is: + p = (shake_index + 1) / 65536 + # Now, the chance that two random numbers will be <= shake_index is p**2. + # And the chance that neither will be is (1 - p)**2. + # With me so far? + # The chance that one will be and one will NOT be is p * (1 - p) * 2. + # The 2 is because they can go in any order: the first could be less, or + # the second could be less. That 2 is actually nCr(2, 1); the number of + # ways of picking one item in any order from a group of two. + # Try it yourself add up those three values and you'll get 1. + + # Right. Hopefully, the following now makes sense. + # There are five cases: four randoms are <= shake_index (which means + # capture), or three are, etc. + return [ + p**i * (1 - p)**(4 - i) * nCr(4, i) + for i in reversed(range(5)) + ]