Skip to content

Instantly share code, notes, and snippets.

@JakeCoxon
Last active August 29, 2015 14:04
Show Gist options
  • Save JakeCoxon/65360313a7566ae1a337 to your computer and use it in GitHub Desktop.
Save JakeCoxon/65360313a7566ae1a337 to your computer and use it in GitHub Desktop.
Attempt at simple concurrent behaviour trees in Javascript
<html>
<head>
</head>
<body>
<script>
var rootContext = {
loop: function loop( context, endPromise, times, _ ) {
if ( times == 0 ) return endPromise.success( );
var sequence = [ 'seq' ].concat( Array.prototype.slice.call( arguments, 3 ) );
function finish() {
loop( context, endPromise, times - 1, sequence );
}
var promise = { success: finish, fail: finish };
runTree( context, promise, sequence );
},
selector: function selector( context, endPromise, _ ) {
var selects = Array.prototype.slice.call( arguments, 2 );
if ( selects.length == 0 ) { return endPromise.fail( ); }
var promise = {
success: endPromise.success,
fail: function( ) {
selector.apply( null, [ context, endPromise ].concat( selects.slice( 1 ) ) );
}
};
runTree( context, promise, selects[ 0 ] );
},
seq: function seq( context, endPromise, _ ) {
var sequence = Array.prototype.slice.call( arguments, 2 );
if ( sequence.length == 0 ) { return endPromise.success( ); }
var promise = {
success: function( ) {
seq.apply( null, [ context, endPromise ].concat( sequence.slice( 1 ) ) );
},
fail: endPromise.fail
};
runTree( context, promise, sequence[ 0 ] );
},
parallel: function( context, endPromise, _ ) {
var processes = Array.prototype.slice.call( arguments, 2 );
var numFinished = processes.length;
var isFail = false;
var promise = {
success: function( ) {
numFinished --;
if ( numFinished == 0 && isFail ) {
endPromise.fail( );
} else if ( numFinished == 0 ) {
endPromise.success( );
}
},
fail: function( ) {
isFail = true;
numFinished --;
if ( numFinished == 0 ) {
endPromise.fail( );
}
}
}
for ( var i = 0; i < processes.length; i++ ) {
runTree( context, promise, processes[ i ] );
}
},
openSignals: {},
signal: function( context, endPromise, signalName ) {
var sig = context.openSignals[ signalName ];
if ( sig ) {
delete context.openSignals[ signalName ];
sig.success( );
endPromise.success( );
} else {
context.openSignals[ signalName ] = endPromise;
}
},
randomSuccess: function( context, endPromise, randomness ) {
if ( Math.random() < ( randomness || 0.5 ) ) {
endPromise.success( );
} else {
endPromise.fail( );
}
},
goToPosition: function( context, endPromise, pos ) {
// Simulate NPC walking to a a position
console.log( "go to position", pos );
setTimeout( endPromise.success, 1000 );
},
print: function( context, endPromise, text ) {
console.log( text );
endPromise.success( );
},
wait: function( context, endPromise, time ) {
setTimeout( endPromise.success, time );
}
}
function runTree( context, promise, expr ) {
var func = expr[ 0 ];
var args = expr.slice( 1 );
context[ func ].apply( this, [ context, promise ].concat( args ) );
}
var result = {
success: function( ) {
console.log( "finished with success" );
},
fail: function( ) {
console.log( "finished with fail" );
}
}
function example() {
runTree( rootContext, result,
["seq",
["parallel",
["loop", 5,
["seq",
["wait", 500],
["randomSuccess", 0.5],
["goToPosition", [4, 5]],
["wait", 500],
["goToPosition", [1, 4]]]],
["loop", 10,
["seq",
["print", "hello"],
["wait", 1000]]]],
["print", "Second thing in the sequence"]] );
}
function exampleRandomBranch() {
runTree( rootContext, result,
["loop", 50,
["selector",
["seq",
["randomSuccess", 0.33],
["print", "first"]],
["seq",
["randomSuccess", 0.5],
["print", "second"]],
["print", "third"]]] );
}
function exampleSynchronise() {
runTree( rootContext, result,
["parallel",
["seq",
["print", "starting 1"],
["wait", 1000],
["print", "sending signal"],
["signal", "sync1"]],
["seq",
["print", "starting 2"],
["print", "wait for signal"],
["signal", "sync1"],
["print", "completed wait for signal"]]] );
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment