Monday 28 January 2019

Web Tutorial: Nike Meme Generator Redux

Dear readers!

Remember that awesome little Nike Meme Generator I made late last year? Well, it's come to my attention that there's a teeny little flaw in its execution. This little sucker works fine with some pics... but when you use a picture that has a great deal of white in it, the contrast totally blows!

Take this lovely picture for example. I downloaded it off the net somewhere. It's just meant as an example, so please don't sue me.

Serena Williams in all
her athletic glory.

See what I mean? Some of the white text just doesn't show up well.


Today, we're going to rectify this issue. Because while I'm not a perfectionist, this strikes me as sloppy. The fix is pretty simple, actually. So here goes...

We start by adding a div tag around the paragraph tags, like so. This is meant to be an overlay of sorts.
<div id="memeContainer">
            <div>
                <p style="margin-top:50%"><?php echo $line1;?><br /><?php echo $line2;?></p>
                <p style="margin-top:30%"><img src="nikelogo.png"> <?php echo $slogan;?></p>
            </div>
</div>


Then we style this thing. It will fill the full width and height of the memecontainer class, with the position property set to absolute. Then we give it a translucent black background.
            #memeContainer
            {
                width: 500px;
                height: 500px;
                padding: 5px;
                margin: 5px;
                float: left;
                outline: 1px solid #DDDDDD;
                background: url(<?php echo "uploads/" . $filecode . "." . $filetype; ?>) center center no-repeat;
                background-size: cover;
                font-family: georgia;
                color: #FFFFFF;
                font-size: 25px;
                -webkit-filter: grayscale(100%);
                filter: grayscale(100%);
                text-align: center;
            }

            #memeContainer div
            {
                position: absolute;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background-color: rgba(0, 0, 0, 0.5);
            }


Refresh. This is how it should look like.


And now, with the same pic... wunderbar!


Print version!


Remember kids... life is not all black and white!
T___T

Tuesday 22 January 2019

Web Tutorial: The Zombies Run Voiceover

Following several years of playing Zombies, Run!, an urge to duplicate the in-game voiceover hit me. You know the voice that informs you of what you've collected every now and then? Yeah, that one.

Perhaps it was acute boredom or professional curiosity, but that itch would not go away... and today's web tutorial is the result. I really got a kick out of this piece of work, and hope you do too. It's basically a lot of string concatenation in conjunction with the VBScript code I wrote a while back.

So, the objective is to have a program that will say random things like...

"Collected a toolbox."

"Collected a toolbox and an overcoat."

"Collected a toolbox, a box of lightbulbs and two spades."

"Collected a toolbox, a sports bra, three books, five footballs and four handfuls of money."

Hold on, what does this actually do?

Nothing useful, I'm afraid. It's an utterly trivial task. I'm like a kitten with a ball of wool at this point.

Without further ado...

Let's begin with the speech code and a randomizer function. Simple enough, right? Upon running it, you get a voice that says "Collected a", and that's it.
Dim speech

Function getrandomno(min, max)
    Randomize
    getrandomno = (Int((max - min + 1) * Rnd + min))
End Function

speech = "Collected a "

Set VObj = CreateObject("sapi.spvoice")
VObj.Speak speech


Now, you'll want to actually have the voice tell you what items you picked up. Declare two more variables, item and item_name. Then declare an array, items, with four elements. Each of these will be an object you picked up.
Dim speech
Dim item, item_name
Dim items(4)
items(0) = "spade"
items(1) = "toolbox"
items(2) = "sports bra"
items(3) = "shotgun"

Function getrandomno(min, max)
    Randomize
    getrandomno = (Int((max - min + 1) * Rnd + min))
End Function

speech = "Collected a"

Set VObj = CreateObject("sapi.spvoice")
VObj.Speak speech


Finally, set item to a random number between 0 and 3. Then set item_name to the string inside the array items, defined by item. Remember to add a space before it! After that, concatenate item_name to speech. Now the voice should say stuff like "Collected a toolbox" or "Collected a spade", randomly!
Dim speech
Dim item, item_name
Dim items(4)
items(0) = "spade"
items(1) = "toolbox"
items(2) = "sports bra"
items(3) = "shotgun"

Function getrandomno(min, max)
    Randomize
    getrandomno = (Int((max - min + 1) * Rnd + min))
End Function

item = getrandomno(0, 3)
item_name = " " & items(item)

speech = "Collected a"

speech = speech & item_name

Set VObj = CreateObject("sapi.spvoice")
VObj.Speak speech


Yep, give yourself a pat on the back. You've just created the core of what is to be our totally spectacular (but still utterly useless, heh heh) program. Now, not everything is prefaced with "a". You can have "a spade" or "a shotgun", but not "a overcoat". It's simply not something you can take for granted, the English language being what it is. With that in mind, some changes are required.

Change items to a two-dimensional array. The first element is the item name, and the second is the "a" or "an".
Dim items(4)
items(0) = Array("spade", "a")
items(1) = Array("toolbox", "a")
items(2) = Array("sports bra", "a")
items(3) = Array("shotgun", "a")


Let's add a new item to the array items.
Dim items(5)
items(0) = Array("spade", "a")
items(1) = Array("toolbox", "a")
items(2) = Array("sports bra", "a")
items(3) = Array("shotgun", "a")
items(4) = Array("overcoat", "an")


Declare a new variable, quantity, that will hold either "a" or "an".
Dim speech
Dim item, item_name, quantity


Also ensure that you now get a random number from 0 to 4 instead. And set quantity to the first element of the array element in items referenced by item!
item = getrandomno(0, 4)
quantity = items(item)(1)


Change the following lines. Now instead of just referencing one array element from items, we derive item_name from the first array element of that array element, and quantity! And we remove the hardcoded "a" from speech. So now, sometimes we'll get "Collected an overcoat"!
item_name = " " & quantity & " " & items(item)(0)

'speech = "Collected a"
speech = "Collected"


Let's take this further. What if sometimes we want a collective item instead of singular? Like, "a box of lightbulbs" or "a handful of money"?

Simple. We extend the items array, by making each item an array of four elements instead of two. The third element is the collective noun, and the fourth is the "a" or "an". In the latest two elements we've added, the second element is always an empty string. With singular items, the third and fourth elements are always empty strings.
Dim items(7)
items(0) = Array("spade", "a", "", "")
items(1) = Array("toolbox", "a", "", "")
items(2) = Array("sports bra", "a", "", "")
items(3) = Array("shotgun", "a", "", "")
items(4) = Array("overcoat", "an", "", "")
items(5) = Array("lightbulbs", "", "box", "a")
items(6) = Array("money", "", "handful", "a")


Add a new variable, unit.
Dim speech
Dim item, item_name, quantity, unit


Make sure the random number is now between 0 to 6. unit is initially an empty string.
item = getrandomno(0, 6)
quantity = items(item)(1)

unit = ""


Now, if the third element of the array element is not an empty string, that means it requires a unit. In this case, set unit to the value of that third element and concatenate " of" to it. Set quantity to the fourth element instead of the second.
item = getrandomno(0, 6)
quantity = items(item)(1)

unit = ""

if items(item)(2) <> "" then
    unit = " " & items(item)(2) & " of"
    quantity = items(item)(3)
end if


Then concatenate unit between quantity and the item name (ensuring there are spaces between them as well), to get item_name! Now you should occasionally get "Collected a box of lightbulbs" or "Collected a handful of money".
item = getrandomno(0, 6)
quantity = items(item)(1)

unit = ""

if items(item)(2) <> "" then
    unit = " " & items(item)(2) & " of"
    quantity = items(item)(3)
end if

item_name = " " & quantity & " " & unit & " " & items(item)(0)

Now for something trickier...

What if you wanted plural items? Like "spades" instead of "spade"? Not every plural simply has an "s" appended to the end. "Boxes" and "Hippopotami" are strong examples.

For this, we'll need to modify the items array again. This time, each element should be an array of five elements instead of four. The fifth element (here's looking at you, Bruce Willis!) will hold the plural form of the item name, or the plural form of the collection.
items(0) = Array("spade", "a", "", "", "spades")
items(1) = Array("toolbox", "a", "", "", "toolboxes")
items(2) = Array("sports bra", "a", "", "", "sports bras")
items(3) = Array("shotgun", "a", "", "", "shotguns")
items(4) = Array("overcoat", "an", "", "", "overcoats")
items(5) = Array("lightbulbs", "", "box", "a", "boxes")
items(6) = Array("money", "", "handful", "a", "handfuls")


Then we set quantity to a random number between, say, 1 to 5.
item = getrandomno(0, 6)
quantity = getrandomno(1, 5)


At the If block, we add an Else condition, and surround the entire block with another If block.
if quantity = 1 then
    if items(item)(2) <> "" then
        unit = " " & items(item)(2) & " of"
        quantity = items(item)(3)
    else
        quantity = items(item)(1)
    end if
else

end if


quantity is singular, so we'll replace the value of quantity with the appropriate "a" or "an". item_name is, quite naturally, the first element of the array.
if quantity = 1 then
    if items(item)(2) <> "" then
        unit = " " & items(item)(2) & " of"
        quantity = items(item)(3)
    else
        quantity = items(item)(1)
    end if

    item_name = items(item)(0)
else

end if


If quantity is more than 1, we use the plural form (the fifth element). And instead of "a" or "an", we use the number! So, no changes to quantity.

If the item is collective, we use the plural form of the collective noun. If not, we simply use the plural form of the item.

Now, you're going to get stuff like "Collected 5 shotguns" or "Collected 3 boxes of lightbulbs", in addition to the ones you already have!
unit = ""

if quantity = 1 then
    if items(item)(2) <> "" then
        unit = " " & items(item)(2) & " of"
        quantity = items(item)(3)
    else
        quantity = items(item)(1)
    end if   

    item_name = items(item)(0)
else
    if items(item)(2) <> "" then
        unit = " " & items(item)(4) & " of"
        item_name = items(item)(0)
    else
        item_name = items(item)(4)
    end if
end if


For the final trick...

What if you wanted different items in different quantities? Like, "a spade and three boxes of lightbulbs"? This one gets a bit more complicated, so I'll need you to pay attention.

First, we need a new variable, no_items.
Dim speech
Dim item, item_name, quantity, unit, no_items


Then we need two new arrays, collections and used. We'll arbitrarily assume that the largest number of different item types will be 5.
Dim speech
Dim item, item_name, quantity, unit, no_items
Dim collections(5), used(5)


After the getrandomno() function, we're going to set no_items to a number between 0 and 4.
Function getrandomno(min, max)
    Randomize
    getrandomno = (Int((max - min + 1) * Rnd + min))
End Function

no_items = getrandomno(0, 4)


Now, wrap all that code we wrote earlier, in a For loop iterating from 0 to no_items. The steps will be there - we're just going to repeat it i number of times.
no_items = getrandomno(1, 4)

for i=0 to no_items step 1
    item = getrandomno(0, 6)
    quantity = getrandomno(1, 5)

    unit = ""

    if quantity = 1 then
        if items(item)(2) <> "" then
            unit = " " & items(item)(2) & " of"
            quantity = items(item)(3)
        else
            quantity = items(item)(1)
        end if   

        item_name = items(item)(0)
    else
        if items(item)(2) <> "" then
            unit = " " & items(item)(4) & " of"
            item_name = items(item)(0)
        else
            item_name = items(item)(4)
        end if
    end if

    item_name = " " & quantity & " " & unit & " " & item_name
next


At the end of it, set the current element of the collections array to the value of item_name.
no_items = getrandomno(1, 4)

for i = 0 to no_items step 1
    item = getrandomno(0, 6)
    quantity = getrandomno(1, 5)

    unit = ""

    if quantity = 1 then
        if items(item)(2) <> "" then
            unit = " " & items(item)(2) & " of"
            quantity = items(item)(3)
        else
            quantity = items(item)(1)
        end if   

        item_name = items(item)(0)
    else
        if items(item)(2) <> "" then
            unit = " " & items(item)(4) & " of"
            item_name = items(item)(0)
        else
            item_name = items(item)(4)
        end if
    end if

    item_name = " " & quantity & " " & unit & " " & item_name

    collections(i) = item_name
next


After setting speech to "Collected", instead of concatenating one item_name to speech, we add all the strings from the collections array. Use a For loop to iterate from 0 to no_items.
speech = "Collected"

for i = 0 to no_items step 1

next

'speech = speech & item_name


If we're not at the end of the list of items, concatenate the value of the current element of collections and a comma to speech.
for i = 0 to no_items step 1
    if i < no_items then
             speech = speech & collections(i) & ","
    else

    end if
next


If we're at the end of the list, there are two possibilities. One, there is only one item type in the collections array, i,e, i is 0. The other is that there are multiple item types and we're at the end.
for i = 0 to no_items step 1
    if i < no_items then
             speech = speech & collections(i) & ","
    else
        if i = 0 then
       
        else
       
        end if
    end if
next


If it's the former, just concatenate the value of the current element of collections to speech. If there were more than one item and we're at the end, concatenate to speech the string " and" (take note of the space!) followed by the value of the current element of collections.
for i = 0 to no_items step 1
    if i < no_items then
             speech = speech & collections(i) & ","
    else
        if i = 0 then
            speech = speech & collections(i)
        else
            speech = speech & " and" & collections(i)
        end if
    end if
next


Now try it!

For the most part, you'll get beautifully constructed speeches like "Collected a sports bra, three spades, two boxes of lightbulbs and a handful of money". But wait... there's a problem. Once in a while, the randomizer repeats itself. So sometimes you may get something like "Collected a spade and two spades". That's not what we want at all, so...

Here, the used array will store all the unique numbers that have been generated.

Remember the line to randomize what item you get? Well, wrap that in a Do-while loop. It should execute at least once. And keep executing while the returned value of the inuse() function with item passed in as an argument, is true. So if the loop is exited, item is guaranteed to be unique within the used array.

And then set the current element of the used array to item.
for i = 0 to no_items step 1
    do while inuse(item) = true
        item = getrandomno(0, 6)
    loop

    used(i) = item

    quantity = getrandomno(1, 5)


Then let's write the inuse() function. This basically tells you if item is already taken.
Function getrandomno(min, max)
    Randomize
    getrandomno = (Int((max - min + 1) * Rnd + min))
End Function

Function inuse(num)

End Function


The parameter is num. Declare dupe as a variable and set it to false. At the end of the function, return dupe.
Function inuse(num)
    Dim dupe

    dupe = false

    inuse = dupe
End Function


Run a For loop that iterates through no_items. If at any point the value of the current element of used matches num, dupe is set to true.
Function inuse(num)
    Dim dupe

    dupe = false

    for j = 0 to no_items step 1
        if used(j) = num then
            dupe = true
        end if
    next

    inuse = dupe
End Function


There, you've got it!

Just for shits and giggles, let's add a whole lot more stuff into the items array!
Dim items(15)
items(0) = Array("spade", "a", "", "", "spades")
items(1) = Array("toolbox", "a", "", "", "toolboxes")
items(2) = Array("sports bra", "a", "", "", "sports bras")
items(3) = Array("shotgun", "a", "", "", "shotguns")
items(4) = Array("overcoat", "an", "", "", "overcoats")
items(5) = Array("lightbulbs", "", "box", "a", "boxes")
items(6) = Array("money", "", "handful", "a", "handfuls")
items(7) = Array("axe", "an", "", "", "axes")
items(8) = Array("trousers", "", "pair", "a", "pairs")
items(9) = Array("food", "", "tin", "a", "tins")
items(10) = Array("book", "a", "", "", "books")
items(11) = Array("fuel", "", "can", "a", "cans")
items(12) = Array("pain meds", "", "bottle", "a", "bottles")
items(13) = Array("football", "a", "", "", "footballs")
items(14) = Array("radio", "a", "", "", "radios")


Don't forget to change the random number generator code!
item = getrandomno(0, 14)


Have fun with your program! Better yet, if you haven't already, check out Zombies, Run!.

Collected a thank-you, good-bye and a see-you-again!
T___T

Thursday 17 January 2019

Pay Platforms: Provider or Police?

What's this I've been hearing about Patreon? Since December last year, people have been leaving Patreon. Accounts have been closed, are being closed as I type this, and the rot does not appear to be stopping.

Patreon is a crowdfunding service that ties content creators and subscribers together to facilitate donations towards the content creators. And lately, Patreon has been behaving more like the Moral Police instead of the service it's purported to be.

Carl Benjamin, a content creator, got his account shut down just last month. The reason given? He was guilty of using some very offensive words... on YouTube. YouTube, not Patreon. On someone else's YouTube channel, not his own. In essence, he had supposedly violated the Community Guidelines on Hate Speech. An excerpt is below:

"Patreon connects creators to their patrons all over the world. We are a global platform built on promoting creativity, which makes us a very inclusive group. Therefore, there is no room on Patreon for hate speech such as calling for violence, exclusion, or segregation. Hate speech includes serious attacks, or even negative generalizations, of people based on their race, ethnicity, national origin, religion, sex, gender, sexual orientation, age, disability or serious medical conditions."

However. When you take context into account - he was trying to illustrate via negative example why it's not OK to use those words - and then do a search of those words on Patreon itself, you get a ton of results where those very same words are used... and in the original context!

What gives, Patreon? When you can't even enforce your own rules consistently and professionally, you lose the right to be taken seriously.

To me, this is a symptom of a larger problem. Tech service providers are using their power to shove their own subjective moral code down the collective throats of their users. That's not a good thing. That's never a good thing. Remember what happened with CloudFlare back in 2017? CEO Matthew Prince, by his own admission, arbitrarily banned The Daily Stormer because they pissed him off. I'm shedding no tears for The Stormers. What concerned me, at the time, was the concept of due process and a properly thought out set of guidelines for such cases.

Wherein lies the difference between Matthew Prince and Jack Conte, CEO of Patreon. Prince had the class to admit that it wasn't the right thing to do. Conte merely doubled down, and is still trying to worm his way out of any admission of wrongdoing for Carl Benjamin's banning. Check out his response to journalist Tim Pool below.

"...we examine behaviors on and off Patreon, and you are correct to point out that there is language that makes it seem like only content on Patreon is reviewed, which is not a constraint that we apply for all categories of the guidelines… we need to make that clearer."

This is basically double-talk for: we have rules, and you need to trust us to enforce them when or if we see the need to. News for you, Conte... trust has to be earned and Patreon, as of now, is a long, long way away from that. It's really friggin' hard to trust folks who establish rules and then enforce those very same rules selectively, sometimes using different interpretations for different people!

There's a hilarious video clip on YouTube that pretty much sums up what's happening here. Though portraying Jack Conte as Hitler, no matter how much of a weasel I think he's being at the moment, seems a bit much. Still, it's funny as all heck. Enjoy!


Who cares about Carl Benjamin anyway?

Not me. I barely knew who he was before this whole saga started. Then I went to watch one of his videos (he's known as Sargon of Akkad on YouTube) and almost got put to sleep. This dude is boring.

But there are people who think he's trash and are glad he was de-platformed. Because they find his opinions repulsive, or some crap like that.

Just gonna say this - it doesn't matter who the victim is. It's a matter of principle. We need to break out of this childish mentality that bad things are good when done to bad people. If we accept objectively bad things when they happen to people we don't care for, then we can't complain when they happen to people we do care about.

If there is no consistency to your principles, you have no principles, period.

More exits!

Jordan Peterson and Dave Rubin have declared their intention to leave Patreon. Sam Harris has elected to take his business elsewhere. And these are just the big hitters. Who else has to leave before Patreon starts to wake up?

Let's be crystal-clear on something. Patreon is not a Free Speech platform, and therefore not obliged to avoid censorship. Patreon was completely within their legal rights in de-platforming whoever they wanted, with or without an explanation. Patreon can act as unprofessionally as they please as long as they don't break the law.

That does not make what Patreon did, either right or sensible. Tech service providers need to realize that they exist at the pleasure of their users, not the other way round. The moment they stop being credible, the moment people stop trusting them, they've lost. Patreon might have been one of the most popular and well-known crowdfunding services on the Internet. Well, they're still well-known now, albeit for the wrong reasons; one of them in a nutshell - they got too big for their goddamn britches.

The USA is engaged in a culture war right now, a war of clashing political ideologies. And that war has escalated. Now if somebody gets offended at your words, they don't just flame you online. They pressure people to take away your livelihood. That's intolerance taken to new, dangerous levels. What's the next logical step? Gunning people down because those people drew cartoons they didn't like?

In Conclusion


Tech service providers are supposed to provide a service within the boundaries of the law. They're not the enforcers of political ideology, nor should they ever aspire to be.

To be Conte-nued?
T___T

Sunday 13 January 2019

How I Became An Agency Contractor (Part 2/2)

With all my rumination over and done with, I replied expressing interest and the interview was on.

I showed up in my standard interview attire, and to my surprise, I did not get asked any technical questions. Well, not if you think stuff like "how many scopes does AngularJS have?" counts. The interviewer was the guy who would be my team lead for the next year or so. It was relaxed; unremarkable. The interviewer informed me that the company was a tech corporation. And not just any tech corporation - one of the largest tech companies worldwide. Contract position notwithstanding, it would be a huge step up from the usual penny-ante minnows I'd had to deal with the last decade.

Editor's Note: The company shall remain unnamed till I cease to work for them. I'm old-school that way.
A nice shiny business card.

The interviewer also informed me that while I would be doing work on that company's behalf, this would not officially make me their employee. I would, technically, be an employee of the Recruitment Agency. He asked me if that would be a problem, and I was astonished. Why would officially being an employee of that company be such a big deal? Bragging rights? Opportunity for promotion? A nice shiny business card with my name on it? I was 39 years old, and past all that now.

The Negotiation

After the interview, the recruiter informed me that I had passed, and we would proceed to talk about my pay. Initially, I had named a salary figure I considered to be outrageous.

They did me one better. They raised it by five percent.

Instead of trying to bargain me down like some of the cheap bastards I've encountered, they were going to pay me more than what I asked for. This was the first time I truly got the feeling that I was playing for the big boys now.
One very long form.

The recruiter then, to my horror, gave me a fifteen-page form to fill in. He explained that this company was functioning as an onsite vendor to a Government Statutory Board, and that the Contract position was meant for that role. So as per Government regulations, I had to fill up that fifteen-page form, also known as the G50 Clearance. I moaned and whinged about it a fair bit, but you know what? At least they didn't ask for my fucking "O" Level Cert.

In addition to that, I was supposed to go for a second interview, this time to meet with members of that Government Statutory Board. The second interview wasn't too bad. To my pleasant surprise, the interviewers had actually gone through my website, and, I assumed, read the posts and tried the demos. That was one of the very first vindications of my effort in setting it up.

Waiting

Now all that was left to do was wait. The information I had provided on that humongously long-winded form had been sent to the Government Statutory Board, where they would take up to a month to check everything out and determine that I wasn't some spy from ISIS, or something.

Those few weeks weren't spent idle. I swam daily, wrote code and did a lot of reading. Now that I had all this free time, I spent it on honing my craft further in preparation for my new job. I was reading up stuff that wasn't just programming - it was everything else related to development. Code clarity, CSS, Web Typography and Colors for User Interface Design. I even picked up Ruby!

I read a lot.

In addition, I watched online web tutorials, caught up on TV that I hadn't had time to watch (like Silicon Valley!) and basically enjoyed life and capped each day off with midnight walks around the park with the girlfriend. My morale had been simmering as I remained cautiously optimistic, but now it was positively soaring.  It was basically weeks of doing stuff I'd always wanted to try, and never quite found the time. Well, now I had time - in spades.

Starting the job

When I finally began my first day in the office, it actually amounted to more waiting. Apparently the machine supposed to be assigned to me wasn't ready, so I spent time reading up procedures and practices, and getting to know my colleagues. The majority of them were from India, with a few from Burma, Vietnam and Philippines. With a shock, I realized that I was the only Singaporean at that level. Not that any of them noticed - with my complexion, I was actually mistaken as someone of some other nationality. The Indians thought I was Burmese, the Burmese thought I was Filipino... and I'd rather not think about what country the Filipinos thought I came from.

It was rough going at first. Everything was in C# and I was having trouble remembering how to use Visual Studio and Team Foundation Server after more than five years of not touching the stuff. It did eventually come back to me but it was an uphill battle. There were days I felt almost totally worthless, and days I felt at the top of my game. There were people I took to, and people I couldn't stand.

In short, it was great.

I wasn't the smartest man in the room, though I was probably one of the oldest. But I'd left my ego behind from day one, so admitting when I didn't know shit, wasn't at all difficult. Plus, that fat paycheck helped. Getting paid that much while expectations of me remained low? Learning all that useful shit while, again, getting paid? Knowing that they let me in because of what I could do and not because of the color of my passport? Absolute heaven.

Epilogue

All in all, I lasted one year, and then they renewed my contract. I've been here eighteen months now, though my contract's expiring this July. It's been a wild ride.

Have a good work week,
T___T

Thursday 10 January 2019

How I Became An Agency Contractor (Part 1/2)

Further to landing a job at this startup back in 2016, it was not to last. Within half a year, the money ran out and the company was forced to let all its staff go. The boss, the dude who had hired me in the first place, was considerate enough to let us know three months in advance so that we could make preparations. Sure, he was the second employer to let me go in the past year or so, but his conduct was exemplary. There was none of that cost-cutting bullshit, at least. He projected the runway and paid us what we were due, right up to the final month.

The job search begins anew...

On my part, I began my job search anew. This was becoming routine by now. I elected to keep coming into the office to work on the company project even though technically, I wasn't an employee anymore. The boss was still at it, since he was the only one left in the company. In the meantime, I hammered out my website using Semantic UI, which I'd picked up during my time in the startup, and continued blogging.

The job search took a couple months. Apparently, plenty of companies in the immediate vicinity were hiring. On the not-so-bright side, some of these were your typical douchebags trying to smoke me with The Probationary Pay Gambit and not even having the class to be subtle about it. In some of these cases, you could practically smell the desperation. But by then, I was numb to it all and took everything in stride.

It was about this time I interviewed for that job at the IT Training center, and encountered my ex-lecturer. As mentioned, it was an interesting interview, but until I got my ACTA, their hands were tied. It was also around this period I got that ill-fated but educational experience in Mozat. And so the search continued.

An obsession with meaningless things

Among some of the interview offers I got, there was a remarkable phenomenon among Government-affiliated positions, such as Singapore Press Holdings and Singapore Polytechnic. I applied, received replies indicating interest, and then they pulled this stunt whereby they asked for my GCE "O" Level Certification. For those not in the know, the "O" Level Cert is the piece of paper that tells employers that you have graduated from secondary school, i.e., you have a basic education. The "O" stands for "Ordinary".

When I queried if this was an absolute necessity to proceed with the interview, they replied to the affirmative - yes, I absolutely must have that cert. Which was when I politely declined the interview. The HR for SPH, in particular, seemed really shocked in her email. It was like no one had ever had the temerity to turn down an interview for Singapore Press Holdings before. Imagine that! Poor girl.

Some may be wondering why I turned them down over such a trifling matter. It was a chance to work for a big company, wasn't it? To understand this, you'd have to see things from the perspective of a tech person. I wasn't some desperate schmuck with no qualifications looking for just any job. The size of the firm didn't matter. I wanted a tech job for career longevity. And any company that was going to obsess over trivialities such as an "O" Level Cert, certainly wasn't worth my time. Seriously, I had a Bachelor's Degree and multiple Diplomas in related fields and a resume that suggested I'd been through the mill... and these clowns wanted to be absolutely certain I had a basic education?!

This may sound arrogant, but I assure you, hubris has nothing to do with it. In tech, what we value is people who can do the work. Certification is secondary. How many Computer Science grads out there can't solve FizzBuzz, for example?

Do not go there.

The insistence on producing such a trivial piece of documentation told me they were companies that didn't have their priorities right - and if that was the case, no matter how big they were, they were a no-go area. I would work for another startup before joining these big boys, any day of the week.

Also, I think I've paid my dues and have earned the right to say no to bullshit... even if said bullshit is coming from some big-fuck Government-affiliated company like SPH.

Being contacted by an Agency

About the time I was wading through a lot of options, I got contacted by a Recruitment Agency. It was in the form of a message sent to my LinkedIn account. They were hiring for a big tech company, and if I was open to a contract position (one year, renewable), we should talk further.

This got me thinking...

Contract Positions

Now all of my life up to now, I've actively avoid Contract positions due to the perceived lack of job stability. Who wants to be in the position where they're constantly looking for a job? Then I thought further. Constantly looking for a job... like now, you mean? I'd been taking only Permanent roles for the last fifteen years. Fat lot of good it did for career stability! The last two companies I'd had a Permanent role in, tanked. And I was in a position that wasn't too far different from if I had taken on Contract positions to begin with.

No more illusions.

All that "career stability" was an illusion. And I was done with the charade.

Also, in a rapidly evolving field like computer technology, staying too long at any one place was a bad career move unless I was planning to climb some corporate ladder or something. It would be hell on my employability in the long run.

Lastly, I was tired of having to explain why I left my last job. Sure, the fact that the last two companies tanked wasn't my fault. But some companies have this habit of trying to parlay it into something that could be construed as my fault, so as to gain a negotiating advantage. (They didn't know that Your Teochewness doesn't negotiate, and despises the practice.) You know the type - you say your company folded and you can practically see the Fire Sale sign blinking over their heads.


Next

The interview process... and beyond!

Saturday 5 January 2019

App Review: Zombies, Run!

"Zombies detected!"

That's the constant refrain you'll get when using this app, Zombies, Run!. I picked it up years ago on a whim and I've been entranced ever since. Have I mentioned how much I really love zombie apocalypse flicks? Well, this app from Six to Start combines fitness and storytelling into one mind-blowing package.


Honestly, I hesitate to call it a "game" because there are so few gameplay elements in it. No matter how fast you run, the results are always the same - you complete the mission and get ready for the next one. At most, you lose a few Supplies when you get caught by chasing zombies. There are a few mini-games in there, but these feel like a tacked-on addition rather than any earnest attempt in actually making Zombies, Run! a game. It's really a zombie apocalypse-themed fitness app, with a storyline.

Stats

This fitness app monitors your speed and distance run, keeping logs and occasionally telling you to run faster, and is best played with a headset. Sound files deliver mission status updates and your imagination does the rest. Functionally, it's like a fitness app had a one-night stand with a radio programme and this is the illegitimate offspring.

The Premise

Britian has fallen to a zombie plague outbreak. You are Runner Five, tasked with making runs outside of base to collect supplies and gather information. On the way, you will encounter zombie hordes, crazed humans and other hostiles.

As the seasons progress, you'll find the scope of your missions expanding, as you not only fight to save your community, but the whole of London, Britain, maybe even the world.

The Aesthetics

The predominantly black and red (with shades of blue, orange and green sparingly applied) theme is bitchin'. And when you do your running at night, it just gives this whole  funky, apocalyptic warrior vibe. The art's great. It's simple and stylish, all at once.

The Experience

The voice acting, plus sound effects, provides this really immersive experience. When the snarls of chasing zombies are heard in your ear, all it takes is a little imagination to make the app come alive. The storyline is pretty cool, as are the characters. You'll soon grow accustomed to their voices - the awkward reediness of Sam Yao, the dulcet tones of Maxine Myers and the no-nonsense starchiness of Janine DeLuca.

The Interface

Navigation is a breeze, and everything you need can be found quite easily. The buttons are fairly large - no squinting and fat-finger syndrome for the most part. From the main menu, you'll see an entire array of options.

Missions!

You can choose Story Missions mode, where you'll go straight to a storyline-themed run. On these runs, you will collect Supplies, Materials and Artifacts.

Story Missions.

Into one mission...

Or you can choose any of the other options which will lead you to any of these Zombies, Run! fitness mini-apps. They all involve you running - but the variations are pretty fun. Some are just endurance training, some are interval training (for speed boosts) and some actually use the mapping system for something other than showing the route taken.

Training Plans.

Airdrop mini-game.

Interval Training.

There are also Supply Runs, a more lighthearted version of the Story Missions mode, where nothing major happens and you're just outrunning zombies and collecting Supplies. (Materials and Artifacts are available only in Story Missions mode).

Supply Runs!


There are also Races introduced into the app from time to time. The dev team loves to stay in touch with their fan base, and it shows. Check out their blog!

Races.

All this stuff, admittedly, is only available if you pay a monthly subscription. The Story Missions are still free... though they'll only be released to you at an interval of a few days at a time if you're not subscribed. Damn, these guys are evil... or just need to eat, like everyone else.

What I liked

The characters and storytelling in the entire series are captivating. My personal favorite has to be Season Two, where most of the cool stuff happens and the characters are just more compelling somehow. Admittedly, that could be because many of the best ones die tragic but heroic deaths. The voice acting is out of this world. London actress Clare Kissane herself plays Runner 8 Sara Smith, who, despite having only appeared in two seasons, is a legend in the Zombies, Run! world due to the absolutely compelling way this character was written.

Descriptions!
Wit. Whatever this app lacks, humor isn't it. It's got an abundance of charm in its little explanatory notes.

Content.

In Story Missions mode, for those who can't decipher British (or Irish, or Scottish) accents very well, there's a recap section (which appears after you have completed that particular mission) where each sound clip can be replayed, along with an explanatory summary.

Radio Abel.

There's also Radio Abel! That's the wisecracking duo of Jack and Eugene who grace your headsets if you continue running after completing a Mission. If you like them, you can actually just listen to them on your own. My larger point is, all this content is isolated for your listening pleasure. So thoughtful!

I started using this app when it was in Season Three, and now it's in Season Seven. The content never seems to stop coming!

You've got mail!

I love, love, love this. The app sends you email whenever you complete a major storyline milestone. There's just so much personality in this little feature.

What I didn't

The base-building potion of the app serves no real gaming purpose. You get Supplies and Materials from Story Missions, which you then use to build the base... but you can tear down buildings and rebuild without penalty. It's just a charming little add-on to the app, but adds no significant value. That said, the descriptions do rock!

Base-building.

More
descriptions!

In the story, there are plenty of characters in gay or lesbian relationships. No problem with that. But somehow the couples in heterosexual relationships always seem to die while those in homosexual relationships thrive. This storyline is pandering just a little too hard to the LGBT community.

Illogical at times. The Runner picks stuff up while running. I can get that. But he (or she) continues to pick stuff up even when, in the storyline, it should be impossible. In Season Two, there's this little sequence where the Runner's hands are tied to the back of a moving jeep, and stuff still gets added to inventory! Weird!

While navigation is largely fine, it would be nice to allow the user to scroll back and forth to previous and next missions, instead of assuming that the user always wants to go to the ext mission. It's a reasonable assumption for the most part, but still...

Sports bras. What is it with this app and sports bras?! I can't run a Story Mission without collecting a sports bra at least two or three times.

Conclusion

While it looks like I have a lot of complaints about the app, these are niggling at best. The pros far, far outweigh the cons. This is one of the few apps I actually pay money to use, and a large part of it is the superb storytelling. Zombies, Run! is a decent fitness app with an irresistible theme. And above all, it's fun.

My Rating

8.5 / 10

Stay safe out there, Runners!
T___T

Tuesday 1 January 2019

Five Code Practices to Observe Starting From 2019

Hi, guys. 2019 is here. Time for more resolutions!

Back in 2016, I made a short list of things to keep to, and to my chagrin, by mid-2017, I'd slacked off on a couple items. Still, I think I made a good go of it. This time round, my resolutions are all code-based.

1. Spacing

I'm absolutely terrible with this. And in 2019, I'd like to sternly remind myself to keep my code readable by putting in spaces where it's warranted. Sure, it makes not a lick of difference to the compiler or interpreter. But this isn't for the benefit of the machine - it's for the benefit of the poor human eyes that have to read my code.

From crap like this...
var x=(y<obj.average?obj.average+y:(obj.average+y)/(obj.items.length+1));


To this!
var x = (y < obj.average ? obj.average + y : (obj.average + y) / (obj.items.length + 1));


2. Naming Conventions

Another thing I'm really bad at. I tend to name variables either with very vague meanings such as temp or str. Worse, I might even use Hungarian Notation like strTemperature or varX. See, this is pointless. Like in the case of varX, no shit it's a variable, right? Barring constants which I don't use much of, most declared elements are variables.

Here, I resolve to reserve Hungarian Notation only for naming HTML elements, such as txtFullName for a text field or rbOptions for a radio button. It's a habit I carried over from my days using Visual Basic, and it's a hard one to break.

A good practice would be to go from...
String txtName;
Int intId;


... to longer, more meaningful Camel Case names.
String previousTransitionAuditorFullName;
Int previousTransitionAuditorId;


3. Readmes

There's a lot of code in my GitHub account. Unfortunately, not all of it is documented with nicely formattered markdown readme files. I'm making the effort to at least make a note of how the code is written, how it should be run, and so on. Specifically, the idea behind the code.

## Functions

**getAverageStat(statType)**
This function accepts a string, *statType* as a parameter and uses its value to iterate through the corresponding array and determine the average value.

**getTotalStat(statType)**
This function accepts a string, *statType* as a parameter and uses its value to iterate through the corresponding array and determine the total value.


I like to think my code is simple enough to read, but a short note in the repository is responsible coding. No one should have to read through my code without first having an idea of what they're reading. At this point in time, I'm trying to make up for my laxness in the past by documenting repositories as and when I find the time.


4. CSS cross browser

Now, if anyone has any experience with cross-browser CSS, it's using browser prefixes such as -moz- or -webkit-. I tended to do the below, thinking that it didn't really matter, which is wrong. The effect may be less than desired depending on what browse the code is running in, and what styling is being used.

.shootingStar
{
    transition: all 4s ease;
    -webkit-transition: all 4s ease;
    -moz-transition: all 4s ease;
    -ms-transition: all 4s ease;
    -o-transition: all 4s ease;    
}


The correct way is this, and it needs to be hammered home. The standard non-prefixed line has to be last.
.shootingStar
{
    -webkit-transition: all 4s ease;
    -moz-transition: all 4s ease;
    -ms-transition: all 4s ease;
    -o-transition: all 4s ease;
    transition: all 4s ease;
}

Editor's Note: The other prefixes are being included as examples. Some of them are no longer in use for the purposes of the transition property.

5. Doctype declaration

Web pages these days start out like this. Would you believe I sometimes miss out the very first line?!
!DOCTYPE html
<html>
    <head>
        <title>Test Title</title>
    </head>

    <body>
    </body>
</html>


It's easy to blame Sublime Text Editor, which I'm using, for this, since it doesn't include the doctype declaration as part of the shortcuts. However, like any professional, the buck stops with me. Missing the doctype potentially causes a whole host of problems that may cause the page to appear incorrectly. I won't go into those here; suffice to say, every time I catch myself doing this, any code I've written will need to be re-tested.

That's it!

There are a lot more code practices I should be picking up, but these should go a long way. Happy 2019, geeks!

echo strStandardSignOffMessage;
T___T