Created
April 17, 2018 05:10
-
-
Save josephg/8a5337a14246f6d9df38ac19d59f80fe to your computer and use it in GitHub Desktop.
Behaviour trees with generators example 2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const e1 = { | |
name: 'George', | |
health: 1000, | |
target: null, | |
} | |
const e2 = { | |
name: 'Phil', | |
health: 1000, | |
target: null, | |
} | |
e1.behave = iflatmap(agentBehaviour(e1)) | |
e2.behave = iflatmap(agentBehaviour(e2)) | |
function *patrol(e) { | |
console.log(e.name, 'patrolling for baddies...') | |
while(true) { | |
yield wait(10) | |
console.log(e.name, 'continues to patrol') | |
} | |
} | |
function *attack(e, target) { | |
console.log(e.name, 'engages', target.name) | |
yield iwhile(() => target.health > 0, function *() { | |
while (true) { | |
if (Math.random() < 0.5) { | |
// Cast fireball | |
console.log(e.name, 'charges a fireball at', target.name) | |
yield wait(10) | |
if (e.health <= 0) throw Error('Impossible!!') | |
const damage = 100 + (Math.random() * 10)|0 | |
target.health -= damage | |
console.log(`${e.name}'s fireball hits ${target.name} for ${damage} damage, ${target.health} hp remaining`) | |
} else { | |
// Attack with sword | |
const damage = 10 + (Math.random() * 10)|0 | |
target.health -= damage | |
console.log(`${e.name}'s sword slices ${target.name} for ${damage} damage, ${target.health} hp remaining`) | |
yield wait(3) | |
} | |
yield wait(5) | |
} | |
}) | |
console.log(target.name, 'is dead!', e.name, 'cheers and hollers') | |
e.target = null | |
//while(true) yield | |
} | |
function *agentBehaviour(e) { | |
yield iwhile(() => e.health > 0, function *() { | |
while (true) { | |
// If we have no target, just patrol around. | |
yield iwhile(() => e.target == null, patrol(e)) | |
// ... Ok we have a target now. Attack!! | |
yield attack(e, e.target) | |
} | |
}) | |
console.log('agent', e.name, 'is dead') | |
} | |
// *** Plumbing. | |
// The state is a stack of iterators. Each tick we'll take the top of the stack | |
// (the stack's last element) and iterate it. | |
// Grab a reference to GeneratorFunction so we can detect nested iterators. | |
const GeneratorFunction = (function *(){})().constructor | |
// Run iterator so long as predicate remains true | |
function *iwhile(pred, iter) { | |
if (typeof iter === 'function') iter = iflatmap(iter()) // Avoids some awkward syntax | |
while (true) { | |
if (!pred()) break | |
const {value, done} = iter.next() | |
if (done) break | |
yield value | |
} | |
} | |
// Helper to wait X frames | |
function *wait(framecount) { | |
while(--framecount) yield // Each call to yield will wait 1 frame | |
} | |
// Wrap an iterator, allowing it to yield other iterators. | |
function *iflatmap(iter) { | |
const stack = [iter] | |
// Used to get values out of nested generators | |
let retval | |
while (stack.length) { | |
const i = stack[stack.length - 1] | |
let {value, done} = i.next(retval) | |
//console.log('iter next', value, done) | |
if (done) { | |
// value is the return value of the function. We'll pass that back to the | |
// yield statement of the caller. We could instead force the caller to | |
// return or something. | |
stack.pop() | |
retval = value | |
} else if (typeof value === 'object' && value.constructor === GeneratorFunction) { | |
// Function yielded a generator. Recurse up. | |
stack.push(value) | |
lastVal = undefined | |
// And recurse | |
} else yield value // Normal value. Yield out and iterate normally. | |
} | |
return retval | |
} | |
// *** Actual runtime code | |
const timer = setInterval(tick, 100) | |
function updateEntity(e) { | |
if (e.dead) return | |
const {done} = e.behave.next() | |
if (done) { | |
console.log('Cleanup', e.name) | |
e.dead = true | |
} | |
} | |
let framecount = 0 | |
function tick() { | |
framecount++ | |
if (e1.target == null && framecount === 10) { | |
console.log('Oh no! They saw each other') | |
e1.target = e2 | |
e2.target = e1 | |
} | |
updateEntity(e1) | |
updateEntity(e2) | |
// Done! | |
if (e1.dead || e2.dead) clearInterval(timer) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ node bt.js | |
George patrolling for baddies... | |
Phil patrolling for baddies... | |
Oh no! They saw each other | |
George engages Phil | |
George's sword slices Phil for 15 damage, 985 hp remaining | |
Phil engages George | |
Phil charges a fireball at George | |
George's sword slices Phil for 10 damage, 975 hp remaining | |
Phil's fireball hits George for 108 damage, 892 hp remaining | |
George's sword slices Phil for 17 damage, 958 hp remaining | |
Phil charges a fireball at George | |
George charges a fireball at Phil | |
Phil's fireball hits George for 109 damage, 783 hp remaining | |
Phil charges a fireball at George | |
George's fireball hits Phil for 106 damage, 852 hp remaining | |
George charges a fireball at Phil | |
Phil's fireball hits George for 105 damage, 678 hp remaining | |
Phil's sword slices George for 17 damage, 661 hp remaining | |
George's fireball hits Phil for 108 damage, 744 hp remaining | |
George's sword slices Phil for 12 damage, 732 hp remaining | |
Phil's sword slices George for 19 damage, 642 hp remaining | |
George charges a fireball at Phil | |
Phil charges a fireball at George | |
George's fireball hits Phil for 106 damage, 626 hp remaining | |
Phil's fireball hits George for 104 damage, 538 hp remaining | |
George's sword slices Phil for 16 damage, 610 hp remaining | |
Phil charges a fireball at George | |
George charges a fireball at Phil | |
Phil's fireball hits George for 100 damage, 438 hp remaining | |
Phil charges a fireball at George | |
George's fireball hits Phil for 100 damage, 510 hp remaining | |
George charges a fireball at Phil | |
Phil's fireball hits George for 100 damage, 338 hp remaining | |
Phil charges a fireball at George | |
George's fireball hits Phil for 107 damage, 403 hp remaining | |
George's sword slices Phil for 19 damage, 384 hp remaining | |
Phil's fireball hits George for 103 damage, 235 hp remaining | |
George's sword slices Phil for 16 damage, 368 hp remaining | |
Phil charges a fireball at George | |
George charges a fireball at Phil | |
Phil's fireball hits George for 107 damage, 128 hp remaining | |
George's fireball hits Phil for 104 damage, 264 hp remaining | |
Phil charges a fireball at George | |
George's sword slices Phil for 14 damage, 250 hp remaining | |
Phil's fireball hits George for 103 damage, 25 hp remaining | |
George charges a fireball at Phil | |
Phil charges a fireball at George | |
George's fireball hits Phil for 108 damage, 142 hp remaining | |
Phil's fireball hits George for 107 damage, -82 hp remaining | |
agent George is dead | |
Cleanup George | |
George is dead! Phil cheers and hollers | |
Phil patrolling for baddies... |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment