Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@ repositories {

dependencies {
testImplementation(kotlin("test"))
testImplementation("io.mockk:mockk:1.13.7")
}

tasks.test {
useJUnitPlatform()
}

kotlin {
jvmToolchain(8)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.tpcly.behaviourtree

@DslMarker
annotation class BehaviourTreeDslMarker
31 changes: 31 additions & 0 deletions src/main/kotlin/com/tpcly/behaviourtree/Builder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.tpcly.behaviourtree

import com.tpcly.behaviourtree.composite.Selector
import com.tpcly.behaviourtree.composite.Sequence
import com.tpcly.behaviourtree.decorator.Inverter
import com.tpcly.behaviourtree.decorator.RepeatUntil
import com.tpcly.behaviourtree.decorator.Succeeder
import com.tpcly.behaviourtree.leaf.Condition
import com.tpcly.behaviourtree.leaf.Leaf
import com.tpcly.behaviourtree.leaf.Perform

fun sequence(random: Boolean = false, init: Sequence.() -> Unit) = initNode(Sequence(random), init)

fun selector(random: Boolean = false, init: Selector.() -> Unit) = initNode(Selector(random), init)

fun inverter(init: () -> TreeNode) = Inverter(init())

fun succeeder(init: () -> TreeNode): Succeeder = Succeeder(init())

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

fun leaf(func: () -> Status) = Leaf(func)

fun condition(predicate: () -> Boolean) = Condition(predicate)

fun perform(func: () -> Unit): Perform = Perform(func)

private fun <T : TreeNode> initNode(node: T, init: T.() -> Unit): T {
node.init()
return node
}
14 changes: 14 additions & 0 deletions src/main/kotlin/com/tpcly/behaviourtree/Composite.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.tpcly.behaviourtree

@BehaviourTreeDslMarker
abstract class Composite : TreeNode {
val children = mutableListOf<TreeNode>()

operator fun TreeNode.unaryPlus() {
children += this
}

operator fun TreeNode.unaryMinus() {
children -= this
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/com/tpcly/behaviourtree/Decorator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.tpcly.behaviourtree

@BehaviourTreeDslMarker
abstract class Decorator(val child: TreeNode) : TreeNode {
}
8 changes: 8 additions & 0 deletions src/main/kotlin/com/tpcly/behaviourtree/Status.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.tpcly.behaviourtree

enum class Status {
SUCCESS,
FAILURE,
RUNNING,
EXIT
}
5 changes: 5 additions & 0 deletions src/main/kotlin/com/tpcly/behaviourtree/TreeNode.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.tpcly.behaviourtree

interface TreeNode {
fun execute(): Status
}
24 changes: 24 additions & 0 deletions src/main/kotlin/com/tpcly/behaviourtree/composite/Selector.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.tpcly.behaviourtree.composite

import com.tpcly.behaviourtree.Composite
import com.tpcly.behaviourtree.Status

class Selector(private val random: Boolean) : Composite() {
override fun execute(): Status {
val children = if (random) {
children.shuffled()
} else {
children
}

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

if (result == Status.SUCCESS) {
return result
}
}

return Status.FAILURE
}
}
24 changes: 24 additions & 0 deletions src/main/kotlin/com/tpcly/behaviourtree/composite/Sequence.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.tpcly.behaviourtree.composite

import com.tpcly.behaviourtree.Composite
import com.tpcly.behaviourtree.Status

class Sequence(private val random: Boolean) : Composite() {
override fun execute(): Status {
val children = if (random) {
children.shuffled()
} else {
children
}

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

if (result == Status.FAILURE) {
return result
}
}

return Status.SUCCESS
}
}
16 changes: 16 additions & 0 deletions src/main/kotlin/com/tpcly/behaviourtree/decorator/Inverter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.tpcly.behaviourtree.decorator

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


class Inverter(child: TreeNode) : Decorator(child) {
override fun execute(): Status {
return when (val result = child.execute()) {
Status.SUCCESS -> Status.FAILURE
Status.FAILURE -> Status.SUCCESS
else -> result
}
}
}
17 changes: 17 additions & 0 deletions src/main/kotlin/com/tpcly/behaviourtree/decorator/RepeatUntil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.tpcly.behaviourtree.decorator

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

class RepeatUntil(private val status: Status, child: TreeNode) : Decorator(child) {
override fun execute(): Status {
var result = child.execute()

while (result != status) {
result = child.execute()
}

return result
}
}
12 changes: 12 additions & 0 deletions src/main/kotlin/com/tpcly/behaviourtree/decorator/Succeeder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.tpcly.behaviourtree.decorator

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

class Succeeder(child: TreeNode) : Decorator(child) {
override fun execute(): Status {
child.execute()
return Status.SUCCESS
}
}
13 changes: 13 additions & 0 deletions src/main/kotlin/com/tpcly/behaviourtree/leaf/Condition.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.tpcly.behaviourtree.leaf

import com.tpcly.behaviourtree.Status
import com.tpcly.behaviourtree.TreeNode

class Condition(val predicate: () -> Boolean) : TreeNode {
override fun execute(): Status {
return when (predicate()) {
true -> Status.SUCCESS
false -> Status.FAILURE
}
}
}
10 changes: 10 additions & 0 deletions src/main/kotlin/com/tpcly/behaviourtree/leaf/Leaf.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.tpcly.behaviourtree.leaf

import com.tpcly.behaviourtree.Status
import com.tpcly.behaviourtree.TreeNode

class Leaf(val func: () -> Status) : TreeNode {
override fun execute(): Status {
return func()
}
}
11 changes: 11 additions & 0 deletions src/main/kotlin/com/tpcly/behaviourtree/leaf/Perform.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.tpcly.behaviourtree.leaf

import com.tpcly.behaviourtree.Status
import com.tpcly.behaviourtree.TreeNode

class Perform(val func: () -> Unit) : TreeNode {
override fun execute(): Status {
func()
return Status.SUCCESS
}
}
39 changes: 39 additions & 0 deletions src/test/kotlin/com/tpcly/behaviourtree/InverterTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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 InverterTests {

@Test
fun testExecutionSuccess() = testExecution(Status.SUCCESS, Status.FAILURE)

@Test
fun testExecutionFailure() = testExecution(Status.FAILURE, Status.SUCCESS)

@Test
fun testExecutionRunning() = testExecution(Status.RUNNING, Status.RUNNING)

@Test
fun testExecutionExit() = testExecution(Status.EXIT, Status.EXIT)

private fun testExecution(inputStatus: Status, expectedOutputStatus: Status) {
// Arrange
val mockNode = mockk<TreeNode>()
every { mockNode.execute() } returns inputStatus

val selector = inverter {
mockNode
}

// Act
val result = selector.execute()

// Assert
assertEquals(expectedOutputStatus, result)
verify(exactly = 1) { mockNode.execute() }
}
}
84 changes: 84 additions & 0 deletions src/test/kotlin/com/tpcly/behaviourtree/SelectorTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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 SelectorTests {
@Test
fun testExecutionSuccess() {
// Arrange
val mockNode = mockk<TreeNode>()
every { mockNode.execute() } returns Status.SUCCESS

val selector = selector {
+mockNode
}

// Act
val result = selector.execute()

// Assert
assertEquals(Status.SUCCESS, result)
verify(exactly = 1) { mockNode.execute() }
}

@Test
fun testExecutionFailure() {
// Arrange
val mockNode = mockk<TreeNode>()
every { mockNode.execute() } returns Status.FAILURE

val selector = selector {
+mockNode
}

// Act
val result = selector.execute()

// Assert
assertEquals(Status.FAILURE, result)
verify(exactly = 1) { mockNode.execute() }
}

@Test
fun testOrder() {
// Arrange
val mockNode = mockk<TreeNode>()
every { mockNode.execute() } returns Status.SUCCESS

val selector = selector {
+mockNode
+mockNode
}

// Act
val result = selector.execute()

// Assert
assertEquals(Status.SUCCESS, result)
verify(exactly = 1) { mockNode.execute() }
}

@Test
fun testEarlyExit() {
// Arrange
val mockNode = mockk<TreeNode>()
every { mockNode.execute() } returns Status.SUCCESS

val selector = selector {
+mockNode
+selector { +leaf { Status.FAILURE } }
+mockNode
}

// Act
val result = selector.execute()

// Assert
assertEquals(Status.SUCCESS, result)
verify(exactly = 1) { mockNode.execute() }
}
}
Loading