Liza Shulyayeva

No More Starving Snails

Dear jar capacity validator,

I’m sorry I ever doubted you. I thought you were killing my snails, but really it’s my lack of you that caused the problem. I am the one guilty of virtual snailslaughter.

Before you refuse to ever speak to me again, let me explain.

(Ok I’m stopping this now)

For the past couple of weeks my snails have been dying of starvation. I didn’t really notice that there was a problem straight away. After some digging I realized that the most delicious item in the jar I was using was at y pos 1180, but for some reason when the snails tried to approach it they never got there. This resulted with them trying to get to the item each minute, but never making it, and starving as a result.

There are two problems here. The most immediate problem is why aren’t the snail’s reaching the item? I quickly realized that this was because I run a jar position validation check before setting target positions for the snails. The max y and x positions are calculated based on the jar’s capacity. In this case, the jar’s capacity was 20. The max y pos of a snail within the jar was 20 * 50, so 1000. So during validation the snail’s targetPosY kept getting set to 1000 because that is the farthest the snail could go.

The problem, it turned out, was that I was doing zero jar posiion validation on the item itself. Something must have at some point set the y position for the item to 1180 (either the autogenerated random position when the item is dropped or maybe I dragged it weirdly on canvas). I manually moved that one item and added a check, so this should never happen again.

The second problem

The second problem became apparent due to this bug. If a snail can’t physically reach a consumable item to feed itself, it needs to switch attention to another item and try to reach it instead. In real life a snail wouldn’t keep beating its head against the glass trying to get the one piece of lettuce on the other side when there is lots of other lettuce sitting around in the jar itself, within easy reach! I will tackle this next.

Detangling Snail Actions. Also a Cat

I started writing this post on the 8th of November and have just now gotten around to finishing it.

Figuring out what my snails are doing and why is getting close to impossible. I’ve got snails eating each other, or refusing to eat proper food, or for some reason not mating even though their sex drive should be pretty high. I used to print stuff out to one big default log file (laravel.log), but this soon became unmanageable because I was printing logs associated with multiple actions for multiple snails at the same time.

I ended up creating a logger for each snail:

    public function getLoggerAttribute() {
        $path = storage_path() . '/logs/' . $this->snailID . '.log';
        if (!File::exists($path)) {
            File::put($path, "");
        };
        $snailLog = new Logger('Snail Logs');
        $snailLog->pushHandler(new StreamHandler($path, Logger::INFO));
        return $snailLog;
    }

So now I can log relevant information to each snail’s individual log file. Example:

$this->snail->logger->addInfo("Override escape for hunger");

I think I need a couple of weeks of just bug fixing. I’m noticing a crapton of little issues. Some are easy things, others are weird behavioural things that I’ve been too lazy to try to debug.

And the reason I’ve been so lazy is because of this new distraction. Meet Rigel the Maine Coon kitten!

Rigel the Maine Coon

Hiking, Beyond Earth, and Gastropoda Update

One cool thing about Stockholm is how easy it is to get to both a central location and a green/forested area.

Even when you’re in a busy cafe in Södermalm or surrounded by shopping centers in T-Centralen water, trees, green walking paths are never far away.

Last weekend we went hiking at the nearby nature preserve. We called it foraging, but didn’t really forage much. We did, however, build a fire and toast walnuts!

Walnut roasting

Beyond Earth

I’ve been playing Beyond Earth this weekend. It has been glorious. Of course the first people I go to war with is the Slavs. They’re such hypocrites - telling me that my expansion has greatly worried them, but when I ask them not to settle near my cities they’re like “We’ll settle wherever we damn well please, it’s our right, yada yada”. Then I rename my city closest to them to Kyivolaev and they declare war on the next turn. They took the city away from me first, but I got it back and then some with my xeno swarms.

I haven’t played any of the Civilization games. The reason I was so excited about Beyond Earth is because I loved Sid Meier’s Alpha Centauri. And I really like Beyond Earth, but…it just doesn’t feel like a true spiritual successor for me. Alpha Centauri was awe-inspiring. I’m sure that was at least in part due to my having never played anything like that before. Even now, when I play SMAC I get that same feeling I did years ago. Beyond Earth just isn’t that for me. I’m going to keep playing it, and I’m going to enjoy it, but at the same time I’m going to try to dig up some of the other recommendations of SMAC-like games that people have mentioned on my Twitter feed.

Gastropoda

Training

Now that I have the general snail approach/run away/bite/swallow/mate decisions working I thought I’d start on training. However, I quickly realized that it’s too early for that. Because snails already learn from every day experiences in the jar, I should start by perfecting those experiences. Because really, if I get this right in theory I don’t even need any other explicit “training” functionality. And if I do want to build it (which I probably will), I can base it entirely on the existing behavior/memory/learning implementation.

So I’m going to go back to expanding the range of interactions a snail can have in its jar and refining its decisions and memories.

Memories

Memories currently work like this:

  • A snail can store up to 5 sensory memories
  • If 3 or more sensory memories are stored about the same object, they get converted into short term memories
  • 5 short term memories of the same neuron being fired in relation to the same object turn into a long term memory. Long term memories start off with a strength of 100. Every 24 hours 10 points is taken off of the strength (and more points are added if the snail gets another 5 short term memories to add to the same long term memory). Long term memories are never deleted, but can be weakened over time.

Biting vs swallowing

I have also implemented a the swallowing motor neuron. Previously, the snail could bite an item to swallow it. It could also bite another snail. However, biting can also be more of an aggressive action - just because a snail is biting something doesn’t mean it wants to eat it. So when a snail does want to try to actually EAT something, it will swallow. Otherwise, it will simply bite.

Survivability

Snails are also tough to keep alive at the moment - they run out of 50% of their energy each day, and I am having to constantly throw food into the jar to keep them well fed. I have tweaked this slightly by calculating how many bites a snail should be able to take between idle event checks (similar to movement). A snail takes a bite every 10 seconds. An event checking all snails’ idle actions runs every 60 seconds, so every SWALLOW neuron firing will actually fire 6 times each minute (of course there is a way to specify a different interval if we are calculating for more or less time between checks).

Documentation

The codebase is getting large and I feel I need to start documenting the project better in terms of both design and technical documentation. I made a doc site on http://gastropoda.readme.io/ though it has no proper content there yet, just the very bare bones beginnings.

Decision-making

Making decisions has been tough. The decision making code is a huge mess right now.

I’ve decided to implement something along the lines of Maslow’s hierarchy of needs. So far there are only two needs - food and sex. I made a separate “Prioritisation” neuron that handles both weight setting in the inputs and later setting “motivations” for the snails when it comes to decision time (eg foodMotivation, sexMotivation).

It’s working a bit better than the previous system (in that snails that refused to eat before are now eating), but is all still very experimental and needs to be observed and tweaked as I go.

The Ongoing Saga of Snail Mating Decisions

I’ve written about snail breeding here before, at length. However, since I now want snail behavior to go through the brain I had to rework the trigger that led to two snails mating and producing offspring. A few different things had to be prototyped to make this work, especially because in a snail’s mind there is no real way of distinguishing food from another snail without learning from experience and some basic instincts to guide the way.

When I started, snails tried to mate with lettuce leaves. Technically possible, but they should learn that this is a little…ineffective.

Then, the snails just kept overshooting each other when trying to approach. Approaching inanimate objects is easy - they don’t move. However, one snail kept targeting the other snail’s current position, and vice versa, and they ended up in the wrong spot. The solution (for now) was to have each snail target the other snail’s target position. This is kind of dumb, because of course the Snail A can’t read minds and see where Snail B is going, so I’ll have to find a non-hacky better way of doing it by basically seeing if the two snails will cross paths on their way to one another and meet each other in the middle instead.

Anyway, then the snails did reach each other but started biting each other as if the other snail was a food item. This resulted in one snail becoming deathly afraid of the other one and trying to run away (and sometimes biting back). Basically the two snails grew to despise each other and want nothing more than to go in opposite directions.

Then they stopped eating.

Then I created scent signatures for other snails and items. Right now this basically just shows an object’s highest macro or fertility rate. This is placeholder, but now snails can “smell” other fertile objects nearby. In the future they’ll also be able to get hungry for certain macros and favor food items which smell more like the thing they’re hungry for.

I also made a sex drive and food drive, so a snail might try to bite an object instead of mating with it if it smells like carbs and its food drive is higher than its sex drive. This can also be used for the snail’s decision about which object it wants to approach (so, we can use this to apply higher weights to certain inputs).

Then I realized there was no good way to test this efficiently without making it easy to move snails around within the jar (without having to go and change positions in the db each time). This way I could place snails next to the objects I wanted them to interact with or detect easier.

And then…then…well, this happened:

(Obviously I need to have them get tired and/or have their sex drive go down after they mate)

The eggs don’t hatch until tomorrow…and I’m both curious and terrified of what will happen then, because I do not yet have a minimum maturity rate for snail breeding.

Went to My First Laravel Meetup

Last Friday night I attended what was apparently the first ever Laravel Meetup in Sweden. I’m glad I actually made myself go, because I’m not really good with social engagements. I don’t tend to go out much and sometimes end up just not bothering and staying home at the last minute.

Anyway, I’m really glad I went. The meetup was hosted by Snowfire and the main talks were about an introduction to Laravel 5.0 and the Laravel IoC container. Both were super interesting and gave me a lot to think about (and a taste of how much I still have to learn about Laravel and PHP in general).

I don’t think I’m going to update to 5.0 until November at the earliest. I have to mentally prepare myself - I’ve already done so many port experiments, and then the actual full port from vanilla PHP to Laravel 4.2, that I’m not looking forward to another large and complicated migration.

Some crooked pictures of the event:

Laravel 5.0 walkthrough

Laravel 5.0 walkthrough 2

Laravel IoC Container

We finished off the night by looking at some Laravel resources and browsing the codebase.

Laravel GitHub

Event Queues

The brain thing is really getting out of control.

Yesterday I ran into this problem:

Fatal error: Maximum function nesting level of '100' reached, aborting!

What caused it was my dumbness. Basically, I was firing the Bite neuron which called the Bite method in the snail class. Then I was checking if the object the snail was biting was an item or another snail. If it was another snail, it called BiteSnail() in the snail class. Here is where it got screwy. I wanted to have the bite do the following:

  • Send pain to the snail being bitten via a manually inserted input to the other snail’s Tactile sensor. Esssentially I was manually pushing a detected object (the biter) to the other snail’s tactile sensor’s detectedArr. To reflect the bite for now I’m using the input’s “roughness” attribute (though this will change) and setting roughness to 55 (the normal snail’s roughness pain threshold is 50)
  • Have the snail being bitten immediately start checking inputs, since you want the snail to react to the bite. The other reason for this is that the recurring action/input checking event creates a new brain each time it runs for each snail, so if we just pushed that input to the other snail’s brain and then forgot about it expecting it to have a reaction the next minute, that input would be totally wiped the next time the event ran.

The problem though was that I was creating a new snail brain, calling all the same functions that were already running on the current snail. The input checks for the two snails were overlapping and either I had too many recursive function calls or I had an infinite loop somewhere (infinite snail-biting? A snail ouroboros?) because the normal solution - increasing xdebug.max_nesting_level was not working. I decided to implement a queue system instead of having a snail check all inputs from another snail that’s checking all inputs.

So now I’ve got this possibly even dodgier thing going where I have a queue in the event model. The event, in this case the CheckIdleSnailActions event that checks inputs for the snails and makes them do stuff, finishes running. If a snail bites another snail during this time we add another checkAllInputs action to the event’s queue. When the event does the thing it’s supposed to do the first time around and gets to the end, it loops through its queue and runs everything in there as well.

Here’s what we push to the queue (we use the brain of the snail being bitten, with the extra input already saved to that brain’s tactile sensor)

$toPush = [
            'object' => $object->brain,
            'function' => 'checkAllInputs'
        ];
array_push($event->queue, $toPush);

And then, at the end of the event’s runEvent method we do this:

    if ($eventFinished) {
        Log::info ('event finished');
        $this->resetRunTime();
        if (count($this->queue) > 0) {
            foreach ($this->queue as $event) {
                Log::info('about to call ' . $event['function']);
                call_user_func_array(array($event['object'],$event['function']), func_get_args());
            }
        }
    }

One crappy thing (of many) is that currently I don’t have a good place to save the event, so I end up passing it from runEvent to the snail controller that loops through the snails and checks their inputs, to the snail brains that get created. It’s kind of a mess.

I think this weekend I’m going to stop working on new brain stuff and just focus on refactoring all of this.

The Snails Are Eating Again

Back to the brain we go.

I already had snails eating food in jars before, but it was kind of a hack. Each snail would just eat a bite of whatever consumable item was in the jar. When I stupidly decided to try to make a simple snail brain, I knew I’d have to make things like eating fit into it. Today I made a very rough version of something like that.

Each snail has a number of motor neurons in its brain. Out of all the motor neurons, there is one for biting and one for swallowing. So far I am bypassing the swallowing completely and just firing the bite neuron and having the snail bite food and swallow it at the same time. Basically…here’s how it works. This is kind of a brain dump so may not actually make any sense.

Items and snails are now given some properties: luminance, temperature, hardness, roughness. There may be others down the line, but for now that’s it.

I’m also setting some general pain thresholds for the snails (so far all the snails have the same thresholds), as well as totally random default properties (as per above):

    public $luminance = 25;
    public $temperature = 20;
    public $roughness = 5;
    public $hardness = 15;
    public $luminancePainThreshold = 100;
    public $temperaturePainThreshold = 50;

Items are a little more tailored - their individual luminance, temp, etc properties can be set by item template in the db (though right now I’m just using some default values there too).

So basically if a snail sees something with a luminance that’s too high it will not be having such a good time.

Oh, and snails now have a currentMood property. In the memory tables (which are already created but not yet in use) there is a consequenceRating property, which reflects an action’s impact on the snail’s currentMood.

So the main thing you need to bite an item is to actually be close enough to it to touch it. I made a small change to checkSensor() to allow for a maxDistance to be passed:

    public function checkSensor($sensorQual, $maxDistance = 999999) {
    // ...blah blah blah

Also, we set luminance, temp, roughness, and hardness to null by default in the array of detected objects and then specify which attributes to grab for each sensor (because we don’t care about roughness if the snail isn’t touching the thing, for example).

                    $toPush = [
                        'id' => $object->$idString,
                        'idString' => $idString,
                        'inputWeight' => $inputWeight,
                        'distance' => $distance,
                        'luminance' => null,
                        'temperature' => null,
                        'roughness' => null,
                        'hardness' => null
                    ];

So our TactileSensor ends up looking like this:

class TactileSensor extends Neuron
{
    
    public function checkForInput() {
        $maxDistance = 5;
        $detectedArr = $this->checkSensor($this->snail->currentTactileReceptorQual, $maxDistance);
        foreach ($detectedArr as &$detectedObject) {
         //   $controller = null;
            if ($detectedObject['idString'] === 'snailID') {
            //    $controller = $this->snailController;
                $object = $this->snailController->findSnail($detectedObject['id']);
            }
            else if ($detectedObject['idString'] === 'itemID') {
            //    $controller = $this->itemController;
                $object = $this->itemController->findItem($detectedObject['id']);

            }
            $detectedObject['hardness'] = $object->hardness;
            $detectedObject['temperature'] = $object->temperature;
            $detectedObject['roughness'] = $object->roughness;
        }
        $this->detectedArr = $detectedArr;

    }
}

Another change is in setWeights(). Whereas before we’d just add something like 0.05 to the inputWeight if an object had been detected by more than one sensor, now we actually add all the inputs together. If a specific sensor had a maxDistance specified we give it a higher inputWeight by default (2 instead of 1) because it had a tougher criteria to meet (not sure I’ll keep this, since the closest object already gets a boost anyway down the line).

Anyway, then in the decision neuron we decide what to do…right now none of the memory stuff is implemented, so we go straight to a random “curious or not” check:

    public function checkDecision($object, $memories = null) {
        Log::info('checkDecision object: ' , $object);
        if (count($memories) === 0) {
            $rand = Utility::RandomInteger(0,1);
            if ($rand === 1) {
                // Curious. Approach if far away
                if ($object['distance'] >= 5) {
                    Log::info('approaching object1: ' . $object['id'] . ' distance: ' . $object['distance']);
                    $this->approachObject($object);
                }
                else {
                    Log::info('TOUCHING. TRY TO BITE ' . $object['id']);
                    $this->biteObject($object);

                }

            }
            else {
                // Indifferent. Ignore;
            }
        }
    }

approachObject() and biteObject() then fire the biting and moveleft/moveright/moveup/movedown motor neurons.

As you can see it’s still not really that decision-makey. Snails will eventually have other criteria involved in whether they try to bite something or not (that is, they won’t necessarily try to bite an object just because they’re touching it).

That’s it for now…at least they’re eating again.

Websocketing It Up for Snail Movement

Over the past few days I’ve paused work on the brain to tackle snail movement on the client.

I ended up using Latchet, a Laravel-specific extended version of Ratchet. To start with I just wanted to get something running. I think I ended up doing this in a really roundabout way…basically we subscribe to a specific jar topic on the client, then ping the server every 5 seconds to get back the target position of each snail within that jar. The snail then crawls toward this target position. Target pos on the server is calculated using the snail’s currentSpeed and each snail crawls at the same speed in the browser, so in theory by the time the snail reaches its target position on the server it will also have reached the same location on the client…maybe…I hope. We’ll see.

    var jarString = 'jar/' + jarID;

    var conn = new ab.Session(
        'ws://gastropoda.app:1111', // The host (our Latchet WebSocket server) to connect to
        function() { // Once the connection has been established
            console.log('established');
            conn.subscribe(jarString, function(topic, event, jarID) {
                var parsedMsg = JSON.parse(event.msg);
                if (allSnailsArr.length > 0) {
                    for (var i = 0; i < allSnailsArr.length - 1; i++) {
                        if (parsedMsg.snailID === allSnailsArr[i].snailID) {
                            allSnailsArr[i].targetPosX = parsedMsg.targetPosX * jarCapacityWidthPercentage / 100;
                            allSnailsArr[i].targetPosY = parsedMsg.targetPosY * jarCapacityWidthPercentage / 100;
                        }
                    }
                 }
            });

            (function checkPositions() {
                conn.call(jarString, "HI WORLD");
                setTimeout(checkPositions, 5000);
            })();
        },
        
        function() {
            // When the connection is closed
            console.log('WebSocket connection closed');
        },
        
        {
            // Additional parameters, we're ignoring the WAMP sub-protocol for older browsers
            'skipSubprotocolCheck': true
        }
    );
</script>

Then, in JarTopic.php:

    public function subscribe($connection, $topic, $jarID = null)

    {

        $jarController = new JarController();

        $jar = $jarController->findJar($jarID);

        $allSnails = $jar->snails;

        foreach ($allSnails as $snail) {

            array_push($this->connectedSnailsArr, $snail->snailID);

        }

        $this->publish($connection, $topic, "BROADCASTTEST", $exclude = array(), $eligible = array());

    }

    public function call($connection, $id, $topic, array $params)

    {

        $this->broadcastTargetPositions($topic);

    }
    public function broadcastTargetPositions($topic) {

        $snailController = new SnailController();

        foreach ($this->connectedSnailsArr as $id) {

            $snail = $snailController->findSnail($id);

            $msg = json_encode([

                'snailID' => $id,

                'targetPosX' => $snail->targetPosX,

                'targetPosY' => $snail->targetPosY

            ]);

            $this->broadcast($topic, array('msg' => $msg ));

        }

    }

You can’t see it…but they’re moving! When they don’t find anything interesting to approach they stop, and when they sense something they’re curious about they start crawling toward it.

Go your own way

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;
    }

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();
    }

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();
    }

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;
    }

}

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;
    }    

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();
    }


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();
    }

sendImpulse():

    private function sendImpulse() {
        $this->destinationNeuron->receiveImpulse($this);
    }

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);
    }

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);
    }
}

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');
    }

}

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.

And We’re Back

I took a one-month break from Gastropoda to participate in the JS13kGames challenge with a tiny game called Breaking Brad

But now it’s back to simulations! It’s taking me a while to remember where exactly I left off with Gastropoda last month, but now I think it’s time to figure out how to calculate a snail’s position within a jar. This is important because each snail will detect other objects (items, snails, etc) differently based on its senses. I have to have an actual location for both objects and snails. Right now I’m setting a position randomly when a snail is hatched in a jar, but I have to decide how I’m going to update this position as it crawls. Currently snails crawl at random on HTML5 canvas - they are already starting their crawl from their DB position, but pick direction randomly and then move back and forth across the screen using their speed attribute. So far all I have are some random notes:

  • User actions while watching rendered snails (ie drag and drop, etc) have to influence thes snail’s position in the db, but only the jar’s owner can move snails around.
  • Do I update the general crawl position on the client and then send that data to the server every x seconds? This wouldn’t really work because more than one user can be watching a snail at any one time, so if we take the random turn direction etc and record it, you will get all kinds of dodgy results in the db. Also I’d then need a separate solution for snail movement when the snails are not being rendered at all. Snails do things/move continuously - in this case a tree that falls in a forest does make a sound even if no one is there to hear it. I think I have to set direction or target position on the server and then have the client reflect this if a user is watching a jar, not the other way around.
  • How do I make this scalable to potentially hundreds/thousands of snails?

Today I’m sick and not thinking straight, so I’m not actually going to even try implementing anything at this point. Back to hot tea for me :(