Skip to content

Instantly share code, notes, and snippets.

@josephg
Created April 17, 2018 05:10
Show Gist options
  • Save josephg/8a5337a14246f6d9df38ac19d59f80fe to your computer and use it in GitHub Desktop.
Save josephg/8a5337a14246f6d9df38ac19d59f80fe to your computer and use it in GitHub Desktop.
Behaviour trees with generators example 2
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)
}
$ 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