Damn, I failed ;D ::)
Here is what I got...
This is a microscopic conscious program.
The aim is to show some principles, and play around.
First, a conscious entity needs sensors and actuators. We'll keep it simple: only 8 numbers as external sensors (input), and 4 numbers as actuators (output). That's completely arbitrary.
var entity = {};
entity.externalSensors = [0, 0, 0, 0, 0, 0, 0, 0];
entity.externalActuators = [0, 0, 0, 0];
What they represent doesn't really matter. Sensors are modified by the outer world. Actuators modifiy the outer world.
Now we'll create simple setter functions for actuators.
entity.setActuator = function(actuatorId, newValue) {
entity.externalActuators[actuatorId] = newValue;
}
We want our program to be able to feel what's going on inside of itself. Let's make 4 internal sensors!
entity.internalSensors = [0, 0, 0, 0];
We only need 3 sensors, for now: one for the eventId, the two others for the "parameters" of the event. But I like 1/2/4/8-like numbers. Our only event so far is setActuator, so let's modify it.
entity.setActuator = function(actuatorId, newValue) {
entity.externalActuators[actuatorId] = newValue;
entity.internalSensors = [0, actuatorId, newValue, 0];
}
Now, whenever we move our muscles, we can feel it, thanks to our internal sensors. The zero in the first internal sensor represents the "setActuator event". It's our eventId: ID of setActuator = 0. The last internal sensor is left to zero because there's nothing more to say about this event.
Good. Now let's make an instant memory. The program will run step by step, and the instant memory will contain sensors and actuators data of the 4 most recent steps.
entity.instantMemory = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];
entity.refreshInstantMemory = function() {
entity.instantMemory.push(
entity.externalSensors
.concat(entity.internalSensors)
.concat(entity.externalActuators)
);
entity.instantMemory.shift();
}
The instant memory is a 4x(8+4+4)= 64 cells grid.
Wow, that's huge! No computer on earth can possibly handle this... Too bad. Well our program will have to focus on a small part of it.
To make our focus system, we need two things: a targeting device, and a compression device.
The targeting device's purpose is to choose what we focus on.
The compression device's purpose is to reduce the amount of data. It doesn't need to be a lossless function. For example, if I say "I met a girl today", I'm compressing a lot of things, but it still has a meaning. In a real world project, this is where you would stuff that wonderful pattern recognition algorithm. But here we'll use a simple function: find the 2 highest equal numbers, and return their sum.
The entity's "target" is an array of 4 numbers between 0 and 63, which are the addresses in the instant memory. That's our targeting device. We'll default to the first 4 sensors.
entity.target = [0, 1, 2, 3];
entity.setTarget = function(targetSlot, newAddress) {
entity.target[targetSlot] = newAddress;
entity.internalSensors = [1, targetSlot, newAddress, 0];
}
Like before, when we modify the focus, we let the program "feel" what it's doing, by updating the internal sensors, with an eventId of 1. So now we have two eventIds: 0 is setActuator, and 1 is setTarget.
Ok, here is a "fetcher", it just fetches and return the targeted values.
entity.xy = function(n) { return { x: n%16, y: Math.floor(n/16) }; }
entity.fetch = function() {
var values = [];
for (var t=0; t<4; t++) {
var addr = entity.xy(entity.target[t]);
values.push(entity.instantMemory[addr.y][addr.x]);
}
return values
}
And here is our compression device. It takes what's in the focus, and gives an interpretation of it. The returned number is the sum of the 2 highest equal numbers in the focus. The returned number describes the situation, even though a lot of data is lost in the process.
entity.interpret = function(values) {
var candidate = 0;
for (var i1=0; i1<3; i1++) {
for (var i2=i1+1; i2<4; i2++) {
if ((values[i1]==values[i2]) && (values[i1]>candidate))
candidate = values[i1];
}
}
return candidate*2;
}
Now we'll make a behavioral memory, in order to store our program's behavior. We keep it really simple stupid, it's just a reactive system, unable to learn. We associate a number returned by the compression device to an action.
entity.behavior = {
2: [0, 2, 3, 0],
6: [0, 1, 0, 0],
8: [1, 0, 2, 0]
}
entity.do = function(action) {
if (action[0]==0) setActuator(action[1], action[2]);
if (action[0]==1) setTarget(action[1], action[2]);
}
And the main loop would be something like:
entity.run = function() {
while (1) {
entity.refreshInstantMemory();
entity.do(
entity.behavior[
entity.interpret(
entity.fetch())]);
}
}
That's it!
You're right, it's absolutely useless as is, because
1) the compression device is completely meaningless
2) the system isn't wired to anything
Instead of an isolated run() loop, if we want to make a library out of it, we would rather provide a step() function like this:
entity.step = function(input) {
entity.externalSensors = input;
entity.refreshInstantMemory();
entity.do(
entity.behavior[
entity.interpret(
entity.fetch())]);
return entity.externalActuators;
}
The user of the library would then include the step() function in the main loop of an environment for the program.
About the feedback loops, what you saw here is not the external feedback loop, which indeed needs a medium to travel through. I consider this to be outside of the bot (it belongs to the environment). An example of external feedback loop is when you move your arm, you can feel your flesh (your muscle) moving. But your arm isn't part of your brain, hence the name "external" feedback.
No, what you saw here is the internal feedback loop. It's just the brain knowing (feeling) what it's doing.
Maybe with a more elaborated compression device, we could make some kind of self-observing Turing tape... a perfect (dumb) brain for Flappy Bird video game! :P