Skip to content

Instantly share code, notes, and snippets.

@Larkenx
Last active September 10, 2020 09:06
Show Gist options
  • Save Larkenx/e33537b860d9a5dc240440f4e36cbeac to your computer and use it in GitHub Desktop.
Save Larkenx/e33537b860d9a5dc240440f4e36cbeac to your computer and use it in GitHub Desktop.
JavaScript Goal Driven AI with Event Streams, based on socket.io's API & Caves of Qud goal driven AI https://youtu.be/4uxN5GqXcaA
export default class EventHandler {
constructor(eventStream) {
this.eventStream = eventStream
}
on(topic, fn) {
this.eventStream.addEventListener(topic, fn)
return this
}
}
export default class EventStream {
constructor() {
this.events = []
this.topics = {} // map of topic string to array of cb functions
}
addEventListener(topic, fn) {
if (topic in this.topics) this.topics[topic].push(fn)
else this.topics[topic] = [fn]
}
emit(topic, params) {
this.events.unshift({ topic, params })
if (topic in this.topics) this.topics[topic].forEach(cb => cb(params))
return this
}
}
import EventHandler from './EventHandler'
import EventStream from './EventStream'
const { sign } = Math
const eventStream = new EventStream()
const FindTheGoldGoal = e => {
return theEntity => {
let { x, y, amount } = e // random info we emit about the gold found somewhere event
console.log(`Goblin is trying to find ${amount} gold pieces somewhere around ${x},${y}!`)
theEntity.moveTowards(x, y)
if (theEntity.x !== x || theEntity.y !== y) {
theEntity.goals.unshift(FindTheGoldGoal(e))
} else {
console.log(`Goblin is at the original gold pieces location!`)
}
}
}
const IdleGoal = e => {
return theEntity => {
console.log('The entity does nothing')
}
}
const RunAwayGoal = e => {
return theEntity => {
let { x, y } = e // run away from a given location
console.log(`Goblin is running away from the danger at ${x},${y}!`)
theEntity.moveTowards(x + 10, y + 10)
let dist = ~~(((theEntity.x - x) ** 2, (theEntity.y - y) ** 2) ** 0.5)
if (dist <= 5) {
theEntity.goals.unshift(RunAwayGoal(e))
} else {
console.log(`Goblin has escaped the danger!`)
}
}
}
class Goblin {
constructor() {
this.events = new EventHandler(eventStream)
this.x = 5
this.y = 5
this.goals = []
this.events
.on('goldFoundSomewhere', e => {
console.log('Gold was found somewhere...adding new find the gold goal')
this.goals.unshift(FindTheGoldGoal(e))
})
.on('nearbyGoblinDied', e => {
console.log(`Goblin is running away because a nearby goblin died!`)
this.goals.unshift(RunAwayGoal(e))
})
}
act() {
if (this.goals.length >= 1) {
this.goals.shift()(this)
} else {
this.goals.unshift(IdleGoal())
}
}
moveTowards(x, y) {
this.x += sign(x - this.x)
this.y += sign(y - this.y)
// console.log(`Goblin moves to ${this.x},${this.y}!`)
}
}
export default function test() {
let goblin = new Goblin()
eventStream.emit('goldFoundSomewhere', { x: 2, y: 2, amount: 20 })
for (let i = 0; i < 5; i++) goblin.act()
eventStream.emit('goldFoundSomewhere', { x: 2, y: 2, amount: 20 })
eventStream.emit('nearbyGoblinDied', { x: 2, y: 2 })
for (let i = 0; i < 10; i++) goblin.act()
}
// Gold was found somewhere...adding new find the gold goal
// 3x Goblin is trying to find 20 gold pieces somewhere around 2,2!
// Goblin is at the original gold pieces location!
// The entity does nothing
// Gold was found somewhere...adding new find the gold goal
// Goblin is running away because a nearby goblin died!
// 6x Goblin is running away from the danger at 2,2!
// Goblin has escaped the danger!
// 4x Goblin is trying to find 20 gold pieces somewhere around 2,2!

For more examples & use cases, feel free to check out my GoalBasedAI, which extends my base Actor class and uses an event handler to push goals to facilitate actor's actions in my turn-based game.

I have an example GreedyGoblin that uses goals & events to create an enemy that likes to find and get gold whenever the player loots items in the game. It uses function to filter out loot events (LootFilter) that only contain gold in them, so that way our FindGoldGoal goal is only added whenever a player loots gold somewhere!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment