Programming Moody : Calculating Probabilities

EDIT : [WORK IN PROGRESS]

So, I first posted this thread in the Other Metagames forum because I thought it could help some Anything Goes players, but it wasn't the right place to post it (as Eevee General told me). This thread can also provide figures when discussing about Moody in general. I'll try to make (and add to this thread) a small python code to calculate the probabilities quickly so that this thread could find its place here.

Calculating the probability of a specific stat of a Pokemon with the Ability Moody to be at stage "i" at "turn n" assuming that we are at "turn 0" (that is to say : the beginning of "turn 1").
Supposing that the Pokemon never gets Knocked Out nor have its stat raised or lowered by anything else than his Ability (Moody).

i ∈ ⟦-6 ; +6⟧
n ∈ ℕ

Moody: At the end of each turn, Moody raises one of the stats of the Pokemon with this Ability by two stages (at random), then decreases another stat by one stage (at random). It can select Accuracy and Evasion. Moody will not attempt to lower a stat that is already at -6 or attempt to increase a stat that is already at +6. Moody will not select the same stat to lower as it raised.
(Taken from Bulbapedia)


So we have a total of 7 stats to deal with (Attack, Defense, Sp. Atk., Sp. Def., Speed, Accuracy and Evasion) and it’s totally random.

========================================================================================================
Statistical approach :​
========================================================================================================
I believe this has already be done before, but I cannot find the old thread.

This approach doesn't give the exact theoretical probabilities but something very close to them. It's reliable enough so that people can discuss about it. I'll probably add a table later.

from random import random
from matplotlib.pyplot import plot, grid, show

def Moody(Stats_Boosts): # Stats_Boosts=[Attack,Defense,SpAtk,SpDef,Speed,Accuracy,Evasion]
---StatPlus=[]
---for i in range(0,len(Stats_Boosts)) :
------if Stats_Boosts < 6 :
---------StatPlus.append(i)
---if StatPlus != [] :
------r = int(len(StatPlus)*random())
------if Stats_Boosts[StatPlus[r]] == 5 :
---------Stats_Boosts[StatPlus[r]] = 6
------else :
---------Stats_Boosts[StatPlus[r]] += 2
---StatMinus=[]
---for i in range(0,len(Stats_Boosts)) :
------if Stats_Boosts > -6 and i != StatPlus[r] :
---------StatMinus.append(i)
---if StatMinus != [] :
------q = int(len(StatMinus)*random())
------Stats_Boosts[StatMinus[q]] -= 1
---return Stats_Boosts

def Stats_Boosts_After_n_Turns_of_Moody(n):
---Stats_Boosts = [0,0,0,0,0,0,0]
---for i in range (1,n+1):
------Stats_Boosts = Moody(Stats_Boosts)
---return Stats_Boosts

def Statistics(n,i): # turn n, i itérations
---freq_minus_6 = 0 ; freq_minus_5 = 0 ; freq_minus_4 = 0 ; freq_minus_3 = 0 ; freq_minus_2 = 0 ; freq_minus_1 = 0 ; freq_0 = 0 ; freq_plus_1 = 0 ; freq_plus_2 = 0 ; freq_plus_3 = 0 ; freq_plus_4 = 0 ; freq_plus_5 = 0 ; freq_plus_6 = 0
---for _ in range (1,i+1):
------L = Stats_Boosts_After_n_Turns_of_Moody(n)
------if L[0] == -6 :
---------freq_minus_6 += 1
------if L[0] == -5 :
---------freq_minus_5 += 1
------if L[0] == -4 :
---------freq_minus_4 += 1
------if L[0] == -3 :
---------freq_minus_3 += 1
------if L[0] == -2 :
---------freq_minus_2 += 1
------if L[0] == -1 :
---------freq_minus_1 += 1
------if L[0] == 0 :
---------freq_0 += 1
------if L[0] == 1 :
---------freq_plus_1 += 1
------if L[0] == 2 :
---------freq_plus_2 += 1
------if L[0] == 3 :
---------freq_plus_3 += 1
------if L[0] == 4 :
---------freq_plus_4 += 1
------if L[0] == 5 :
---------freq_plus_5 += 1
------if L[0] == 6 :
---------freq_plus_6 += 1
---return [freq_minus_6/i, freq_minus_5/i, freq_minus_4/i, freq_minus_3/i, freq_minus_2/i, freq_minus_1/i, freq_0/i, freq_plus_1/i, freq_plus_2/i, freq_plus_3/i, freq_plus_4/i, freq_plus_5/i, freq_plus_6/i]

def Representer_Courbe(n,i): # n turns, i itérations
---Y_minus_6=[] ; Y_minus_5=[] ; Y_minus_4=[] ; Y_minus_3=[] ; Y_minus_2=[] ; Y_minus_1=[] ; Y_0=[] ; Y_plus_1=[] ; Y_plus_2=[] ; Y_plus_3=[] ; Y_plus_4=[] ; Y_plus_5=[] ; Y_plus_6=[] ; X=[]
---for j in range (1,n+1):
------X.append(j)
------L = Statistics(j,i)
------Y_minus_6.append(L[0])
------Y_minus_5.append(L[1])
------Y_minus_4.append(L[2])
------Y_minus_3.append(L[3])
------Y_minus_2.append(L[4])
------Y_minus_1.append(L[5])
------Y_0.append(L[6])
------Y_plus_1.append(L[7])
------Y_plus_2.append(L[8])
------Y_plus_3.append(L[9])
------Y_plus_4.append(L[10])
------Y_plus_5.append(L[11])
------Y_plus_6.append(L[12])
---plot(X,Y_minus_6,color=[0,0,1])
---plot(X,Y_minus_5,color=[1/12,1/12,5/6])
---plot(X,Y_minus_4,color=[2/12,2/12,4/6])
---plot(X,Y_minus_3,color=[3/12,3/12,3/6])
---plot(X,Y_minus_2,color=[4/12,4/12,2/6])
---plot(X,Y_minus_1,color=[5/12,5/12,1/6])
---plot(X,Y_0,color=[1,1,0])
---plot(X,Y_plus_1,color=[1,5/6,0])
---plot(X,Y_plus_2,color=[1,4/6,0])
---plot(X,Y_plus_3,color=[1,3/6,0])
---plot(X,Y_plus_4,color=[1,2/6,0])
---plot(X,Y_plus_5,color=[1,1/6,0])
---plot(X,Y_plus_6,color=[1,0,0])
---grid(True) ; show()

Maybe the code can be improved a little bit. Anyway, here are the results for turn 1 to turn 100 with 10,000 iterations for each turn.
At the end all stats are at +6 except one stat at +5. The system converge toward a limit. This can easily be proven analytically. (Correct me if I'm wrong because I do not know the English mathematical vocabulary).

Graph :​


========================================================================================================
Analytical approach :​
========================================================================================================

/
 
Last edited:
have you tried to verify those probabilities?
Because, just based on the ability's description you gave, I'm arriving at a different understanding.

First, a random stat S is chosen for increment by 2. The probability for S = X is

P(S=X) = 1 / (7-n),​

IF Value(S) ≠ +6. Here, n is the number of Stats currently at +6.
It is zero if Value(X) = +6.
--
Second, another Stat s is selected for decrement by 1. The probability for s = X is

P(s=X) = 1 / (7-m-1)​

for S unequal to X and Value(S) ≠ -6. Here, m is the number of Stats currently at -6.
It is 0 if either X = S or if Value(X) = -6.

In conlusion, we would have:
Code:
P(s=X) = [1 - P(S=X)] * 1 / (7-m-1)
IF X is currently not at -6.
This can be written as
Code:
P(s=X) = [1 - P(S=X)] * P( Value(X)=-6 ) * 1 / (7-m-1),
but will probably just cause unnecessary confusion. since P( Value(X)=-6 ) is always either 1 or 0 (just a test function, effectively) :P


You seem to have been assuming that the stat to increment S and the stat to decrement s are chosen at the same time, thus the symmetry in your formula. But the description states that the increment is chosen first, and then the decrement is chosen so as to not be the increment.

Edit: The description is unclear about what happens if all stats are already at +6, and what if all are at -6. (In the 2nd case, the stat to decrement has no valid choice since it would have to be the one that was just incremented.)
 
Last edited:
No, I didn't verified the probabilities (I don't know how I could do this).
And yes, you are right I messed up a few things. I'll try to fix them.

EDIT: about the description. The two cases : All stats at +6 and all stats at -6 will never happen.

If at turn "n" all stats are at "+6" : that means that at turn "n-1" one stat was at "+4" or "+5" (the stat that has been raised), that 5 stats were at "+6", and that one stat was at "+7" (the stat that has been lowered) which is impossible.

If at turn "n" all stats are at "-6" : that means that at turn "n-1" one stat was at "-8" (the stat that has been raised) which is impossible, that 5 stats were at "-6", and that one stat was at "-5" (the stat that has been lowered).
 
Last edited:
No, I didn't verified the probabilities (I don't know how I could do this).
Ah, I was thinking to get into a few games with moody and see if the frequencies match your expectations.

Alternatively, here is the source code PokemonShowdown uses to handle the ability "Moody" (just hit F3 and search for the word). I'm assuming they did a bit of research to make sure it works like it does in the games. https://github.com/Zarel/Pokemon-Showdown/blob/master/data/abilities.js
 
I did a few corrections. If someone could tell me how I should write/represent the matrix "A" it would be great.
 
Last edited:
So... I got into a few 1,000,000 games with moody and here are the frequencies :

Just kidding, I added a statistical approach to the issue (in the first post), and with the code everyone can now calculate the probabilities.
 
Moody will not attempt to lower a stat that is already at -6 or attempt to increase a stat that is already at +6. Moody will not select the same stat to lower as it raised.

Judging by your graph it looks like you didn't take this into account? It should definitely tend towards to 100% rather than 86.6%. Also these are statistical results yes?

empirical results in my beloved pure mathematics n__n
 
yuruuu: You're assuming that the end state will be 6 6 6 6 6 6 6. (Every stat at +6 with 100% probability.)
This would be the case if Moody first decreased a stat, then increased another.

Instead, note that it's the other way around: First the increment, then the decrement.
The stable end state is 6 6 6 6 6 6 5, with a randomly chosen stat at +5, and the state 6 6 6 6 6 6 6 cannot be reached.

Now, the probability of any given stat being at +5 rather than +6 approaches 1/7 = 14.285714..%. Conversely, p(+6, p) approaches 85.714285..%.

Edit: Note that 14.285714..% is the limit approached by the second curve from the top. The only reason they don't visually line up is that the top ends at .9 rather than 1.0
 
Last edited:
yuruuu: You're assuming that the end state will be 6 6 6 6 6 6 6. (Every stat at +6 with 100% probability.)
This would be the case if Moody first decreased a stat, then increased another.

Instead, note that it's the other way around: First the increment, then the decrement.
The stable end state is 6 6 6 6 6 6 5, with a randomly chosen stat at +5, and the state 6 6 6 6 6 6 6 cannot be reached.

Now, the probability of any given stat being at +5 rather than +6 approaches 1/7 = 14.285714%. Conversely, p(+6, p) approaches 85.714285%.
My mistake. Misread bulbapedia.
 

Users Who Are Viewing This Thread (Users: 1, Guests: 0)

Top