Hi old friends,
I hope you're fine, in good health and feeling good. I wish you a nice end of year.
:)
I propose:
Don't try to create a mind. That's impossible.
Instead, only try to build an entire world.
Then you'll realize that, this world is a mind.
What do you think?
function World(opt = {}) {
this.systems = opt.systems || [];
this.things = opt.things || {};
this.cutRequests = [];
this.delRequests = [];
// every thing will be part of the whole
this.whole = this.thing();
}
World.prototype.genId = (function() {
let id = 0n;
return function(prefix) {
return prefix + (id++);
}
})();
World.prototype.partof = function(compoundId, componentId) {
// getter: get things which are part of argument 1
if (!componentId) return this.things[compoundId].madeof;
// setter: attach a new component to argument 1
this.things[compoundId].madeof.push(componentId);
this.things[componentId].partof.push(compoundId);
}
World.prototype.madeof = function(componentId, compoundId) {
// getter: get things which are made of argument 1
if (!compoundId) return this.things[componentId].partof;
// setter: attach argument 1 to a new compound
this.things[compoundId].madeof.push(componentId);
this.things[componentId].partof.push(compoundId);
}
World.prototype.thing = function(t = {}, edges) {
// getter version
if (typeof t == "string")
return this.things[t.toString()];
// setter version
let id = this.genId('T');
this.things[id] = {
value: t,
next: null,
partof: [],
madeof: []
};
// always part of the whole
if (this.things[this.whole])
this.madeof(this.whole, id);
// edges can be set up at creation
// things they link must already exist
if (edges) {
if (edges.partof)
for (let compoundId of edges.partof)
this.madeof(id, compoundId);
if (edges.madeof)
for (let componentId of edges.madeof)
this.partof(id, componentId);
}
return id;
}
World.prototype.cut = function(tid1, tid2) {
// edge-cuts are not immediate
// they are requested first
this.cutRequests.push([tid1, tid2]);
}
World.prototype.cutNow = function(tid1, tid2) {
// if edges still exist
if (!((tid1 in this.things) && (tid2 in this.things))) return;
// tid1 is not part of tid2 anymore
this.things[tid1].partof =
this.things[tid1].partof.filter(tid => tid != tid2);
// tid1 is not made of tid2 anymore
this.things[tid1].madeof =
this.things[tid1].madeof.filter(tid => tid != tid2);
// tid2 is not part of tid1 anymore
this.things[tid2].partof =
this.things[tid2].partof.filter(tid => tid != tid1);
// tid2 is not made of tid1 anymore
this.things[tid2].madeof =
this.things[tid2].madeof.filter(tid => tid != tid1);
}
World.prototype.del = function(tid) {
// thing deletions are not immediate
// they are requested first
this.delRequests.push(tid);
}
World.prototype.delNow = function(tid) {
// if things still exist
if (!(tid in this.things)) return;
// they're not part of anything anymore
for (let compoundId of this.things[tid].partof)
this.things[compoundId].madeof =
this.things[compoundId].madeof.filter(t => t != tid);
// they're not made of anything anymore
for (let componentId of this.things[tid].madeof)
this.things[componentId].partof =
this.things[componentId].partof.filter(t => t != tid);
delete this.things[tid]
}
World.prototype.system = function(opt) {
let id = this.genId('S');
// systems will run in order of creation
this.systems.push({
id,
// triggers is a list of qualifying functions
triggers: opt.triggers,
// template function calculates the next world state
template: opt.template,
// systems may be deactivated dynamically
active: !opt.inhibited
});
return id;
}
World.prototype.status = function(sid, active) {
if (arguments.length == 1)
// toggle
this.systems[sid].active = !this.systems[sid].active;
else
// set
this.systems[sid].active = !!active;
return this.systems[sid].active;
}
World.prototype.qualify = function(system, thing) {
// each trigger of a system
// must find at least 1 component
// that meets its requirements
for (let trigger of system.triggers) {
let greenlight = false;
for (let componentId of thing.madeof)
if (trigger(this.things[componentId])) {
greenlight = true;
break;
}
// else the system does not qualify
if (!greenlight) return false;
}
return true;
}
World.prototype.step = function() {
// run systems
// if they are active
// and where they qualify
// to prepare the next world state
for (let system of this.systems)
if (system.active)
for (let tid in this.things)
if (this.qualify(system, this.things[tid]))
this.things[tid].next =
system.template(this.things[tid]);
// apply edge-cut requests
for (let request of this.cutRequests)
this.cutNow(request[0], request[1]);
this.cutRequests = [];
// apply thing deletion requests
for (let request of this.delRequests)
this.delNow(request);
this.delRequests = [];
// make the next world state actual
for (let tid in this.things) {
if (this.things[tid].next)
this.things[tid].value = this.things[tid].next();
this.things[tid].next = null;
}
}
// ==============================
let w = new World();
let me = w.thing({ who: "me" }, {
madeof: [
w.thing({ type: "human" }),
w.thing({ x: 5, y: 8 })
]
})
let testsys = w.system({
triggers: [
function(component) {
return component.value.type == "human";
}
],
template: function(thing) {
console.log(thing.value.who);
}
});
w.step();
function World(opt = {}) {
this.systems = opt.systems || [];
this.arbiters = opt.arbiters || {};
this.changers = opt.changers || {};
this.things = opt.things || {};
this.livingThings = new Set();
this.cutRequests = [];
this.delRequests = [];
this.wishlist = {};
}
World.prototype.genId = (function() {
let id = 0n;
return function(prefix) {
return prefix + (id++);
}
})();
World.prototype.live = function() {
// add to list of things that can change
for (let arg of Array.from(arguments))
for (let tid of Array.isArray(arg) ? arg : [arg])
this.livingThings.add(tid);
}
World.prototype.partof = function(compoundId, componentId) {
// getter: get things which are part of argument 1
if (!componentId) return this.things[compoundId].madeof;
// setter: attach a new component to argument 1
this.things[compoundId].madeof.push(componentId);
this.things[componentId].partof.push(compoundId);
// they are now active
this.live(compoundId, componentId);
}
World.prototype.madeof = function(componentId, compoundId) {
// getter: get things which are made of argument 1
if (!compoundId) return this.things[componentId].partof;
// setter: attach argument 1 to a new compound
this.things[compoundId].madeof.push(componentId);
this.things[componentId].partof.push(compoundId);
// they are now active
this.live(compoundId, componentId);
}
World.prototype.thing = function(t = {}, edges) {
// getter version
if (typeof t == "string")
return this.things[t.toString()];
// setter version
let id = this.genId('T');
this.things[id] = {
id,
value: t,
next: null,
partof: [],
madeof: []
};
// edges can be set up at creation
// things they link must already exist
if (edges) {
if (edges.partof)
for (let compoundId of edges.partof)
this.madeof(id, compoundId);
if (edges.madeof)
for (let componentId of edges.madeof)
this.partof(id, componentId);
}
// new things need to be handled
this.live(id);
return id;
}
World.prototype.cut = function(tid1, tid2) {
// edge-cuts are not immediate
// they are requested first
this.cutRequests.push([tid1, tid2]);
}
World.prototype.cutNow = function(tid1, tid2) {
// if edges still exist
if (!((tid1 in this.things) && (tid2 in this.things))) return;
// all of these are active now
this.live(
tid1,
tid2,
this.things[tid1].partof,
this.things[tid1].madeof,
this.things[tid2].partof,
this.things[tid2].madeof
);
// tid1 is not part of tid2 anymore
this.things[tid1].partof =
this.things[tid1].partof.filter(tid => tid != tid2);
// tid1 is not made of tid2 anymore
this.things[tid1].madeof =
this.things[tid1].madeof.filter(tid => tid != tid2);
// tid2 is not part of tid1 anymore
this.things[tid2].partof =
this.things[tid2].partof.filter(tid => tid != tid1);
// tid2 is not made of tid1 anymore
this.things[tid2].madeof =
this.things[tid2].madeof.filter(tid => tid != tid1);
}
World.prototype.del = function(tid) {
// thing deletions are not immediate
// they are requested first
this.delRequests.push(tid);
}
World.prototype.delNow = function(tid) {
// if things still exist
if (!(tid in this.things)) return;
// surroundings are active
this.live(
this.things[tid].partof,
this.things[tid].madeof
);
// they're not part of anything anymore
for (let compoundId of this.things[tid].partof)
this.things[compoundId].madeof =
this.things[compoundId].madeof.filter(t => t != tid);
// they're not made of anything anymore
for (let componentId of this.things[tid].madeof)
this.things[componentId].partof =
this.things[componentId].partof.filter(t => t != tid);
delete this.things[tid]
}
World.prototype.arbiter = function(domain, body) {
this.arbiters[domain] = body;
}
World.prototype.changer = function(domain, body) {
this.changers[domain] = body;
}
World.prototype.system = function(opt) {
let id = this.genId('S');
// systems will run in order of creation
this.systems.push({
id,
// triggers is a list of qualifying functions
triggers: opt.triggers,
// template function calculates the next world state
template: opt.template,
// systems may be deactivated dynamically
active: !opt.inhibited
});
return id;
}
World.prototype.status = function(sid, active) {
if (arguments.length == 1)
// toggle
this.systems[sid].active = !this.systems[sid].active;
else
// set
this.systems[sid].active = !!active;
return this.systems[sid].active;
}
World.prototype.qualify = function(system, thing) {
// each trigger of a system
// must find at least 1 component
// that meets its requirements
for (let trigger of system.triggers) {
let greenlight = false;
for (let componentId of thing.madeof)
if (trigger(this.things[componentId])) {
greenlight = true;
break;
}
// else the system does not qualify
if (!greenlight) return false;
}
return true;
}
World.prototype.step = function() {
// run systems
// if they are active
// and where they qualify
// to prepare the next world state
for (let system of this.systems)
if (system.active)
for (let tid of this.livingThings.values())
if (this.qualify(system, this.things[tid])) {
let wish = system.template(this.things[tid]);
if (wish) {
if (!this.wishlist[wish.domain])
this.wishlist[wish.domain] = [];
this.wishlist[wish.domain].push(wish.decision);
}
}
// reset the list of active things
this.livingThings.clear();
// if there are conflicts on a domain
// solve them if an arbiter is deployed on this domain
for (let domain in this.wishlist)
if (this.arbiters[domain])
this.wishlist[domain] =
this.arbiters[domain](this.wishlist[domain]);
// make the next world state actual
for (let domain in this.wishlist)
this.changers[domain](this.wishlist[domain]);
this.wishlist = {};
// apply edge-cut requests
for (let request of this.cutRequests)
this.cutNow(request[0], request[1]);
this.cutRequests = [];
// apply thing deletion requests
for (let request of this.delRequests)
this.delNow(request);
this.delRequests = [];
}
// ==============================
let w = new World();
w.arbiter("console logging", function(decisionList) {
return decisionList.join(' & ');
});
w.changer("console logging", function(decision) {
console.log(decision);
});
w.system({
triggers: [
function(component) {
return component.value.type == "human";
}
],
template: function(thing) {
return {
domain: "console logging",
decision: thing.value.who
}
}
});
let you = w.thing({ who: "you" }, {
madeof: [
w.thing({ type: "human" }),
w.thing({ x: 6, y: 8 })
]
})
let me = w.thing({ who: "me" }, {
madeof: [
w.thing({ type: "human" }),
w.thing({ x: 5, y: 8 })
]
})
w.step();
On the other hand, when I create less "noble" stuff (like games), my approach is direct and quick.
Example. A door has an "openable" component, so when it receives an "open" order, it opens. Then you implement a "lockable" component, with a stronger weight that "openable". When the door receives an "open" message, since "lockable" is stronger than "openable", it can prevent the door from opening. And so on.
function World(opt = {}) {
this.things = opt.things || {};
this.behaviors = opt.behaviors || {};
this.changers = opt.changers || {};
this.views = opt.views || {};
this.cutRequests = [];
this.delRequests = [];
}
World.prototype.genId = (function() {
let id = 0n;
return function(prefix) {
return prefix + (id++);
}
})();
World.prototype.update = function(tid) {
this.updateViews(tid);
this.collectBehaviors(tid);
}
World.prototype.collectBehaviors = function(tid) {
let list = [];
// behaviors depend on the components
for (let componentId of this.things[tid].madeof)
if (this.things[componentId].value.behaviors)
for (let behavior of this.things[componentId].value.behaviors)
if (!list.includes(behavior))
list.push(behavior);
let that = this;
list.sort(
function(a, b) {
return that.behaviors[a].weight - that.behaviors[b].weight;
}
);
this.things[tid].orderedBehaviors = list;
}
World.prototype.partof = function(compoundId, componentId, noUpdate) {
// getter: get things which are part of argument 1
if (!componentId) return this.things[compoundId].madeof;
// setter: attach a new component to argument 1
this.things[compoundId].madeof.push(componentId);
this.things[componentId].partof.push(compoundId);
if (!noUpdate) this.update(compoundId);
}
World.prototype.madeof = function(componentId, compoundId, noUpdate) {
// getter: get things which are made of argument 1
if (!compoundId) return this.things[componentId].partof;
// setter: attach argument 1 to a new compound
this.things[compoundId].madeof.push(componentId);
this.things[componentId].partof.push(compoundId);
if (!noUpdate) this.update(compoundId);
}
World.prototype.thing = function(t = {}, edges) {
// getter version
if (typeof t == "string")
return this.things[t.toString()];
// setter version
let id = this.genId('T');
this.things[id] = {
id,
value: t,
partof: [],
madeof: [],
orderedBehaviors: []
};
// edges can be set up at creation
// things they link must already exist
if (edges) {
if (edges.partof)
for (let compoundId of edges.partof)
this.madeof(id, compoundId);
if (edges.madeof)
for (let componentId of edges.madeof)
this.partof(id, componentId, true);
this.update(id);
}
return id;
}
World.prototype.cut = function(tid1, tid2) {
// edge-cuts are not immediate
// they are requested first
this.cutRequests.push([tid1, tid2]);
}
World.prototype.cutNow = function(tid1, tid2) {
// if edges still exist
if (!((tid1 in this.things) && (tid2 in this.things))) return;
// tid1 is not part of tid2 anymore
this.things[tid1].partof =
this.things[tid1].partof.filter(tid => tid != tid2);
// tid1 is not made of tid2 anymore
this.things[tid1].madeof =
this.things[tid1].madeof.filter(tid => tid != tid2);
// tid2 is not part of tid1 anymore
this.things[tid2].partof =
this.things[tid2].partof.filter(tid => tid != tid1);
// tid2 is not made of tid1 anymore
this.things[tid2].madeof =
this.things[tid2].madeof.filter(tid => tid != tid1);
this.update(tid1);
this.update(tid2);
}
World.prototype.del = function(tid) {
// thing deletions are not immediate
// they are requested first
this.delRequests.push(tid);
}
World.prototype.delNow = function(tid) {
// if things still exist
if (!(tid in this.things)) return;
// they're not part of anything anymore
for (let compoundId of this.things[tid].partof) {
this.things[compoundId].madeof =
this.things[compoundId].madeof.filter(t => t != tid);
this.update(compoundId);
}
// they're not made of anything anymore
for (let componentId of this.things[tid].madeof) {
this.things[componentId].partof =
this.things[componentId].partof.filter(t => t != tid);
}
delete this.things[tid];
}
World.prototype.everything = function() {
return Object.keys(this.things);
}
World.prototype.behavior = function(name, weight, wishmaker) {
// check no existing behavior has the same weight
for (let b in this.behaviors)
if (this.behaviors[b].weight == weight)
throw `behavior "${name}" has the same weight (${weight}) as "${b}"`;
// check no existing behavior has the same name
if (name in this.behaviors)
throw `behavior "${name}" already defined`;
this.behaviors[name] = { name, weight, wishmaker };
}
World.prototype.changer = function(name, body) {
this.changers[name] = body;
}
World.prototype.send = function(tid, message) {
let wishStack = [];
// all behaviors contribute to the wish stack
for (let b of this.things[tid].orderedBehaviors) {
let wish = this.behaviors[b].wishmaker(
tid,
message,
wishStack
);
if (wish) wishStack.push(wish);
}
if (wishStack.length == 0) return;
let finalWish = wishStack[wishStack.length - 1];
// call for actual changes
if (finalWish.changer)
this.changers[finalWish.changer](
tid,
finalWish
);
}
World.prototype.value = function(tid, key, val) {
// all getter
if (!key) return this.things[tid].value;
// key getter
if (!val) return this.things[tid].value[key];
// setter
this.things[tid].value[key] = val;
// update
for (let id of this.things[tid].partof)
this.update(id);
}
World.prototype.assign = function(tid, val) {
Object.assign(this.things[tid].value[key], val);
// update
for (let id of this.things[tid].partof)
this.update(id);
}
World.prototype.view = function(view, selectors) {
// getter version
if (!selectors)
return Array.from(this.views[view].list.values());
// setter version
this.views[view] = {
list: new Set(),
selectors
};
}
World.prototype.updateViews = function(tid) {
for (let view in this.views) {
let accept = true;
// every selector must be satisfied
for (let selector of this.views[view].selectors) {
let greenlight = false;
for (let componentId of this.things[tid].madeof)
// one component is enough to satisfy the selector
if (selector(this.things[componentId])) {
greenlight = true;
break;
}
if (!greenlight) {
accept = false;
break;
}
}
if (accept) this.views[view].list.add(tid);
else this.views[view].list.delete(tid);
}
}
/*
when receiving a message
the behavior of a thing
depends on its components
*/
let w = new World();
let t = w.thing.bind(w);
w.changer("vision", function(tid, wish) {
console.log("[vision] " + wish.vision);
});
w.behavior("sight", 1, function(tid, msg, wishStack) {
if (msg.image)
return {
changer: "vision",
vision: "you see " + msg.image
}
});
w.behavior("protection from curses", 3, function(tid, msg, wishStack) {
let uncurse;
for (let wish of wishStack)
if (wish.type == "curse")
uncurse = wish.blocked;
return uncurse;
});
w.behavior("blind curse", 2, function(tid, msg, wishStack) {
let lastWish = wishStack[wishStack.length - 1];
if (lastWish.changer == "vision")
return {
type: "curse",
blocked: lastWish
};
});
w.view("able to see", [
component => component.value.organ == "eyes"
]);
console.log(w.view("able to see"));
let me = t({ who: "me" }, {
madeof: [
t({ race: "gobelin" }),
t({ organ: "eyes", behaviors: ["sight"] }),
t({ object: "ring", behaviors: ["protection from curses"] }),
t({ curse: "blind", behaviors: ["blind curse"] }),
]
});
console.log(w.view("able to see"));
w.send(me, { image: "gold coins" })