This site is currently in read-only mode during migration to a new platform.
You cannot post questions, answers or comments, as they would be lost during the migration otherwise.
0 votes

I have an array of objects. Each object can have properties. I need a take a random element of filtered array by some condition. For i.e. random element with top priority. The problem that I don't know what priority is top, so I can't just go through array and make new array with that priority. Or condition can be more complicated (for i.e. random object with health more 50 and closer to player than 100)

in Engine by (275 points)

1 Answer

0 votes

Here is my variant of solution of this problem:

extends Node

var arr = ["Tst1", "Tst2", "Tst3", "Test4", "Test5", "t1", "t2", "tt1", "tt2"]
var dict_arr = [{"value": 50}, {"value": 100, "name": "test1"}, {"value": 100, "name": "test2"}, {"value": 20}, {"value": 110, "name": "test3"}, {"value": 110, "name": "test4"}, {"value": 110, "name": "test5"}]

func _ready():
    randomize()
    print(random_element(arr))
    print(random_element_with_biggest_property(dict_arr, "value"))
    print(random_element_with_condition(arr, funcref(self, "lengthBigger2")))
    print(random_element_with_condition(arr, funcref(self, "lengthBigger3")))
    print(random_element_with_condition(arr, funcref(self, "lengthBigger4")))

func random_element(array):
    return array[randi() % array.size()]

func random_element_with_biggest_property(array, property : String):
    if array.empty():
        return null

    var accepted = []
    var biggestValue = array[0].get(property) if property in array[0] else null

    for element in array:
        if property in element:
            var value = element.get(property)

            if value > biggestValue:
                accepted.clear()
                biggestValue = value
                accepted.append(element)
            elif value == biggestValue:
                accepted.append(element)

    if accepted.empty():
        return null
    else:
        return random_element(accepted)

func random_element_with_condition(array, conditionalFunc : FuncRef):
    if array.empty():
        return null

    var accepted = []

    for element in array:
        if conditionalFunc.is_valid() and conditionalFunc.call_func(element):
            accepted.append(element)

    if accepted.empty():
        return null
    else:
        return random_element(accepted)

func lengthBigger2(element):
    return element.length() > 2

func lengthBigger3(element):
    return element.length() > 3

func lengthBigger4(element):
    return element.length() > 4

Can you review it? What problems does it have? What is about performance?

Is it possible to find more elegant solution?

by (275 points)

I'm a little confused, in your question you call them objects with properties, but here they seem to be string literals with loosely associated values in a dictionary (the string literals in your dictionary do not exactly match your capitalization in your array, and the first item in your dictionary has no "name" key).
When I read your question I expected custom objects with member variables as properties, which is probably how I would do it. Do these objects have representation in your game beyond "test" (are they monsters, players, items, etc)? The solution you've got now adds a convoluted (IMO) layer of abstraction that divorces the original data from how you want to manipulate it. In other words, those dictionaries have to be populated somehow, and you may introduce bugs in the transcription process that can be difficult to diagnose. If you instead used an array of references to objects in memory, potentially with a custom class (declared at the top of the object's script with the class_name keyword in GDScript), you could construct a for loop to iterate over the collection, creating another collection of references based on some condition, and then randomly choosing out of that second collection.
A hypothetical example using custom objects with class_name keyword and for loop member variable checking (not a complete implementation of what you want, but hopefully will get you there):
Class1.gd:

extends Object

class_name Class1

var foo = 20
var bar = "spam"

Class2.gd:

extends Object

class_name Class2

var foo = 20
var baz = "runcible"

debug_scene.gd:

extends Node2D

var collection = []

func _ready():

    for i in 10:
        var object1 = Class1.new()
        var object2 = Class2.new()
        collection.push_back(object1)
        collection.push_back(object2)

    for item in collection:
        print("item number", collection.find(item))
        if "foo" in item:
            print("item has member variable foo")
        if "bar" in item:
            print("item has member variable bar")
        if "baz" in item:
            print("item has member variable baz")

output:

item number0
item has member variable foo
item has member variable bar
item number1
item has member variable foo
item has member variable baz
item number2
item has member variable foo
item has member variable bar
item number3
item has member variable foo
item has member variable baz
...

in your question you call them objects with properties, but here they
seem to be string literals with loosely associated values in a
dictionary

it will be scene nodes and gdscript objects in the real game. I guess, that interface the same as with the dictionaries

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.