Randomizing distribution points on a list of things

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
kyuuuri
Posts: 340
Joined: 09 Jan 2016, 19:20

Randomizing distribution points on a list of things

13 Aug 2019, 00:10

Hello, I'm making a random character generator for an rpg like d&d where I have a list of "profiles" (like archer, swordman, etc.), 6 stats (Agi, Dex, etc.), a list of weapons, a list of armors and a list of skills.
With all that in mind I have X and Y amount of points depending on the lvl of the npc, X being stat points and Y being skill points.

What I need it to do:
  1. Randomly select a level. Done
  2. Randomly select a profile. Done
  3. Randomly distribute stat points depending on that profile and lvl. Need Help
  4. Randomly distribute skill points depending on that profile and lvl. Need Help
  5. Randomly picking an armor, helmet and weapons depending on level and profile. Done
For 3.
The rules for this part are:
  1. Stat cannot be lower than 5.
  2. Stat cannot be higher than 31.
For 4.
The rules for this part are:
  1. Skill can be 0.
  2. Skill cannot be higher than 61.
My problem comes when I need to distribute the points randomly in a way that it has more "weight" in some specific skills/stats. For example, an Archer would have more Dex because the bow skill depends on Dex, this will make it have more points in Dex related skills.
Also, I was thinking about looping a random on each skill but again this will make it have less points or make some weird results like: "Bow skill: 61, Running skill: 50, Talking skill: 50" and the rest would be 0 because it ran out of points on the first 3.

So my final question is: Is it posible to distribute a defined amount of points in a list of "skills" adding some "weight" to some of them without having the problems I explained before?

Thanks in advance
User avatar
Sir Teddy the First
Posts: 94
Joined: 05 Aug 2019, 12:31
Contact:

Re: Randomizing distribution points on a list of things

13 Aug 2019, 07:09

Hi,
I just came up with this solution for the Stat-Distribution, but it should be the exact same thing with the skillpoints and you should be able to adapt that yourself.

This is my code, I'll explain it below.

Code: Select all

#SingleInstance Force

StatList := ["Str", "Con", "Dex", "Int", "Wis", "Char"]
StatArray := Array()

StatPoints := 85

Limit_Min := 5
Limit_Max := 31


if % (Floor(StatPoints/StatList.Length()) < Limit_Min)
{
    MsgBox %  "ERROR! Not enough Points! Minimum: " StatList.Length() * Limit_Min
    return
}

; >>>>> Generate Stats

Prof_Archer := [1,1,1.8,1.3,1,1.1] ; Stat-Weight (Multiplier, minimum of 1)

WeightArray := Array()

Loop, % StatList.Length()
{
    if Prof_Archer[A_Index] != 1
        WeightArray.Push(A_Index)
}

 ;Weight Stats

Loop, % StatList.Length()
{
    NumCache := A_Index
    NoLoop := false
    Loop, % WeightArray.Length()
    {
        NumTest := WeightArray[A_Index]
        if (NumCache = NumTest)
            NoLoop := true
    }
    if NoLoop
    {
        StatArray[A_Index] := 0
        continue
    }
        
    Random RanStat, %Limit_Min%, % Min(Limit_Max, (Floor(StatPoints/StatList.Length())) - 1)
    PointTrack += RanStat
    
    StatArray[A_Index] := RanStat  
}

RanAv := PointTrack / (StatList.Length() - WeightArray.Length())

Loop, % WeightArray.Length()
{  
    Num := WeightArray[A_Index]
    StatWeighted := Ceil((Prof_Archer[Num] * RanAv))
    
    StatArray[Num] := StatWeighted
    PointTrack += StatWeighted
    
    ; Make sure these are the highest numbers
    
    for index, value in StatArray
    {
        Loop, % WeightArray.Length()
        {
            If (index = WeightArray[A_Index])
                continue, 2
        }
        if (value >= StatWeighted)
        {
            StatNew := value - 1 - (value - StatWeighted)
            
            StatArray[index] := StatNew
            PointTrack := PointTrack - 1 - (value - StatWeighted)
        }    
    }
}


LeftPoints := StatPoints - PointTrack

Loop, % Floor(LeftPoints / StatArray.Length()) * StatArray.Length()
{
    ArrayNum := Mod(A_Index, StatArray.Length())
    if !ArrayNum
        ArrayNum := StatArray.Length()
    
    NumCache := StatArray[ArrayNum]
    NumCache++
    PointTrack++
    
    StatArray[ArrayNum] := NumCache
}

if (PointTrack < StatPoints)
{   
    BorderNum := Limit_Max
    Loop, % Prof_Archer.Length()
    {
        NumCache := Prof_Archer[A_Index]
        
        if (NumCache > 1 && NumCache < BorderNum && PointTrack < StatPoints)
        {
            StatCache := StatArray[A_Index]
            StatCache++
            
            StatArray[A_Index] := StatCache
            
            BorderNum := NumCache
            
            PointTrack++
        }
    }
}

LeftPoints := StatPoints - PointTrack

if Leftpoints
{
    Loop, %Leftpoints%
    {
        Random, RandInt, 1, % StatArray.Length()
        StatCache := StatArray[RandInt]
        StatCache++
            
        StatArray[RandInt] := StatCache
        
        PointTrack++
    }
}

;LeftPoints := StatPoints - PointTrack
;MsgBox %LeftPoints%

; Final Output
MsgBox % "Strength:`t" StatArray[1] "`n" "Constitution:`t" StatArray[2] "`n" "Dexterity:`t" StatArray[3] "`n" "Intelligence:`t" StatArray[4] "`n" "Wisdom:`t`t" StatArray[5] "`n" "Charisma:`t" StatArray[6] "`n"
Explanation:
  • The Stat-List-Array's solely purpose is for you to see in which way the stats are ordered inside the arrays. These are just some dummy-stats, you can change them and add more, just make sure that the number of stats matches the number of multipliers inside the profile.
  • I just set the StatPoints for my testing to 100, you can adjust that to whatever you want, even make it dependent on the level of the character
  • Limit_Min and Limit_Max are pretty self-explanatory
  • The "if-Statement" will just make sure that there are enough StatPoints to reach the minimum amount per Stat
  • The "Prof_Archer" is especially important: Here you define a list of stats that will have a special weight to them: 1 means no additional weight and 1 is also the minimum. Everything above 1 will weight that skill accordingly. I think it is clear that you should not assign a special weight to all skills, that would not make any sense at all
  • The "WeightArray" is just there to memorize the index of the stats that have a special weight applied
  • The "Weighting-Process" works like this: First of all the "unweighted" stats are assigned random values with a maximum of a bit less than what they would have if the stat-points were distributed equally.
    Then, the stat points that have been spent so far are added and the average is determined.
    The "weighted" stats use this average mutliplied with their multiplier you set in the profile, so that they always have higher values than the rest.
    To ensure that this is always the case, the "StatArray" is scanned and stats that have the same value than the weighted ones are decreased
  • In the end, if there are enough additional statpoints, all stats are incremented, if not, only the weighted ones and any extra statpoints are distributed randomly. This ensures that the weighted ones still have the highest numbers
I hope this solves your problem :think: .
:eh: :think:
kyuuuri
Posts: 340
Joined: 09 Jan 2016, 19:20

Re: Randomizing distribution points on a list of things

13 Aug 2019, 16:52

Thank you for the reply, I really like that approach. I don't know why it keeps adding more stat points than it should. For example when using 30 it should add 5 to each stat because there are 6 and the minimum is 5 so 6*5=30 but I end up with some stats having 4,9,7,5,6
User avatar
berban
Posts: 97
Joined: 14 Apr 2014, 03:20

Re: Randomizing distribution points on a list of things

13 Aug 2019, 18:33

Hey kyuuuri! I was intrigued by this question so I actually made a function to create normally distributed random numbers. You can find the code here: https://www.autohotkey.com/boards/viewtopic.php?f=6&t=67066

What this does is allows you to get a random number that is weighted around a central value, just like most things are in real life. For instance, while humans may range from 3 to 8 feet in height, it isn't accurate to say that 20% of people are between 7 and 8 feet tall. However, this is what you will get if you use a regular random number function.

To understand how it works, you need to understand the concept of a standard deviation. The standard deviation represents the flatness of the bell curve. A higher standard deviation is like a wider bell curve with more results further from the average (the peak of the curve).

In your particular case, you say the stat needs to be between 5 and 31. That would give you a mean (average) of 18. So you might want to choose a standard deviation of about 3.5. That would result in 90% of the results being between 9 and 27. You can test it out and choose a higher or lower value depending on how much spread you want.

I don't know if this is what you were looking for, but at any rate I thought it was an interesting exercise so no worries if it doesn't end up being relevant :)
User avatar
Sir Teddy the First
Posts: 94
Joined: 05 Aug 2019, 12:31
Contact:

Re: Randomizing distribution points on a list of things

14 Aug 2019, 02:54

Hi,
whichever method you prefer, just for the sake of completion:

Code: Select all

#SingleInstance Force

StatList := ["Str", "Con", "Dex", "Int", "Wis", "Char"]
StatArray := Array()

StatPoints := 40

Limit_Min := 5
Limit_Max := 31


if (StatPoints < 8*Limit_Min)
{
    MsgBox %  "ERROR! Not enough Points! Minimum: " 8 * Limit_Min
    return
}

; >>>>> Generate Stats

Prof_Archer := [1.5,1,1,1,1.7,1] ; Stat-Weight (Multiplier, minimum of 1)

WeightArray := Array()

Loop, % StatList.Length()
{
    if Prof_Archer[A_Index] != 1
        WeightArray.Push(A_Index)
}

 ;Weight Stats

Loop, % StatList.Length()
{
    NumCache := A_Index
    NoLoop := false
    Loop, % WeightArray.Length()
    {
        NumTest := WeightArray[A_Index]
        if (NumCache = NumTest)
            NoLoop := true
    }
    if NoLoop
    {
        StatArray[A_Index] := 0
        continue
    }
        
    Random RanStat, %Limit_Min%, % Min(Limit_Max, (Floor(StatPoints/StatList.Length())))
        
    PointTrack += RanStat
    
    StatArray[A_Index] := RanStat  
}

RanAv := PointTrack / (StatList.Length() - WeightArray.Length())


Loop, % WeightArray.Length()
{  
    Num := WeightArray[A_Index]
    StatWeighted := Ceil((Prof_Archer[Num] * RanAv))
    
    StatArray[Num] := StatWeighted
    PointTrack += StatWeighted
    
    ; Make sure these are the highest numbers
    
    for index, value in StatArray
    {
        Loop, % WeightArray.Length()
        {
            If (index = WeightArray[A_Index])
                continue, 2
        }
        if (value >= StatWeighted)
        {
            StatNew := value - 1 - (value - StatWeighted)
            PointTrack := PointTrack - 1 - (value - StatWeighted)
            
            If (StatNew < Limit_Min)
            {
                Loop
                {
                    StatNew++
                    PointTrack++    
                }Until (StatNew >= Limit_Min)
            }
            StatArray[index] := StatNew
            
        }    
    }
}


; Check if there are too many StatPoints
if (PointTrack > StatPoints)
{
    ;Decrease randomly
    Loop
    {
        Random, RandInt, 1, % StatArray.Length()
        
        NumCache := StatArray[RandInt]
        
        if (NumCache > Limit_Min)
        {
            NumCache--
            PointTrack--
        }
        
        StatArray[RandInt] := NumCache
    }Until (PointTrack < StatPoints)
}

else
{
    ; Incremend every value if there are enough points
    Loop, % Floor((StatPoints - PointTrack) / StatArray.Length()) * StatArray.Length()
    {
        ArrayNum := Mod(A_Index, StatArray.Length())
        if !ArrayNum
            ArrayNum := StatArray.Length()
        
        NumCache := StatArray[ArrayNum]
        NumCache++
        PointTrack++
        
        StatArray[ArrayNum] := NumCache
    }
}

; Incremend the weighted stats
if (PointTrack < StatPoints)
{   
    BorderNum := Limit_Max
    Loop, % Prof_Archer.Length()
    {
        NumCache := Prof_Archer[A_Index]
        
        if (NumCache > 1 && NumCache < BorderNum && PointTrack < StatPoints)
        {
            StatCache := StatArray[A_Index]
            StatCache++
            
            StatArray[A_Index] := StatCache
            
            BorderNum := NumCache
            
            PointTrack++
        }
    }
}

if (PointTrack > StatPoints)
{
    MsgBox Maximal Number of Points has been exceeded!
    return
}


; Increment randomly
if (PointTrack < StatPoints)
{
    Loop, % (StatPoints - PointTrack)
    {
        Random, RandInt, 1, % StatArray.Length()
        StatCache := StatArray[RandInt]
        StatCache++
            
        StatArray[RandInt] := StatCache
        
        PointTrack++
    }
}

; Final Output
MsgBox % "Strength:`t" StatArray[1] "`n" "Constitution:`t" StatArray[2] "`n" "Dexterity:`t" StatArray[3] "`n" "Intelligence:`t" StatArray[4] "`n" "Wisdom:`t`t" StatArray[5] "`n" "Charisma:`t" StatArray[6] "`n"
I did not think of the possibility that the script might put out more points than it is actually allowed to. I changed a few things, but most notably:
  • The script won't run if the lower limit is too low, meaning that if you distribute the stats equally, there will be no more space for weighting, which is the case for 30 skillpoints with a minimum of 5
  • The script will now always output the right amount of points, even if your weighting is way to high, but for numbers smaller than 100 I would suggest keeping the multipliers in the range of 1 to 2
That's it. I hope it works now.
:eh: :think:
kyuuuri
Posts: 340
Joined: 09 Jan 2016, 19:20

Re: Randomizing distribution points on a list of things

15 Aug 2019, 12:39

Hello, I'm not able to test the scripts because I'm won't be at home until weekend. Just wanted to pass by and say thank you for answering, will answer again once I check both scripts.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: No registered users and 333 guests