Skip to content
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ sequence.
Executes child only when condition is met, return status of the child if executed, otherwise failure.

```kotlin
val example = gate(predicate = { true }) {
val example = gate(validate = { true }) {
perform {
println("Hello, world")
}
Expand All @@ -247,7 +247,7 @@ val example = gate(predicate = { true }) {
```

```kotlin
val example = gate(predicate = { false }) {
val example = gate(validate = { false }) {
perform {
println("Hello, world")
}
Expand Down
6 changes: 3 additions & 3 deletions src/main/kotlin/com/tpcly/behaviourtree/Action.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package com.tpcly.behaviourtree

@BehaviourTreeDslMarker
abstract class Action(override val name: String) : TreeNode {
abstract fun action(): Status
abstract fun action(blackboard: Blackboard): Status

override fun execute(): TreeNodeResult {
return TreeNodeResult(this, action())
override fun execute(blackboard: Blackboard): TreeNodeResult {
return TreeNodeResult(this, action(blackboard))
}
}
22 changes: 22 additions & 0 deletions src/main/kotlin/com/tpcly/behaviourtree/Blackboard.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.tpcly.behaviourtree

class Blackboard(private val values: MutableMap<String, Any> = mutableMapOf()) {
private val observers = mutableListOf<BlackboardObserver>()

fun attach(observer: BlackboardObserver) {
observers.add(observer)
}

fun detach(observer: BlackboardObserver) {
observers.remove(observer)
}

operator fun get(key: String): Any? {
return values[key]
}

operator fun <T : Any> set(key: String, value: T) {
values[key] = value
observers.forEach { it.update(key, value) }
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/com/tpcly/behaviourtree/BlackboardObserver.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.tpcly.behaviourtree

interface BlackboardObserver {
fun update(key: String, value: Any)
}
14 changes: 8 additions & 6 deletions src/main/kotlin/com/tpcly/behaviourtree/Builder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,19 @@ fun succeeder(name: String = "", init: () -> TreeNode): Succeeder = Succeeder(na

fun repeatUntil(name: String = "", status: Status, init: () -> TreeNode) = RepeatUntil(name, status, init())

fun gate(name: String = "", predicate: () -> Boolean, init: () -> TreeNode) = Gate(name, predicate, init())
fun gate(name: String = "", validate: (blackboard: Blackboard) -> Boolean, init: () -> TreeNode) = Gate(name, validate, init())

fun action(name: String = "", func: () -> Status) = object : Action(name) {
override fun action(): Status {
return func()
fun gate(name: String = "", key: String, value: Any, init: () -> TreeNode) = Gate(name, { it[key] == value }, init())

fun action(name: String = "", func: (blackboard: Blackboard) -> Status) = object : Action(name) {
override fun action(blackboard: Blackboard): Status {
return func(blackboard)
}
}

fun condition(name: String = "", predicate: () -> Boolean) = Condition(name, predicate)
fun condition(name: String = "", predicate: (blackboard: Blackboard) -> Boolean) = Condition(name, predicate)

fun perform(name: String = "", func: () -> Unit): Perform = Perform(name, func)
fun perform(name: String = "", func: (blackboard: Blackboard) -> Unit): Perform = Perform(name, func)

private fun <T : TreeNode> initNode(node: T, init: T.() -> Unit): T {
node.init()
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/tpcly/behaviourtree/TreeNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package com.tpcly.behaviourtree
interface TreeNode {
val name: String

fun execute(): TreeNodeResult
fun execute(blackboard: Blackboard = Blackboard()): TreeNodeResult
}
10 changes: 7 additions & 3 deletions src/main/kotlin/com/tpcly/behaviourtree/action/Condition.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.tpcly.behaviourtree.action

import com.tpcly.behaviourtree.Action
import com.tpcly.behaviourtree.Blackboard
import com.tpcly.behaviourtree.Status

class Condition(override val name: String = "", val predicate: () -> Boolean) : Action(name) {
override fun action(): Status {
return when (predicate()) {
class Condition(
override val name: String = "",
val predicate: (blackboard: Blackboard) -> Boolean
) : Action(name) {
override fun action(blackboard: Blackboard): Status {
return when (predicate(blackboard)) {
true -> Status.SUCCESS
false -> Status.FAILURE
}
Expand Down
10 changes: 7 additions & 3 deletions src/main/kotlin/com/tpcly/behaviourtree/action/Perform.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.tpcly.behaviourtree.action

import com.tpcly.behaviourtree.Action
import com.tpcly.behaviourtree.Blackboard
import com.tpcly.behaviourtree.Status

class Perform(override val name: String = "", val func: () -> Unit) : Action(name) {
override fun action(): Status {
func()
class Perform(
override val name: String = "",
val func: (blackboard: Blackboard) -> Unit
) : Action(name) {
override fun action(blackboard: Blackboard): Status {
func(blackboard)
return Status.SUCCESS
}
}
5 changes: 3 additions & 2 deletions src/main/kotlin/com/tpcly/behaviourtree/composite/Selector.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.tpcly.behaviourtree.composite

import com.tpcly.behaviourtree.Blackboard
import com.tpcly.behaviourtree.Composite
import com.tpcly.behaviourtree.Status
import com.tpcly.behaviourtree.TreeNodeResult

class Selector(name: String = "", private val random: Boolean) : Composite(name) {
override fun execute(): TreeNodeResult {
override fun execute(blackboard: Blackboard): TreeNodeResult {
val children = if (random) {
children.shuffled()
} else {
Expand All @@ -15,7 +16,7 @@ class Selector(name: String = "", private val random: Boolean) : Composite(name)
val results = mutableListOf<TreeNodeResult>()

for (child in children) {
val result = child.execute()
val result = child.execute(blackboard)
results.add(result)

if (result.status == Status.SUCCESS || result.status == Status.ABORT) {
Expand Down
5 changes: 3 additions & 2 deletions src/main/kotlin/com/tpcly/behaviourtree/composite/Sequence.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.tpcly.behaviourtree.composite

import com.tpcly.behaviourtree.Blackboard
import com.tpcly.behaviourtree.Composite
import com.tpcly.behaviourtree.Status
import com.tpcly.behaviourtree.TreeNodeResult

class Sequence(name: String = "", private val random: Boolean) : Composite(name) {
override fun execute(): TreeNodeResult {
override fun execute(blackboard: Blackboard): TreeNodeResult {
val children = if (random) {
children.shuffled()
} else {
Expand All @@ -15,7 +16,7 @@ class Sequence(name: String = "", private val random: Boolean) : Composite(name)
val results = mutableListOf<TreeNodeResult>()

for (child in children) {
val result = child.execute()
val result = child.execute(blackboard)
results.add(result)

if (result.status == Status.FAILURE || result.status == Status.ABORT) {
Expand Down
9 changes: 5 additions & 4 deletions src/main/kotlin/com/tpcly/behaviourtree/decorator/Gate.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package com.tpcly.behaviourtree.decorator

import com.tpcly.behaviourtree.Blackboard
import com.tpcly.behaviourtree.Decorator
import com.tpcly.behaviourtree.TreeNode
import com.tpcly.behaviourtree.TreeNodeResult

class Gate(
name: String,
val predicate: () -> Boolean,
val predicate: (blackboard: Blackboard) -> Boolean,
child: TreeNode
) : Decorator(name, child) {
override fun execute(): TreeNodeResult {
if (predicate()) {
val result = child.execute()
override fun execute(blackboard: Blackboard): TreeNodeResult {
if (predicate(blackboard)) {
val result = child.execute(blackboard)
return TreeNodeResult(this, result.status, listOf(result))
}

Expand Down
10 changes: 3 additions & 7 deletions src/main/kotlin/com/tpcly/behaviourtree/decorator/Inverter.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package com.tpcly.behaviourtree.decorator

import com.tpcly.behaviourtree.Decorator
import com.tpcly.behaviourtree.Status
import com.tpcly.behaviourtree.TreeNode
import com.tpcly.behaviourtree.TreeNodeResult

import com.tpcly.behaviourtree.*

class Inverter(name: String, child: TreeNode) : Decorator(name, child) {
override fun execute(): TreeNodeResult {
val result = child.execute()
override fun execute(blackboard: Blackboard): TreeNodeResult {
val result = child.execute(blackboard)
val status = when (result.status) {
Status.SUCCESS -> Status.FAILURE
Status.FAILURE -> Status.SUCCESS
Expand Down
11 changes: 4 additions & 7 deletions src/main/kotlin/com/tpcly/behaviourtree/decorator/RepeatUntil.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
package com.tpcly.behaviourtree.decorator

import com.tpcly.behaviourtree.Decorator
import com.tpcly.behaviourtree.Status
import com.tpcly.behaviourtree.TreeNode
import com.tpcly.behaviourtree.TreeNodeResult
import com.tpcly.behaviourtree.*

class RepeatUntil(name: String = "", private val status: Status, child: TreeNode) : Decorator(name, child) {
override fun execute(): TreeNodeResult {
override fun execute(blackboard: Blackboard): TreeNodeResult {
val results = mutableListOf<TreeNodeResult>()

var result = child.execute()
var result = child.execute(blackboard)
results.add(result)

while (result.status != status && result.status != Status.ABORT) {
result = child.execute()
result = child.execute(blackboard)
results.add(result)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.tpcly.behaviourtree.decorator

import com.tpcly.behaviourtree.Blackboard
import com.tpcly.behaviourtree.Decorator
import com.tpcly.behaviourtree.TreeNode
import com.tpcly.behaviourtree.TreeNodeResult

class Succeeder(name: String = "", child: TreeNode) : Decorator(name, child) {
override fun execute(): TreeNodeResult {
val result = child.execute()
override fun execute(blackboard: Blackboard): TreeNodeResult {
val result = child.execute(blackboard)
return TreeNodeResult.success(this, listOf(result))
}
}
59 changes: 59 additions & 0 deletions src/test/kotlin/com/tpcly/behaviourtree/BlackboardTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.tpcly.behaviourtree

import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlin.test.Test
import kotlin.test.assertEquals

internal class BlackboardTests {
@Test
fun testSet() {
// Arrange
val mapMock = mockk<MutableMap<String, Any>> {
every { this@mockk[any()] = any() } answers {}
}

val blackboard = Blackboard(mapMock)

// Act
blackboard["test"] = true

// Assert
verify(exactly = 1) { mapMock["test"] = true }
}

@Test
fun testGet() {
// Arrange
val mapMock = mockk<MutableMap<String, Any>> {
every { this@mockk[any()] } returns true
}

val blackboard = Blackboard(mapMock)

// Act
val result = blackboard["test"]

// Assert
assertEquals(true, result)
verify(exactly = 1) { mapMock["test"] }
}

@Test
fun testObserverUpdate() {
// Arrange
val observerMock = mockk<BlackboardObserver> {
every { update(any(), any()) } answers {}
}

val blackboard = Blackboard()
blackboard.attach(observerMock)

// Act
blackboard["test"] = true

// Assert
verify(exactly = 1) { observerMock.update("test", true) }
}
}
Loading