Saturday, 28 August 2021

App Review: Alto (Part 2/2)

Alto's Odyssey is a follow-up to Alto's Adventure. Everything remains pretty much the same except for the environment. There are some nifty new features to enjoy.


Sequels generally don't turn out better than the original, especially if they are so much alike. However, Alto's Odyssey gives the original some serious competition.

The Premise

This game has an identical premise to its predecessor. There are some new characters, but this is largely cosmetic because these characters have the same abilities as the old ones.


There are some new locations as well. These have different backgrounds and effects.



The Aesthetics

Beautiful as the last one, and there are tiny additional details that you may not see right away unless you look closely.

The Experience

Alto's Oddessey provided everything I loved about the original, and then some.

For real, it's like Alto's Adventure with a different environment and new tricks. Just about everything that made Alto's Adventure enjoyable, makes it into this one in some form or other.

The Interface

There's very little new about the interface, except for the rock walls, water pools and balloons. They add a new dimension to the physics of the game, as mastery of some of these new elements may be crucial to crossing chasms. In particular, balloons now allow you to do a quadruple flip!



What I liked

Everything I liked about the original.

Animated backgrounds. The layered backgrounds animate occasionally. Buildings crumble. Trees stir in the wind. Balloons drift. And it's all so subtle you don't catch it unless you look closely.


Share via email. The captions are cute!



The Feather has been replaced by a Lotus Flower, and it allows you to survive falls and break rocks! The crate drop feature is cool as well.



Birds of paradise. They are really awesome, you can have more than one, and they help you collect coins.



What I didn't

In the original, you are chased by nomads with whips. In the sequel, you are chased by lemurs. Doesn't really feel like an upgrade.


Some blocky visuals show up at times, and I don't really see a reason for them to exist.




The Wingsuit still sucks. Honestly, I can't see a compelling reason for it to exist. It does help in some cases, but the player can do just fine without it.

Conclusion

Noodlecake Studios achieved almost the impossible with this one. The original was good; the successor just wins by a hair. Play either, or both games. Revel in the artistry!

My Rating

9.5 / 10

Alto-gether excellent game,
T___T

Thursday, 26 August 2021

App Review: Alto (Part 1/2)

Once in a while, there comes a game with a simple premise but with such jaw-dropping excellence in implementation that I simply have to take some time out to showcase it. The Alto games, both Alto's Adventure and its successor Alto's Odyssey, are two such exalted examples from the labs of Canadian software company Noodlecake Studios.


The first app I'm going to review is Alto's Adventure, which was released in 2015.


While the concept was nothing new, it boasted such breathtaking visuals and audio that it caught the attention of gamers worldwide. And deservedly so. The screenshots I am about to show you are nothing short of exquisite. In fact, I would even go so far to say that my screenshots do not do the game justice.

The Premise

This game is what we call and "endless runner", where the avatar navigates obstacles across a landscape that is generated on the fly, and goes on as long as the player does not crash and burn. Along the way, points are accumulated and power-ups are collected, eventually resulting in exciting game features.


The titular Alto (or any other characters the player may choose to use) is a skier who is sliding down a limitless expanse of mountains.


During a game, there are varying objectives for a player to complete. Once all three objectives are met, the player goes up a level. At certain levels, certain options open up.


Coins are collected on the way, and spent on things such as items, increasing their power, skipping objectives or picking up where you last left off.


The Aesthetics

Did I say "breathtaking"? That's an understatement. The weather effects - falling rain, lightning strikes, billowing wind, springy sunshine, starry skies - are rendered so beautifully that every second you play the game feels like a visual reward.



And not just a visual reward either. The audio is a great accompanmient to the animation. With birds chirping and llamas bleating, along with the thumps of Alto hitting the hillsides and skidding over puddles, sound effects add greatly to the atmosphere. And then it's time to focus on the music. The music is near-perfection, ranging in pace from cheerful energy to pure serenity as Alto zips merrily along.


If you play this game without sound, you're doing yourself a huge disservice. The audio is almost half the experience.

The Experience

What can I say about this? It was amazing. At some point I stopped giving a damn about it being a game and just wanted to enjoy what Alto's Adventure was giving me.

The Interface

Swipe to jump. Tap and hold to execute flips, double flips and even triple flips. If in flight, tap and hold to glide. For a certain marsupial character, you can even tap mid-jump to double-jump! Like many "endless runners", the controls are simple. Alto's Adventure has more fancy tricks than the average endless runner, yet the controls are kept simple enough.

What I liked

The graphics, animation, sound and music all contribute to a stunning atmosphere - I have mentioned this much. In fact, this component of the game is so powerful that there is even a non-player mode where you can glide across the landscape without worrying about in-game survival.

Chases. Every once in a while, your character disturbs a nomad at rest and this provokes a chase which may end with your character beig cut down. This adds a certain amount of tension from an interesting source.


Power-ups. Most of the power-ups available in the Workshop are pretty neat. I especially like the Coin Magnet.


Multiple characters. As you level up in the game, you gradually unlock other characters that have different abilities, until you unlock the final character that has all their abilities!


Screenshots. Alto's Adventure has this neat little feature which allows you to take screenshots by pausing the game and accessing that feature. This is because the action is so fast-paced that trying to take screenshots the "normal" way would be almost impossible.


Zen Mode. If you don't want to deal with navigating obstacles or meeting objectives or collecting points; and just want to appreciate the gorgeous graphics and beautiful sound effects, Zen Mode allows you to do just that. If you crash and burn while "playing", your character automatically gets up and continues! This also doubles as a good practice mode.




What I didn't

There's almost nothing I didn't like about this game. Sure, it could get repetitive after a while, but that's par for the course for all endless runner games anyway.

If you consume a Feather, falling into a chasm doesn't "kill" you. You just hop right out and continue. This feels weird to me.

The Wingsuit. This is one of the special features of the game. Once you accumulate enough momentum, you are able to fly. It's probably just me, but I find it clumsy and I could totally do without this feature.




Conclusion

I spent countless hours playing Alto's Adventure, and none of it feels wasted. This is an absolute gem of a game due to the concept, implementation and loving attention to detail. The experience is phenomenal.

My Rating

9 / 10

Next

There's a sequel to this wonderful game. Watch for the next review!

Saturday, 21 August 2021

Ten Hilarious Tech-related Drake Memes

R&B artiste Drake is an icon - in more ways than one. His facial expressions have been turned into a hilarious series of memes that usually follow this format: a comical refusal followed by a comical approval. Usually really bad advice. And some of these memes fall under the tech category.

1. Programming jokes


Learn programming.

This one had me do a spit-take. It's so deliberately and delightfully ridiculous.

2. Getting a new job


Refactoring.

I felt a little personally attacked when I saw this one, not gonna lie.

3. Variable declaration

Variables.

The first panel is actually the recommended way. The second panel shows the long-winded and line-intensive way which is still syntactically correct but can be a pain in the ass to maintain.

4. Coding before thinking

Coding and thinking.

Well, dammit, the shade of it all. This is another newbie mistake given the Drake meme treatment.

5. What's more efficient?

10 minutes.

A programmer's natural instinct is to automate stuff... but sometimes we have to consider the context.

6. Using HTML

HTML.

This one is ridiculous to the point of being funny.

7. The For loop variables

More variables.

Imagine that the variables we're so used to using, aren't exactly best practice.

8. Tabs vs spaces

???

This one is creative and minimalist!

9. Recursion

Recursion.

Another creative meme.

10. Copy-paste

Coding.

Now this is not irony. Programmers are encouraged to do work using the least amount of effort... though overdoing the copy-pasting is a bad practice.

Phew!

These side-splitting memes are something else. And some of them hit very close to home for software devs.


DRakishly yours,
T___T

Sunday, 15 August 2021

Equal work, equal pay?

In recent years, American footballer and equality activist Megan Rapinoe made waves when she demanded equal pay for female athletes in soccer. This was controversial, as expected, but for me, an avid football fan, the issue was quite cut-and-dry.

You see, I'm all for equal rights. I believe that women can be equal to men in their endeavors. And I agree that the average female professional athlete would kick my aging butt in a game of football.

Unfortunately, I also believe in meritocracy - everybody gets paid according to the value they deliver. And Rapinoe's demands, in that context, made no logical sense.

Women in soccer.

Yes, the US Women's soccer team have won three Women's World Cup Finals, among other tournaments. They've accomplished a lot, and that's not up for debate. That said, it is also a fact that the Women's World Cup drew only a fraction of what the World Cup generated in terms of revenue.

Sports is a business first and foremost, pure and simple. Female athletes earning less probably isn't some kind of elaborate plot designed to keep women down. In order for women to earn as much as their male counterparts, the business of women's soccer would first have to bring in as much revenue. And in order for that to happen, women's soccer would have to enjoy the same popularity and exposure. And for that to happen, a lot more people would need to be interested in watching women's soccer in the first place. The FA can do better. But how much better is really a constraint of economics. Better equipment? Amenities? Access to training? Sure. But pay equal to that of the men? That would require substantially more funding. It's not something that can simply be conjured up out of nowhere.

The Programmer Equivalent of this situation

The Megan Rapinoe situation was brought to mind because recently, a friend remarked to me that someone with my years of experience should be earning a lot more than what I was currently drawing, because he had seen how much programmers were making in the bank that he was working in.

And it struck me as a very simplistic, very typical layperson thing to say.

When you examine this statement, it is extremely flawed on many levels. Years of experience does not mean the same thing in the software industry as it does in many other industries, mostly because the software industry changes so fast.

But the bigger point I wanted to make was this.

My company was not in the financial industry. Therefore, whatever value I provided to my company would not be anywhere on the scale of what a single programmer could bring to a bank, which probably rakes in at least ten times in revenue. And if remuneration is based mostly on the perceived value of what a professional brings to his or her organization, it stands to reason that I would not make as much as my counterparts in the banking industry.

And that was what came to mind when Megan Rapinoe's publicized efforts for equal pay were reported.

Conclusion

Higher pay for the sake of higher pay is nonsensical. The value has to be there. The same way women's soccer does not bring in as much revenue as men's soccer, an ICT Department in an average company does not deliver the same business value as that same department in a bank. And as such, that higher pay cannot be justified.

With many issues, I tie them to either football or software to form better analogies. But in this case, the parallels seem somewhat direct.

Show me the money!
T___T

Sunday, 8 August 2021

Spot The Bug: Twenty-twenty

Today's Spot The Bug session is about reports; specifically, report dates. We'll be going through some good old-fashioned PHP and examining a very common pitfall.

Let's get started,
bug-hunters!

There was a case last year in November where I had to supply an inventory report, containing an opening balance at a certain date, right in the header. As the bug revolves mostly around the header, I won't reproduce the entire code base here.
<body>
    <?php
        $reportDate = date("j M yy");
        $opening = date("1 M yy");
        $closing = date("1 M yy", strtotime($opening . " +1 months -1 days"));
    ?>

    <h1>Inventory Report</h1>
    <p>Report Generated On <?php echo $reportDate; ?></p>
    <hr />
    <table>
        <tr>
            <td width="50%" valign="top">
                Opening balance as at <?php echo $opening; ?>
            </td>

            <td width="50%" valign="top">
                <table>
                    <tr>
                        <td width="30%">
                            <b>Attn:</b>
                        </td>

                        <td width="70%">
                             Agnes Ng
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <b>Contact:</b>
                        </td>

                        <td>
                             11225566
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <b>Designation:</b>
                        </td>

                        <td>
                             Account Manager
                        </td>
                    </tr>
                </table>
            </td>
        </tr>
    </table>


This was what it produced, which looked correct.


What went wrong

However, at the end of January, users alerted me to an anomaly in the report. This was freaky. An opening balance set at a hundred years from now?!


Why it went wrong

This was an issue with the date format I had used - most notably, the year. My intention had been to use a four-digit year, but I had been using "yy". "y" produces "20" if the current year is 2020. So "yy" naturally produced "2020".

However, in 2021, it would produce 2121!


How I fixed it

All I needed to do was this.
<?php
    $reportDate = date("j M Y");
    $opening = date("1 M Y");
    $closing = date("1 M Y", strtotime($opening . " +1 months -1 days"));
?>


And there it was!


Moral of the story

All this could have been avoided if I had tested more robustly - instead of just testing a month ahead, it would have been better to test a couple years into the future. Normally, the most insidious bugs don't show up as a compilation error, or even right away. Today's example was one such bug.

I guess hindsight is always 20-20!
T___T

Wednesday, 4 August 2021

Web Tutorial: The Encryptor/Decryptor

Sometimes code projects can be whimsical. Years ago, I was watching this Gerard Butler movie, Geostorm. It was utterly forgettable overall, but one part inspired me to go home and begin coding.

Butler's character was exchanging secret messages with his onscreen brother. There would be a block of text, and a cipher key consisting of a series of numbers. Using the key, words were extracted from the block of text to form the secret message. A simple idea, and one which I was itching to try out in code.

And today, we are going to do just that. This will be in PHP, with a HTML interface.

How it works

For example, take this wall of text.
Worshippers beware. In the book of Revelations, praise the Lord. Each day is a new opportunity. Day or night, we must be on guard and watch our every move.


And this key.
243645

 
The first number is 2, so take the 2nd word from the wall of text.
Worshippers beware. In the book of Revelations, praise the Lord. Each day is a new opportunity. Day or night, we must be on guard and watch our every move.


beware.


The next number is 4, so take the 4th word from the wall of text, counting from the last word picked.
Worshippers beware. In the book of Revelations, praise the Lord. Each day is a new opportunity. Day or night, we must be on guard and watch our every move.


beware. of


And so on, and so for. The entire message says "beware. of the new night, guard". Basically, beware of the new night guard.
Worshippers beware. In the book of Revelations, praise the Lord. Each day is a new opportunity. Day or night, we must be on guard and watch our every move.


So now you know how it works. Let's get to coding!

Here's some HTML.
<!DOCTYPE html>
<html>
    <head>
        <title>The Encryptor/Decryptor</title>
        <script>

        </script>
    </head>

    <body>

    </body>
</html>


Create the JavaScript function, decrypt(). Leave it alone for now.
<script>
    function decrypt()
    {

    }
</script>


In the HTML body, we need a placeholder div to display decrypted text. The id will be txtMessageDecrypt. We'll also need a form. The form won't be used right away, but its contents will be.
<body>
    <div id="txtMessageDecrypt">

    </div>

    <form method="POST" action="index.php">

    </form>

</body>


Within the form, we need some more elements. First, a textarea tag to type the message in. Then a text input to enter in the key. And finally, a button to decrypt. It will call the decrypt() function.
<form method="POST" action="index.php">
    <textarea id="txtMessageEncrypt" name="txtMessageEncrypt" rows="15" cols="100"></textarea>

    <br /><br />

    Key:
    <input id="txtKey" name="txtKey" value="">

    <br /><br />

    <input type="button" onclick="decrypt();" value="Decrypt">

</form>


And here's the result.


Now we will focus on the decrypt() function. We first declare variables txtEnc, txtDec and txtKey, assigning to them the elements referenced by txtMessageEncrypt, txtMessageDecrypt and txtKey.
<script>
    function decrypt()
    {
        var txtEnc = document.getElementById("txtMessageEncrypt");
        var txtDec = document.getElementById("txtMessageDecrypt");
        var txtKey = document.getElementById("txtKey");

    }
</script>


We now derive words from txtEnc. It will be an array of all the words in txtEnc's text content.
function decrypt()
{
    var txtEnc = document.getElementById("txtMessageEncrypt");
    var txtDec = document.getElementById("txtMessageDecrypt");
    var txtKey = document.getElementById("txtKey");

    var words = txtEnc.value.split(" ");
}


We derive decryptor the same way, from txtKey. This will be an array of all the characters in txtKey's text value.
function decrypt()
{
    var txtEnc = document.getElementById("txtMessageEncrypt");
    var txtDec = document.getElementById("txtMessageDecrypt");
    var txtKey = document.getElementById("txtKey");

    var words = txtEnc.value.split(" ");
    var decryptor = txtKey.value.split("");
}


And then we define a variable, encrytable, and set it to true by default. This will determine if the values of words and decryptor are suitable for decryption.
function decrypt()
{
    var txtEnc = document.getElementById("txtMessageEncrypt");
    var txtDec = document.getElementById("txtMessageDecrypt");
    var txtKey = document.getElementById("txtKey");

    var words = txtEnc.value.split(" ");
    var decryptor = txtKey.value.split("");
    var encryptable = true;
}


Now if either words or decryptor are empty, encryptable is false. Because we can't encrypt an empty string or use an empty key to encrypt.
var words = txtEnc.value.split(" ");
var decryptor = txtKey.value.split("");
var encryptable = true;

if (words.length == 0 || decryptor.length == 0)
{
    encryptable = false;
}



Then we check for lengths. If decryptor has more characters than words has words, then encryption is impossible as well.
if (words.length == 0 || decryptor.length == 0)
{
    encryptable = false;
}
else
{
    if (decryptor.length > words.length) encryptable = false;
}


Finally, we run through each of decryptor's characters. If any of the characters are 0 or not a number, encryptable is false. This means that decryptor must be made of numbers greater than 0.
if (words.length == 0 || decryptor.length == 0)
{
    encryptable = false;
}
else
{
    if (decryptor.length > words.length) encryptable = false;

    for (var i = 0; i < decryptor.length; i++)
    {
        if (decryptor[i] == "0" || isNaN(decryptor[i])) encryptable = false;
    }

}


Now, declare a variable, decrypted.
if (words.length == 0 || decryptor.length == 0)
{
    encryptable = false;
}
else
{
    if (decryptor.length > words.length) encryptable = false;

    for (var i = 0; i < decryptor.length; i++)
    {
        if (decryptor[i] == "0" || isNaN(decryptor[i])) encryptable = false;
    }
}

var decrypted = "";


The next step will be performed only if encryptable is true.
var decrypted = "";

if (encryptable)
{

}


Inside this If block, we declare ptr and set it to -1. Now we iterate through each element in decryptor.
var decrypted = "";

if (encryptable)
{
    var ptr = -1;

    for (var i = 0; i < decryptor.length; i++)
    {

    }

}


In the For loop, increment ptr by the value of the current element. And then grab the word by using ptr to reference the array words, and add the word to the string decrypted, along with a space.
var decrypted = "";

if (encryptable)
{
    var ptr = -1;

    for (var i = 0; i < decryptor.length; i++)
    {
        ptr += parseInt(decryptor[i]);
        decrypted += words[ptr] + " ";

    }
}


And finally, place the entire string decrypted inside txtDec.
var decrypted = "";

if (encryptable)
{
    var ptr = -1;

    for (var i = 0; i < decryptor.length; i++)
    {
        ptr += parseInt(decryptor[i]);
        decrypted += words[ptr] + " ";
    }

    txtDec.innerHTML = decrypted;
}


Let's try the example I gave earlier.


Now use this key.


Click Decrypt, and this happens!


More functionality

To add more value to this little application, let's go ahead and add en email feature. We already have a form tag, so let us add an email field and button.
<form method="POST" action="index.php">
    <textarea id="txtMessageEncrypt" name="txtMessageEncrypt" rows="15" cols="100"><?php echo $message;?></textarea>

    <br /><br />

    Key:
    <input id="txtKey" name="txtKey" value="">

    <br /><br />

    <input type="button" onclick="decrypt();" value="Decrypt">

    <br /><br />

    <input id="txtEmail" name="txtEmail" value="">
    <input type="submit" value="Send Message">

</form>


Here's how it should look. It's not pretty, but it's functional.


What we now need is PHP to handle the data. We first check if txtEmail has been sent by POST.
<body>
    <?php
        if (isset($_POST["txtEmail"]))
        {

        }
    ?>

    <div id="txtMessageDecrypt">

    </div>


If so, we create a message incorporating the encrypted message in a link.
<body>
    <?php
        if (isset($_POST["txtEmail"]))
        {
            $html = "You have received a message.\n";
            $html .= "Visit the link http://localhost/encryptor/index.php?message=". urlencode($_POST["txtMessageEncrypt"]) ." to decrypt it ";
            $html .= "using your decryptor key.\n";

        }
    ?>
    <div id="txtMessageDecrypt">

    </div>


Here, we create a subject and send the email.
<body>
    <?php
        if (isset($_POST["txtEmail"]))
        {
            $html = "You have received a message.\n";
            $html .= "Visit the link http://www.teochewthunder.com/demo/encryptor/index.php?message=". urlencode($_POST["txtMessageEncrypt"]) ." to decrypt it ";
            $html .= "using your decryptor key.\n";

            $subject = "This is a Decryptor test";

            if (mail ($_POST["txtEmail"], $subject , $html))
            {
                echo "Mail sent";
            }

        }
    ?>
    <div id="txtMessageDecrypt">

    </div>






Depending on your local server email settings, you should receive an email. There's a link in that email that incorporates the encrypted message.


Let's add a bit of code at the start of the PHP block to handle this. Declare the variable, message. Then set message to get the value from the URL if it exists.
<?php
    $message = "";

    if (isset($_GET["message"]))
    {
        $message = $_GET["message"];
    }


    if (isset($_POST["txtEmail"]))
    {


Then insert this into the textbox.
<textarea id="txtMessageEncrypt" name="txtMessageEncrypt" rows="15" cols="100"><?php echo $message;?></textarea>


Now when you click that link, the message is displayed! And you can then proceed to decrypt it using your key.


Final words

This isn't the best way to implement it, of course. In some areas, it's actually rather clumsy. But you can definitely see the idea behind it, and sometimes that's what happens. Feel free to implement your own encryption!

Cryptically yours,
T___T