<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[Liza Shulyayeva]]></title>
  <link href="http://liza.io/atom.xml" rel="self"/>
  <link href="http://liza.io/"/>
  <updated>2016-02-07T23:37:20+01:00</updated>
  <id>http://liza.io/</id>
  <author>
    <name><![CDATA[Liza Shulyayeva]]></name>
    
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[Snaily Updates: BrainHub + Runners, SnailLife Logo]]></title>
    <link href="http://liza.io/snaily-updates-logging/"/>
    <updated>2016-02-06T23:44:15+01:00</updated>
    <id>http://liza.io/snaily-updates-logging</id>
    <content type="html"><![CDATA[<p>I finally have two BrainRunners working on a DigitalOcean droplet, and one BrainHub on another droplet queueing and assigning tasks to the runners. </p>

<p>It’s still rough, but let’s start with the BrainHub’s scheduled artisan commands (Artisan is the CLI that comes with Laravel):</p>

<p><code>QueueIdleBrainChecks</code> runs every minute. </p>

<div><div class="CodeRay">
  <div class="code"><pre>    <span class="keyword">public</span> <span class="keyword">function</span> <span class="function">handle</span>()
    {
        <span class="comment">// Find snail due for brain check.</span>
        <span class="local-variable">$snailController</span> = <span class="keyword">new</span> <span class="constant">SnailController</span>();
        <span class="local-variable">$allIdleSnails</span> = <span class="local-variable">$snailController</span>-&gt;getAllIdleSnails();

        <span class="keyword">foreach</span> (<span class="local-variable">$allIdleSnails</span> <span class="keyword">as</span> <span class="local-variable">$snail</span>) {
            <span class="comment">// Seconds since last brain check</span>
            <span class="local-variable">$diff</span> = <span class="constant">Carbon</span>::now()-&gt;diffInSeconds (<span class="constant">Carbon</span>::parse(<span class="local-variable">$snail</span>-&gt;brainCheckedAt));
            <span class="keyword">if</span> (<span class="local-variable">$diff</span> &gt;= <span class="integer">60</span>) {
                <span class="local-variable">$existingQueuedBrainCheck</span> = <span class="constant">QueuedBrain</span>::where(<span class="string"><span class="delimiter">'</span><span class="content">snailID</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="content">=</span><span class="delimiter">'</span></span>, <span class="local-variable">$snail</span>-&gt;snailID)-&gt;first();
                <span class="comment">// If brain check is not already queued, queue a new check</span>
                <span class="keyword">if</span> (<span class="local-variable">$existingQueuedBrainCheck</span> === <span class="predefined-constant">null</span>) {
                    <span class="local-variable">$queuedBrain</span> = <span class="keyword">new</span> <span class="constant">QueuedBrain</span>();
                    <span class="local-variable">$queuedBrain</span>-&gt;snailID = <span class="local-variable">$snail</span>-&gt;snailID;
                    <span class="local-variable">$queuedBrain</span>-&gt;save();
                }
            }
        }
    }
</pre></div>
</div>
</div>

<p>This basically just gets all living idle snails from the <code>snail_life</code> db and creates a new brain check entry in the <code>brain_hub</code> db.</p>

<p>Also every minute we run the <code>AssignBrainsToRunners</code> artisan command:</p>

<div><div class="CodeRay">
  <div class="code"><pre>    <span class="keyword">public</span> <span class="keyword">function</span> <span class="function">handle</span>()
    {
        <span class="local-variable">$brainRunnerRetriever</span> = <span class="keyword">new</span> <span class="constant">BrainRunnerRetriever</span>();
        <span class="local-variable">$allIdleBrainRunners</span> = <span class="local-variable">$brainRunnerRetriever</span>-&gt;getAllIdleBrainRunners();
        <span class="local-variable">$taskRetriever</span> = <span class="keyword">new</span> <span class="constant">TaskRetriever</span>();
        <span class="keyword">foreach</span> (<span class="local-variable">$allIdleBrainRunners</span> <span class="keyword">as</span> <span class="local-variable">$brainRunner</span>) {
            <span class="local-variable">$task</span> = <span class="local-variable">$taskRetriever</span>-&gt;<span class="constant">GetNextQueuedTask</span>();
            <span class="keyword">if</span> (<span class="local-variable">$task</span> !== <span class="predefined-constant">null</span>) {
                <span class="local-variable">$brainRunner</span>-&gt;assignQueuedTask (<span class="local-variable">$task</span>);
            }
        }
    }
</pre></div>
</div>
</div>

<p>This finds any available (idle) brain runners and assigns the next queued tasks to them.</p>

<p>In the <code>BrainRunner</code> model:</p>

<div><div class="CodeRay">
  <div class="code"><pre>    <span class="keyword">public</span> <span class="keyword">function</span> <span class="function">assignQueuedTask</span>(<span class="local-variable">$task</span>) {
        <span class="comment">// Change status of BrainRunner to 1 - Busy</span>
        <span class="local-variable">$this</span>-&gt;updateStatusCode(<span class="integer">1</span>);
        <span class="local-variable">$url</span> = <span class="local-variable">$this</span>-&gt;url() . <span class="predefined-constant">DIRECTORY_SEPARATOR</span> . <span class="string"><span class="delimiter">'</span><span class="content">api/runTask</span><span class="delimiter">'</span></span>;
        <span class="predefined">Log</span>::info(<span class="string"><span class="delimiter">'</span><span class="content">assignQueuedTask url: </span><span class="delimiter">'</span></span> . <span class="local-variable">$url</span>);
        <span class="local-variable">$postfields</span> = <span class="string"><span class="delimiter">'</span><span class="content">taskID=</span><span class="delimiter">'</span></span> . <span class="local-variable">$task</span>-&gt;id . <span class="string"><span class="delimiter">'</span><span class="content">&amp;snailID=</span><span class="delimiter">'</span></span> . <span class="local-variable">$task</span>-&gt;snailID . <span class="string"><span class="delimiter">'</span><span class="content">&amp;runnerID=</span><span class="delimiter">'</span></span> . <span class="local-variable">$this</span>-&gt;id . <span class="string"><span class="delimiter">'</span><span class="content">&amp;hubURL=</span><span class="delimiter">'</span></span> . env(<span class="string"><span class="delimiter">'</span><span class="content">APP_URL</span><span class="delimiter">'</span></span>);

        <span class="local-variable">$curl</span> = curl_init();
        curl_setopt_array(<span class="local-variable">$curl</span>, <span class="predefined">array</span>(
            <span class="constant">CURLOPT_RETURNTRANSFER</span> =&gt; <span class="integer">1</span>,
            <span class="constant">CURLOPT_URL</span> =&gt; <span class="local-variable">$url</span>,
            <span class="constant">CURLOPT_POST</span> =&gt; <span class="integer">1</span>,
            <span class="constant">CURLOPT_POSTFIELDS</span> =&gt; <span class="local-variable">$postfields</span>,
            <span class="constant">CURLOPT_TIMEOUT_MS</span> =&gt; <span class="integer">2500</span>
        ));
        <span class="local-variable">$resp</span> = curl_exec(<span class="local-variable">$curl</span>);
        curl_close(<span class="local-variable">$curl</span>);

        <span class="comment">// Delete task from queue</span>
        <span class="local-variable">$task</span>-&gt;<span class="predefined">delete</span>();
    }
</pre></div>
</div>
</div>

<p>The brain runner then runs the task:</p>

<div><div class="CodeRay">
  <div class="code"><pre>    <span class="keyword">public</span> <span class="keyword">function</span> <span class="function">RunTask</span>(<span class="local-variable">$task</span>) {
        <span class="keyword">try</span>{
            <span class="local-variable">$this</span>-&gt;taskID = <span class="local-variable">$task</span>[<span class="string"><span class="delimiter">'</span><span class="content">taskID</span><span class="delimiter">'</span></span>];
            <span class="local-variable">$this</span>-&gt;hubURL = <span class="local-variable">$task</span>[<span class="string"><span class="delimiter">'</span><span class="content">hubURL</span><span class="delimiter">'</span></span>];
            <span class="local-variable">$this</span>-&gt;runnerID = <span class="local-variable">$task</span>[<span class="string"><span class="delimiter">'</span><span class="content">runnerID</span><span class="delimiter">'</span></span>];

            <span class="comment">// Get runner specific logger</span>
            <span class="local-variable">$utility</span> = <span class="keyword">new</span> <span class="constant">Utility</span>(<span class="local-variable">$this</span>-&gt;taskID);
            <span class="local-variable">$logger</span> = <span class="local-variable">$utility</span>-&gt;logger;
            <span class="local-variable">$snailController</span> = <span class="keyword">new</span> <span class="constant">SnailController</span>();
            <span class="local-variable">$logger</span>-&gt;addInfo(<span class="string"><span class="delimiter">'</span><span class="content">INFO: RunTask: Initialized SnailActionController</span><span class="delimiter">'</span></span>);

            <span class="comment">// The recurring event is actually not required anymore,</span>
            <span class="comment">// but we kind of hack it together because before the BrainHub</span>
            <span class="comment">// the snail brains relied on it and still do temporarily.</span>
            <span class="local-variable">$event</span> = <span class="keyword">new</span> <span class="constant">RecurringEvent</span>();
            <span class="local-variable">$event</span>-&gt;name = <span class="string"><span class="delimiter">'</span><span class="content">IdleSnailAction</span><span class="delimiter">'</span></span>;
            <span class="local-variable">$logger</span>-&gt;addInfo(<span class="string"><span class="delimiter">'</span><span class="content">INFO: RunTask: Initialized IdleSnailAction Event</span><span class="delimiter">'</span></span>);

            <span class="comment">// Find the snail</span>
            <span class="local-variable">$snail</span> = <span class="local-variable">$snailController</span>-&gt;findSnail(<span class="local-variable">$task</span>[<span class="string"><span class="delimiter">'</span><span class="content">snailID</span><span class="delimiter">'</span></span>]);
            <span class="keyword">if</span> (<span class="local-variable">$snail</span> === <span class="predefined-constant">null</span>) {
                <span class="local-variable">$logger</span>-&gt;addError(<span class="string"><span class="delimiter">'</span><span class="content">ERROR: Snail ID </span><span class="delimiter">'</span></span> . <span class="local-variable">$task</span>[<span class="string"><span class="delimiter">'</span><span class="content">snailID</span><span class="delimiter">'</span></span>] . <span class="string"><span class="delimiter">'</span><span class="content"> NOT FOUND.</span><span class="delimiter">'</span></span>);
            }
            <span class="keyword">else</span> {
                <span class="local-variable">$logger</span>-&gt;addInfo (<span class="string"><span class="delimiter">'</span><span class="content">INFO: RunTask: Found Snail ID: </span><span class="delimiter">'</span></span> . <span class="local-variable">$snail</span>-&gt;snailID);
                <span class="local-variable">$snailActionCoordinator</span> = <span class="keyword">new</span> <span class="constant">SnailActionCoordinator</span>();
                <span class="local-variable">$snailActionCoordinator</span>-&gt;checkAction([<span class="local-variable">$snail</span>], <span class="integer">60</span>, <span class="local-variable">$event</span>);
                <span class="local-variable">$logger</span>-&gt;addInfo (<span class="string"><span class="delimiter">'</span><span class="content">INFO: RunTask: Action Checked</span><span class="delimiter">'</span></span>);
            }

            <span class="local-variable">$logger</span>-&gt;addInfo (<span class="string"><span class="delimiter">'</span><span class="content">INFO: RunTask: Reported task as finished</span><span class="delimiter">'</span></span>);

            <span class="comment">// Save log to S3 (might rip this out later as we don't need to keep that many logs anyway)</span>
            <span class="local-variable">$utility</span>-&gt;saveLog(<span class="local-variable">$this</span>-&gt;runnerID);
        }
        <span class="keyword">catch</span>(\<span class="predefined-constant">Exception</span> <span class="local-variable">$e</span>){
            <span class="local-variable">$logger</span>-&gt;addError(<span class="string"><span class="delimiter">'</span><span class="content">ERROR: RunTask: Exception Caught, cancelling task</span><span class="delimiter">'</span></span>);
            <span class="local-variable">$logger</span>-&gt;addError(<span class="local-variable">$e</span>);
        }
        <span class="local-variable">$this</span>-&gt;reportTaskFinish();
    }
</pre></div>
</div>
</div>

<p>The BrainHub connects to both the main SnailLife database and the BrainHub database. BrainRunners can only connect to the SnailLife database. The only thing that ever reads from or modifies the BrainHub DB is the BrainHub itself. </p>

<h2 id="snaillife-logo-and-website">SnailLife logo and website</h2>

<p>I have been getting really sick of looking at the ugly black and white SnailLife website. So I decided to try and make it a little more exciting. It’s still ugly, but at least it’s colorful-ugly now! I stumbled across a bunch of open source logos over at Logodust. I felt like taking a break from the BrainHub for a while and messed around with combining two of the logos and adding an eye stalk to make something vaguely resembling a snail. The logo is also quite versatile in case I ever decide to ditch the snail idea, since I’ve been told it looks like a chinchilla or one of those deep water fish with the light in front of its face as well…</p>

<p><img src="http://liza.io/images/galleries/gastropoda/snaillife-logo.png" alt="SnailLife logo" /></p>

<p>So now the site is, though by no means great, just a little less bland:</p>

<p><img src="http://liza.io/images/galleries/gastropoda/snaillife-site.png" alt="SnailLife website" /></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[BrainHub and BrainRunner - Finally Over the Hump]]></title>
    <link href="http://liza.io/brainhub-and-brainrunner-finally-over-the-hump/"/>
    <updated>2016-01-24T11:59:21+01:00</updated>
    <id>http://liza.io/brainhub-and-brainrunner-finally-over-the-hump</id>
    <content type="html"><![CDATA[<p>I’ve started this post maybe twenty times now, since before Christmas, and each time I keep putting it off, thinking “I’ll just blog when I have this next bit done.” But each “next bit” is followed by something else, and then something else, into infinity.</p>

<p>So I’ll just write an update.</p>

<p>Since before the holidays I’ve been working on BrainHub and BrainRunner, which I’ve already written about. Basically - checking each brain every minute as part of the main SnailLife brain was becoming unmanageable. All background processes will be moved to complementary apps away from the user-facing SnailLife application. The idea is to have a BrainHub controlling tasks sent to individual BrainRunners (which are hosted on other DigitalOcean or EC2 droplets).</p>

<p>So, here is the first iteration of the BrainHub admin site. The BrainHub has its own database of queued tasks and BrainRunners but also connects to the main SnailLife database and imports a package called SnailLifeCore to be able to get information about brains due for a check and allow admin SnailLife users to log in/control the hub.</p>

<p><img src="http://liza.io/images/galleries/gastropoda/brainhub1.png" alt="BrainHub admin site" /></p>

<p>It is sort of functional in that BrainHub runs 2 scheduled tasks:</p>

<ul>
  <li><code>QueueIdleBrainChecks</code></li>
  <li><code>AssignBrainsToBrainRunners</code></li>
</ul>

<p>Both of these run every minute. The first gets a list of brains that need to be processed and puts them into the <code>queued_brains</code> table in the <code>brainhub</code> db.</p>

<p>The second, <em>AssignBrainsTobrainRunners</em>, looks for any idle brain runners (brain runners with status code <code>0</code>) and assigns the next brain in the queue to them. Then, the brain runner checks the brain and reports back to BrainHub with the result, which releases the runner to process the next brain in the queue.</p>

<p>Right now there are some issues - runners don’t get consistently released, for example. Should be easy enough to fix, but for now I’ve added an emergency release button to the admin site (you can see it above).</p>

<p>But <em>right</em> right now I am working on logging. The brain runner creates a new log for each brain task it runs. These then need to be backed up to AWS S3 (as opposed to being stored on the Droplet itself), and then the admin site will display the logs by-task for each runner.</p>

<p>There is a mountain of work to do on this but it feels like I’m sort of over the main hump of setting up the core package and the BrainHub and BrainRunner apps to sit alongside the main SnailLife app.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[The Brain Scope Is Growing: BrainHub, BrainRunners, Brains]]></title>
    <link href="http://liza.io/the-brain-scope-is-growing-brainhub/"/>
    <updated>2015-12-21T18:17:06+01:00</updated>
    <id>http://liza.io/the-brain-scope-is-growing-brainhub</id>
    <content type="html"><![CDATA[<p>I’ve been MIA for a while, I know. I haven’t stopped working on SnailLife - I just wanted to get to some sort of completion with the brain refactoring phase before talking about it. Now, however, I realize there won’t be any completion for a long while and I may as well post an update.</p>

<p>I found a memory leak in the brain. Multiple, actually. And while going through and fixing those I realized the current way of running snail brain checks was completely unsustainable - made clear by the same incident that exposed the leak(s): 1500 snail eggs hatching on the same day.</p>

<p>Right now every minute all living snails run an action check - where we use the snail brain to see what objects the snail detects in its surroundings, how it feels about them/if it remembers any previous encounters with them, and how it will react to them. It loops through these snails one by one - with 1500 snails this takes quite a while. Aside from optimizing the inner workings of the brain itself I realized doing one giant for loop just wasn’t going to be sustainable as the count of snails in the world grows.</p>

<p>@drparham in the Laravel Slack channel gave me an idea and based on that I have come up with a hopefully more scalable plan:</p>

<ul>
  <li>Move all core classes into a private package (SnailLifeCore)</li>
  <li>Have Laravel handle only the user-facing parts of the app. No background tasks or recurring events.</li>
  <li>Move all background and scheduled tasks to a Lumen app (a microframework based on Laravel, much more bare-bones and faster)</li>
  <li>Two new Lumen apps to be constructed: BrainRunner and BrainHub</li>
  <li>BrainRunners can run brain check events for smaller groups of snail brains - eg 10 vs 1500. Multiple instances of BrainRunners can be spun up on an as-needed basis. Eventually they can even be made to autoscale via AWS E3 instances.</li>
  <li>The BrainHub queues and distributes tasks among BrainRunners and processes the results (probably to have some sort of admin UI). It finds free BrainRunners to run brain action checks for certain snails and distributes them as efficiently as it can.</li>
</ul>

<p>I need to fit the rest of the events somewhere. The brain checks are by far the most complicated, but we also have breeding events, stable temperature check events, item rotting, market delivery, auction timing events, etc etc. I suspect just one app can handle these remaining events easily. Something like an “EventRunner” based on the “BrainRunner”.</p>

<p>So far the following parts are done:</p>

<ul>
  <li>Main classes are in a separate private package called SnailLifeCore</li>
  <li>BrainHub and BrainRunner apps are created with SnailLifeCore imported</li>
</ul>

<p>It’s safe to say the complexity of the brain and related management systems is greater than the complexity of the rest of the simulation combined at this point. But it’s ok - I’m soon going on my 3rd year working on SnailLife and haven’t even begun to lose interest yet. This is the longest hobby project I’ve ever stuck with! I’m glad the thing I’ve been dreaming about building for the last 10 years is really as interesting to work on as I imagined.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Snaily Updates, Nov 10 - Family Trees and Inbreeding Coefficient]]></title>
    <link href="http://liza.io/snaily-updates-3/"/>
    <updated>2015-11-10T12:06:05+01:00</updated>
    <id>http://liza.io/snaily-updates-3</id>
    <content type="html"><![CDATA[<ul>
  <li>I did some research on snail ailments. This particular page was especially useful. Though SnailLife snails are not aquatic, some of these still seem very relevant: http://applesnail.net/content/various/snail_disease.php</li>
  <li>Also did research on inbreeding in snails and complications it could cause. Apparently one commonly observed side effect is shell thinning. I then did some reading on finding the inbreeding coefficent and how to calculate it.</li>
  <li>Looked into Google Visualization API to display the family trees and started a test implementation.</li>
  <li>Started actually using Taiga.io, I’ve been wanting to try it out for a while.</li>
</ul>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Snaily Updates - Bug Fixing and Cleaning Out Old Stuff]]></title>
    <link href="http://liza.io/snaily-updates-2/"/>
    <updated>2015-11-09T12:39:54+01:00</updated>
    <id>http://liza.io/snaily-updates-2</id>
    <content type="html"><![CDATA[<p>Snaily Updates are going to be really brief posts with some short “daily” updates on SnailLife. I haven’t updated in a while because I’ve mostly just been refactoring and bug fixing. But it’s important, and even though I’m not working on new features right now I still want to document some of my progress and what I do each day.</p>

<ul>
  <li>Fixed an issue with migration to dead snail table trying to set snail position in a non-existent jar.</li>
  <li>Added message template for egg hatching</li>
  <li>Renamed UserMessageManager to UserMessageSender</li>
  <li>Renamed SnailHistoricalEventManaer to SnailHistoricalEventGenerator</li>
  <li>Get rid of BreedingController as everything it was used for is now in more specific classes</li>
  <li>Get rid of SnailOrganController</li>
  <li>Get rid of TrainingController since there is now no unique training functionality - all snail conditioning is done via interaction in jars and pre/post race action assignments.</li>
</ul>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[My Livecoding.tv Account Deletion Saga]]></title>
    <link href="http://liza.io/my-livecoding-dot-tv-account-deletion-saga/"/>
    <updated>2015-10-30T17:23:49+01:00</updated>
    <id>http://liza.io/my-livecoding-dot-tv-account-deletion-saga</id>
    <content type="html"><![CDATA[<p><strong>Update 3, Nov 2 2015 (PM):</strong> Well, it’s been…strange. First, it seems my account was finally deleted by Livecoding.tv (thank you!) After being up all night with libel posted all over it, hours after Jamie Green (the other Livecoding co-founder) and Sam Altman (Y Combinator president) replied to the <a herf="https://news.ycombinator.com/item?id=10486476" target="_blank">Hacker News thread</a> claiming they were trying to do something, something finally happened. I have heard nothing from either of them since their original comments and am not sure exactly what spurred the removal of slander from the profile and its eventual deletion. I know some other Livecoding users quit and had a discussion with the CEO in live chat also.</p>

<p>The account was then recreated, seemingly by a third party under the same username. They originally replaced a modified version of the libel on the profile and eventually changed the description. It seems to be someone else who has a problem with Livecoding.tv and maybe wanted to use this whole saga to their advantage.</p>

<p>First, the CEO of Livecoding has again emailed me this morning and, almost unbelievably, tried to lie yet again. This was before the deletion was done. Our email conversations consisted of this (messages go from bottom up):</p>

<ul>
  <li><a href="http://i.imgur.com/Ea55D6G.png" target="_blank">http://i.imgur.com/Ea55D6G.png</a></li>
  <li><a href="http://i.imgur.com/NT6xH5X.png" target="_blank">http://i.imgur.com/NT6xH5X.png</a></li>
</ul>

<p>He then went to the Hacker News thread about this and started posting defensive copy-pasted comments telling people they were cowards for not listing their names. After this there was more of pretty much the same on Twitter - copy-pasted replies to tweets about Livecoding.</p>

<p>Some responses to this weird and confused behaviour and this unbelievably bad response to the situation on HN have been borderline, and in some cases over-the-line, cruel. As I mentioned in a comment on the thread, I did not write this post to make a joke out of anyone or be cruel to a person who now seems like they might be unwell. I am the last person to defend how Livecoding.tv has handled this and don’t plan on censoring any accounts of my experiences with them, but in my opinion it is important to stay mature in these interactions and not become a complete asshole.</p>

<p>Anyway, at this point with my account deleted (though username now taken by someone else, but they have now removed my name from the new profile) I think I’ve gotten the best possible outcome. It’s obvious that Livecoding still has no interest in treating this in a professional manner and that they have a lot of growing to do in terms of their approach to communicating with their users, but as long as my details are off of that site that’s all I can realistically expect at this point.</p>

<p>I really hope this post will not need any more updates.</p>

<hr />

<p><strong>Update 2, Nov 2 2015:</strong> The text on my profile has just been changed to “Marked for removal from database”. Perhaps they fixed their “cron job”.</p>

<p><a href="https://twitter.com/elgruntox" target="_blank">@elgruntox</a> mentioned on Twitter that that he just got banned also and actually managed to have an exchange with the CEO:</p>

<ul>
  <li><a href="https://twitter.com/elgruntox/status/661063421283012608" target="_blank">https://twitter.com/elgruntox/status/661063421283012608</a></li>
  <li><a href="https://twitter.com/elgruntox/status/661063608302866432" target="_blank">https://twitter.com/elgruntox/status/661063608302866432</a></li>
</ul>

<p>I’m not sure where the first line is coming from about any kind of information that they were “doing migrations” (I have had no such communication). I did have communication that my account was deleted as mentioned in the original post below, but that turned out to be false. My videos were already deleted because I deleted them myself one by one before they changed my password, put in a redirect back to home on my account, and updated my profile for me.</p>

<p>Anyway, it seems they removed the libel and replaced it with a more generic message. I guess we’re just back at square one now.</p>

<hr />

<p><strong>Update 1, Nov 1 2015:</strong> It looks like Livecoding have now taken the liberty of updating my profile for me, claiming I am “spamming them” after this review got some attention: <a href="https://archive.is/T3Ms7" target="_blank">Archive.is link</a>.</p>

<p>I guess writing an account of your experience with their site is somehow considered spam to Livecoding. How professional of them.</p>

<p>The original post follows below:</p>

<hr />

<p>A few months ago I figured I’d try streaming my development of SnailLife (or parts thereof). I considered Twitch, but ended up going for a site called Livecoding.tv because it seemed smaller and more obscure. My main goal for streaming would be to keep myself focused knowing someone <em>may</em> be watching - I didn’t actually want to build any great audience for myself as I didn’t want to build up any kind of expectation of streaming, plus I knew that my streams would probably just be really boring. It was more a motivator for me to not procrastinate and browse Reddit or HN and focus on my work.</p>

<p>So I set up an account on Livecoding and did my first stream. A few people actually came in and asked questions about the project, which was cool. I got a message and/or email from someone from Livecoding asking if or when I was able to stream, or what my streaming schedule might look like. I said I would try to stream whenever I could, but with a busy job, a sick cat, and life in general streaming was simply not on my list of priorities so I could not commit to a schedule.</p>

<p>I streamed a few more times and then life got in the way as expected (ie my cat almost died) and I stopped. Once every few weeks I would get another message from Livecoding (outside of their usual automated newsletter) asking what my streaming schedule might be as well as saying that they want to get more female streamers on the platform. Basically emails like this:</p>

<blockquote>
  <p>“Where have you been? ;) We plan to heavily increase the number of female Livecoders so if you have ideas on how we could achieve this, let us know. Could you invite other female coders you know to Livecoding.tv?”</p>
</blockquote>

<p>And this:</p>

<blockquote>
  <p>“Hey Yeliz - This is Michael, founder of Livecoding.tv</p>
</blockquote>

<blockquote>
  <p>I like your snail game.  I want to use you to inspire other women and young girls to get excited about software development.</p>
</blockquote>

<blockquote>
  <p>How often are you on Livecoding.tv?”</p>
</blockquote>

<p>Each time one of these came in I kept explaining that with everything else going on streaming was not my highest priority and I would not commit to a schedule - so in reply to “how often?” the answer was basically “not often”. At some point it got pretty frustrating to keep receiving these emails, but whatever.</p>

<p>A couple of weeks ago I got another email from Livecoding. “Viewer Feedback” - with “tips” on how to get more people to view my stream. Including handy items like:</p>

<p><img src="http://liza.io/images/livecodingsaga/thankslivecoding.png" alt="Yeah thanks, Livecoding" /></p>

<p>This looked like an at least partially automated email, but I am not sure to what extent because in the few streams I did do I chose to <em>not</em> talk and just code, and maybe interact with people in the text chat when they had question. This was sort of the thing that led to my not feeling like being on Livecoding anymore. I just felt there was too much recurring pressure to stream when I clearly communicated that I will <em>not</em> be streaming regularly. Not only that, but I never wanted to stream to get a big viewership or cater to viewers - I wanted to stream for <em>my</em> benefit, to keep <em>myself</em> focused. These unsolicted “tips” indicated to me that Livecoding clearly did not seem like the right channel for this anymore.</p>

<p>I decided to delete my account until I can work with Livecoding’s expectations and actually stream on a regular basis as they kept asking me to do.</p>

<p>I quickly found out that you can’t just delete your account. You have to put in a support request asking them to delete your account. So that is what I did and here is the exchange that followed:</p>

<p><img src="http://liza.io/images/livecodingsaga/msg1.png" alt="Asking Livecoding to remove my account" />
<img src="http://liza.io/images/livecodingsaga/msg2.png" alt="Livecoding asks why I want my account deleted" />
<img src="http://liza.io/images/livecodingsaga/msg3.png" alt="I explain my reasons for wanting to delete my account" />
<img src="http://liza.io/images/livecodingsaga/msg4.png" alt="Livecoding promises to delete my account soon" /></p>

<p>So far so good. They asked why I wanted to delete the account, I explained, they said they’d do it soon. The next day I asked for some clarification on when the deletion would actually take place:</p>

<p><img src="http://liza.io/images/livecodingsaga/msg5.png" alt="Asking Livecoding for clarification on when my account will be deleted" /></p>

<p>This was met with silence.</p>

<p>7 days later I sent another email, now feeling a little bit frustrated. It has now been over a week. Here is the exchange that followed:</p>

<p><img src="http://liza.io/images/livecodingsaga/msg6.png" alt="Reminded by another email, I follow up with Livecoding on my account deletion" />
<img src="http://liza.io/images/livecodingsaga/msg7.png" alt="Livecoding stating that their email newsletters are separate from their account deletion" />
<img src="http://liza.io/images/livecodingsaga/msg8.png" alt="My explaining that despite the newsletter being separate, I checked and my account is still not deleted" />
<img src="http://liza.io/images/livecodingsaga/msg9.png" alt="Livecoding states my account will be deleted 'on our next cron job. Nothing complex.'" />
<img src="http://liza.io/images/livecodingsaga/msg10.png" alt="I ask when the next cron job is scheduled to run" /></p>

<p>My last question was, again, met with silence.</p>

<p>At this point I spoke out publicly on Twitter and got the following reply from Livecoding’s Twitter account - of course, my account would be deleted “soon”. I let them know that their last “soon” was over a week ago with no clarification of when their “soon” is. Guess what answer I got. That’s right. None, as usual.</p>

<p><img src="http://liza.io/images/livecodingsaga/twittermsg1.png" alt="Livecoding replies to my tweet: We'll process your request soon" />
<img src="http://liza.io/images/livecodingsaga/twittermsg2.png" alt="I let them know that their last 'soon' was over a week ago and that they have not yet clarified when 'soon' will be" /></p>

<p>So I started posting daily updates about the status of my account on Twitter. The situation at this point was frustrating and the incompetence was laughable. I updated my Livecoding profile to highlight the problem. I mused if posting some inappropriate content in my profile would encourage them to finally delete my account.</p>

<p>I didn’t post anything bad in my profile yet, but just the mention of it seems to have kicked some gears into motion, because I received this email this morning, from Michael the great Livecoding CEO.</p>

<p><img src="http://liza.io/images/livecodingsaga/msg11.png" alt="Livecoding replies: sorry for the delay. We had a lot going on. Done now." /></p>

<p>I guess that cron job had a lot going on. But hey, excellent! Something happened! All is right with the world! We’re all good!</p>

<p>However after checking the site I saw that my profile was still there…I was no longer able to log in at least. But judging by past experience and the fact that my profile was still visible I was suspicious and tried to change my password, just in case. Well what do you know - it worked. A password reset form was sent to me. Now I was getting angry. This seems to <em>not</em> just be a case of delay or plain incompetence - the Livecoding CEO just lied about deleting my account. I wonder in what world changing someone’s password amounts to an account deletion - Livecoding’s world, apparently!</p>

<p>Now angry at the blatant lie, I posted on Twitter about this and replied to Michael’s last email:</p>

<p><img src="http://liza.io/images/livecodingsaga/msg12.png" alt="I reply letting them know that my account has NOT been deleted" /></p>

<p>He (or someone from Livecoding, there is no name signed anymore) quickly responded with the following:</p>

<p><img src="http://liza.io/images/livecodingsaga/msg13.png" alt="Livecoding: Do you and your friends want to disturb us with this and spamming? We will just ignore you at this point" /></p>

<p>I’m not sure who “me and my friends” are. I think he was talking about my discussions on Twitter. We certainly haven’t been spamming, a couple of people have simply been discussing my issues with getting my account deleted. My response was:</p>

<p><img src="http://liza.io/images/livecodingsaga/msg14.png" alt="I reply explaining why I spoke out publicly, asking if they ever plan to delete my account as requested." /></p>

<p>And that was it. Again, the expected silence took over and there was no further reply. My profile still sits on Livecoding, undeleted. But they DID put in a redirect back to home on login attempt, presumably to make sure I didn’t post those porn links I joked about.</p>

<p>The seeming incompetence Livecoding management is just plain laughable at this point and I am still not sure what exactly their problem is with deleting an account. Are they just unable to do this? Is their system this crappy? Judging by their lie claiming my account was deleted when it was not it seems they actually want to keep my data and profile on their site for some weird reason. But why?! Why list instructions on how to delete your account on your website and then not honor them?</p>

<p>Of course there are sites that outright refuse to delete all of your information. Livecoding was not one of those sites - Livecoding repeatedly promised to delete my accout “soon”, then went on to claim it deleted my account when my account was still there, visible, and with a resettable password. The way they have handled this in such an unprofessional, misleading manner makes them seem totally untrustworthy and extremely <em>shady</em> in my mind.</p>

<p>I remember Michael’s request in one of his spammy manual emails from earlier: <strong>“Could you invite other female coders you know to Livecoding.tv?”</strong></p>

<p>Dear Michael: sure I can! But I won’t. I will instead warn other coders (not just female, since you may not know this but we don’t gather in gender specific female herds), but anyone interested in streaming their projects.  Livecoding’s unprofessionalism, continued pressure to stream, unwillingness to let go of your data despite really loving the word “soon” is simply not worth the hassle.</p>

<p>So, Livecoding/Michael, here are some unsolicited “tips” from me to you this time:</p>

<ul>
  <li>Get a proper support person. Who is not you, since you are not good at interacting with your users.</li>
  <li>If you <em>must</em> make people put in a manual ticket to delete their account, actually do what you say you will and <em>delete their account</em></li>
  <li>Do not lie to your users and claim you deleted their account when it is blatantly obvious that their account is still there. Out of the dilly-dallying, the seemingly non-existent cron jobs, etc, this is the thing that left the <em>worst</em> impression of Livecoding to me. When you start blatantly lying to people you know you just turned a very bad corner.</li>
</ul>

<p>To use your preferred phrasing from before, processing a support ticket to delete a user account should have been “nothing complex”. I still do not know what your problem is with actually doing this.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Snail Organs, Immunity, and Ageing]]></title>
    <link href="http://liza.io/snail-organs/"/>
    <updated>2015-10-01T12:54:41+02:00</updated>
    <id>http://liza.io/snail-organs</id>
    <content type="html"><![CDATA[<p>Organs! Immune systems! Old age! Dying from old age!</p>

<p>I’m super excited. I was thinking: “How do I implement old age and death, and health in general? Is health just energy? When the snail is out of energy, it dies? But that seems too simplistic.”</p>

<p>At this point I should have realized that ‘simplistic’ is a <em>good</em> thing. Instead I decided to try to mimic a scenario that is a little closer to real life. Again, I am no snail expert or biologist and mostly I just…guessed. I want to tweak the system when I move more into the research phase of the project vs the implementing random features and fixing bugs phase. I have a feeling this should have been done the other way around.</p>

<p>Anyway - why does any living organism die? It seems that we die either because of some trauma or injury to our internal organs or…old age. But what is old age, really? How is it different from trauma or injury to our internal organs?</p>

<p>From the very brief amount of reading I’d done it’s partly about telomeres and how efficiently our cells can keep dividing as we get older. Telomeres protect our chromosomes. Each time a cell divides, our telomeres shorten until eventually our cells can’t divide anymore. And then what?</p>

<p>I think that’s when we become more prone to age-related diseases. The immune system is apparently highly related to the length of  the telomeres. And when your immune system gets worse our bodies can no longer handle various illnesses as well as they used to, which eventually results in that first thing - trauma or injury to our internal organs that we can no longer recover from.</p>

<p><img src="http://liza.io/images/2015/10/rigel.jpg" alt="" style="float:left" />My cat Rigel has a damaged bladder. A vet “broke” him and he has been handicapped ever since. The bad news is that he may now need to be on medication for the rest of his life. The good news is that there is a chance, especially since he is so young, that his bladder will <em>regenerate</em> over time. The cells and nerves of his bladder may still kick themselves into shape over months or years and he may be able to take a little less medicine or maybe, if a miracle happens, eventually none at all!</p>

<p>This is how I want the snails to work. Organ damage can be sustained, but with a high enough immune system the snail may be able to recover and at least partially heal/regenerate itself. As the immune system weakens organ damage becomes permanent and the snail is not as able to recover. Eventually the snail is just too weak, its organs start failing, and it dies.</p>

<h2 id="the-details">The details</h2>

<p>I’ve kind of done it in a really hacky way…like all the experimental features so far…which is pretty much all the features.</p>

<h3 id="organs">Organs</h3>
<p>So a snail has a base organ efficiency score and an organ efficiency <em>percentage</em>. Eg:</p>

<ul>
  <li>baseHeartEffic: 60</li>
  <li>heartEfficPerc: 100%</li>
  <li>baseLungEffic: 45</li>
  <li>lungEfficPerc: 90%</li>
</ul>

<p>The organs so far are:</p>

<p><img src="http://liza.io/images/galleries/gastropoda/organ-function.png" alt="" /></p>

<p>Yes, I know a snail has more organs than this. But I have sort of stayed away from organs that already are represented by sensors in the brain. These include eyes (vision is accounted for), mouth (taste is accounted for), tentacles (touch is accounted for). They <em>will</em> be added eventually, but I wanted to focus on the others for now.</p>

<p>All snails start with 100% efficiency for each organ. At first I was thinking of not having  <code>baesXEffic</code> at all and just have a 0-100 percentage for each snail, but I realized this would be boring. All snails are not created equal. Just as Secretariat had a very large heart, a snail can have a much stronger or weaker organ than average. A simple percentage standard across all snails would not allow for the amazing outliers of nature that we see in real life.</p>

<p><img src="http://liza.io/images/2015/10/secretariat.jpg" alt="" /></p>

<h3 id="ageing">Ageing</h3>

<p>Snails already have a maturity rate (the rate at which they grow after birth). I decided to reuse this for <em>ageing</em>. Snails age every hour. Until a snail is mature it grows each hour based on its maturity rate. Once a snail is mature it begins to <em>age</em> and <em>deteriorate</em> every hour. The amounts may be small - something like 0.05% decrease in immunity per hour. You will be able to boost immunity with supplements, medicines, etc, but for now we are just focusing on the snail’s natural rate of ageing here.</p>

<p>Each minute we check for idle snail actions. Each minute we will also check for any <em>organ damage</em> as a result of those actions. For example, if a snail is operating at 95-100% physical effort it may have a higher chance of heart, lung, or foot damage.</p>

<h3 id="recovery">Recovery</h3>

<p>I have not built recovery in yet. I want to see how long a snail might live without <em>any</em> recovery of the organs. Recovery is to follow!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Wild Snails Around the World]]></title>
    <link href="http://liza.io/wild-snails-around-the-world/"/>
    <updated>2015-09-24T22:16:16+02:00</updated>
    <id>http://liza.io/wild-snails-around-the-world</id>
    <content type="html"><![CDATA[<p>Did you know that there is an entirely separate formula to calculate the distance between two points on the surface of a sphere? I mean…it kind of makes sense when you think about it. The shortest distance between two points without accounting for the curvature of the sphere would go right through the sphere itself, making it pretty useless when calculating physical distance on, for example, the surface of the Earth.</p>

<p>I’ve been working on creating some variation in the attributes of wild snails you might catch in different parts of the world. It’s not perfect, but the snails you find in Mobile, Alabama now look generally different from the snails you might find in Stockholm, Sweden.</p>

<p>So far the general testing rules are thus:</p>

<ul>
  <li>The snail shells get less red and more blue in them as you travel away from the equator</li>
  <li>Snail shells get more green in them as you travel East</li>
  <li>Snail <em>pattern</em> colors are the opposite of the above</li>
  <li>Snail eye colors are always randomized</li>
  <li>Snail pattern shapes and sizes also remin random for now, but will definitely change with the region at some later stage</li>
</ul>

<p>I have added an idealTemp column to the snail table. The ideal temperature of snails you find in the wild in various regions tends to change (warmer toward equator and colder toward the poles). This means you will need to be quite careful when trading with others or buying other people’s snails (or even sending your snail overseas for a race on a foreign track). You’d need to closely control temperature and evaluate how your snail might perform on a track where it is currently 40C when it is used to a temperature of 20C. Temperature conditioning will need to be a thing.</p>

<p>Admittedly with these rules the snails you tend to find end up being a little more boring. Instead of a totally random mishmash of colors you end up with something more constrained. But I think this is ok. Wild snails are <em>not</em> meant to be beautiful or exciting - users are supposed to <em>breed</em> for those traits. If you regularly find unique, amazing snails in the wild there won’t be as much incentive or excitement in learning about your snail’s genetic traits and breeding for a desired result.</p>

<p>As I mentioned before, it takes a couple of seconds for HTML5 geolocation to get your coordinates (and then a few more seconds to get city/country name via the Geoname API). If you for some reason click to search for a wild snail <em>before</em> your location is loaded the coordinates of the search default to the coordinates of your stable.</p>

<p>In addition users will eventually be able to release their snails into the wild. At first it will just mean that someone else can find them. Down the line maybe the wild snails will be able to breed amongst themselves and change the attributes of wild snails in that region.</p>

<p>I have taken some screenshots of wild snails you may find in different places. Here they are:</p>

<h2 id="mobile-alabama-us">Mobile, Alabama, US</h2>
<p><img src="http://liza.io/images/galleries/gastropoda/wildsnails/Mobile/1.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Mobile/2.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Mobile/3.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Mobile/4.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Mobile/5.png" alt="" /></p>

<h2 id="setauket-new-york-us">Setauket, New York, US</h2>
<p><img src="http://liza.io/images/galleries/gastropoda/wildsnails/Setauket/1.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Setauket/2.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Setauket/3.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Setauket/4.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Setauket/5.png" alt="" /></p>

<h2 id="san-francisco-california-us">San Francisco, California, US</h2>
<p><img src="http://liza.io/images/galleries/gastropoda/wildsnails/SF/1.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/SF/2.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/SF/3.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/SF/4.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/SF/5.png" alt="" /></p>

<h2 id="perth-western-australia">Perth, Western Australia</h2>
<p><img src="http://liza.io/images/galleries/gastropoda/wildsnails/Perth/1.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Perth/2.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Perth/3.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Perth/4.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Perth/5.png" alt="" /></p>

<h2 id="stockholm-sweden">Stockholm, Sweden</h2>
<p><img src="http://liza.io/images/galleries/gastropoda/wildsnails/Stockholm/1.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Stockholm/2.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Stockholm/3.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Stockholm/4.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Stockholm/5.png" alt="" /></p>

<h2 id="kyoto-japan">Kyoto, Japan</h2>
<p><img src="http://liza.io/images/galleries/gastropoda/wildsnails/Kyoto/1.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Kyoto/2.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Kyoto/3.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Kyoto/4.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/Kyoto/5.png" alt="" /></p>

<h2 id="the-north-pole">The North Pole</h2>
<p><img src="http://liza.io/images/galleries/gastropoda/wildsnails/NorthPole/1.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/NorthPole/2.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/NorthPole/3.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/NorthPole/4.png" alt="" />
<img src="http://liza.io/images/galleries/gastropoda/wildsnails/NorthPole/5.png" alt="" /></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[More Location and Temperature Stuff]]></title>
    <link href="http://liza.io/more-location-and-temperature-stuff/"/>
    <updated>2015-09-18T17:10:43+02:00</updated>
    <id>http://liza.io/more-location-and-temperature-stuff</id>
    <content type="html"><![CDATA[<p>I realized I didn’t actually include any implementation details when writing my sleepy post about stable locations and temperature the other night. I figured I’d elaborate on that now.</p>

<p>Having never used HTML5 geolocation features before, I kind of winged it and hacked together something that works for now. Currently I get the user’s location in two cases - on registration, and when searching for wild snails.</p>

<p>Here is how it happens on the registration page:</p>

<div><div class="CodeRay">
  <div class="code"><pre>
    <span class="tag">&lt;script&gt;</span>
<span class="inline">        getLocation();
        <span class="keyword">function</span> <span class="function">getLocation</span>() {
            console.log(<span class="string"><span class="delimiter">'</span><span class="content">getLocation here</span><span class="delimiter">'</span></span>);
            <span class="keyword">if</span> (Modernizr.geolocation) {
                console.log(<span class="string"><span class="delimiter">'</span><span class="content">geolocation in</span><span class="delimiter">'</span></span>);
                navigator.geolocation.getCurrentPosition(createHiddenInput, geolocationError);
            } <span class="keyword">else</span> {
                console.log(<span class="string"><span class="delimiter">'</span><span class="content">no native support</span><span class="delimiter">'</span></span>);
            }
        }

        <span class="keyword">function</span> <span class="function">createHiddenInput</span>(position) {
            <span class="keyword">var</span> latitude = position.coords.latitude;
            <span class="keyword">var</span> longitude = position.coords.longitude;
            console.log(<span class="string"><span class="delimiter">'</span><span class="content">latitude: </span><span class="delimiter">'</span></span> + latitude);
            console.log(<span class="string"><span class="delimiter">'</span><span class="content">longitude: </span><span class="delimiter">'</span></span> + longitude);

            jQuery(document).ready(<span class="keyword">function</span>() {
                <span class="predefined">$</span>(<span class="string"><span class="delimiter">'</span><span class="content">#registrationForm</span><span class="delimiter">'</span></span>).append(<span class="string"><span class="delimiter">'</span><span class="content">&lt;input type=&quot;hidden&quot; name=&quot;stableLatitude&quot; value=</span><span class="delimiter">'</span></span> + latitude + <span class="string"><span class="delimiter">'</span><span class="content">/&gt;</span><span class="delimiter">'</span></span>);
                <span class="predefined">$</span>(<span class="string"><span class="delimiter">'</span><span class="content">#registrationForm</span><span class="delimiter">'</span></span>).append(<span class="string"><span class="delimiter">'</span><span class="content">&lt;input type=&quot;hidden&quot; name=&quot;stableLongitude&quot; value=</span><span class="delimiter">'</span></span> + longitude +<span class="string"><span class="delimiter">'</span><span class="content">/&gt;</span><span class="delimiter">'</span></span>);
                <span class="predefined">$</span>(<span class="string"><span class="delimiter">'</span><span class="content">#registrationForm</span><span class="delimiter">'</span></span>).append(<span class="string"><span class="delimiter">'</span><span class="content">Stable Location: </span><span class="delimiter">'</span></span> + getCityName(latitude, longitude) + <span class="string"><span class="delimiter">'</span><span class="content">,</span><span class="delimiter">'</span></span> + getCountryName(latitude, longitude));

            });
        }</span>
    <span class="tag">&lt;/script&gt;</span>
</pre></div>
</div>
</div>

<p>Problem - retrieving the location takes a few seconds. If the user happens to register too quickly their location will not be retrieved. As a potential solution I am considering disabling the Submit button until the location is ready…however, what if they don’t want to allow me to retrieve their location at all?</p>

<p>If the user does not let me retrieve their location, it will be set by default to some place in Louisiana. Maybe then I can just display a “Don’t want to be in Louisiana? Wait while we get your location!” message…</p>

<p>So the latitude and longitude is stored for the user’s new account. Then to get the country and city name when displaying where the user is we use the Geonames API. Example:</p>

<div><div class="CodeRay">
  <div class="code"><pre><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">function</span> <span class="function">GetCityName</span>(<span class="local-variable">$latitude</span>, <span class="local-variable">$longitude</span>) {
    <span class="local-variable">$cityName</span> = <span class="predefined-constant">null</span>;
    <span class="keyword">if</span> (<span class="constant">Utility</span>::<span class="constant">InternetOn</span>()) {
        <span class="local-variable">$url</span> = <span class="string"><span class="delimiter">&quot;</span><span class="content">http://api.geonames.org/findNearbyPlaceName?lat=</span><span class="delimiter">&quot;</span></span> . <span class="local-variable">$latitude</span> . <span class="string"><span class="delimiter">&quot;</span><span class="content">&amp;lng=</span><span class="delimiter">&quot;</span></span> . <span class="local-variable">$longitude</span> . <span class="string"><span class="delimiter">&quot;</span><span class="content">&amp;username=myusername</span><span class="delimiter">&quot;</span></span>;
        <span class="predefined">Log</span>::info (<span class="string"><span class="delimiter">'</span><span class="content">connection</span><span class="delimiter">'</span></span>);
        <span class="local-variable">$xmlDoc</span> = <span class="keyword">new</span> \<span class="constant">DOMDocument</span>();
        <span class="local-variable">$xmlDoc</span>-&gt;load(<span class="local-variable">$url</span>);
        <span class="local-variable">$cityNameNode</span> = <span class="local-variable">$xmlDoc</span>-&gt;getElementsByTagName(<span class="string"><span class="delimiter">&quot;</span><span class="content">name</span><span class="delimiter">&quot;</span></span>);
        <span class="local-variable">$cityName</span> = <span class="predefined-constant">null</span>;
        <span class="keyword">if</span> (<span class="local-variable">$cityNameNode</span>-&gt;length &gt; <span class="integer">0</span>) {
            <span class="local-variable">$cityName</span> = <span class="local-variable">$cityNameNode</span>-&gt;item(<span class="integer">0</span>)-&gt;nodeValue;
        }
    }

    <span class="keyword">return</span> <span class="local-variable">$cityName</span>;
}
</pre></div>
</div>
</div>

<p>To get temperature I originally tried OpenWeatherMap but that seems very slow and unreliable. So I am trying out The Dark Sky:</p>

<div><div class="CodeRay">
  <div class="code"><pre><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">function</span> <span class="function">GetCurrentTemperature</span>(<span class="local-variable">$latitude</span>, <span class="local-variable">$longitude</span>) {
    <span class="local-variable">$url</span> = <span class="string"><span class="delimiter">&quot;</span><span class="content">https://api.forecast.io/forecast/MYAPIKEY/</span><span class="delimiter">&quot;</span></span> . <span class="local-variable">$latitude</span> . <span class="string"><span class="delimiter">&quot;</span><span class="content">,</span><span class="delimiter">&quot;</span></span> . <span class="local-variable">$longitude</span>;

    <span class="local-variable">$JSON</span> = <span class="predefined">file_get_contents</span>(<span class="local-variable">$url</span>);
    <span class="local-variable">$data</span> = json_decode(<span class="local-variable">$JSON</span>);

    <span class="local-variable">$f</span> = <span class="local-variable">$data</span>-&gt;currently-&gt;temperature;
    <span class="local-variable">$c</span> = <span class="constant">Utility</span>::<span class="constant">FahrenheitToCelsius</span>(<span class="local-variable">$f</span>);

    <span class="keyword">return</span> <span class="local-variable">$c</span>;
}
</pre></div>
</div>
</div>

<p>The Dark Sky gives me 1000 free API calls per day. I blew through that in under an hour because I was stupid and calling the API each time I needed to get the temperature. Which is a lot…passive events alone blow through about 200 calls in 5 minutes.</p>

<p>So I added a <code>currentTemp</code> field in the User table and a <code>temp_updated_at</code> field. I added a recurring event to update the temperature once per hour.</p>

<p>Now it is time to start varying wild snail traits based on location you are looking in. I might even embed a Google Map on the wild-snail-collection page so that instead of just clicking “Look under a rock” or whatever you can click on a spot in your general vicinity on a map and see what snail pops up there!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Bringing Snail Stables Down to Earth]]></title>
    <link href="http://liza.io/bringing-snail-stables-down-to-earth/"/>
    <updated>2015-09-16T18:28:31+02:00</updated>
    <id>http://liza.io/bringing-snail-stables-down-to-earth</id>
    <content type="html"><![CDATA[<p>SnailLife snail stables have always lived in the ether - in some virtual universe with no physical location. Since I’m trying to make SnailLife based in reality, users’ snail stables should also be based in real locations. This is why now, upon registration, the user’s physical location is used as the location of their snail stable.</p>

<p>Right now in my snail stable it is 15.5 degrees Celsius. I don’t think I’m going to simulate building insulation, so without temperature control it would be about the same temperature in each jar (I may take humidity from the jar’s substrate into account, and have other items adding warmth or cold aside from temperature control gadgets). The user won’t see their <em>exact</em> jar temperature until they install a thermometer in the jar, and they won’t be able to regulate the temperature without installing a temperature controller.</p>

<p><img src="http://liza.io/images/galleries/gastropoda/temperature.png" alt="Temperature" /></p>

<p>Snail jars have had temperature for a long time, but now that temperature is influenced by real life weather at the stable’s location. This will in turn influence the snails within the jar: their health, mood, etc (this last part is not yet implemented…well, it is only implemented very very roughly).</p>

<p>Different snails will adapt to live better in certain climates, and you will find different kinds of snails in different places. This way if you happen to physically be traveling the world you could hunt for wild snails away from your stable and find totally different patterns, colors, and traits.</p>

<p>Of course, this can only happen if you allow SnailLife to get your coordinates by using HTML5 Geolocation feature. Right now there is no fallback…I was thinking of falling back on location of IP, but that might make it easy for people to cheat and place their stables in rare locations. For now I think I might put you in a really boring part of the world by default when geolocation is not permitted…</p>

<p>Aside from affecting snail health and such in the future, temperature in a jar already affects how quickly consumable items rot. Things rot faster at a higher temperature. </p>

<p>Up next I want to think of a good way to vary snail types based on coordinates. There will be some manually set snail types that are available only in certain areas of the world if I want to make some super cool rare location (eg - get a special snail if you’re at the NASA headquarters!), but for the most part I want to create that variation automatically. I just have to think of a good way to approach this.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Snail Stable Breeding Brands]]></title>
    <link href="http://liza.io/snail-stable-breeding-brands/"/>
    <updated>2015-09-05T08:18:05+02:00</updated>
    <id>http://liza.io/snail-stable-breeding-brands</id>
    <content type="html"><![CDATA[<p>I haven’t made a SnailLife update in a while, but I had a good reason! For about half a month I’ve been working on an entry into the <a href="http://js13kgames.com">js13kGames</a> challenge. I’ve entered the challenge annually since it started and this year was meant to be no exception. Unfortunately…I wasn’t really into it this year. It felt like work, and with things at actual work being so busy I found myself not having the time or motivation to sit down and work on it after I get home.</p>

<p>When I work on SnailLife it tends to be relaxing, so I figured why mess with a good thing and decided to go back to snails!</p>

<p>The first thing I did to get back into it was implement stable brands. When a snail is born at a particular user’s stable, if that user has a 3 character identifying brand selected that brand is applied to the snail. It is then displayed in front of the snail’s full name along with its titles (have I talked about titles yet? Snails can become Champions and Supreme Champions if they win enough races). So a snail named Crimson Sunchaser born at Lazerbeam Stables who currently holds two Champion titles and 1 Supreme Champion title and has <em>also</em> birthed or sired enough champion foals to earn it the Champion Breeder title would have its full name displayed as follows:</p>

<p>[LZB] SCH CHx2 CHB Crimson Sunchaser</p>

<p>Ie… Lazerbeam Stables Supreme Champion, Champion x2, Champion Breeder Crimson Sunchaser</p>

<p>Of course most people would refer to this snail by its simple barn name, Crimson Sunchaser.</p>

<p>Currently held champion titles can change (if you get enough CH titles it turns into an SCH for example), but the stable brand can <em>never</em> be removed or modified. If a snail is sold, gifted, transferred in some other way - it will be permanently branded with the stable it was born in.</p>

<p>And that’s it for now! Up next I am implement pre and post-race snail instructions.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[I Have Become Crazy Cat Lady, the Buyer of Cat Strollers]]></title>
    <link href="http://liza.io/i-have-become-crazy-cat-lady/"/>
    <updated>2015-08-30T09:27:41+02:00</updated>
    <id>http://liza.io/i-have-become-crazy-cat-lady</id>
    <content type="html"><![CDATA[<p>I am sorry in advance for what you are about to read. If witnessing the evolution of a mere human into Crazy Cat Lady is something you may find disturbing, turn back now and pretend you never came to this godforsaken place.</p>

<p>I got my cats a cat stroller. Specifically…<a href="http://www.amazon.com/Pet-Gear-No-Zip-Stroller-Rose/dp/B00CGYE1W6">this cat stroller</a> (in green). This thing had better last us for <em>life</em>.</p>

<p>First, reasoning (aka excuses):</p>

<ul>
  <li>Our cats like being outside, both on our cat-enclosed porch and on harnesses in the shared yard. BUT they can’t really walk like a dog does (it’s more like they walk and we follow them around), which makes it difficult to show them new places.</li>
  <li>Our cats are heavy. Rigel has pretty much outgrown his original plastic carrier (which wasn’t that great quality to begin with). A cat stroller would provide a more comfortable method of transportation (such as to the vet) for both me and him - I don’t have to lug a heavy cat around in a carrier and he gets a much more smooth and comfy ride.</li>
  <li>Here in Stockholm it is perfectly acceptable to take your pets on public transport. Having had to take Rigel on public transport before, I know how stressful it can be. All the new sounds and smells and people. One thing that doesn’t help is him being low down on the floor in his plastic carrier. Cats like to be up high. Aside from being more comfortable and roomy, the stroller gives the cats a higher position to sit in, hopefully helping them feel a little less stressed and intimidated.</li>
</ul>

<p>So anyway, it came a few days ago. Here it is:</p>

<p><img src="http://liza.io/images/2015/08/stroller/indoors.jpg" alt="Pet Gear NV Pet stroller" /></p>

<p>Kaytu took to it straight away. She jumped in and I rolled her around the house. Rigel took a little longer, but eventually did the same thing.</p>

<p><img src="http://liza.io/images/2015/08/stroller/inpram.jpg" alt="Cat in Pet Gear stroller" /></p>

<p>A little later it was time for our first walk! We had a very quick walk around the small shared yard area out front. I didn’t want to go too far too soon and overwhelm them.</p>

<p><img src="http://liza.io/images/2015/08/stroller/firsttrip.jpg" alt="Pushing cat in cat stroller" /></p>

<p>Even David decided to partake in the Walk of Shame:</p>

<p><img src="http://liza.io/images/2015/08/stroller/walk-of-shame.jpg" alt="Cat stroller walk of shame" /></p>

<hr />

<p>This morning we took our first longer trip. We didn’t go too far this time, just to a large playground with a small wooded area about a 5-10 minute walk away (in non-cat-stroller-time!)</p>

<p><img src="http://liza.io/images/2015/08/stroller/twocatsonestroller.jpg" alt="Two Cats One Stroller" /></p>

<p>Kaytu seems to have taken a liking to the stroller as soon as it came, even faster than Rigel. <em>But</em> Rigel was more comfortable being outside at a new place. Maybe it’s because he’s been out of the house so much in taxis and on trains out of necessity. I parked the stroller near a bench (the foot brake has already come in handy) and opened the secure top cover. Both cats already had harnesses and their retractable leads on. Rigel stepped out onto the bench and went to explore the nearby bushy area straight away.</p>

<p><img src="http://liza.io/images/2015/08/stroller/rigelexit.jpg" alt="Rigel goes to explore" /></p>

<p>Kaytu, though, was uncomfortable. I don’t think she’s ever been this far from home. I put the cover down a little more to shade her from the outside world and we followed Rigel around in the stroller.</p>

<p><img src="http://liza.io/images/2015/08/stroller/kaytunervous.jpg" alt="Nervous cat stroller cat" /></p>

<p>After some exploration we slowly made our way back in the direction of the house.</p>

<p><img src="http://liza.io/images/2015/08/stroller/bush.jpg" alt="Cat exploring a bush" /></p>

<p><img src="http://liza.io/images/2015/08/stroller/guarding.jpg" alt="Cat next to cat stroller" /></p>

<p>We rode with the hood up, my holding the leads. Kaytu was a lot more comfortable by this time and sat up front sniffing around. Then she actually decided to jump down and do some exploring of her own.</p>

<p><img src="http://liza.io/images/2015/08/stroller/kaytuexplorer.jpg" alt="Cat explores the world" /></p>

<p>At one point Rigel jumped back in the stroller, rode there for a few minutes, and then hopped back out to do more roaming.</p>

<p><img src="http://liza.io/images/2015/08/stroller/rigelback.jpg" alt="Rigel cat jumps into cat stroller" /></p>

<p><img src="http://liza.io/images/2015/08/stroller/patrol.jpg" alt="Rigel the cat walks next to pet stroller" /></p>

<p>The cool thing is he actually WALKED most of the way home! Kaytu was content to stay in the stroller and be pushed around, but Rigel walked behind me. It was slow going - he’d walk a few seconds, then stop to look around. I would call him and he’d walk again, then stop again. I think he’s starting to learn what “Come” means!</p>

<p><img src="http://liza.io/images/2015/08/stroller/come.jpg" alt="Rigel learns to come!" /></p>

<p>At one point we were passed by a huge retriever looking dog. Rigel didn’t seem worried at all - he just sat there and looked at it. The owner made the dog sit a few meters away until the dog calmed down (it was very excited), muttering “Friend! Friend!” while pointing at Rigel. Finally Rigel got bored and headed back in the direction of home.</p>

<p>By the time we got back to the yard Rigel was sitting in the stroller and Kaytu was sitting on top of it - it was quite a sight.</p>

<p><img src="http://liza.io/images/2015/08/stroller/strollerbuddies.jpg" alt="Arriving home in cat stroller" /></p>

<p><img src="http://liza.io/images/2015/08/stroller/melancholystroller.jpg" alt="Kaytu trying to figure ot what just happened" /></p>

<p><img src="http://liza.io/images/2015/08/stroller/home.jpg" alt="Cat on top of stroller" /></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Pee-training Rigel]]></title>
    <link href="http://liza.io/pee-training-rigel/"/>
    <updated>2015-08-03T16:58:47+02:00</updated>
    <id>http://liza.io/pee-training-rigel</id>
    <content type="html"><![CDATA[<p><em>Note:</em> This is part of a series of posts about Rigel the Maine Coon kitten who was given a urinary infection by a veterinarian in Stockholm during a routine surgery and has been struggling with peeing since. You can read more in the <a href="http://liza.io/rigel">Rigel section</a>.</p>

<p>Rigel turned 1 year old on July 22. I was almost afraid to celebrate. We didn’t think he was going to make it to 8 months, having been scheduled to be put to sleep way back in…was it March…and then miraculously peeing at the last minute, on the day we thought we were going to lose him. Here’s a photo of him on July 22:</p>

<p><img src="http://liza.io/images/2015/08/rigel-1-year.jpg" alt="1 year old Maine Coon" /></p>

<p>For his Birthday we finished cat-proofing our porch to let Rigel and Kaytu sit there under supervision without a harness. They love it - Rigel now bolts downstairs whenever he hears me going to the door and demands to be let out every morning.</p>

<p><img src="http://liza.io/images/2015/08/rigel-on-porch.jpg" alt="1 year old Maine Coon on porch" /></p>

<p>The peeing has been…interesting. He has taken to peeing twice a day, but only one of these times seems to be of his own free will. Basically - he has been peeing on his own at 7-8:30am. Then, at 10pm, we bring the other litter box, water, and toys into the bedroom, open a window crack to get some air, and shut all of us in there. David and I watch a show and try to leave Rigel alone (that is, we’re in the same room but not bothering him). We don’t open the door to let the cats out until Rigel pees. Originally this started as a way for me to keep a closer eye on him, to avoid waking up in the morning to find pee in one of the boxes and not be sure which cat it was. This way I can hear when one of them goes in the same room and wake up to see if it’s Rigel or Kaytu. If Rigel goes to pee we let them out because we know he’s peed enough and any pees we find the next morning will probably be Kaytu’s.</p>

<p>But Rigel seems to have seen the connection between him urinating and being let out of the room. Gradually the time between him being locked in and peeing has decreased, though it varies still. A few times he’s entered the box right after shut-in. I guess it’s his way of saying “Screw you guys I don’t want to be in the same room as you and I’ll pee to prove it.”</p>

<p>Sometimes it seems he really doesn’t <em>want</em> to pee when we do this. Last night, for example, he sat on the windowsill as usual for 20-30 minutes. Then we hopped off and went to consider one litter box…no go…then the other…still no go. Then he walked to the door and made a frustrated Maine Coon chattering sound, pawing at the handle. Seeing that it was no use he begrudgingly stomped into one of the boxes, did a nice pee, and went back to the door - at this point we of course opened it.</p>

<p>I guess he’s been accidentally “pee-trained”. On the one hand this seems good - he can make himself pee “on command” when there is something he wants (though the conditions are pretty strict - 10pm on the dot, both boxes in room, window cracked open…it’s like a ritual). On the other hand why doesn’t he <em>feel</em> like peeing by this point himself? If he pees at ~8am that’s 14 hours from morning pee to lock-in. We <em>know</em> he has plenty of urine in him by then. Why does he wait for us to lock him in to actually go?!</p>

<p>Anyway, as long as he’s getting pee out I’m happy. He’s still on medication and we don’t know what the future holds. Every day we’re grateful for a good Rigel pee.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Laravel Log File Backups to S3]]></title>
    <link href="http://liza.io/laravel-log-file-backups-to-s3/"/>
    <updated>2015-08-02T12:51:05+02:00</updated>
    <id>http://liza.io/laravel-log-file-backups-to-s3</id>
    <content type="html"><![CDATA[<p>SnailLife does a lot of logging for debugging purposes. Aside from the general <code>laravel.log</code> I have separate loggers for each snail and also write logs for stuff like deleted items etc in separate files.</p>

<p>The problem is all the space this takes up on my Digital Ocean droplet in Laravel’s storage folder. If I leave it for a week or two it fills up and I’ll suddenly find my droplet inaccessible or some recurring commands not being able to finish properly due to insufficient space.</p>

<p>Instead of culling the logs more aggressively I decided to set up a backup to Amazon S3. With <a href="http://laravel.com/docs/5.0/filesystem">Laravel 5’s filesystems</a> this ended up being a pretty simple process.</p>

<p>First I set up an S3 bucket called <code>snaillife-storage</code> with a user that has <code>getObject</code>, <code>createObject</code>, and <code>deleteObject</code> permissions.</p>

<p>I set the S3 credentials in the <code>.env</code> configuration file:</p>

<pre><code>S3_KEY=blah
S3_SECRET=blah
S3_REGION=website-us-east-1
S3_BUCKET=snaillife-storage
</code></pre>

<p>Note that I set the region here just in case but in reality I don’t use it. In <code>config/filesystems.php</code> I set up the S3 disk using these credentials (the region setting is removed. I also changed the local disk root to <code>storage_path()</code>):</p>

<div><div class="CodeRay">
  <div class="code"><pre><span class="string"><span class="delimiter">'</span><span class="content">local</span><span class="delimiter">'</span></span> =&gt; [
  <span class="string"><span class="delimiter">'</span><span class="content">driver</span><span class="delimiter">'</span></span> =&gt; <span class="string"><span class="delimiter">'</span><span class="content">local</span><span class="delimiter">'</span></span>,
  <span class="string"><span class="delimiter">'</span><span class="content">root</span><span class="delimiter">'</span></span>   =&gt; storage_path(),
],

<span class="string"><span class="delimiter">'</span><span class="content">s3</span><span class="delimiter">'</span></span> =&gt; [
  <span class="string"><span class="delimiter">'</span><span class="content">driver</span><span class="delimiter">'</span></span> =&gt; <span class="string"><span class="delimiter">'</span><span class="content">s3</span><span class="delimiter">'</span></span>,
  <span class="string"><span class="delimiter">'</span><span class="content">key</span><span class="delimiter">'</span></span>    =&gt; env(<span class="string"><span class="delimiter">'</span><span class="content">S3_KEY</span><span class="delimiter">'</span></span>),
  <span class="string"><span class="delimiter">'</span><span class="content">secret</span><span class="delimiter">'</span></span> =&gt; env(<span class="string"><span class="delimiter">'</span><span class="content">S3_SECRET</span><span class="delimiter">'</span></span>),
  <span class="string"><span class="delimiter">'</span><span class="content">bucket</span><span class="delimiter">'</span></span> =&gt; env(<span class="string"><span class="delimiter">'</span><span class="content">S3_BUCKET</span><span class="delimiter">'</span></span>),
],
</pre></div>
</div>
</div>

<p>Then I made a new artisan command called <code>BackUpLogs</code>:</p>

<div><div class="CodeRay">
  <div class="code"><pre><span class="inline-delimiter">&lt;?php</span> <span class="keyword">namespace</span> <span class="constant">App</span>\<span class="constant">Console</span>\<span class="constant">Commands</span>;

<span class="keyword">use</span> <span class="constant">Illuminate</span>\<span class="constant">Console</span>\<span class="constant">Command</span>;
<span class="keyword">use</span> <span class="constant">Storage</span>;
<span class="keyword">use</span> <span class="predefined">Log</span>;
<span class="keyword">use</span> <span class="constant">Carbon</span>\<span class="constant">Carbon</span>;
<span class="keyword">use</span> <span class="constant">App</span>;

<span class="keyword">class</span> <span class="class">BackUpLogs</span> <span class="keyword">extends</span> <span class="constant">Command</span> {

    <span class="comment">/**
     * The console command name.
     *
     * @var string
     */</span>
    <span class="keyword">protected</span> <span class="local-variable">$name</span> = <span class="string"><span class="delimiter">'</span><span class="content">BackUpLogs</span><span class="delimiter">'</span></span>;

    <span class="comment">/**
     * The console command description.
     *
     * @var string
     */</span>
    <span class="keyword">protected</span> <span class="local-variable">$description</span> = <span class="string"><span class="delimiter">'</span><span class="content">Back up logs to Amazon S3</span><span class="delimiter">'</span></span>;

    <span class="comment">/**
     * Execute the console command.
     *
     * @return mixed
     */</span>
    <span class="keyword">public</span> <span class="keyword">function</span> <span class="function">handle</span>()
    {
        <span class="keyword">if</span> (!<span class="constant">App</span>::isLocal()) {
            <span class="local-variable">$localDisk</span> = <span class="constant">Storage</span>::disk(<span class="string"><span class="delimiter">'</span><span class="content">local</span><span class="delimiter">'</span></span>);
            <span class="local-variable">$localFiles</span> = <span class="local-variable">$localDisk</span>-&gt;allFiles(<span class="string"><span class="delimiter">'</span><span class="content">logs</span><span class="delimiter">'</span></span>);
            <span class="local-variable">$cloudDisk</span> = <span class="constant">Storage</span>::disk(<span class="string"><span class="delimiter">'</span><span class="content">s3</span><span class="delimiter">'</span></span>);
            <span class="local-variable">$pathPrefix</span> = <span class="string"><span class="delimiter">'</span><span class="content">snailLogs</span><span class="delimiter">'</span></span> . <span class="predefined-constant">DIRECTORY_SEPARATOR</span> . <span class="constant">Carbon</span>::now() . <span class="predefined-constant">DIRECTORY_SEPARATOR</span>;
            <span class="keyword">foreach</span> (<span class="local-variable">$localFiles</span> <span class="keyword">as</span> <span class="local-variable">$file</span>) {
                <span class="local-variable">$contents</span> = <span class="local-variable">$localDisk</span>-&gt;get(<span class="local-variable">$file</span>);
                <span class="local-variable">$cloudLocation</span> = <span class="local-variable">$pathPrefix</span> . <span class="local-variable">$file</span>;
                <span class="local-variable">$cloudDisk</span>-&gt;put(<span class="local-variable">$cloudLocation</span>, <span class="local-variable">$contents</span>);
                <span class="local-variable">$localDisk</span>-&gt;<span class="predefined">delete</span>(<span class="local-variable">$file</span>);
            }
        }
        <span class="keyword">else</span> {
            <span class="predefined">Log</span>::info(<span class="string"><span class="delimiter">'</span><span class="content">BackUpLogs not backing up in local env</span><span class="delimiter">'</span></span>);
        }
    }
}
</pre></div>
</div>
</div>

<p>Note that the directory you specify in <code>$localDisk-&gt;allFiles($dir)</code> should be relative to the root path of the local disk - an absolute path does not work.</p>

<p>In <code>Kernel.php</code> I set this to run every hour:</p>

<div><div class="CodeRay">
  <div class="code"><pre><span class="local-variable">$schedule</span>-&gt;command(<span class="string"><span class="delimiter">'</span><span class="content">BackUpLogs</span><span class="delimiter">'</span></span>)-&gt;cron(<span class="string"><span class="delimiter">'</span><span class="content">5 * * * *</span><span class="delimiter">'</span></span>);
</pre></div>
</div>
</div>

<p>So now every hour all the files in my storage/logs directory are backed up to my S3 bucket and deleted from the local disk.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[SnailLife Messaging System]]></title>
    <link href="http://liza.io/snaillife-messaging-system/"/>
    <updated>2015-07-25T20:43:17+02:00</updated>
    <id>http://liza.io/snaillife-messaging-system</id>
    <content type="html"><![CDATA[<p>“What’s SnailLife”, you say? Well I’m glad you asked! My snail simulation has gone through a couple of names…and even though I was never 100% happy with Gastropoda it was the best I could come up with - a name that was unique and didn’t allow the project to sound too “gamey” (because it’s not a game). All of the cooler names I could come up with weren’t suitable for various reasons (like domain name availability and such).</p>

<p>But recently I found out about the .life TLD! And I decided that nowadays we have such a varied domain name landscape that .com isn’t as important as it used to be, and definitely not for an obscure hobby project. So Gastropoda is now SnailLife!
On to the messaging:</p>

<h2 id="messaging">Messaging</h2>

<p>I noticed when working on the simulation that it was tough to figure out who died and why when snails disappeared from a jar.</p>

<p>(Sidenote…I just realized…in real life a snail wouldn’t just disappear from a jar if it died. It would sit there and start decomposing until someone put it away. Maybe this should be the case with SnailLife, too).</p>

<p>Anyway, I wanted something to notify me immediately when something important happens, like a snail is born or dies. So I made a rudimentary messaging system to receive notifications from the simulation, which should be able to be pretty easily expanded into a user-to-user messaging system.</p>

<p>First I made a <code>user_messages</code> table with the following columns:</p>

<ul>
  <li>messageID</li>
  <li>recipientID</li>
  <li>senderID</li>
  <li>subject</li>
  <li>content</li>
  <li>isRead</li>
  <li>created_at</li>
  <li>updated_at</li>
</ul>

<p>Then I made a <code>UserMessage</code> model that looks like this for now:</p>

<div><div class="CodeRay">
  <div class="code"><pre><span class="inline-delimiter">&lt;?php</span> <span class="keyword">namespace</span> <span class="constant">App</span>;

<span class="keyword">use</span> <span class="constant">Illuminate</span>\<span class="constant">Database</span>\<span class="constant">Eloquent</span>\<span class="constant">Model</span>;

<span class="keyword">class</span> <span class="class">UserMessage</span> <span class="keyword">extends</span> <span class="constant">Model</span> {

    <span class="keyword">public</span> <span class="keyword">static</span> <span class="local-variable">$rules</span> = <span class="predefined">array</span>(
        <span class="string"><span class="delimiter">'</span><span class="content">recipientID</span><span class="delimiter">'</span></span>     =&gt; <span class="string"><span class="delimiter">'</span><span class="content">integer</span><span class="delimiter">'</span></span>,
        <span class="string"><span class="delimiter">'</span><span class="content">senderID</span><span class="delimiter">'</span></span>   =&gt; <span class="string"><span class="delimiter">'</span><span class="content">integer</span><span class="delimiter">'</span></span>,
        <span class="string"><span class="delimiter">'</span><span class="content">subject</span><span class="delimiter">'</span></span>    =&gt; <span class="string"><span class="delimiter">'</span><span class="content">alpha_num_spaces</span><span class="delimiter">'</span></span>,
        <span class="string"><span class="delimiter">'</span><span class="content">content</span><span class="delimiter">'</span></span>    =&gt; <span class="string"><span class="delimiter">'</span><span class="content">alpha_num_spaces</span><span class="delimiter">'</span></span>,
        <span class="string"><span class="delimiter">'</span><span class="content">isRead</span><span class="delimiter">'</span></span>     =&gt; <span class="string"><span class="delimiter">'</span><span class="content">boolean</span><span class="delimiter">'</span></span>
    );

    <span class="keyword">protected</span> <span class="local-variable">$primaryKey</span> = <span class="string"><span class="delimiter">'</span><span class="content">messageID</span><span class="delimiter">'</span></span>;

    <span class="keyword">protected</span> <span class="local-variable">$fillable</span> = [<span class="string"><span class="delimiter">'</span><span class="content">recipientID</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="content">senderID</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="content">subject</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="content">content</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="content">isRead</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="content">created_at</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="content">updated_at</span><span class="delimiter">'</span></span>];

    <span class="keyword">public</span> <span class="keyword">function</span> <span class="function">recipient</span>() {
        <span class="local-variable">$this</span>-&gt;hasOne(<span class="string"><span class="delimiter">'</span><span class="content">App</span><span class="content">\U</span><span class="content">ser</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="content">userID</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="content">recipientID</span><span class="delimiter">'</span></span>);
    }

    <span class="keyword">public</span> <span class="keyword">function</span> <span class="function">sender</span>() {
        <span class="local-variable">$this</span>-&gt;hasOne(<span class="string"><span class="delimiter">'</span><span class="content">App</span><span class="content">\U</span><span class="content">ser</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="content">userID</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="content">senderID</span><span class="delimiter">'</span></span>);
    }

    <span class="keyword">public</span> <span class="keyword">function</span> <span class="function">getSenderUserNameAttribute</span>() {
        <span class="local-variable">$username</span> = <span class="string"><span class="delimiter">'</span><span class="content">SnailLife</span><span class="delimiter">'</span></span>;
        <span class="keyword">if</span> (<span class="predefined">isset</span>(<span class="local-variable">$this</span>-&gt;sender)) {
            <span class="local-variable">$username</span> = <span class="local-variable">$this</span>-&gt;sender-&gt;username;
        }
        <span class="keyword">return</span> <span class="local-variable">$username</span>;
    }

    <span class="keyword">public</span> <span class="keyword">function</span> <span class="function">updateMessage</span>(<span class="local-variable">$propertiesToUpdate</span>) {
        <span class="local-variable">$this</span>-&gt;update(<span class="local-variable">$propertiesToUpdate</span>);
        <span class="keyword">return</span> <span class="predefined-constant">true</span>;
    }
}
</pre></div>
</div>
</div>

<p>When a snail is killed or born we create a new message for the user. For example:</p>

<div><div class="CodeRay">
  <div class="code"><pre><span class="keyword">if</span> (<span class="local-variable">$this</span>-&gt;isEgg) {
    <span class="local-variable">$notification</span> = [
        <span class="string"><span class="delimiter">'</span><span class="content">recipientID</span><span class="delimiter">'</span></span> =&gt; <span class="local-variable">$this</span>-&gt;ownerID,
        <span class="string"><span class="delimiter">'</span><span class="content">subject</span><span class="delimiter">'</span></span>     =&gt; <span class="string"><span class="delimiter">'</span><span class="content">An egg has died!</span><span class="delimiter">'</span></span>,
        <span class="string"><span class="delimiter">'</span><span class="content">content</span><span class="delimiter">'</span></span>     =&gt; <span class="string"><span class="delimiter">'</span><span class="content">Egg ID </span><span class="delimiter">'</span></span> . <span class="local-variable">$this</span>-&gt;snailID . <span class="string"><span class="delimiter">'</span><span class="content"> has died. Cause of death: </span><span class="delimiter">'</span></span> . <span class="local-variable">$cod</span>
    ];
}
<span class="keyword">else</span> {
    <span class="local-variable">$notification</span> = [
        <span class="string"><span class="delimiter">'</span><span class="content">recipientID</span><span class="delimiter">'</span></span> =&gt; <span class="local-variable">$this</span>-&gt;ownerID,
        <span class="string"><span class="delimiter">'</span><span class="content">subject</span><span class="delimiter">'</span></span>     =&gt; <span class="string"><span class="delimiter">'</span><span class="content">A snail (</span><span class="delimiter">'</span></span> . <span class="local-variable">$this</span>-&gt;snailID . <span class="string"><span class="delimiter">'</span><span class="content">) has died!</span><span class="delimiter">'</span></span>,
        <span class="string"><span class="delimiter">'</span><span class="content">content</span><span class="delimiter">'</span></span>     =&gt; <span class="string"><span class="delimiter">'</span><span class="content">SnailID ID </span><span class="delimiter">'</span></span> . <span class="local-variable">$this</span>-&gt;snailID . <span class="string"><span class="delimiter">'</span><span class="content"> has died. Cause of death: </span><span class="delimiter">'</span></span> . <span class="local-variable">$cod</span>
    ];
}
<span class="local-variable">$notification</span> = <span class="keyword">new</span> <span class="constant">UserMessage</span>(<span class="local-variable">$notification</span>);
<span class="local-variable">$notification</span>-&gt;save();
</pre></div>
</div>
</div>

<p>(An egg is really just an instance of a snail, just one without a birthDate, so when an egg or snail dies it’s handled in the same model).</p>

<p>Then there’s the view. When logged in the user gets a notification of unread messages in the header:</p>

<div><div class="CodeRay">
  <div class="code"><pre><span class="tag">&lt;p&gt;</span>
    @if (count(Auth::user()-<span class="error">&gt;</span>unreadMessages) <span class="error">&gt;</span> 0)
        <span class="tag">&lt;a</span> <span class="attribute-name">href</span>=<span class="string"><span class="delimiter">&quot;</span><span class="content">/account/messages</span><span class="delimiter">&quot;</span></span><span class="tag">&gt;</span><span class="tag">&lt;img</span> <span class="attribute-name">src</span>=<span class="string"><span class="delimiter">&quot;</span><span class="content">{!! URL::asset('assets/img/graphics/icons/envelope.png') !!}</span><span class="delimiter">&quot;</span></span> <span class="attribute-name">alt</span>=<span class="string"><span class="delimiter">&quot;</span><span class="content">You have unread messages</span><span class="delimiter">&quot;</span></span><span class="tag">&gt;</span><span class="tag">&lt;/a&gt;</span> - You've got mail!
    @endif
<span class="tag">&lt;/p&gt;</span>
</pre></div>
</div>
</div>

<p>Oh, we get unread messages in the User model using an Eloquent <code>hasMany</code> relationship:</p>

<div><div class="CodeRay">
  <div class="code"><pre><span class="keyword">public</span> <span class="keyword">function</span> <span class="function">unreadMessages</span>() {
    <span class="keyword">return</span> <span class="local-variable">$this</span>-&gt;hasMany(<span class="string"><span class="delimiter">'</span><span class="content">App</span><span class="content">\U</span><span class="content">serMessage</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="content">recipientID</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="content">userID</span><span class="delimiter">'</span></span>)-&gt;where(<span class="string"><span class="delimiter">'</span><span class="content">isRead</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="content">=</span><span class="delimiter">'</span></span>, <span class="integer">0</span>);
}
</pre></div>
</div>
</div>

<p>Once they click through they get taken to their message page (ignore the double-death messages. That’s being fixed right now…):</p>

<p><img src="http://liza.io/images/galleries/gastropoda/messages.png" alt="User messages" /></p>

<p>There they can click to view the individual message, delete messages, mark them as read, etc.</p>

<p>Pretty simple and gets the job done for now.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Update on Rigel and Response From DjurAkuten]]></title>
    <link href="http://liza.io/update-on-rigel-and-response-from-djurakuten/"/>
    <updated>2015-07-01T11:41:58+02:00</updated>
    <id>http://liza.io/update-on-rigel-and-response-from-djurakuten</id>
    <content type="html"><![CDATA[<p>Context: <a href="http://liza.io/the-month-from-hell/">The Month from Hell</a>.</p>

<h2 id="djurakuten">DjurAkuten</h2>
<p>After numerous attempts David finally got through to DjurAkuten, the clinic that did Rigel’s neutering surgery and decided to put a catheter in him because they thought his bladder looked big - the one he left with a urinary infection and an inability to pee.</p>

<p>He spoke to them about what had happened. They requested that we email them all of Rigel’s journals, which we did the same day. A week or two later a letter came in the mail (in Swedish) where they claimed that they had zero responsibility for what happened to Rigel because his bladder was already flaccid when he got there. I’m not sure if they even read their own journals on him. DjurAkuten themselves wrote in their records that they found no blockage and no infection in Rigel’s urine and that his bladder contracted normally while being drained during castration - it was certainly not flaccid. They simply thought his bladder was large and decided to poke around with a catheter during what was meant to be a routine surgery.</p>

<p>It’s disappointing because either they are contradicting their own paperwork without even reading it or they are being intentionally misleading. I might upload all of his journals here later for anyone who’s curious and is willing to either translate or reads Swedish.</p>

<p>Here was my email to them in response, sent a few weeks ago, to which we have had no reply even after calling (I have censored the names of the other clinics involved here):</p>

<blockquote>
  <p>Attn: Ms Susanne Åhman regarding Rigel‏</p>

  <p>Dear Ms Åhman,</p>

  <p>I am contacting you through email because it is faster than sending physical mails back and forth.</p>

  <p>Please let me know if I misunderstood your mail to us about Rigel, but it seems you are saying DjurAkuten takes zero responsibility for anything that’s happened because our kitten came in with a large bladder? The part in your mail about him having a flaccid bladder is not correct. Your own journals have said that his bladder contracted normally after you decided to stick a catheter in him: “efter tömning drar ub ihop sig vad som ser ut som normalt.” It was certainly not flaccid as your own records show.</p>

  <p>In fact, the first time your own journals mention a flaccid bladder was when we brought him back to you the 2nd time when he was unable to pee after your treatment.</p>

  <p>We are certainly not saying that the problems were as a result of his castration as your mail seems to imply - we are saying his problems are as a result of your introducing a urinary infection while sticking a catheter in him during what was supposed to be simply a castration surgery. There was no good reason to do this.</p>

  <p>An Internal Medicine specialist has examined him and communicated that cat organs, just like human organs, can vary in size and shape. We have been told that just because a bladder is large does not mean it has a problem. The fact that he developed an infection after you decided to put a catheter in him is also indicative of an issue being introduced at your clinic. Our kitten was perfectly happy and healthy before his procedure at your clinic resulted in a urinary infection. I can assure you he did not come in sick with an infected, distended bladder. I know this not just by our own observation of him and his very normal urination frequency before your treatment, but by looking at your own journal notes - you decided to put a catheter in him when you noticed his bladder was large and tested for infection. You found no infection. You have also said that his bladder contracted normally after being drained at that time. Your own vet spoke to me in person after the neutering surgery and said that his large bladder can be totally normal for him and not cause any further problems, especially since he’s been peeing normally all along. We came back to you when our kitten could not pee despite straining to after your surgery and catheterization. You stuck ANOTHER catheter in him to drain him AGAIN and said that he now suddenly has an infection. This is what started this entire chain of events - us bringing a healthy, happy kitten to DjurAkuten for a routine surgery and leaving with a kitten who was unable to pee and had to go through a subsequent bladder biopsy surgery, perineal urethrostomy, and countless catheters, blood tests, and needles being stuck into his bladder. He was sent home to die and we still have no idea how he managed to survive after all this. He is still on heavy medication, special food, and constant watch (we still monitor his urine clumps in a box daily to make sure he is urinating enough after this entire ordeal). We have just begun to get over everything he’s been through and everything we’ve gone through to keep him alive, but now seeing you claim that he came to you already sick despite all evidence to the contrary is even more upsetting.</p>

  <p>So - am I understanding correctly in that you claim DjurAkuten did not have anything to do with the initial problem - him being unable to pee after being catheterized at your clinic - even though you tested (and cleared) him for infection, despite the fact that other veterinarians have indicated that he was fine before he saw you and got sick after you decided to stick a catheter in him, and that the treatment he received at your clinic and subsequent infection are more than likely related? You will note that in the journals [other clinic] concludes that his original infection was iatrogenic in nature, ie, introduced in the catheterization process. Please confirm if this is the case, as I want to make sure I have correct information about your standpoint on this before we decide whether to pursue a formal complaint and proceeding to warn our friends, breeders, and the public about your clinic.</p>

  <p>Kind regards,</p>

  <p>Liza Shulyayeva</p>
</blockquote>

<p>We are still hoping that they will reply and clarify what they meant by their mail. It’s possible that it was some sort of misunderstanding, and that DjurAkuten didn’t mean to suggest that Rigel was sick all along despite all evidence indicating otherwise. And if they were really trying to convince us that we brought them a sick kitten despite their own records indicating otherwise I will be extremely disappointed as this would indicate their miscomprehension of their own and other hospitals’ records at best and a blatant lie at worst.</p>

<h2 id="rigel">Rigel</h2>
<p>Rigel has been suffering from yet another urinary infection after this whole debacle. Perineal Urethrostomy increases the likelihood of infection, and I guess he got unlucky. He is on antibiotics and painkillers/anti-inflammatories. He seems to have gotten worse over the last day (peeing blood again and straining, and starting to pee outside the box) so I rushed another urine sample to the hospital at 4-5am this morning and managed to get an appointment with a vet this afternoon. By the time we had our appointment they had already gotten the results of the sample and said that there was no more bacteria! What was an infection is now a case of cystitis, so our next steps are to:</p>

<ul>
  <li>Finish course of antibiotics</li>
  <li>Appetite booster every other day and special appetite-boosting food</li>
  <li>Continue with Cystease supplement for bladder lining</li>
  <li>Start giving Zylkene supplement for stress</li>
  <li>Continue giving Metacam for 5 days for pain relief and as an anti-inflammatory.</li>
  <li>Minimize noise and stress as much as possible (coincidentally this seems to have started when David’s kids came over to stay for two weeks a couple of days ago, so noise and excitement maybe triggered something, even though he really likes them).</li>
</ul>

<p>They also took a blood test and checked his kidney, liver, and other values. Everything seems to be normal, which is a load off my chest.</p>

<p>It’s crazy that one bad incident at a bad vet has spun us into what seems to be an ongoing cycle of vet visits, tests, surgeries, hospital stays, two closets of cat medicine, and endless vet/medicine bills. It’s still amazing to me that he has survived all of these hospital stays and other procedures with his affectionate personality and happiness intact. The difference in his and Kaytu’s approach to life is obvious, though. Even though they’re best friends it’s obvious that Kaytu, with her seemingly carefree personality, hasn’t been through what Rigel has been through - you can see it in their eyes and in how they approach strangers and the outside world. He’s a very strong kitten and we will keep fighting for him and his quality of life no matter what it takes.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Snaily Updates]]></title>
    <link href="http://liza.io/snaily-updates/"/>
    <updated>2015-06-29T18:32:35+02:00</updated>
    <id>http://liza.io/snaily-updates</id>
    <content type="html"><![CDATA[<p>I was going to try to remember all the things I’ve done in the last month on snails, but this seems impossible. So here’s a lazy list of git commits within this time:</p>

<pre><code>* 5 seconds ago, User messaging and cause of death [deploy:development]
* 4 days ago, Some race view fixes, test out increasing AMR for wild snails since too little energy is being burnt during races. [deploy:development]
* 4 days ago, Cancel races that have been unfinished for 24 hours, freeing up others to rent the race jar. [deploy:development]
* 5 days ago, Remove old unneeded Minion and Latchet stuff [deploy:development]
* 5 days ago, Remove unneeded logs, remove old breeding stuff that is no longer required with the brain [deploy:development]
* 5 days ago, Fixes and cleanup, new racing jar in seeder [deploy:development]
* 5 days ago, Remove start countdown from finished races, fix race result view [deploy:development]
* 5 days ago, Fix race finish [deploy:development]
* 5 days ago, Bug fixing in racing and memory saving [deploy:development]
* 6 days ago, Admin option to return foreign snails to owners, get parent jar [deploy:development]
* 6 days ago, Racing refactoring; stop destroying jars since we'll reuse them [deploy:development]
* 7 days ago, More flexibility for historical event logging; correctly represent snails trying to mate with items [deploy:development[
* 8 days ago, Substrate item creation fix [deploy:development]
* 8 days ago, Gather snails to middle of jar, more exact movement, seed race jars for Mr Casinir [deploy:development]
* 2 weeks ago, Ability to humanely euthanise eggs in jar [deploy:development]
* 2 weeks ago, Some safeguards for user entering snail in race before it was returned from previous race [deploy:development]
* 2 weeks ago, Replace finished and started columns with finishDate, startDate. Auto refresh jar page when race has started. [deploy:development]
* 3 weeks ago, Fix move check for jar view [deploy:development]
* 3 weeks ago, use View [deploy:development]
* 3 weeks ago, Fix swallowing, isolate mood update [deploy:development]
* 3 weeks ago, Resident register [deploy:development]
* 3 weeks ago, Draw gravestone under snail pattern [deploy:development]
* 3 weeks ago, Add rainbow bridge page [deploy:development]
* 3 weeks ago, Display estimated race start time [deploy:development]
* 3 weeks ago, Fix fertility spray [deploy:development]
* 3 weeks ago, Fix bank link [deploy:development]
* 3 weeks ago, Only list items user owns in closet [deploy:development]
* 3 weeks ago, Fix for jar installation [deploy:development]
* 3 weeks ago, Add bank hint to tutorial [deploy:development]
* 3 weeks ago, Switch to Single log mode [deploy:development]
* 4 weeks ago, CustomValidator [deploy:development]
* 4 weeks ago, Stop comparing an item to itself... [deploy:development]
* 4 weeks ago, User shouldn't be able to mix a substrate item with itself [deploy:development]
* 4 weeks ago, Toggle disabling item action form elements on action select [deploy:development]
* 4 weeks ago, Some item stuff [deploy:development]
</code></pre>

<p>I also decided to try live streaming Gastropoda development. I’ve only done a couple of sessions so far but it does help to keep me focused. Surprisingly enough a few people actually watch and ask questions. I didn’t really think anyone would be that interested in watching someone code some weirdo snail app. It’s nice because a lot of the “Have you thought about doing this” or “What about this” suggestions people have made are things I <em>have</em> thought of and/or already have implemented!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Items]]></title>
    <link href="http://liza.io/all-about-items/"/>
    <updated>2015-05-27T11:58:05+02:00</updated>
    <id>http://liza.io/all-about-items</id>
    <content type="html"><![CDATA[<p>After finishing yesterday’s post about generating images for user-combined substrate items I realized that I never really provided an explanation about how items work in general (or how they work for now, anyway).</p>

<p>We have a few different item-associated models in Gastropoda. They are (model followed by associated table in parinetheses)</p>

<ul>
  <li>ItemType (item_types)</li>
  <li>ItemTemplate (item_templates)</li>
  <li>ItemUserTemplate (item_user_templates)</li>
  <li>ItemNutrition (item_nutrition)</li>
  <li>ItemUserNutrition (item_user_nutrition)</li>
  <li>Item (items)</li>
</ul>

<p>Item types are very generic. Right now I have the following item types:</p>

<ul>
  <li>consumable</li>
  <li>decorative</li>
  <li>substrate</li>
  <li>jar</li>
  <li>breeding</li>
  <li>terrain</li>
  <li>training</li>
  <li>misc</li>
</ul>

<p>ItemTemplates are pregenerated item…templates that can exist in the world. Note that these are not the existing <em>items</em> in the world, they are just things that could be instantiated into in-world items. Each item template is associated with an item type. Right now they are (template name, item type):</p>

<ul>
  <li>Lettuce Leaf (consumable)</li>
  <li>Medium Sweet Potato (consumable)</li>
  <li>Spark (decorative)</li>
  <li>Basic Small Jar (jar)</li>
  <li>Enthusiast Small Jar (jar)</li>
  <li>7-Day Fertility Spray (breeding)</li>
  <li>Grooved Glass 2mm (terrain)</li>
  <li>Attractor (training)</li>
  <li>Garden Dirt (substrate)</li>
  <li>1kg Pure Fine Sand (substrate)</li>
  <li>1kg Dry Oak Leaves (substrate)</li>
</ul>

<p>ItemTemplates <em>can</em> have an associated nutrition template. All consumables and substrate items have their own entries in the <code>item_nutrition</code> table.</p>

<p>Items are the actual instances of these item templates existing in the world. They are usually created when stores are restocked or when users mix their own substrate. An item entry consists of basic information about the instance, like:</p>

<ul>
  <li>itemID</li>
  <li>templateID</li>
  <li>ownerID</li>
  <li>jarID</li>
  <li>bitesTaken</li>
  <li>posX</li>
  <li>posY</li>
  <li>isUserTemplate</li>
</ul>

<p>Which brings me to user templates. Any mixing of substrate creates a new item template, or rather an ItemUserTemplate, and saves it in the <code>item_user_templates</code> table. Substrate also gets its own nutrition entry, in <code>item_user_nutrition</code>. This allows us to easily distinguish user generated items from basic items in the simulation and treat them differently. Then on an instance of an item, if <code>isUserTemplate</code> is true, we know to look for associated templates in the <code>user</code> tables and not the regular tables.</p>

<p>That’s pretty much it. The most annoying part about this setup is that there are so many inter-table dependencies. An item without a template entry is a broken item. A consumable without a template that has an associated nutrition record is broken. Etc…</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Images for Combined Substrate]]></title>
    <link href="http://liza.io/images-for-combined-substrate/"/>
    <updated>2015-05-26T20:31:57+02:00</updated>
    <id>http://liza.io/images-for-combined-substrate</id>
    <content type="html"><![CDATA[<p>The Laravel 5 migration is complete and I’m back to substrate mixing and brain bug fixing!</p>

<p>Actually, I think now that basic mixing is done I’m going to do a few weeks of just bug fixing. The substrate has no effect on snails’ attributes or behaviour yet, but it will. For now you can just mix different substrate items together into new substrate items.</p>

<p>I’m still not sure what the best way to create images for the custom-mixed substrate might be. The possibilities are pretty much endless - you can mix any substrate-type item with any other substrate-type item.</p>

<p>So what happens is - the user goes to their closet. They pick two substrate items to mix together. Then this whole mess happens:</p>

<div><div class="CodeRay">
  <div class="code"><pre> <span class="keyword">protected</span> <span class="keyword">function</span> <span class="function">mixSubstrate</span>() {
    <span class="local-variable">$itemID1</span> = <span class="constant">Input</span>::get(<span class="string"><span class="delimiter">'</span><span class="content">itemID1</span><span class="delimiter">'</span></span>);
    <span class="local-variable">$itemID2</span> = <span class="constant">Input</span>::get(<span class="string"><span class="delimiter">'</span><span class="content">itemID2</span><span class="delimiter">'</span></span>);

    <span class="local-variable">$item1</span> = <span class="local-variable">$this</span>-&gt;findItem(<span class="local-variable">$itemID1</span>);
    <span class="local-variable">$item2</span> = <span class="local-variable">$this</span>-&gt;findItem(<span class="local-variable">$itemID2</span>);

    <span class="local-variable">$item1WeightG</span> = <span class="local-variable">$item1</span>-&gt;template-&gt;weightG;
    <span class="local-variable">$item2WeightG</span> = <span class="local-variable">$item2</span>-&gt;template-&gt;weightG;

    <span class="local-variable">$resultingItemName</span> = <span class="constant">Input</span>::get(<span class="string"><span class="delimiter">'</span><span class="content">itemname</span><span class="delimiter">'</span></span>);

    <span class="local-variable">$item1Attributes</span> = <span class="local-variable">$item1</span>-&gt;template-&gt;nutrition-&gt;getAttributes();
    <span class="local-variable">$item2Attributes</span> = <span class="local-variable">$item2</span>-&gt;template-&gt;nutrition-&gt;getAttributes();

    <span class="local-variable">$imageTitle</span> = <span class="predefined">preg_replace</span>(<span class="string"><span class="delimiter">'</span><span class="content">/</span><span class="content">\s</span><span class="content">+/</span><span class="delimiter">'</span></span>, <span class="string"><span class="delimiter">'</span><span class="delimiter">'</span></span>, <span class="local-variable">$resultingItemName</span>) . <span class="string"><span class="delimiter">'</span><span class="content">.png</span><span class="delimiter">'</span></span>;
    <span class="local-variable">$fileToSaveTo</span> = <span class="string"><span class="delimiter">'</span><span class="content">/assets/img/items/terrain/custom/</span><span class="delimiter">'</span></span> . <span class="local-variable">$imageTitle</span>;

    <span class="local-variable">$resultingImage</span> = <span class="constant">Utility</span>::<span class="constant">MergeTwoImages</span>(<span class="local-variable">$item1</span>-&gt;fullImagePath, <span class="local-variable">$item2</span>-&gt;fullImagePath, <span class="local-variable">$fileToSaveTo</span>);

    <span class="comment">// Create resulting item template</span>
    <span class="local-variable">$resultingItemTemplate</span> = <span class="keyword">new</span> <span class="constant">ItemUserTemplate</span>();
    <span class="local-variable">$resultingItemTemplate</span>-&gt;description = <span class="string"><span class="delimiter">'</span><span class="content">Custom item created by combining </span><span class="delimiter">'</span></span> . <span class="local-variable">$item1</span>-&gt;template-&gt;name . <span class="string"><span class="delimiter">'</span><span class="content"> and </span><span class="delimiter">'</span></span> . <span class="local-variable">$item2</span>-&gt;template-&gt;name;
    <span class="local-variable">$resultingItemTemplate</span>-&gt;weightG = <span class="local-variable">$item1WeightG</span> + <span class="local-variable">$item2WeightG</span>;
    <span class="local-variable">$resultingItemTemplate</span>-&gt;name = <span class="local-variable">$resultingItemName</span>;
    <span class="local-variable">$resultingItemTemplate</span>-&gt;typeID = <span class="local-variable">$item1</span>-&gt;template-&gt;typeID;
    <span class="local-variable">$resultingItemTemplate</span>-&gt;ownerID = <span class="constant">Auth</span>::user()-&gt;userID;
    <span class="local-variable">$resultingItemTemplate</span>-&gt;imageSubdir = <span class="string"><span class="delimiter">'</span><span class="content">terrain/custom/</span><span class="delimiter">'</span></span>;
    <span class="local-variable">$resultingItemTemplate</span>-&gt;imageName = <span class="local-variable">$imageTitle</span>;
    <span class="local-variable">$resultingItemTemplate</span>-&gt;save();

    <span class="comment">// Create resulting item nutrition template</span>
    <span class="local-variable">$resultingItemNutrition</span> = <span class="keyword">new</span> <span class="constant">ItemUserNutrition</span>();
    <span class="local-variable">$resultingItemNutrition</span>-&gt;templateID = <span class="local-variable">$resultingItemTemplate</span>-&gt;templateID;
    <span class="keyword">foreach</span> (<span class="local-variable">$item1Attributes</span> <span class="keyword">as</span> <span class="local-variable">$key</span> =&gt; <span class="local-variable">$value</span>) {
        <span class="keyword">if</span> (<span class="local-variable">$key</span> !== <span class="string"><span class="delimiter">'</span><span class="content">templateID</span><span class="delimiter">'</span></span> &amp;&amp; <span class="local-variable">$key</span> !== <span class="string"><span class="delimiter">'</span><span class="content">updated_at</span><span class="delimiter">'</span></span> &amp;&amp; <span class="local-variable">$key</span> !== <span class="string"><span class="delimiter">'</span><span class="content">created_at</span><span class="delimiter">'</span></span>) {
            <span class="local-variable">$resultingItemNutrition</span>-&gt;<span class="local-variable">$key</span> = <span class="local-variable">$item1Attributes</span>[<span class="local-variable">$key</span>] + <span class="local-variable">$item2Attributes</span>[<span class="local-variable">$key</span>];
        }
    }
    <span class="local-variable">$resultingItemNutrition</span>-&gt;save();

    <span class="comment">// Create item of new template</span>
    <span class="local-variable">$resultingItemProperties</span> = [
        <span class="string"><span class="delimiter">'</span><span class="content">templateID</span><span class="delimiter">'</span></span> =&gt; <span class="local-variable">$resultingItemTemplate</span>-&gt;templateID,
        <span class="string"><span class="delimiter">'</span><span class="content">ownerID</span><span class="delimiter">'</span></span> =&gt; <span class="constant">Auth</span>::user()-&gt;userID,
        <span class="string"><span class="delimiter">'</span><span class="content">isUserTemplate</span><span class="delimiter">'</span></span> =&gt; <span class="integer">1</span>
    ];
    <span class="local-variable">$resultingItem</span> = <span class="local-variable">$this</span>-&gt;createNewItem(<span class="local-variable">$resultingItemProperties</span>);

    <span class="comment">// Delete original items</span>
    <span class="local-variable">$item1</span>-&gt;deleteItem();
    <span class="local-variable">$item2</span>-&gt;deleteItem();

    <span class="keyword">return</span> <span class="constant">Redirect</span>::back()-&gt;with(<span class="string"><span class="delimiter">'</span><span class="content">resultingItem</span><span class="delimiter">'</span></span>, <span class="local-variable">$resultingItem</span>);
}
</pre></div>
</div>
</div>

<p>To actually create the image for the new item I added a new utility function:</p>

<div><div class="CodeRay">
  <div class="code"><pre><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">function</span> <span class="function">MergeTwoImages</span>(<span class="local-variable">$imgPath1</span>, <span class="local-variable">$imgPath2</span>, <span class="local-variable">$fileToSaveTo</span> = <span class="predefined-constant">null</span>) {
    <span class="local-variable">$x</span> = <span class="integer">50</span>;
    <span class="local-variable">$y</span> = <span class="integer">50</span>;
    <span class="local-variable">$root</span> = <span class="predefined">$_SERVER</span>[<span class="string"><span class="delimiter">'</span><span class="content">DOCUMENT_ROOT</span><span class="delimiter">'</span></span>];

    <span class="local-variable">$finalImage</span> = imagecreatetruecolor(<span class="local-variable">$x</span>, <span class="local-variable">$y</span>);
    imagesavealpha(<span class="local-variable">$finalImage</span>, <span class="predefined-constant">true</span>);

    <span class="local-variable">$sortedImages</span> = [<span class="local-variable">$imgPath1</span>, <span class="local-variable">$imgPath2</span>];
    <span class="predefined">sort</span>(<span class="local-variable">$sortedImages</span>);

    <span class="local-variable">$image1Layer</span> = imagecreatefrompng(<span class="local-variable">$root</span> . <span class="local-variable">$sortedImages</span>[<span class="integer">0</span>]);
    <span class="local-variable">$image2Layer</span> = imagecreatefrompng(<span class="local-variable">$root</span> . <span class="local-variable">$sortedImages</span>[<span class="integer">1</span>]);

    <span class="local-variable">$opacity</span> = <span class="integer">50</span>;

    <span class="constant">ImageCopyMerge</span>(<span class="local-variable">$finalImage</span>, <span class="local-variable">$image1Layer</span>, <span class="integer">0</span>, <span class="integer">0</span>, <span class="integer">0</span>, <span class="integer">0</span>, <span class="integer">50</span>, <span class="integer">50</span>, <span class="local-variable">$opacity</span>);
    <span class="constant">ImageCopyMerge</span>(<span class="local-variable">$finalImage</span>, <span class="local-variable">$image2Layer</span>, <span class="integer">0</span>, <span class="integer">0</span>, <span class="integer">0</span>, <span class="integer">0</span>, <span class="integer">50</span>, <span class="integer">50</span>, <span class="local-variable">$opacity</span>);

    <span class="predefined">header</span>(<span class="string"><span class="delimiter">'</span><span class="content">Content-Type: image/png</span><span class="delimiter">'</span></span>);
    <span class="keyword">if</span> (<span class="local-variable">$fileToSaveTo</span>) {
        <span class="local-variable">$fileToSaveTo</span> = <span class="local-variable">$root</span> . <span class="local-variable">$fileToSaveTo</span>;
        imagepng(<span class="local-variable">$finalImage</span>, <span class="local-variable">$fileToSaveTo</span>);
    }
    <span class="keyword">return</span> <span class="local-variable">$finalImage</span>;
}
</pre></div>
</div>
</div>

<p>The results look like this (the below is experimenting with mixing 1kg oak leaves with 1kg sand):</p>

<p><img src="http://liza.io/images/galleries/gastropoda/substrate-mixing.png" alt="Mixed substrate" /></p>

<p>It’ll do for now. After some bug fixing I’ll go back to substrate and start having it actually have an effect on the snails.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Migrating to Laravel 5]]></title>
    <link href="http://liza.io/migrating-to-laravel-5/"/>
    <updated>2015-05-06T20:32:12+02:00</updated>
    <id>http://liza.io/migrating-to-laravel-5</id>
    <content type="html"><![CDATA[<p>A few days ago I decided that there’s no use putting it off any longer - it’s time to upgrade to Laravel 5. I’ve been sitting on 4.2 for months and 5 is a major revision to…well…everything.</p>

<p>I knew this would be a large setback in terms of feature work. The migration would break things, things that would make me wish I’d never done it at all. In addition the plugins I’d been using for 4.2 would no longer be compatible with 5 (in fact, I don’t think any of them are…)</p>

<p>I was using:</p>

<ul>
  <li>Confide for user authentication</li>
  <li>Dispatcher for scheduled commands</li>
  <li>Latchet for a Laravel-specific implementation of Ratchet.</li>
</ul>

<p>The first two I just got rid of completely, using Laravel 5’s authentication features and new command scheduling feature that does exactly what Dispatcher used to for me.</p>

<p>The last one I haven’t even started on yet. It’s going to be the toughest.</p>

<p>The most “breaking” update in Laravel 5 has been namespaces. I’ve been sitting here adding namespace declaration to all of my files. It’s time consuming, but better in the long run and actually pretty relaxing. If this is the biggest problem I’ll run into during this migration I’ll be happy (Latchet aside, of course).</p>

<p>Anyway, within the next couple of days I hope to have the migration complete and functional. Then it’s back to substrate and brains!</p>
]]></content>
  </entry>
  
</feed>
