Skip to content

Instantly share code, notes, and snippets.

@josephg
Last active April 17, 2018 06:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save josephg/f1dcae6bdbd121cf94dcb0905bde6eb0 to your computer and use it in GitHub Desktop.
Save josephg/f1dcae6bdbd121cf94dcb0905bde6eb0 to your computer and use it in GitHub Desktop.
Behaviour trees with generators example 2
// 2 entities, George and Phil will meet on this fateful night
const e1 = {
name: 'George',
health: 400,
target: null,
}
const e2 = {
name: 'Phil',
health: 400,
target: null,
}
e1.behave = agentBehaviour(e1)
e2.behave = 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)
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(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.
// Run iterator so long as predicate remains true
function *iwhile(pred, iter) {
if (typeof iter === 'function') iter = iter() // Avoids some awkward syntax
while (true) {
if (!pred()) return
const {value, done} = iter.next()
if (done) return value // Wrap return value of iterator
else yield value
}
}
// Helper to wait X frames
function *wait(framecount) {
while(--framecount) yield // Each call to yield will wait 1 frame
}
// *** 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)
}
$ 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 19 damage, 981 hp remaining
Phil engages George
Phil charges a fireball at George
George charges a fireball at Phil
πŸ”₯ Phil's fireball hits George for 103 damage, 897 hp remaining
Phil charges a fireball at George
πŸ”₯ George's fireball hits Phil for 105 damage, 876 hp remaining
George charges a fireball at Phil
πŸ”₯ Phil's fireball hits George for 101 damage, 796 hp remaining
βš”οΈ Phil's sword slices George for 14 damage, 782 hp remaining
πŸ”₯ George's fireball hits Phil for 101 damage, 775 hp remaining
George charges a fireball at Phil
βš”οΈ Phil's sword slices George for 11 damage, 771 hp remaining
Phil charges a fireball at George
πŸ”₯ George's fireball hits Phil for 107 damage, 668 hp remaining
βš”οΈ George's sword slices Phil for 11 damage, 657 hp remaining
πŸ”₯ Phil's fireball hits George for 100 damage, 671 hp remaining
George charges a fireball at Phil
Phil charges a fireball at George
πŸ”₯ George's fireball hits Phil for 106 damage, 551 hp remaining
πŸ”₯ Phil's fireball hits George for 106 damage, 565 hp remaining
George charges a fireball at Phil
Phil charges a fireball at George
πŸ”₯ George's fireball hits Phil for 107 damage, 444 hp remaining
πŸ”₯ Phil's fireball hits George for 104 damage, 461 hp remaining
George charges a fireball at Phil
βš”οΈ Phil's sword slices George for 15 damage, 446 hp remaining
Phil charges a fireball at George
πŸ”₯ George's fireball hits Phil for 103 damage, 341 hp remaining
George charges a fireball at Phil
πŸ”₯ Phil's fireball hits George for 107 damage, 339 hp remaining
Phil charges a fireball at George
πŸ”₯ George's fireball hits Phil for 105 damage, 236 hp remaining
George charges a fireball at Phil
πŸ”₯ Phil's fireball hits George for 103 damage, 236 hp remaining
βš”οΈ Phil's sword slices George for 12 damage, 224 hp remaining
πŸ”₯ George's fireball hits Phil for 101 damage, 135 hp remaining
Phil charges a fireball at George
George charges a fireball at Phil
πŸ”₯ Phil's fireball hits George for 107 damage, 117 hp remaining
πŸ”₯ George's fireball hits Phil for 107 damage, 28 hp remaining
βš”οΈ Phil's sword slices George for 12 damage, 105 hp remaining
βš”οΈ George's sword slices Phil for 16 damage, 12 hp remaining
Phil charges a fireball at George
George charges a fireball at Phil
πŸ”₯ Phil's fireball hits George for 106 damage, -1 hp remaining
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