veekun_pokedex/pokedex/formulae.py

110 lines
4.1 KiB
Python

# 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
on a Pokémon's status tab.
"""
# Remember: this is from C; use floor division!
return (base_stat * 2 + iv + effort // 4) * level // 100 + 5
def calculated_hp(base_hp, level, iv, effort):
"""Similar to `calculated_stat`, except with a slightly different formula
used specifically for HP.
"""
# Shedinja's base stat of 1 is special; its HP is always 1
if base_hp == 1:
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))
]