Braaaaaainsss

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:

So far I only have the sensory memory table, which currently consists of the following columns:

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:

And the motor neurons in there so far are:

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.

comments powered by Disqus