• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[JASS] Pick Random Unit without BJ

Status
Not open for further replies.
Level 18
Joined
Nov 1, 2006
Messages
1,612
This is a snippet of the code I was working on.

It definitely works and picks a completely random unit every time. I was looking to get around using the BJ function GroupPickRandomUnit() which also calls another BJ GroupPickRandomEnum()

Before I continue with it, what do you guys think? Does it look like it would be more efficient than just calling the random unit function?

JASS:
function Random_Filter takes nothing returns boolean
    //Add your qualifiers here for what type of units you want to pick randomly from
    return GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0 and GetUnitTypeId(GetFilterUnit()) == 'e000' or GetUnitTypeId(GetFilterUnit()) == 'e002'
endfunction

function Trig_PickRandom_Actions takes nothing returns nothing
    local unit t = GetTriggerUnit()
    local unit u
    local player p = GetOwningPlayer(t)
    local real x
    local real y
    local integer i = 0
    local integer j = 0
    local integer e
    local integer v
    local integer z
    local group g = CreateGroup()
    local group h = CreateGroup()
    call GroupEnumUnitsInRange(g, x, y, 99999.00, Condition(function Random_Filter))
    //Replace the upper limit integer with how many random units you would like picked
    set v = GetRandomInt(0, 1)
    loop
        exitwhen j == v
        set j = (j + 1)
        set z = CountUnitsInGroup(g)
        //A random unit is picked below
        set e = GetRandomInt(0, z)
        set i = 0
        loop
            exitwhen i == e
            set i = (i+1)
            set u = FirstOfGroup(g)
            call GroupRemoveUnit(g, u)
            if i == e then
                //Do Actions to the random unit
                    set x = GetUnitX(u)
                    set y = GetUnitY(u)
                    call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Demon\\DarkPortal\\DarkPortalTarget.mdl", x, y))
            else
                //Adds unused (counted) units to second group
                call GroupAddUnit(h, u)
            endif
        endloop
        set e = CountUnitsInGroup(h)
        set i = 0
        //Adds unused removed (counted) units back into first group
        loop
            exitwhen i == e
            set i = (i+1)
            set u = FirstOfGroup(h)
            call GroupAddUnit(g, u)
            call GroupRemoveUnit(h, u)
        endloop
    endloop
    call DestroyGroup(g)
    call DestroyGroup(h)
    set g = null
    set h = null
    set t = null
    set u = null
    set p = null
endfunction
 
Last edited:
Level 20
Joined
Jul 14, 2011
Messages
3,213
What about checking a random integer?

Set FirstOfGroup = asdf
If GetRandomInt(0, 100) <= 25 then
do your stuff
else
remove the unit from the group and add it to another group. If any unit is picked in the first group, loop through the second group where you stored all the non-picked units... and so on untill a unit is picked :)

Just a suggestion thou, rulerofiron99 suggestion is better.
 
You could fill a unit array and use a random index.

Set i = 0
For Group
set unit = FirstOfGroup
set i = i + 1
u = unit[random int between 0 and i]


wouldnt u need to do it like this tho.
Set i = 0
For Group
set i = i + 1
set unit = FirstOfGroup
u = unit[random int between 1 and i]

the i in ur case would be one number higher than the actual units indexed. or am i just that worn out today ?
 
I think I would stick with the BJ versus that method (the method in the original post). The "CountUnitsInGroup()" itself is pretty intense/goes through a very similar process as GroupPickRandomUnit. The algorithm of GroupPickRandomUnit is a bit unorthodox (the units that are iterated towards the end sometimes have a lower chance of being picked), but it will usually suffice for most situations.

If you are going with your own random method, I'd do as ruler said, except I'd use a ForGroup() if you need to keep the units in the group (if not, then you can use the first of group method):
JASS:
globals
    unit array units
    integer index
endglobals

function GroupCallback takes nothing returns nothing
    set index = index + 1
    set units[index] = GetEnumUnit()
endfunction

function GroupPickRandomUnitEx takes group g returns unit
    set index = 0
    call ForGroup(g, function GroupCallback)
    return units[GetRandomInt(1, index)]
endfunction

eh, something like that. Untested.

EDIT: @death: I think you're worn out. The first unit assigned is unit[0]. However, in that case it should be 0 to (i - 1), unless you have the exitwhen right after the set unit = FirstOfGroup(g).
 
EDIT: @death: I think you're worn out. The first unit assigned is unit[0]. However, in that case it should be 0 to (i - 1), unless you have the exitwhen right after the set unit = FirstOfGroup(g).


yes thats exactly wat i meant.
if u do it the way i posted the first one would be stored in 1 and then there is no need for i - 1 at the end.
 
Purge, thanks for the input. I do wonder though that if this spell can theoretically be cast simultaneously by separate players, wouldn't that be a problem considering the use of globals?

It should work just fine in any case. When you call ForGroup(group, code), the enumeration/ForGroup() thread is executed before going on to the other actions. For example:
JASS:
function Callback takes nothing returns nothing 
    call BJDebugMsg("Hello")
endfunction

function Test takes nothing returns nothing 
    call ForGroup(g, function Callback)
    call BJDebugMsg("Test")
endfunction

"Hello" should appear X times (number of units in the group) before "Test" appears. In a similar fashion, when you call two functions at the same time, it will go through the actions entirely before moving on to the next one.
JASS:
call GroupPickRandomUnitEx(g)
call GroupPickRandomUnitEx(g)
Both will choose a random unit, and they work just fine independently. :) Even if you do something like "GroupClear(g)" after a GroupPickRandomUnit(), it will still choose a random unit properly due to the order in which things are executed in Warcraft III.
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
Wouldn't this give you a random unit from a unitgroup at the end of looping?
target would be set x times depending how many units are picked.

JASS:
                loop
                    set u = FirstOfGroup(bj_lastCreatedGroup)
                    exitwhen null == u
                    call GroupRemoveUnit(bj_lastCreatedGroup, u)
                    //if Filter(this.caster, this.owner, u) then
                        set this.target = u
                    //endif
                endloop
 
Level 3
Joined
Sep 9, 2009
Messages
658
How exactly do you pick random units with FirstOfGroup anyway? From reading what I can find, I know it involves arrays and integer counter and GetRandomInt but how do you set it up? Like this?

JASS:
    loop
        set index = index + 1
        set u[index] = FirstOfGroup(g)
        exitwhen u[index] == null
        set randomunit[index] = u[index]
        call GroupRemoveUnit(g, u[index])
    endloop
    
    call KillUnit(randomunit[GetRandomInt(1, index)])
    
    loop
        exitwhen index == 0
        set randomunit[index] = null
        set index = index - 1
    endloop
 
easier way is this. note im on my phone so some things may be wrong

JASS:
loop
    set u = FirstOfGroup( g)
    exitwhen u == null
    if GetRandomInt( 1, 100) < 26 then
        set randomUnit = u
        set u = null
        exitwhen u == null
    endif
    call GroupRemoveUnit( g, u)
endloop

This is not truely random tho as a unit can not always be picked and there is a higher chance for earlier units in the group to be picked.
If u want a truely random thing then use the one u posted above my post
 
Level 20
Joined
Jul 14, 2011
Messages
3,213
JASS:
globals
    unit array Units
endglobals

function GetRandomUnit takes real x, real y, real AoE returns unit
    local integer i= 0
    local group g = CreateGroup()

    call GroupEnumUnitsInRange(g, x, y, AoE, null)
    
    loop
        set Units[i] = FirstOfGroup(g)
        exitwhen Units[i] == null
        call GroupRemoveUnit(g, Units[i])
        set i = i+1
    endloop

    call DestroyGroup(g)
    set g = null

    return Units[GetRandomInt(0, i)]
endfunction
 
Last edited:
Level 16
Joined
Dec 15, 2011
Messages
1,423
JASS:
local unit array units
local unit YourUnit
local integer index = 0

loop
    set u = FirstOfGroup(g)
    exitwhen u = null
    set units[i] = u
    set i = i+1
    call GroupRemoveUnit(g, u)
endloop

set YourUnit = units[GetRandomInt(0, i)]
set units = null

You need a loop to null a unit array, just so you know :)

The group also needs to have the units readded from the array.

Or just go with a ForGroup()
 
You need a loop to null a unit array, just so you know :)

The group also needs to have the units readded from the array.

Or just go with a ForGroup()

forgroup would be harder for this as u would need a global unit array to get this to work.

@1)ark_NiTe
u can do something like this which puts everything into one loop even the nulling of the units. It is basically close to the other option u have tho.

JASS:
function RandomUnit takes group g returns nothing
    local unit array u
    local integer i = 0
    loop
        set i = i + 1
        set u[i] = FirstOfGroup(g)
        if u[i] == null then
            set i = i - 1
            loop
                call KillUnit(u[GetRandomInt(1, i)])
                exitwhen i == 0
                set u[i] = null
                set i = i - 1
            endloop
        endif
        call GroupRemoveUnit(g, u[i])
    endloop
endfunction
 
Level 18
Joined
Nov 1, 2006
Messages
1,612
u can do something like this which puts everything into one loop even the nulling of the units.

You basically took my script and consolidated it by half. That's awesome. I'll try out a variation of that and see how it works. Again though, anyone know if this would be significantly more efficient than
JASS:
GroupPickRandomUnit()
?
 
Level 18
Joined
Nov 1, 2006
Messages
1,612
JASS:
function GroupPickRandomUnit takes group whichGroup returns unit
    local boolean wantDestroy = bj_wantDestroyGroup
    set bj_wantDestroyGroup = false

    set bj_groupRandomConsidered = 0
    set bj_groupRandomCurrentPick = null
    call ForGroup(whichGroup, function GroupPickRandomUnitEnum)
    if (wantDestroy) then
        call DestroyGroup(whichGroup)
    endif
    return bj_groupRandomCurrentPick
endfunction

function GroupPickRandomUnitEnum takes nothing returns nothing
    set bj_groupRandomConsidered = bj_groupRandomConsidered + 1
    if (GetRandomInt(1,bj_groupRandomConsidered) == 1) then
        set bj_groupRandomCurrentPick = GetEnumUnit()
    endif
endfunction

I think I'll stick with the alternative we've created. At the very least, it doesn't call another function to operate.
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
@death: You're still doing it in two loops; you've just made it unnecessarily complex.
The code is also buggy since it won't break out of the outside loop. The exitwhen would only break out of the inner loop.

I'm pretty sure what Purge posted earlier is better. Using a global array should remove the need to null since it would get continually used when the function is called.
Spart's code is the FirstOfGroup alternative to ForGroup though it looks more complicated because it's doing other stuff.
 
Level 20
Joined
Jul 14, 2011
Messages
3,213
You got my script before I editedit :) I removed the local unit array, replaced with a global. Just 1 loop to index and 1 random int to choose from.

Here it's edited: http://www.hiveworkshop.com/forums/2392264-post17.html

I'm not pretending you use the function as it is. It's the the basic idea suggestion of indexing then picking random index. I guess a filter must be added.

There is no harm on having a global unit array for this and simplifies everything.

EDIT: I removed "u" unit and used only Units
 
Last edited:
However, it reduces the computational effort by a huge amount. A global variable is nothing comparing to having to do two loops (or a loop within a loop in your case). ForGroup() with a global unit array is the fastest method here.

hmm i cant really see that happening although it may be true.

each of my groups only run once and first of group is faster than for group by a very small amount.
 
Status
Not open for further replies.
Top