// ============================================================
// Node: Core Status Types, Ref Cell, Node Interface,
// and Composite Node Constructors
//
// Status represents the outcome of a single node tick:
// BTSuccess - the node completed its goal successfully
// BTFailure - the node failed to achieve its goal
// BTRunning - the node is still working (multi-tick action)
//
// Node is the fundamental unit: it holds two closures:
// tick_fn - called each frame to advance the node
// reset_fn - called to restore the node to its initial state
//
// Composite nodes combine multiple child nodes:
// Sequence - succeed only when ALL children succeed (AND)
// Selector - succeed when ANY child succeeds (OR)
// Parallel - tick ALL children simultaneously
// ============================================================
///|
pub(all) enum Status {
BTSuccess
BTFailure
BTRunning
} derive(Eq, Debug)
///|
/// to_string returns a human-readable representation of the Status.
pub fn Status::to_string(self : Status) -> String {
match self {
Status::BTSuccess => "Success"
Status::BTFailure => "Failure"
Status::BTRunning => "Running"
}
}
///|
pub impl Show for Status with fn output(self, logger) {
logger.write_string(self.to_string())
}
// ============================================================
// Ref: A mutable reference cell (workaround for MoonBit closures)
// ============================================================
///|
pub struct Ref[T] {
mut val : T
}
///|
/// Create a new Ref holding the given value.
pub fn[T] Ref::new(val : T) -> Ref[T] {
{ val, }
}
///|
/// Get the current value.
pub fn[T] Ref::get(self : Ref[T]) -> T {
self.val
}
///|
/// Set the value.
pub fn[T] Ref::set(self : Ref[T], val : T) -> Unit {
self.val = val
}
// ============================================================
// Node: The fundamental behavior tree unit
// ============================================================
///|
pub struct Node {
tick_fn : (Blackboard) -> Status
reset_fn : () -> Unit
}
///|
/// Node::new creates a node from tick and reset closures.
pub fn Node::new(
tick_fn : (Blackboard) -> Status,
reset_fn : () -> Unit,
) -> Node {
{ tick_fn, reset_fn }
}
///|
/// tick executes the node for one frame and returns its Status.
pub fn Node::tick(self : Node, bb : Blackboard) -> Status {
(self.tick_fn)(bb)
}
///|
/// reset restores the node to its initial state.
pub fn Node::reset(self : Node) -> Unit {
(self.reset_fn)()
}
// ============================================================
// Sequence Node (AND Gate)
//
// Ticks children left-to-right. Returns:
// - BTRunning if any child is Running (remembers position)
// - BTFailure on the first child that Fails (resets all)
// - BTSuccess when all children Succeed
// ============================================================
///|
/// sequence_node creates a Sequence composite node.
pub fn sequence_node(children : Array[Node]) -> Node {
let running_child : Ref[Int] = Ref::new(0)
let tick = fn(bb) {
let mut i = running_child.get()
while i < children.length() {
let child = children[i]
let status = child.tick(bb)
match status {
Status::BTRunning => {
running_child.set(i)
return Status::BTRunning
}
Status::BTFailure => {
for c in children {
c.reset()
}
running_child.set(0)
return Status::BTFailure
}
Status::BTSuccess => i = i + 1
}
}
running_child.set(0)
Status::BTSuccess
}
let reset = fn() {
running_child.set(0)
for c in children {
c.reset()
}
}
Node::new(tick, reset)
}
// ============================================================
// Selector Node (OR Gate)
//
// Ticks children left-to-right. Returns:
// - BTRunning if any child is Running (remembers position)
// - BTSuccess on the first child that Succeeds (resets all)
// - BTFailure when all children Fail
// ============================================================
///|
/// selector_node creates a Selector composite node.
pub fn selector_node(children : Array[Node]) -> Node {
let running_child : Ref[Int] = Ref::new(0)
let tick = fn(bb) {
let mut i = running_child.get()
while i < children.length() {
let child = children[i]
let status = child.tick(bb)
match status {
Status::BTRunning => {
running_child.set(i)
return Status::BTRunning
}
Status::BTSuccess => {
for c in children {
c.reset()
}
running_child.set(0)
return Status::BTSuccess
}
Status::BTFailure => i = i + 1
}
}
running_child.set(0)
Status::BTFailure
}
let reset = fn() {
running_child.set(0)
for c in children {
c.reset()
}
}
Node::new(tick, reset)
}
// ============================================================
// Random Selector Node
//
// Like a Selector but shuffles children before each full scan.
// Useful for non-deterministic AI behavior (e.g., patrol routes).
// Uses a simple LCG pseudo-random generator seeded externally.
// ============================================================
///|
/// random_selector_node creates a non-deterministic Selector.
/// `seed` is a mutable seed Ref for the internal LCG.
pub fn random_selector_node(children : Array[Node], seed : Ref[Int]) -> Node {
let order : Ref[Array[Int]] = Ref::new([])
let initialized : Ref[Bool] = Ref::new(false)
// LCG: next = (a * x + c) mod m (Numerical Recipes parameters)
let lcg_next = fn(s : Int) -> Int { (s * 1664525 + 1013904223) % 2147483647 }
let shuffle = fn(arr : Array[Int]) -> Unit {
let n = arr.length()
let mut i = n - 1
while i > 0 {
let s = lcg_next(seed.get())
seed.set(s)
let j = (s % (i + 1) + (i + 1)) % (i + 1)
let tmp = arr[i]
arr[i] = arr[j]
arr[j] = tmp
i = i - 1
}
}
let tick = fn(bb) {
if !initialized.get() {
let indices : Array[Int] = []
let mut k = 0
while k < children.length() {
indices.push(k)
k = k + 1
}
shuffle(indices)
order.set(indices)
initialized.set(true)
}
let ord = order.get()
let mut idx = 0
while idx < ord.length() {
let child = children[ord[idx]]
let status = child.tick(bb)
match status {
Status::BTSuccess => {
for c in children {
c.reset()
}
initialized.set(false)
return Status::BTSuccess
}
Status::BTRunning => return Status::BTRunning
Status::BTFailure => idx = idx + 1
}
}
initialized.set(false)
Status::BTFailure
}
let reset = fn() {
initialized.set(false)
for c in children {
c.reset()
}
}
Node::new(tick, reset)
}
///|
pub(all) enum ParallelPolicy {
SuccessAll
SuccessOne
} derive(Eq, Debug)
// ============================================================
// Parallel Node
//
// Ticks ALL children on every tick (no short-circuiting).
// Policy controls when the Parallel returns Success/Failure:
// SuccessAll: succeed only when all children succeed; fail on any failure
// SuccessOne: succeed when any child succeeds; fail when all fail
// ============================================================
///|
/// parallel_node creates a Parallel composite node.
pub fn parallel_node(children : Array[Node], policy : ParallelPolicy) -> Node {
let tick = fn(bb) {
let mut success_count = 0
let mut failure_count = 0
let mut running_count = 0
for child in children {
let status = child.tick(bb)
match status {
Status::BTSuccess => success_count = success_count + 1
Status::BTFailure => failure_count = failure_count + 1
Status::BTRunning => running_count = running_count + 1
}
}
let _ = running_count
match policy {
ParallelPolicy::SuccessAll =>
if failure_count > 0 {
Status::BTFailure
} else if success_count == children.length() {
Status::BTSuccess
} else {
Status::BTRunning
}
ParallelPolicy::SuccessOne =>
if success_count > 0 {
Status::BTSuccess
} else if failure_count == children.length() {
Status::BTFailure
} else {
Status::BTRunning
}
}
}
let reset = fn() {
for c in children {
c.reset()
}
}
Node::new(tick, reset)
}