Skip to content

Commit

Permalink
added tests for weighted random composites
Browse files Browse the repository at this point in the history
  • Loading branch information
lostptr committed Aug 17, 2023
1 parent b1e08a8 commit 3f78afa
Show file tree
Hide file tree
Showing 11 changed files with 300 additions and 68 deletions.
6 changes: 5 additions & 1 deletion addons/beehave/nodes/composites/randomized_composite.gd
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const WEIGHTS_PREFIX = "Weights/"
@export var use_weights: bool:
set(value):
use_weights = value
if use_weights and Engine.is_editor_hint():
if use_weights:
_update_weights(get_children())
_connect_children_changing_signals()
notify_property_list_changed()
Expand Down Expand Up @@ -112,13 +112,17 @@ func _update_weights(children: Array[Node]) -> void:


func _on_child_entered_tree(node: Node):
# print('%s has entered the tree' % node.name)
_update_weights(get_children())

if not node.renamed.is_connected(_on_child_renamed):
node.renamed.connect(_on_child_renamed)


func _on_child_exiting_tree(node: Node):
if node.renamed.is_connected(_on_child_renamed):
node.renamed.disconnect(_on_child_renamed)

var children = get_children()
children.erase(node)
_update_weights(children)
Expand Down
11 changes: 6 additions & 5 deletions addons/beehave/nodes/composites/sequence_random.gd
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
@icon("../../icons/sequence_random.svg")
class_name SequenceRandomComposite extends RandomizedComposite


signal reseted(new_order)
# Emitted whenever the children are shuffled.
signal reset(new_order: Array[Node])

## Whether the sequence should start where it left off after a previous failure.
@export var resume_on_failure: bool = false
Expand Down Expand Up @@ -80,12 +80,13 @@ func _get_reversed_indexes() -> Array[int]:


func _reset() -> void:
_children_bag = get_shuffled_children()
reseted.emit(_children_bag)
var new_order = get_shuffled_children()
_children_bag = new_order.duplicate()
_children_bag.reverse() # It needs to run the children in reverse order.
reset.emit(new_order)


func get_class_name() -> Array[StringName]:
var classes := super()
classes.push_back(&"SequenceRandomComposite")
return classes

22 changes: 0 additions & 22 deletions node_2d.gd

This file was deleted.

38 changes: 0 additions & 38 deletions node_2d.tscn

This file was deleted.

37 changes: 35 additions & 2 deletions test/nodes/composites/sequence_random_test.gd
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ var action2: ActionLeaf
func before_test() -> void:
tree = auto_free(load(__tree).new())
action1 = auto_free(load(__count_up_action).new())
action1.name = 'Action 1'
action2 = auto_free(load(__count_up_action).new())
action2.name = 'Action 2'
sequence = auto_free(load(__source).new())
sequence.random_seed = RANDOM_SEED
var actor = auto_free(Node2D.new())
Expand Down Expand Up @@ -63,14 +65,45 @@ func test_random_even_execution() -> void:
assert_that(action2.count).is_equal(2)


func test_weighted_random_sampling() -> void:
sequence.use_weights = true
sequence._weights[action1.name] = 2
assert_dict(sequence._weights).contains_key_value(action1.name, 2)
assert_dict(sequence._weights).contains_key_value(action2.name, 1)

action1.status = BeehaveNode.RUNNING
action2.status = BeehaveNode.RUNNING

assert_array(sequence._children_bag).is_empty()

assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING)

# Children are in reverse order; aka action1 will run first.
assert_array(sequence._children_bag)\
.contains_exactly([action2, action1])

# Only action 1 should have executed.
assert_that(action1.count).is_equal(1)
assert_that(action2.count).is_equal(0)

action1.status = BeehaveNode.SUCCESS

assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING)

assert_that(action1.count).is_equal(2)
assert_that(action2.count).is_equal(1)

sequence.use_weights = false


func test_return_failure_of_none_is_succeeding() -> void:
action1.status = BeehaveNode.FAILURE
action2.status = BeehaveNode.FAILURE

assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE)

assert_that(action1.count).is_equal(1)
assert_that(action2.count).is_equal(0)
assert_that(action1.count).is_equal(0)
assert_that(action2.count).is_equal(1)


func test_clear_running_child_after_run() -> void:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
extends Node2D

@onready var sequence_random: SequenceRandomComposite = %SequenceRandom
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[gd_scene load_steps=5 format=3 uid="uid://dhhw4ej2jbyha"]

[ext_resource type="Script" path="res://addons/beehave/nodes/beehave_tree.gd" id="1_10c1m"]
[ext_resource type="Script" path="res://test/randomized_composites/runtime_changes/RuntimeChangesTestScene.gd" id="1_folsk"]
[ext_resource type="Script" path="res://addons/beehave/nodes/composites/sequence_random.gd" id="2_k8ytk"]
[ext_resource type="Script" path="res://test/actions/mock_action.gd" id="3_kqvkq"]

[node name="RuntimeChangesTestScene" type="Node2D"]
script = ExtResource("1_folsk")

[node name="BeehaveTree" type="Node" parent="."]
script = ExtResource("1_10c1m")

[node name="SequenceRandom" type="Node" parent="BeehaveTree"]
unique_name_in_owner = true
script = ExtResource("2_k8ytk")
random_seed = 12345
use_weights = true
Weights/Idle = 1
Weights/Run = 1
"Weights/Attack Meele" = 1
"Weights/Attack Ranged" = 1

[node name="Idle" type="Node" parent="BeehaveTree/SequenceRandom"]
script = ExtResource("3_kqvkq")

[node name="Run" type="Node" parent="BeehaveTree/SequenceRandom"]
script = ExtResource("3_kqvkq")

[node name="Attack Meele" type="Node" parent="BeehaveTree/SequenceRandom"]
script = ExtResource("3_kqvkq")

[node name="Attack Ranged" type="Node" parent="BeehaveTree/SequenceRandom"]
script = ExtResource("3_kqvkq")
105 changes: 105 additions & 0 deletions test/randomized_composites/runtime_changes/runtime_changes_test.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# GdUnit generated TestSuite
class_name RuntimeChangesTest
extends GdUnitTestSuite
@warning_ignore("unused_parameter")
@warning_ignore("return_value_discarded")


const __source = "res://test/randomized_composites/runtime_changes/RuntimeChangesTestScene.tscn"
const __mock_action = "res://test/actions/mock_action.gd"

func create_scene() -> Node2D:
var scene = auto_free(load(__source).instantiate())
return scene


func create_new_action():
var new_action = auto_free(load(__mock_action).new())
new_action.name = "New Attack"
return new_action


func test_add_child() -> void:
var scene = create_scene()
var runner := scene_runner(scene)

runner.set_time_factor(100.0)

var weights_before = scene.sequence_random._weights.duplicate()

runner.simulate_frames(10)

var new_action = create_new_action()
scene.sequence_random.add_child(new_action)

# Weights should have a new key with the added child.
assert_dict(scene.sequence_random._weights)\
.contains_key_value(new_action.name, 1)

# All other children's weights should be the same.
for node in weights_before.keys():
assert_dict(scene.sequence_random._weights)\
.contains_key_value(node, weights_before[node])

runner.simulate_frames(10) # Everything should work fine afterwards.


func test_remove_child() -> void:
var scene = create_scene()
var runner := scene_runner(scene)

runner.set_time_factor(100.0)

var weights_before: Dictionary = scene.sequence_random._weights.duplicate()

runner.simulate_frames(10)

var removed_action = runner.find_child(weights_before.keys()[0])
scene.sequence_random.remove_child(removed_action)

# Weights should not have that action anymore.
assert_dict(scene.sequence_random._weights)\
.not_contains_keys([removed_action.name])

# All other children's weights should be the same.
var other_children = weights_before.keys()\
.filter(func(k): return k != removed_action.name)
for node in other_children:
assert_dict(scene.sequence_random._weights)\
.contains_key_value(node, weights_before[node])

removed_action.queue_free()

runner.simulate_frames(10) # Everything should work fine afterwards.


func test_rename_child() -> void:
var scene = create_scene()
var runner := scene_runner(scene)

runner.set_time_factor(100.0)

var weights_before: Dictionary = scene.sequence_random._weights.duplicate()

runner.simulate_frames(10)

var renamed_action = runner.find_child(weights_before.keys()[0])
var previous_name = renamed_action.name
renamed_action.name = "Renamed Action"

# Weights should not have the old action name anymore.
assert_dict(scene.sequence_random._weights)\
.not_contains_keys([previous_name])

# Weights should have the new name with the default weight.
assert_dict(scene.sequence_random._weights)\
.contains_key_value(renamed_action.name, 1)

# All other children's weights should be the same.
var other_children = weights_before.keys()\
.filter(func(k): return k != previous_name)
for node in other_children:
assert_dict(scene.sequence_random._weights)\
.contains_key_value(node, weights_before[node])

runner.simulate_frames(10) # Everything should work fine afterwards.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
extends Node2D

signal done

# How many iterations should the test run.
@export var test_sample_count: int = 1_000

@onready var sequence_random: SequenceRandomComposite = %SequenceRandom

var reset_count: int = 0
var sample_count: Dictionary = {}


func _on_sequence_reset(new_order: Array[Node]):
reset_count += 1
var first = new_order[0]
if not sample_count.has(first.name):
sample_count[first.name] = 0
sample_count[first.name] += 1

if reset_count >= test_sample_count:
done.emit()


func get_final_results() -> Dictionary:
var final_results = {}
for node in sample_count.keys():
var perc = float(sample_count[node]) / float(reset_count) * 100.0
final_results[node] = perc
return final_results
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[gd_scene load_steps=5 format=3 uid="uid://dkaaauniwk8vr"]

[ext_resource type="Script" path="res://test/randomized_composites/weighted_sampling/WeightedSamplingTestScene.gd" id="1_jp21r"]
[ext_resource type="Script" path="res://addons/beehave/nodes/beehave_tree.gd" id="1_lxyfw"]
[ext_resource type="Script" path="res://addons/beehave/nodes/composites/sequence_random.gd" id="2_rx8pw"]
[ext_resource type="Script" path="res://test/actions/mock_action.gd" id="3_kgdbt"]

[node name="WeightedSamplingTestScene" type="Node2D"]
script = ExtResource("1_jp21r")

[node name="BeehaveTree" type="Node" parent="."]
script = ExtResource("1_lxyfw")

[node name="SequenceRandom" type="Node" parent="BeehaveTree"]
unique_name_in_owner = true
script = ExtResource("2_rx8pw")
random_seed = 12345
use_weights = true
Weights/Idle = 1
Weights/Run = 1
"Weights/Attack Meele" = 1
"Weights/Attack Ranged" = 1

[node name="Idle" type="Node" parent="BeehaveTree/SequenceRandom"]
script = ExtResource("3_kgdbt")

[node name="Run" type="Node" parent="BeehaveTree/SequenceRandom"]
script = ExtResource("3_kgdbt")

[node name="Attack Meele" type="Node" parent="BeehaveTree/SequenceRandom"]
script = ExtResource("3_kgdbt")

[node name="Attack Ranged" type="Node" parent="BeehaveTree/SequenceRandom"]
script = ExtResource("3_kgdbt")

[connection signal="reset" from="BeehaveTree/SequenceRandom" to="." method="_on_sequence_reset"]
Loading

0 comments on commit 3f78afa

Please sign in to comment.