Wednesday 26 February 2020

Five Good Questions To Ask Interviewers

During a job interview, candidates are often required to ask questions of the interviewer. The lack of intelligent questions, to the interviewer, indicates a lack of either interest or critical thinking.

And they're absolutely right.

Therefore, here are some questions to ask during a tech interview; indeed, any job interview. You may notice that all of these questions are pretty much the same questions that interviewers seem to love asking candidates. That's not a coincidence, and believe it or not, no, I'm not being facetious. Those are some pretty relevant questions a candidate needs to ask.

Also, if an interviewer is asking the candidate these questions, it stands to reason that they think these questions are important and as such, they shouldn't mind having these questions turned back on them. Turn-about is fair play, right?

1. Where do you see this company in five years?

It's a question best asked of someone in Management. Perhaps the CEO or COO. The question is also better phrased as "what is the general plan for this company in five years to ten years?"

This, obviously, is an inversion performed on the classic interview question "where do you see yourself in five years?" The only difference is, it's far more important for a company to be able to answer that question, than it is for an employee.

You see, it's not that vital for an employee to have a career plan. Plenty of programming jobs can be done quite ably by people who don't have a career plan. To some people, a job is simply just a job, and they'll look at you funny if you suggest that they ought to have some kind of plan in mind.

What's the direction?

But that's one employee. A company is made out of multiple employees. It's still feasible for all these employees to not have career plans (though I would not recommend it), but what if the company itself doesn't have a plan? What if the company has no idea what general direction it's heading towards? You can learn a lot about the company you may be joining, by learning more about its plans. Or lack thereof.

2. Why did the last employee leave?

This is an inversion of "why do you intend to leave your company?". It really depends on whether you're filling a vacancy. If so, it's a question that HR needs to answer. Try to couch it in more neutral language such as "What was the official reason my predecessor provided for leaving?"

The answer isn't the important thing here - the purpose of this question is to observe HR to see how she (pardon me, I've never encountered male HR for some reason) answers it. Is she evasive or defensive? Forthright? Reeking of politically correct bullshit? Or worse, is she somehow surprised that you would dare ask this question?

Why'd your predecessor leave?

This tells you the nature of HR in the workplace. Are they Management flunkies, or actual professional HR personnel? You'll want to be prepared either way.

Also, it's a penetrating question that shows your interviewer that you're already thinking about what it would be like to work there. It shows that you want to be aware of any potential pitfalls you may encounter in the workplace.

3. What are some of the challenges you face at the job?

This one is best posed to a potential co-worker, if one happens to be at the interview. If not, the Hiring Manager should have a fairly good idea as well. It's a inverted version of the question some interviewers may ask of you, to gauge your problem-solving ability.

What are the obstacles?

However, the purpose here is not to gauge the ability of your potential co-worker, but to find out what you will be dealing with at the workplace. The line of questioning may yield some interesting answers. Some companies deal in big data and their main challenges involve data visualization. Some have security issues. Some may encounter difficulties explaining concepts to non-technical people.

More importantly, it expresses a desire to be useful. By asking this question, it gives the impression that you're thinking about how best you can contribute. It's indirectly asking the company what you can do for them, instead of what they can do for you. In fact, if asked why you asked this question (not that I think anyone with a single working brain cell needs this to be explained), you should totally tell them this.

4. Why should I join this company?

Now this is a reversal of the classic question "Why should I hire you?" or "Tell me your greatest strength." With those questions, the interviewer wants you to sell yourself.

Asking that question back in turn may be fair, but it can also come off as unreasonably arrogant depending on how it's phrased. This can be asked of a Manager. "What are some of the biggest selling points of being employed by this company?" This gives them a chance to brag, and despite what they tell you, people love to brag.

Why should I join your team?

Now, if this is asked of a potential co-worker, consider asking this instead - "Personally, from your point of view, what are some of the best things about this company?" Not only are you asking him or her for a personal opinion, it also implies that you're interested in him or her as an individual. People take to that.

5. Why should I not join this company?

And of course, naturally, we'd have to explore the reverse of the last question - "Tell me your weaknesses." The purpose of this question, again, varies depending who is asking that question, and who is being asked. Interviewers who ask this question usually want to see if the candidate has both self-awareness and honesty. But when you ask the interviewer this question, you may want to phrase it like this - "What are some of the reasons I might not want to join this company?"

What's the catch?

Look, it's a no-bullshit question. Everyone is aware that nothing is perfect. Therefore, anyone asking this question of the interviewer is most likely sincerely trying to manage his or her own expectations. And honestly, only candidates who are somewhat serious (or at least trying to give that impression) about their application would ask this.

Also, asking this question gives you a chance to brag, in turn. For example, if the interviewer says something along the lines of "sometimes deadlines need to be met and there's OT", this is the perfect opportunity to shrug and give a "been there, done that" response.

All in all...

These are fair questions to ask, especially since interviewers consider them fair questions to ask you. Be respectful and professional, but ask those questions.

Any questions?
T___T

Friday 21 February 2020

POFMA saves the day... kind of

It was with a touch of grim schadenfreude that I read the news that Facebook had, in its own words, been "compelled by law" to block the fake news peddler States Times Review (STR).

I've never been a huge proponent of the Protection from Online Falsehoods and Manipulation Act (POFMA). Back in 2019 when it was first introduced, I expressed concerns that it would give the Singapore Government too much discretionary power to censor. But I was also painfully conscious of the fact that as much as some Singaporeans demand full transparency and Freedom of Speech from the Government, they've also done precious little to show that they can handle those things with the responsibility and gravitas they deserve.

In the long run, without being exposed to such poisonous elements of society, how can Singaporeans learn to discern for themselves instead of outsourcing their thinking to the Government? Is that any way forward for a First World Nation?

Fast-forward almost a year later, amid the panic-causing outbreak of the Wuhan Coronavirus, newly christened COVID-19. A Facebook page States Times Review posted fake news on several different occasions. Here are some of its ludicrous claims:

The Government is unable to trace the source of infection for any of the infected COVID-19 cases in Singapore. Nonsense.

Singapore has run out of surgical masks. Minister Chan Chun Sing has debunked this clearly (and entertainingly) enough.

Seven countries have since banned travel to Singapore, citing lack of confidence in the Singapore government's public health measures. Lack of confidence? Oh, really?

(all these claims, and more, may be more thoroughly debunked on Factually.)

All this was designed to sow public panic and confusion, distrust towards the Government and undermine confidence in Singapore while drawing as many eyeballs to the website as possible.

The POFMA hammer.

The Singapore Government stepped in, wielding POFMA as a hammer, to strike at STR, ordering STR to issue correction notices to the false articles. When STR failed to comply, POFMA was again used, this time on Facebook itself, to block Facebook user access to STR. Now STR is an Online Declared Location, and may no longer be accessed within Singapore.

I may still have my reservations regarding POFMA... but it could not have happened to a more deserving site than STR. As a citizen of Singapore; indeed, as a human being, I find the opportunistic act of capitalizing on the fear of death amid a very real and very delicate situation, both irresponsible and morally repugnant.

Did the Singapore Government do the right thing?

What is the right thing, in this instance? Let things stand, and trust Singaporeans to be able to hold steady instead of being misled by charlatans? Or act swiftly to stifle malicious rumor-mongering that could spell disaster if fear spreads?

I know what I'd prefer. STR is not a site I visit often, if at all, for obvious reasons. Calling it a "joke" would be charity because satire actually has value. STR is not satire and has no place in a society that wants to be governed by reason. And therefore, in a perfect world, no one would pay that stinking heap of rubbish any mind, thus removing the necessity behind the Singapore Government's recent actions. In a perfect society, people would be free to choose, and they would choose to give STR the attention it deserves - none.

Time running out.

But this isn't a perfect world, or a perfect society. Far from it. And this is not a situation that allows for a generous margin of error. This is a situation which is dangerously fluid, where time is a luxury.

The Singapore Government did not do the "right" thing. They did the only thing they could, with whatever tools they had. I don't like it, but perhaps it's for the best.

Facebook's reaction

Facebook did not seem altogether too happy about this. In a statement to BBC, their spokesperson said the following.
"We believe orders like this are disproportionate and contradict the government's claim that POFMA would not be used as a censorship tool."

"We've repeatedly highlighted this law's potential for overreach and we're deeply concerned about the precedent this sets for the stifling of freedom of expression in Singapore."


Facebook doesn't have to like Singapore's laws - I'm not sure I'm entirely fond of them myself sometimes - but if Facebook wants to continue operating within Singapore, it is going to have to comply with those laws. There is no room for negotiation there. And if they're not willing to do so, they should feel free to adhere to those fancy principles of Freedom of Expression and forego the 5 million or so user accounts on this island. It's a pittance compared to the few billion they've already foregone in China.

Respect our laws.

I use Facebook without paying a cent. I'm on board with Facebook hitting me with advertisements, monitoring my conversations or tracking whatever I do on their platform. I consider it a fair trade. But I draw a hard, firm line at Facebook having the audacity to even suggest that our laws are wrong.

Whether they are or not, isn't the point. Facebook can shove its "deep concern" somewhere dark and hopefully painful. Facebook is a Social Media platform, and as much as I respect Facebook as a tech entity, the issue of Singapore's "freedom of expression" is something to be decided by Singaporeans, not Americans. Americans may be in love with their own brand of Democracy despite (or because of) the fact that it produced the relentless source of hilarity that is President Donald Trump. They may not have the imagination required to even comprehend how entire societies can choose not to live the way Americans do.

But that's their fucking problem.

Know your place, Facebook.

Just another slow (fake) news day,
T___T

Monday 17 February 2020

App Review: Paper Wings

Chirpy, cheery and fun - these are just a few of the adjectives that can be used to describe Paper Wings, a mobile game from Fil Games that's billed as an environmentally-conscious time-filler.



Paper Wings is a basic left-and-right console game with very pretty effects centered around origami avians on origami landscapes; hence the name. Other than that, there's really nothing much else to say about it - gameplay is really that basic.

The Premise

You control a bird. The bird flies around, avoiding obstacles and collecting enough points to advance to the next level, where things get even more dangerous.



The Aesthetics

Paper Wings is a thing of beauty, and I don't say this lightly. Everything is rendered in brilliant color and surreal landscapes. And the truly awesome thing is the simplicity of the entire setup. For a game so basic, it certainly is well put-together.

The Experience

It's tranquil, its cute, and it's light-hearted gaming activity unless you take it too seriously. Paper Wings is as much about ecological awareness as it is about scoring points.

The Interface

Left, right. Tap and hold to rotate. Release to let the bird glide using momentum. Takes a while to get the hang of.

What I liked

I could spend all day raving about the sheer aesthetic beauty of this game. Suffice to say, Paper Wings has gorgeous backdrops and simple but breathtaking animation. The movement of the birds can be stiff, but that can easily be excused by the fact that they are supposed to be paper birds. However, the way they're able to glide through the screen is just magical in its implementation.


The sound effects - birds chirping and cawing, wings flapping, wind whooshing, water splashing - are masterfully done. It has an incredibly calming effect. There's even a challenge based upon sound effects! And speaking of that...

Challenges are creatively done. Some of them require you to explore the interface and discover winning combinations.



The variety of the birds on offer is mind-boggling. Once you accumulate a certain number of coins or achieve a certain number of tasks, you unlock a new bird. My personal favorite is the crow.

Little touches. The interface interacts with you in unexpected ways. It's really cute.

There's even plenty of ecological commentary within the app, along with how we can preserve avian wildlife. A mobile game with a climate conscience! Wow!

What I didn't


Emotional manipulation. The bird says it's missed you if you're gone long enough. Nice touch, I guess, but I really could do without the feels, dammit!

Video-watching is thankfully optional if you don't mind missing out on certain birds and replaying levels from scratch. Because their videos are pretty grating.

Having the entire level lost as soon as even one coin sinks into the water seems to me a bit too draconian. This pretty much means constant replaying of levels (and video-watching if you don't want to replay a level) if you get a less-than-perfect score.


There's a bonus associated with catching a coin immediately after it descends from the sky, but it's annoyingly inconsistent as to how it's implemented. Sometimes you need to catch it as soon as it appears, other times you can get away with a one-second delay.

Conclusion

This is a great app to have in your phone if you like a nice soothing distraction. Gameplay isn't groundbreaking exactly, but you'll get plenty of kicks out of this nonetheless. Paper Wings is a little more than your run-of-the-mill mobile game - it's a labor of love, and it shows.

My Rating

6.5 / 10

This game is totally fly!
T___T

Wednesday 12 February 2020

Web Tutorial: Tic-tac-toe, Valentine's Day Edition (Part 2/2)

The current level of intelligence used in this JavaScript game is zero. The computer takes its turn with no strategy whatsoever, only using available squares and a random number as criteria. We can leverage on the existing code to determine strategic actions for the computer to take, rather than random ones.

In the game object, at the getFinalSquare() method. It should accept a parameter string, mark.
getRandomBlankSquare: function()
{
    var blanks = this.board.filter(function (x) {return x.value == ""});

    if (blanks.length == 0)
    {
        return null;
    }
    else
    {
        var rand = this.getRandomNumber(0, blanks.length - 1);
        return this.board.findIndex(function (x) {return x.square == blanks[rand].square});   
    }
},
getFinalSquare: function(mark)
{

},
nextTurn: function()
{
    this.turn =  (this.turn == "x" ? "o" : "x");
    user.myTurn();
},


It will return null by default; meaning, no strategic path was found. As in the getRandomBlankSquare() method, you first check for blank squares. If there are no blank squares, return null. If there are, also possibly return null.
getFinalSquare: function(mark)
{
    var blanks = this.board.filter(function (x) {return x.value == ""});

    if (blanks.length == 0)
    {
        return null;
    }
    else
    {
        return null;
    }
},


If there are blank squares, that means there is a possibility that a strategic path can be found. For this, we need to iterate through the game object's winPatterns array.
getFinalSquare: function(mark)
{
    var blanks = this.board.filter(function (x) {return x.value == ""});

    if (blanks.length == 0)
    {
        return null;
    }
    else
    {
        for (var i = 0; i < this.winPatterns.length; i++)
        {

        }

        return null;
    }
},


Within the For loop, define three variables; marked, filled - both set to 0 - and finalSquare, which is null by default.
getFinalSquare: function(mark)
{
    var blanks = this.board.filter(function (x) {return x.value == ""});

    if (blanks.length == 0)
    {
        return null;
    }
    else
    {
        for (var i = 0; i < this.winPatterns.length; i++)
        {
            var marked = 0;
            var filled = 0;
            var finalSquare = null;
        }

        return null;
    }
},


Iterate through each element of the winPatterns array. Because each element is in turn an array of three elements, remember? If the value is occupied by mark, increment marked. If the value is occupied (whether by mark or anything else), increment filled. If the value is blank, set finalSquare to the value of that square.
getFinalSquare: function(mark)
{
    var blanks = this.board.filter(function (x) {return x.value == ""});

    if (blanks.length == 0)
    {
        return null;
    }
    else
    {
        for (var i = 0; i < this.winPatterns.length; i++)
        {
            var marked = 0;
            var filled = 0;
            var finalSquare = null;

            for (j = 0; j < 3; j++)
            {
                if (this.winPatterns[i][j].value == mark) marked++;
                if (this.winPatterns[i][j].value != "") filled++;
                if (this.winPatterns[i][j].value == "") finalSquare = this.winPatterns[i][j].square;
            }
        }

        return null;
    }
},


Now outside of the inner For loop and in the main For loop, check if marked is 2 and filled is less than 3. That basically means that for that winPattern, two slots have been filled by the user's mark and one remains for victory. Once that's established, there's no need to search further - just return the index value of that square.

If no strategic paths are found at all, the method will return null.
getFinalSquare: function(mark)
{
    var blanks = this.board.filter(function (x) {return x.value == ""});

    if (blanks.length == 0)
    {
        return null;
    }
    else
    {
        for (var i = 0; i < this.winPatterns.length; i++)
        {
            var marked = 0;
            var filled = 0;
            var finalSquare = null;

            for (j = 0; j < 3; j++)
            {
                if (this.winPatterns[i][j].value == mark) marked++;
                if (this.winPatterns[i][j].value != "") filled++;
                if (this.winPatterns[i][j].value == "") finalSquare = this.winPatterns[i][j].square;
            }

            if (marked == 2 && filled < 3)
            {
                return this.board.findIndex(function (x) {return x.square == finalSquare});
            }
        }

        return null;
    }
},


Let's get back to the myTurn() method of the user object. We need to expand on this. Basically, before attempting to get a random blank square, the program must first check for a strategic move that can be made.

First, run the getFinalSquare() method using "x" as the argument. This searches for a "win" scenario where "x" would win the game. That's the first priority. If no "win" scenario exists, the next thing to do is try to stop user "o" from winning. So again, we run the getFinalSquare() method, but this time using "o" as an argument. And if there is no "win: scenario for "o" either, just use a random blank square.
setTimeout
(
    function()
    {
        var finalSquare = game.getFinalSquare("x");

        if (finalSquare == null)
        {
            finalSquare = game.getFinalSquare("o");

            if (finalSquare == null)
            {
                var randomBlankSquare = game.getRandomBlankSquare();

                if (randomBlankSquare == null)
                {
                    document.getElementById("user_" + game.turn + "_face").innerHTML = "&#9786";
                    document.getElementById("user_" + game.turn + "_text").innerHTML = "Looks like it's a draw.";
                    game.started = false;
                }
                else
                {
                    user.takeTurn(randomBlankSquare, "x");
                }
            }
        }
    },
    1000
)


Here, we set the Else blocks to take the appropriate turn if there are strategic paths to take.
setTimeout
(
    function()
    {
        var finalSquare = game.getFinalSquare("x");

        if (finalSquare == null)
        {
            finalSquare = game.getFinalSquare("o");

            if (finalSquare == null)
            {
                var randomBlankSquare = game.getRandomBlankSquare();

                if (randomBlankSquare == null)
                {
                    document.getElementById("user_" + game.turn + "_face").innerHTML = "&#9786";
                    document.getElementById("user_" + game.turn + "_text").innerHTML = "Looks like it's a draw.";
                    game.started = false;
                }
                else
                {
                    user.takeTurn(randomBlankSquare, "x");
                }
            }
            else
            {
                user.takeTurn(finalSquare, "x");
            }
        }
        else
        {
            user.takeTurn(finalSquare, "x");
        }
    },
    1000
)


Now try this! If you place your token in the center of the board hoping to get a diagonal row...


...this should happen!


And if you try to get a row while the computer is already on its way there...


...this happens!


Happy Valentine's Day, you crazy couples!

Celebrate love, romance, and all that shit. Just seize the day and enjoy the memories!

X my ♥,
T___T

Monday 10 February 2020

Web Tutorial: Tic-tac-toe, Valentine's Day Edition (Part 1/2)

Can you feel the romance in the air?! Valentine's Day is coming up, lovebirds, and boy do I have the web tutorial for you today.

Remember last year when we made this neat little Tic-tac-toe game in HTML, CSS and JavaScript? Today, we will revisit it and improve on it. We'll first put a little Valentine's Day theme to it, and then we'll even implement some artificial intelligence. All while leveraging on the existing code.

First, the colors!

It wouldn't be Valentine's Day if we didn't saturate this sucker with unhealthy doses of red and pink, so let's make some changes to the CSS. That's right... turn the buttons red, and pink on a mouseover.
.btn
{
    font-family: "Comic Sans MS";
    font-size: 2em;
    display: block;
    width: 5em;
    height: 1.5em;
    border-radius: 0.5em;
    border: 0px solid;
    background-color: #FF0000;
    color: #FFFFFF;
    margin: 1em auto 0 auto;
}

.btn:hover
{
    background-color: #FF8888;
    color: #FF0000;
}

A good beginning...

The board needs a little makeover as well. Lighter shades of pink should do it.
#board_container
{
    width: 330px;
    height: 330px;
    margin: 0 auto 0 auto;
    background-color: #FFCCCC;
    padding-top: 10px;
    padding-left: 10px;
}

.square
{
    width: 100px;
    height: 100px;
    margin-right: 10px;
    margin-bottom: 10px;
    float: left;
    outline: 1px solid #FFEEEE;
    background-color: #FFFFFF;
    cursor: pointer;
}


And for good measure, we'll even change the colors of the users.
#user_x_container
{
    color: #FF0000;
}

#user_o_container
{
    color: #FF8888;
}


Ooh, sappy, eh?


Now, for the tokens, instead of "X" and "O", we'll use hearts of a different color.
{
    content: "\2764";
    color: #FF0000;
}

.o:before
{
    content: "\2764";
    color: #FF8888;
}


And upon winning, this shade of pink should contrast nicely with both colors!
.win
{
    background-color: #FF4444;
}


Here you go.


Oh hey, here's a cheeky touch.
<h1>Let's play Tic-tac-t&hearts;e!</h1>


Damn, so cheesy.


Some randomly fun features

I think it's a wee bit boring to have the facial expressions unchanged, and the messages look repetitive. So let's introduce a random element into all this.

Add the getRandomRemark() method into the game object.
var game =
{
    turn: "x",
    started: true,
    board:
    [
        {square: "a", value: ""},
        {square: "b", value: ""},
        {square: "c", value: ""},
        {square: "d", value: ""},
        {square: "e", value: ""},
        {square: "f", value: ""},
        {square: "g", value: ""},
        {square: "h", value: ""},
        {square: "i", value: ""}
    ],
    winPatterns:
    [
        [
            {square: "a", value: ""},
            {square: "b", value: ""},
            {square: "c", value: ""}
        ],
        [
            {square: "a", value: ""},
            {square: "d", value: ""},
            {square: "g", value: ""}
        ],
        [
            {square: "a", value: ""},
            {square: "e", value: ""},
            {square: "i", value: ""}
        ],
        [
            {square: "b", value: ""},
            {square: "e", value: ""},
            {square: "h", value: ""}
        ],
        [
            {square: "c", value: ""},
            {square: "f", value: ""},
            {square: "i", value: ""}
        ],
        [
            {square: "c", value: ""},
            {square: "e", value: ""},
            {square: "g", value: ""}
        ],
        [
            {square: "d", value: ""},
            {square: "e", value: ""},
            {square: "f", value: ""}
        ],
        [
            {square: "g", value: ""},
            {square: "h", value: ""},
            {square: "i", value: ""}
        ]
    ],
    init: function()
    {
        this.started = true;
        this.turn = "x";

        for (var i = 0; i < this.board.length; i++)
        {
            this.board[i].value = "";
        }

        for (var i = 0; i < this.winPatterns.length; i++)
        {
            for (var j = 0; j < this.winPatterns[i].length; j++)
            {
                this.winPatterns[i][j].value = "";
            }
        }

        for (var i = 0; i < 9; i++)
        {
            document.getElementById("square" + i).className = "square";
        }

        var texts = document.getElementsByClassName("text");
        for (var i = 0; i < texts.length; i++)
        {
            texts.innerHTML = "";
        }
    },
    play: function()
    {
        document.getElementById("btnPlay").style.display = "none";
        document.getElementById("btnQuit").style.display = "block";
        document.getElementById("game_container").style.display = "block";
        this.nextTurn();
    },
    quit: function()
    {
        this.init();
        document.getElementById("btnQuit").style.display = "none";
        document.getElementById("btnPlay").style.display = "block";
        document.getElementById("game_container").style.display = "none";
    },
    getRandomNumber: function(min, max)
    {
        return Math.floor((Math.random() * (min - max + 1)) + max);
    },
    getRandomBlankSquare: function()
    {
        var blanks = this.board.filter(function (x) {return x.value == ""});

        if (blanks.length == 0)
        {
            return null;
        }
        else
        {
            var rand = this.getRandomNumber(0, blanks.length - 1);
            return this.board.findIndex(function (x) {return x.square == blanks[rand].square});   
        }
    },
    nextTurn: function()
    {
        this.turn =  (this.turn == "x" ? "o" : "x");
        user.myTurn();
    },
    checkWin: function(mark)
    {
        var findSquare;

        for (var i = 0; i < this.winPatterns.length; i++)
        {
            if (this.winPatterns[i][0].value == mark && this.winPatterns[i][1].value == mark && this.winPatterns[i][2].value == mark)
            {
                for (var j = 0; j < 3; j++)
                {
                    var winPatterns = this.winPatterns;
                    findSquare = this.board.findIndex(function (x) {return x.square == winPatterns[i][j].square});
                    document.getElementById("square" + findSquare).className += " win";                           
                }

                return true;
            }   
        }

        return false;
    },
    getRandomRemark: function()
    {
   
    }               
}


Define a variable, remarks. It will be an array of objects.
getRandomRemark: function()
{
    var remarks =
    [

    ];
}   


Each object has the properties face and text. face is the HTML symbol used to accompany the remark, and text is the remark itself.
getRandomRemark: function()
{
    var remarks =
    [
        {face: "&#9786;", text: "Looks like it's my turn!"},
        {face: "&#9854;", text: "Brilliant move!"},
        {face: "&#9785;", text: "Ouch!"},
        {face: "&#9863;", text: "Um..."},
        {face: "&#9863;", text: "This is hard."},
        {face: "&#9786;", text: "Let me see..."},
        {face: "&#9786;", text: "I'm gonna cream you!"},
        {face: "&#9786;", text: "Ooh. Nice."}
    ];
}


Grab a random number using the existing getRandomNo() method, passing in 0 and the length of remarks minus 1, as arguments, and assign the value to the variable rand.
getRandomRemark: function()
{
    var remarks =
    [
        {face: "&#9786;", text: "Looks like it's my turn!"},
        {face: "&#9854;", text: "Brilliant move!"},
        {face: "&#9785;", text: "Ouch!"},
        {face: "&#9863;", text: "Um..."},
        {face: "&#9863;", text: "This is hard."},
        {face: "&#9786;", text: "Let me see..."},
        {face: "&#9786;", text: "I'm gonna cream you!"},
        {face: "&#9786;", text: "Ooh. Nice."}
    ];

    var rand = this.getRandomNumber(0, remarks.length - 1);
}


Finally, return the array element pointed to by rand.
getRandomRemark: function()
{
    var remarks =
    [
        {face: "&#9786;", text: "Looks like it's my turn!"},
        {face: "&#9854;", text: "Brilliant move!"},
        {face: "&#9785;", text: "Ouch!"},
        {face: "&#9863;", text: "Um..."},
        {face: "&#9863;", text: "This is hard."},
        {face: "&#9786;", text: "Let me see..."},
        {face: "&#9786;", text: "I'm gonna cream you!"},
        {face: "&#9786;", text: "Ooh. Nice."}
    ];

    var rand = this.getRandomNumber(0, remarks.length - 1);
    return remarks[rand];   
}


In the myTurn() method of the user object, remove this line.
myTurn: function()
{
    document.getElementById("user_x_text").innerHTML = "";
    document.getElementById("user_o_text").innerHTML = "";
    //document.getElementById("user_" + game.turn + "_text").innerHTML = "My turn!";


Instead, declare the variable remark and set it to the object returned by calling the getRandomRemark() method of the game object.
myTurn: function()
{
    document.getElementById("user_x_text").innerHTML = "";
    document.getElementById("user_o_text").innerHTML = "";
    //document.getElementById("user_" + game.turn + "_text").innerHTML = "My turn!";

    var remark = game.getRandomRemark();


Then use the face and text properties to populate these placeholders.
myTurn: function()
{
    document.getElementById("user_x_text").innerHTML = "";
    document.getElementById("user_o_text").innerHTML = "";
    //document.getElementById("user_" + game.turn + "_text").innerHTML = "My turn!";

    var remark = game.getRandomRemark();
    document.getElementById("user_" + game.turn + "_text").innerHTML = remark.text;
    document.getElementById("user_" + game.turn + "_face").innerHTML = remark.face;


Also, in your HTML, add ids for these placeholders.
<div id="game_container">
    <div id="user_x_container" class="player">
        <div class="token" id="user_x_face">&#9786;</div>
        <div id="user_x_text" class="text"></div>
    </div>

    <div id="board_container">
        <div id="square0" class="square" onclick="user.takeTurn(0, 'o');"></div>
        <div id="square1" class="square" onclick="user.takeTurn(1, 'o');"></div>
        <div id="square2" class="square" onclick="user.takeTurn(2, 'o');"></div>
        <div id="square3" class="square" onclick="user.takeTurn(3, 'o');"></div>
        <div id="square4" class="square" onclick="user.takeTurn(4, 'o');"></div>
        <div id="square5" class="square" onclick="user.takeTurn(5, 'o');"></div>
        <div id="square6" class="square" onclick="user.takeTurn(6, 'o');"></div>
        <div id="square7" class="square" onclick="user.takeTurn(7, 'o');"></div>
        <div id="square8" class="square" onclick="user.takeTurn(8, 'o');"></div>
    </div>

    <div id="user_o_container" class="player">
        <div class="token" id="user_o_face">&#9786;</div>
        <div id="user_o_text" class="text"></div>
    </div>
</div>


Before I forget, also populate the "face" placeholder when the game is won.
if (game.checkWin(mark))
{
    document.getElementById("user_" + mark + "_face").innerHTML = "&#9786";
    document.getElementById("user_" + mark + "_text").innerHTML = "I win!";
    game.started = false;
}
else
{
    game.nextTurn();
}


Oh, ho! Now things are getting interesting!



Next

Let's simulate some intelligence in this game!

Thursday 6 February 2020

Spot The Bug: The Calendar Conundrum

Hello and welcome! It's time for another episode of Spot The Bug!

I know you're
out there.

Ruby is a nice language to use with a syntax that is smooth as silk, but surprise, surprise... you can get code bugs in Ruby just as easily as any other language.

Here's what happened when I tried to use Ruby to write a script that would display a calendar view for the current month. For the record, it was this month of this year - February 2020.

In here, calStr is a list of dates, each one separated by a tab, and a break every time Saturday is encountered.
require "date"

nowDate = Date.today
currentDate = Date.new(nowDate.year, nowDate.month, 1)
lastDate = currentDate >> 1
lastDate = (lastDate - 1)
lastDay = lastDate.day

puts "Sun\tMon\tTue\tWed\tThu\tFri\tSat"
calStr = "";

for i in 1..lastDay do
    currentDate = Date.new(2020, 2, i)

    calStr = calStr + i.to_s + "\t"

    if (currentDate.wday == 6)
        calStr = calStr + "\n"
    end
end

puts calStr


Not too bad, right? Except the first day doesn't start on a Sunday, so the calendar is all wrong.


No problem! What I'll do is make another For loop before the For loop, to iterate from 0 to the first day. Then I'll print a tab character for every day before the first day!
puts "Sun\tMon\tTue\tWed\tThu\tFri\tSat"
calStr = "";

for i in 0..currentDate.wday do
    calStr = calStr + "\t"
end

for i in 1..lastDay do
    currentDate = Date.new(2020, 2, i)

    calStr = calStr + i.to_s + "\t"

    if (currentDate.wday == 6)
        calStr = calStr + "\n"
    end
end

What went wrong

Um... nope. For some reason, now the first day, 1, was out of alignment. But everything else was in alignment.


This only meant one thing - the number of tabs before the first date, was wrong. There was an extra tab.

Why it went wrong

See, one of the nice things about Ruby is that it utilizes ranges. Instead of painstakingly typing out stuff like for (int i = 0; i < x; i++), you can simply go for i in 1..x do. 1..x is the range and you can do a lot more with that. Follow this link for more about Ruby ranges!

Anyway, where I went wrong was the range. Ruby range syntax goes like this...

If you want to go from 0 to 10, you use this.
0..10


If you want to go from 0 to 9, excluding 10, you use this. See the extra dot?
0...10


I only had two dots in my range 0..currentDate.wday. Therefore the script would churn out a tab character from 0 to the first weekday... including the first weekday! So even if the first weekday was on a Sunday, there would still be a tab in front of it!

How I fixed it

That's right, all it took was an extra dot. Which meant only the number of tabs from 0 (Sunday) to the first weekday of the month (Saturday), five in all, would be printed.
for i in 0...currentDate.wday do
    calStr = calStr + "\t"
end


Voila!


Moral of the story

It's usually the very tiny things that elude us. And in this case, it was literally a dot.

Till next time..............................
T___T

Sunday 2 February 2020

Four Hundred Dollars Below Budget

My monthly budgeting follows a strict routine. Every month, my company pays me by transferring funds into Account A. Every month on the 25th, I transfer a thousand dollars into Account B from Account A, and all my personal spending draws from Account B. Now, I don't spend exactly a thousand a month, but it's close enough. Any amount I put in or withdraw from any of my accounts sends a message to my phone.

Yes, I'm a control freak like that. There was a time when I lived from paycheck to paycheck, and it's resulted in certain habits.

So imagine my consternation when recently, before performing my monthly funds transfer into Account B, I looked into Account B and found that there was an extra four hundred dollars just sitting there. Four hundred. Not forty. Where had it come from? None of my notifications showed an incoming transaction by that amount. Was there a bill I'd forgotten to pay? Had the Singapore Government inserted money into my account (some National Budget or other, maybe?) and somehow forgotten to tell me?

I'm rich!!!

People told me I was fretting over nothing. Extra money in your account is a good thing, they said. Better than less money. Just relax and enjoy it.

Utter rubbish. That's the simplistic mentality of laypeople who don't write code for a living.

You see, people tend to focus on the end result, but the process is just as important, if not more. If there is a desirable result and you want to replicate it, it stands to reason that you have to repeat the process under the same conditions. If you don't even know what process should be repeated or the conditions to repeat it under, how are you to replicate that result?

An Example In Code

Here's an example in JavaScript. Say I have two functions, a() and b(). They each accept a parameter, x. Now let me run the function a() using 2 as an argument.

a(2);


Now, if a()'s formula was this...
a(2);

function a(x)
{
    return x * 2;
}


The result would be 4. Because 2 x 2 is 4, right?
4


But what if the formula for b() was this? And then we ran b() using 2 as an argument?
b(2);

function a(x)
{
    return x * 2;
}

function b(x)
{
    return x * x;
}


The result would be the same. Because x is 2, thus it would still be (2 x 2 = 4)!
4


That's a very simple example of how an identical result does not equate to an identical formula. Obviously, x multiplied by itself is not the same formula as x multiplied by 2, unless x is 2. What if x wasn't 2? What if x was some other value, say, 10? Then the two formulae would yield very different results.

The Takeaway

Thus, when testing code, you test with a variety of input and in a variety of environments, to ensure that the formula is correct. Because if you get what appears to be the correct result while applying an incorrect formula, your code is still incorrect. And this will lead to further problems down the road.

Apply the correct formula.


As a professional who codes for a living, I've had to sometimes apply certain code-related principles to life. If you want to ensure that your results are favorable, you have to apply the correct process with the correct input. It was never an issue of whether there was more or less money in my account. People were missing the point by a mile. The issue was that I didn't know where that money came from. That made me uncomfortable. It was imperative that I understand why there was extra money in my account.

Finally...

So where did that money come from? The answer was a lot simpler than I'd imagined.

During those few months, I was so busy with work that I didn't have time to go out and spend money. No fancy dinners, no movies, no whatever. And I was certainly too busy to check and see how much money there was in Account B before the monthly funds transfer. Thus whatever money that was left over at the end of the month, just kept accumulating.

And when I finally found the time to look, the sum had grown to four hundred dollars. Since it wasn't a result of anybody transferring money to my account, I naturally never received any notification.

Looks like I'm not as savvy as I thought, eh?

Warm regard$,
T___T