Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- 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)
- }
Add Comment
Please, Sign In to add comment