I appear to have gotten myself into a bit of a mess.
“Make a snail brain/nerve-cluster for your snails,” they said. “It’ll be easy”, they said.
No. Nobody said that. Who would say that?! I got into this mess on my own and now I don’t know if I can dig myself out. I’ve already spent so much effort on this brain thing that it’s too late to turn back.
And it’s not all bad - it’s coming along, and it’s fun to think about this stuff. The thing is, I have no idea what I’m doing. I’m just making up how I think a snail brain should work. I’m afraid at some point, probably when I’m very far in, I will realize that I’m doing everything all wrong and have to start from scratch.
Snails have a very primitive brain. They are capable of associative learning. This is what I want to simulate.
A couple of nights ago I talked through the parts of the brain I have so far with David pretending to listen. It helped a lot, since when I got back to snailing after js13kGames I had forgotten where I left off or wtf any of what I’d written meant. Now, as a refresher, I’m going to go over it again here.
Typing this up I can already see a bunch of stuff I need to fix, or scrap completely. I guess doing this does help.
The brain
Each snail has a brain:
protected function getBrainAttribute() {
$brain = new Brain($this);
return $brain;
}
{:lang=“php”}
In the brain constructor, we tell the brain which snail it belongs to (since it is not a Model we can’t use the usually handy belongsTo
relationship). We also create the olfactory, optical, tactile, and gustatory sensors and what I’m calling the attention and recognition neurons.
function __construct($snail) {
$this->snail = $snail;
$this->olfactorySensor = new OlfactorySensor($snail);
$this->opticalSensor = new OpticalSensor($snail);
$this->tactileSensor = new TactileSensor($snail);
$this->gustatorySensor = new GustatorySensor($snail);
$this->attentionNeuron = new AttentionNeuron($snail);
$this->recognitionNeuron = new RecognitionNeuron();
}
{:lang=“php”}
Each sensor is a child class of Neuron.
Every minute a job that checks for recurring events runs. One of these recurring events runs checkAllInputs()
within the brain.
public function checkAllInputs() {
$this->olfactorySensor->checkForInput();
$this->opticalSensor->checkForInput();
$this->tactileSensor->checkForInput();
$this->gustatorySensor->checkForInput();
$this->setWeights();
}
{:lang=“php”}
The checkForInput()
function then does this. We pass the snail’s current olfactory receptor quality into the checkSensor function.
class OlfactorySensor extends Neuron
{
public function checkForInput() {
$detectedArr = $this->checkSensor($this->snail->currentOlfactoryReceptorQual);
$this->detectedArr = $detectedArr;
}
}
{:lang=“php”}
We check the sensor like this in the main Neuron class.
public function checkSensor($sensorQual) {
// Create a new jar controller and get all objects inside the jar that the snail is in.
$jarController = new JarController();
$allObjectsInJar = $jarController->getAllObjectsInJar($this->snail->jar->jarID);
// Create a blank array of detected objects.
$detectedArr = [];
// Set point1 to the snail's current position from the db.
$point1 = [
'x' => $this->snail->posX,
'y' => $this->snail->posY
];
// Loop through each object in the jar and get its position.
foreach ($allObjectsInJar as $object) {
// Set point2 to object's current position from db.
$point2 = [
'x' => $object->posX,
'y' => $object->posY
];
// Get the distance between point1 (the snail) and point2 (the object).
$distance = Utility::getDistance($point1, $point2); // in cm
// If the distance is less than or equal to the sensor quality, object is detected
if ($distance <= $sensorQual) {
// What KIND of an object is it? Another snail? Item?
// Because each of these have their own tables in the DB and their own primary key columns we need to get this information.
// We do this by using the name of the object class and appending "ID" to it.
// This is the primary key column name.
$idString = strtolower(get_class($object)) . 'ID';
// If the ID of the object === ID of the snail that has detected it,
// continue through that iteration of the loop and do nothing futher, since we don't need the snail to detect itself.
if ($object->$idString === $this->snail->snailID) {
continue;
}
// Otherwise, create an array with the details of the object we're detected.
$toPush = [
'id' => $object->$idString,
'idString' => $idString,
'inputWeight' => 1,
'distance' => $distance
];
array_push($detectedArr, $toPush);
}
}
return $detectedArr;
}
{:lang=“php”}
The next step in the checkAllInputs()
function is setWeights()
. This is where all kinds of screwed up stuff happens:
protected function setWeights() {
// Create an array of all sensors we care about
$allSensors = [
'olfactory' => $this->snail->currentOlfactoryReceptorQual,
'optical' => $this->snail->currentOpticalReceptorQual,
'tactile' => $this->snail->currentTactileReceptorQual,
'gustatory' => $this->snail->currentGustatoryReceptorQual
];
// Create blank array of detected IDs
$allDetectedIDs = [];
// Get index of highest value in array and value itself
$bestReceptors = Utility::doubleMax($allSensors);
// Best receptor name is receptor + Sensor.
// Note: why not just name them with Sensor already appended in allSensors array?
$bestReceptorName = $bestReceptors['i'] . 'Sensor';
Log::info('best receptor array: ' , $this->$bestReceptorName->detectedArr);
// Loop through all objects detected by best receptor by reference and add 0.5 to their input weight
foreach ($this->$bestReceptorName->detectedArr as &$object) {
$object['inputWeight'] += 0.5;
}
// Merge objects detected by all sensors into one array
$allDetected = array_merge($this->olfactorySensor->detectedArr, $this->opticalSensor->detectedArr, $this->tactileSensor->detectedArr, $this->gustatorySensor->detectedArr);
// Create blank array for deduped detected object list
$allDetectedDeDuped = [];
// Count occurrences of idential ID in array
$countedArr = array_count_values(array_map(function($value){return $value['id'];}, $allDetected));
// Loop through array of counted values
foreach ($countedArr as $objectCount) {
// Loop through all detected objects
foreach ($allDetected as &$object) {
// Add 0.1 for each occurrence (so if object is detected by multiple sensors, its inputWeight is higher)
$object['inputWeight'] += 0.1 * $objectCount;
$count = 0;
// Loop through all detected deduped array
foreach ($allDetectedDeDuped as $dedupedDetectedObject) {
// If object id already exists, add to count
if ($object['id'] === $dedupedDetectedObject['id']) {
$count++;
}
}
// If count is 0/object isn't already in there
if ($count === 0) {
// Push to array of all detected deduped
array_push($allDetectedDeDuped, $object);
}
}
}
/**** whaaaaaaaattttttt??! *****/
// Get closest object
$closestObject = null;
// Loop through all detected deduped array
foreach ($allDetectedDeDuped as $object) {
// If closest object hasn't yet been set...
if ($closestObject === null) {
// Set this object as closest object
$closestObject = $object;
}
// If distance to this object is less than distance to current closest object...
if ($object['distance'] < $closestObject['distance']) {
$closestObject = $object;
}
}
// Loop through deduped...wait again?
foreach($allDetectedDeDuped as &$object) {
// If this is the closest object, add to inputWeight
if ($object['id'] === $closestObject['id']) {
$object['inputWeight'] += 0.05;
}
// Create new synapse for this impulse
$synapse = new Synapse($object['inputWeight'], $this->attentionNeuron, $object);
}
// Pick object to focus on
$this->attentionNeuron->chooseFocus();
}
{:lang=“php”}
And here is what we have in the Synapse constructor:
function __construct($inputWeight, $destinationNeuron, $object = null ) {
$this->inputWeight = $inputWeight;
$this->destinationNeuron = $destinationNeuron;
$this->targetObjectID = $object['id'];
$this->targetObjectClass = $object['idString'];
$this->targetObjectInputWeight = $object['inputWeight'];
$this->sendImpulse();
}
{:lang=“php”}
sendImpulse():
private function sendImpulse() {
$this->destinationNeuron->receiveImpulse($this);
}
{:lang=“php”}
In this case the destination neuron is the attention neuron.
public function receiveImpulse($impulse) {
$object = [
'id' => $impulse->targetObjectID,
'idField' => $impulse->targetObjectClass,
'inputWeight' => $impulse->targetObjectInputWeight
];
array_push($this->allObjects, $object);
}
{:lang=“php”}
After all this is done, and the attention neuron has a list of objects with their IDs, ID column name, and inputWeight, we try and pick which object the snail will focus on.
function chooseFocus() {
$focusedObject = null;
foreach ($this->allObjects as $object) {
// If there is no focused object, set the first one to focused
if ($focusedObject === null) {
$focusedObject = $object;
}
// If the focused object inputWeight < current object inputWeight...
if ($focusedObject['inputWeight'] < $object['inputWeight']) {
// Set current object as focused
$focusedObject = $object;
}
}
// Focus chosen, send to recognition neuron
$brain = $this->snail->brain;
$brain->recognitionNeuron->checkRecognition($focusedObject);
}
}
{:lang=“php”}
And that’s sort of where I left off on the brain side so far:
class RecognitionNeuron extends Neuron
{
function checkRecognition($object) {
Log::info('checking if object id ' . $object['id'] . ' is recognized');
}
}
{:lang=“php”}
The memory tables
Right now I am preparing the memory tables. There will be 3:
- memories_sensory - for sensory memories, kept for just a few minutes
- memories_st - for short term memories. If an object collects n sensory memories it will be stored in short term memory
- memores_lt - for long term memories. This never gets deleted, but memories get weaker as they get older (but they can be refreshed)
So far I only have the sensory memory table, which currently consists of the following columns:
- memoryID
- snailID
- objectID
- objectIDField
- objectProximity
- neuronFiredID
- consequenceRating
I also have a table of motor neurons, which is what I’ll specify in neuronFiredID
. It is basically the action the snail took in relation to the memory. So far it just consists of two columns:
- neuronID
- name
And the motor neurons in there so far are:
- move_left
- move_right
- move_up
- move_down
- bite
- mate
- touch
- swallow
The memory models
I’ve also created a memory model class, with child classes for sensory, short term, and long term memories. Each memory has a belongsTo
Eloquent relationship with a snail. A snail hasMany
memories. And of course there is a MemoryController. This is pretty much where I left off.
Like I said - I have a feeling I’m going to look at this in the morning and decide that it’s all wrong and needs to be scrapped. But I guess even if that’s the case, it will be a lesson learned and the next version will be better.