Showing posts with label interview. Show all posts
Showing posts with label interview. Show all posts

Tuesday, 11 August 2026

The Case against Hunger, Enthusiasm and Other Useless Buzzwords

After interviews with prospective candidates are concluded, when I ask fellow interviewers for impressions on the candidate, I sometimes get very strange answers.

"I dunno, the enthusiasm wasn't there."

"Has the skills. Not sure if he has the hunger."

"He just didn't seem excited enough at the prospect of working here."

I'll be honest - that mode of thinking is alien to me. I understand why the owner of a company would think his company is special, the same way the mother of the ugliest child on Earth would think her tyke was Heaven-sent. My question really is: why should others be expected to feel the same way? 

A face only a mother
could love.

Why, pray tell, should anyone, much less a fully qualified tech worker, be excited to be working for your company in particular? Is this really a reasonable expectation? Are you genuinely at the level where being allowed to work for you should be taken as an honor? What loses me is when people start giving themselves airs. Buddy, your company sells spaghetti and cheese fries. That's perfectly respectable. But stop talking about your "sacred mission" like you're solving world hunger.

What they probably really wanted

I suspect what they really meant was - we don't want people to just show up for the money and give the bare minimum. Which sounds more reasonable and actually even sympathetic.

They probably also meant that they don't want this thing to be too transactional. But here's the thing, cupcake - it's all transactional. You're literally running a business here. People need money to pay their bills. They need jobs to have that money. You're offering a job - look, I don't really need to explain this part, do I? 

And let's just quit sugarcoating it: Many of the employers who are fetishizing "hunger" can't handle genuine hunger. That level of hunger involves going above and beyond, yes. It also involves constantly looking out for the next better opportunity and coldly leaving their former employer in the dust if needs must. How do I know this? Well, I was young and dumb once. And yes, I was hungry. Starving, even. My employers were happy to benefit from my hunger, as they should. They were less happy when I left, and my hunger started benefitting other employers.

Here, eat some shit.

No, an alarming number of the employers who say they want "hunger" and "enthusiasm", generally mean that they want someone more exploitable. Someone who will eat shit, smile, and thank their employer for the opportunity to eat shit.

Now, making some grandstanding claim about how exploitation is morally wrong, would make me a huge hypocrite. All my professional life, I have been exploiting my employers' need for labor and tech expertise, the same way they've been exploiting my need for money. It's mere capitalism; very mutual, very respectable.

I just wish people would be honest about what they want, instead of abusing the thesaurus for fancy terms like "hunger", "grit", "self-starter", "go-getter" and the usual cringe LinkedIn buzzwords. Employers, employees; we're on different sides of the chessboard but we're essentially playing the same game. No need to get all self-righteous about it.

The case against enthusiasm

If I wanted to be crude (or compare myself to a sex worker), employers like that remind me of a line I heard in a movie once, and for the life of me I can't remember which one, or even who said it. Was it Al Pacino? I'm certainly hearing it in Pacino's voice, specifically the one he used in The Devil's Advocate. He was talking about how ridiculous a guy was because he hired the services of a hooker and actually expected her to enjoy the sex. Basically, not only did the unfortunate woman have to blow him, she had to pretend it tasted like candy.

Just like candy.

These employers not only want their employees to run through brick walls for them, they want these employees to act like they've been given the highest privilege. Come on, now. Are you grateful that your employees choose to work here? If not, why should the reverse be true? I'm not even asking for humility here; that might be a step too far. Perhaps something more manageable, like dialling that hubris down to less cartoonish levels.

Do I have that requisite level of bright-eyed enthusiasm? Is that why I've been in this company for the last five years? Hell no, it's just the way I work. And that's exactly my point. Asking someone to be in the company out of "enthusiasm" is like asking someone to marry you purely for "love". All these are feelings. And feelings in human beings are even less predictable than an LLM's responses.

Nothing wrong with feelings either, but at some point you're going to need something more concrete and dare I say, sustainable.

What you want is work ethic. Something that persists regardless of a worker's feelings. After all, you don't pay for their feelings. You pay for their time, and their output.

The Takeaway

Any idiot can demand enthusiasm. And any idiot can fake it.

It's not wrong to ask for enthusiasm. But you need to know what part this enthusiasm is supposed to play. "Enthusiasm" is a fuzzy metric that too many employers use to identify employees whom they can subsequently exploit.

Hunger cuts both ways. If you want anything less, you don't want "hungry". You want a wage slave asymmetric employment relationship. At least acknowledge that.

Stay hungry, but stop being foolish!
T___T

Friday, 8 May 2026

Ten Reasons To Keep Treating Job Interviews As Target Practice

For the longest time, I've been an advocate of getting practice in job interviews... using actual job interviews. I make no apology for this; in fact, at a certain stage in my career, I made it a point to do this regularly. And I will continue to advise professionals to keep applying for jobs and going for job interviews, even - or especially - if they have no current intention of switching jobs. Hey, life is about at least opening new doors to see what lies beyond, if not going through them.

Opening new doors.

Don't feel too bad about potentially wasting the time of the companies you apply at. How many companies post job ads and conduct interviews for positions they have no intention of filling, just so they can technically bypass the requirements set out by the TAFEP? Two can play at that game.

Plus, there are some really compelling reasons to do it. Here's a list of some really big ones.

1. Muscle memory

You know what they say about practice making perfect? Well, there's no such thing when it comes to interviews (or anything, really). But you can get awfully good at it. What about mock interviews? Well, mock interviews are great and all, but at some point you're going to have to stop hitting the punching bag and square off against someone who's actually trying to hit you back.

This won't hit back.

People also tell me that they have better things to do than to attempt interviews "for fun". This is a huge misrepresentation of what I'm recommending. Of course, if it's fun for you, that's a bonus. But it's not the main point.

Nothing about this is supposed to be fun. Think of this as working out, where you condition your body, strengthen it, in the event that one day it might be ready when called upon. This is the career interview equivalent of exercising in a gym.

2. Confidence

At the very least, you'll get a lot more comfortable at it.

Even when it's a high stakes interview where there's a lot riding on it and you actually care about the outcome, there's no denying that prior practice helps. A big part of you will feel like you've seen and done all this before. There will be a certain familiarity to the entire process that, due to the comfort said familiarity brings, will go a long way towards offsetting any nerves.

Staying chill.

People have remarked on how remarkably at ease I seem during job interviews. What they don't know is that this has nothing to do with my people skills. I still find other human beings tiresome and annoying. It's simply that after a few hundred interviews with actual companies and dealing with all their bullshit, it stops being fresh and exciting. As a result, it also stops being anxiety-inducing. Seriously, no matter how unique and special some interviewers seem to think their interview process is, the truth is that the vast majority of the time, it's like most other interview processes.

Tech round? Mock project? Whiteboard? I've seen it. I've done it. I've even conducted it. Nothing makes me nervous. Because almost none of it is new.

Reusing the boxing analogy from before, the best way to get over a fear of being punched in the face, is to get punched in the face frequently. Similarly, to get over a fear of rejection from interviewers, you need to get rejected often. From real interviews.

3. Develop market awareness

The tech industry is wide and varied. Even if you narrow it down to software development, or even further down to web development, the fact is that different employers have different requirements. They may need proficiency or even just passing familiarity with new software, new frameworks, new software libraries that you may never even have heard of.

And it's true that you don't know what you don't know. So if you don't even have any idea where to start improving, how are you going to improve? It certainly isn't going to be by repeating the same old stuff over and over. That may work for some industries. But no, not software development.

Laundry list of requirements.

But every time one applies for a new job, guess what - the application often comes with an oh-so-helpful wish list of skills that the employer is looking out for. Sure, some of it is laughably impractical, but this is generally where you can scan the list for things that look unfamiliar to you, and then start exploring.

You know the D3 and HighCharts demos I have done? They both started out as items on a job application list of requirements. I Googled the terms and the rest is history.

4. Broaden horizons

There are sights you don't get too see very often. Huge, sprawling offices that overlook the sparkling waves of Marina Bay. Fancy commercial towers. Lush lobbies.

Not to say you can't see those things without going for a job interview... but really, what better time? Surely not during your free time when you undoubtedly have more pressing concerns and better things to do?

See the sights.

Aside from the sights, sometimes companies have interesting ways of testing interviewers, such as through puzzles or case studies. Some of them were so fun I turned them into web demos. Tetris in vanilla JavaScript? Rock, Paper, Scissors app? All that came from some applicant assignment sheet.

Also, I know I earlier said that most interview questions are lame and formulaic. Most. Sometimes interviewers surprise you with a question you haven't heard before. That's very valuable. It not only expands your experience, it tests your ability to think on your feet.

Take the experience as a way to broaden your horizons. Add to the growing list of stuff you can later confidently claim to have seen.

5. Learn how to conduct

One side effect of having attended a whole shitload of interviews in your career, is that you learn how to conduct them.

Well... not quite.

Let me qualify that. You don't exactly learn how to conduct them. That kind of experience comes from actually conducting them, just as the know-how to get through job interviews comes form actually attending job interviews.

Conducting an
interview.

But you do learn an awful lot about how to conduct them. For example, you would know from firsthand experience how cringey some questions sound, such as the classic "where do you see yourself in five years?" and the ultimate douchebaggy "why should we hire you?", and hopefully avoid being such a cliché.

You would learn, from the interviewee's point of view, what makes a bad impression on them, and avoid falling into those traps.

6. Zero consequences

Using real companies and real job applications for practice, is like honing your craft in a real environment with no stakes whatsoever.

If you weren't all that (or even at all) interested in the job, it doesn't matter if you fail. Your ego might get bruised a little bit, but that's about it.

Target practice.

What are they going to do if they find out you were just using them for practice? Not hire you? Big deal. It's not like you were actually interested to begin with. Practice, remember? Are they going to sure no one ever hires you in the industry? Please, they wish they had this much clout. This is Singapore, not Silicon Valley.

My entire point is that there are no consequences for failure. This is a consequence-free activity dressed up as high-stakes operation. You pretend to yourself that this is a job you really want, though in actual fact you could care less. As interviews go, the only thing less stressful is a mock interview, and those are pretty lame.

7. Future-proofing

"But I'm happy where I am!"

Oh, you sweet summer child. You don't have to be unhappy where you are, to go for a job interview. Especially when the objective is practice. But it probably would add a huge dose of motivation.

If you're happy and
you know it...

It's a hedge against the eventuality that you no longer want to work in your current place of employment, for whatever reason. No relationship lasts forever, especially not one that's predicated on a nebulous human emotion like "happiness".

And when - when, not if - that happens, you'll be glad you bothered. Your resume's already somewhat up-to-date due to all the regular practice. The last interview's still fresh in your mind, along with all the land mines you stepped on in the process. You're ready to go.

At the very least, you'll have gotten a good idea of how much your skills and abilities are worth in the market.

8. Bragging

Anyone with even an ounce of ego likes to brag about their accomplishments. Well, sunshine, here's your chance. A sanctioned space for you to safely brag about all your professional accomplishments. Talk about how you solved the persistent rounding error, that intermittent asynchronous event bubbling issue, and optimized that query to reduce latency to milliseconds.

Not only are you expected to do so, you are encouraged to. That's the entire point - to sell yourself.

I'll confess - this is one of the biggest reasons for me to attend any job interview. Not only do I get to brag, I get to brag to an interviewer who knows exactly what I'm bragging about. That's not a small thing. You see, anyone can brag. But who you brag to makes a hell lot of difference.

Toot that horn!

As a tech, if you're bragging to people who don't have the know-how to properly appreciate the brilliance of what you're bragging about, it can be like casting pearls before swine.

But when you're speaking to someone who has a pretty good idea as to why your accomplishments are impressive, that is sweet. And the cherry on top? Impress that someone enough and you may just win a job offer.

9. Eureka moments

During interviews, you're asked to describe your current work, and the challenges you face. And this can have a few effects.

You'll talk about the problems you're tackling, and how you either solved them, or are attempting to solve them. And we all know what happens when you talk them through someone, especially if you're explaining those problems well enough: you get an Eureka moment. Honestly, a lot of problems are solved just by talking the problem through with someone; anyone actually. You don't really need to be in a job interview to get that Eureka moment.

Your Eureka moment.

But can you get that Eureka moment during a job interview when you're talking about your work? Yes, absolutely. I wouldn't make it the reason to do job interviews, but it's one of the possible side-effects.

10. Gaining Perspective

One thing may happen when you go for those interviews and discover all the things you wouldn't have otherwise. You may actually realize how good you have it. You may gain a newfound appreciation of your workplace, warts and all. If seeing how things are out there makes you happier at your job, that's value.

The grass is browner on the
other side.

Granted, some places seem more fun to work in. They're also potentially messy, have less benefits, and are a pain in the butt to commute to. It could be any number of things, or all of them.

In the process, you may just discover that you're really grateful to have this job. And that's fine. Just don't be too grateful. Your career depends on it.

I like to call it the "grass is browner" syndrome.

Final notes

I haven't followed my own advice in years. I do still think it's good - even great - advice. But for me, it's started to show diminishing returns. The gains just aren't there anymore once you've clocked in the hours. Still, what this means is that there are gains. I can walk into an interview and feel little to no anxiety. I can answer most questions glibly, and even if I don't impress sufficiently in the end, I'm rarely too bothered by it.

Do exercise caution. It's entirely possible that you'll become better at passing interviews than actually doing the work... and that's the last thing you should want.

Let's review this in five years!
T___T

Monday, 4 May 2026

The Case of the Awkward Tech Interviewee

It's been a while since I attended a job interview. As an applicant, anyway.

Recently, I was on the other side of the interview, reviewing candidates for a tech position within my company. The interviewee in question was a nervous-looking Malaysian. There wasn't anything that really stood out, except for his awkwardness. Which, in itself, was a problem. You see, for tech positions, sometimes one can get away with not being able to communicate that well. The stereotype of socially-inept but brilliant techies exists for a reason.

Brilliant but
awkward?

However, this particular position was for a leadership role. And lack of communication skills just wasn't going to fly. I could tell that my co-interviewer had already written our applicant off and was about to call it in, but I persisted. We'd already scheduled and allotted the time; I figured that I might as well develop the muscle memory I needed for being an interviewer.

Besides, it could have been just nerves. Some people have told me I can be intimidating, a statement I consider laughable. Please, look at this face. Nothing about me is remotely intimidating.

Either way, I decided to give him a question he could answer. I asked him to explain what he would do to combat SQL Injection. His answer, predictably, was "Stored Procedures". My follow-up, probably just as predictably, was why Stored Procedures? What do Stored Procedures do that normal SQL queries don't?

He stammered. Hemmed. Hawed.

After a few minutes, I put him out of his misery. I told him to Google the term "paramterized queries". And then told him that in future, if anyone ever asked him a question like that again, he should refer to that, rather than simply say "Stored Procedures". It would be a better, more complete, more correct answer. And more importantly, it would be an answer that instilled confidence in the asker of the question, confidence that the answerer knew his shit.

After we were thanked him for his time and bade him goodbye, my co-interviewer turned to me and asked me why I'd spent so much time on this guy. Did I see some potential that he'd missed?

I replied, simply, that we obviously weren't going to give him the job... so I might as well give him something.

People commented on my perceived generosity, or at least, on me "paying it forward". I don't think either is true. This was decidedly not about making the tech industry a better place, or even giving back.

Back then...

Upon further reflection, something similar happened to me in the days when I was the applicant. There were times when I sensed I had failed the interview, and when the interviewer asked if I had any questions, I ventured out of the box.

I asked for advice. And this paid off in spades. They proceeded to tell me more or less where I'd gone wrong. We all knew I wouldn't make it past this round, but they at least thought I deserved a fighting chance... somewhere else. They wanted me to succeed, again, somewhere else. I guess it helped that His Teochewness has such a winning personality.

Seeking answers.

I think that helping candidates along is a very natural instinct on the part of any tech interviewer who takes their job seriously. Of course, it helps if the applicant doesn't piss them off. One may object on the grounds that this helps applicants cheat on their next tech interview. That's only true if they get asked the exact same question next interview. Also, let's be real, answers are all over the internet.

And from a cynical point of view, even if it's true that you're enabling inferior candidates to "cheat", they'd likely be "cheating" at the interviews of your competitors. Nobody really needs me to explain this one now, do they?

It's probably also true that techies generally like to look out for other techies. Some little tribal instinct there. Us geeks against the laypersons.

To conclude...

Interviews need not be an adversarial exercise. Your interviewers want you to succeed, or at least not turn out to be a total waste of time. Selfishly, because they already invested the time and energy into prepping for this interview, and conducting it. From that point of view, they get nothing if you turn out to be a dud.

Therefore, they don't need to love you. They just need to not severely dislike you. And the rest takes care of itself.

Without question,
T___T

Sunday, 11 May 2025

Web Tutorial: Paper Rock Scissors Game (Part 1/2)

During the early months of the COVID-19 outbreak, I applied for a job where the interviewers sent me a coding test. The objective? To write a Paper Rock Scissors Game in HTML, CSS and JavaScript.

Paper Rock Scissors

At the time, they requested that I keep the GitHub repository for the code private. It's been five years and I figure AI has rendered their fun little tests moot. In any case, I rewrote my code from scratch and hopefully my style now has improved.

The Stated Requirements

1. Implement a Paper Rock Scissors Game in HTML/CSS/JavaScript.
2. The game should be extensible to include Spock and Lizard.
3. The game should include an option for the computer to play against itself.

Let's begin!

Here's some starting HTML boilerplate. In the CSS, we set the general font face and font size. I've set divs to have a red outline. In the JavaScript, we want an object, paprocksc. I know, that's quite the name, eh?
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Paper Rock Scisscors</title>

        <style>
            body
            {
                text-align: center;
                font-size: 14px;
                font-family: verdana;
            }

            div { outline: 1px solid red; }
        </style>

        <script>
            let paprocksc =
            {

            };
        </script>
    </head>
    
    <body>

    </body>
</html>


Let's get some HTML structure out of the way. We have a header. We also have the CSS class gameContainer styling the outermost div.
<body>
    <h1>Paper, Rock, Scissors</h1>

    <div class="gameContainer">

    </div>

</body>


Then two divs, each styled using player, and left and right respectively. They have different ids. And let's have a hr tag after all this.
<body>
    <h1>Paper, Rock, Scissors</h1>

    <div class="gameContainer">
        <div class="player left">

        </div>
        <div class="player right">

        </div>
        <hr />
    </div>
</body>


In the CSS, gameContainer occupies a certain width and height. player occupies roughly half the width. left makes its element float left, and right does the exact opposite.
<style>
    body
    {
        text-align: center;
        font-size: 14px;
        font-family: verdana;
    }

    div { outline: 1px solid red; }

    .gameContainer
    {
        width: 100%;
        height: 600px;
    }

    .player
    {
        width: 45%;
        height: 300px;
    }

    .left
    {
        float: left;
    }

    .right
    {
        float: right;
    }
</style>


And hr, that comes right after these two divs, has the clear property set to both so that we don't end up with collapsing issues.
<style>
body
{
text-align: center;
font-size: 14px;
font-family: verdana;
}

div { outline: 1px solid red; }

.gameContainer
{
width: 100%;
height: 600px;
}

.player
{
width: 45%;
height: 300px;
}

.left
{
float: left;
}

.right
{
float: right;
}

hr
{
clear: both;
}

</style>


This is what it looks like!


We're going to use this image next. I made this myself - just a white question mark over a black square.

symbol.jpg

In each div styled using the player CSS class, we have one div. They have different ids, and are styled using the symbol CSS class. Because we want them to be floated a certain direction, we style them using right and left, respectively. And we hard-code the background image to use symbol.jpg.
<div class="player left">
    <div id="player_0" class="symbol right" style="background-image: url(symbol.jpg);"></div>
</div>
<div class="player right">
    <div id="player_1" class="symbol left" style="background-image: url(symbol.jpg);"></div>
</div>


In the CSS, symbol is a circle, thus width and height properties are the same and border-radius is set to 50%. The background-related properties ensure that the background image used covers the entire element. outline is set to a thick white line. These are the defaults.
.right
{
    float: right;
}

.symbol
{
    width: 200px;
    height: 200px;
    border-radius: 50%;
    margin-top: 10px;
    background-size: cover;
    background-repeat: no-repeat;
    background-position: center center;
    outline: 15px solid rgb(255, 255, 255);
}


hr
{
    clear: both;
}


Take a look at how it's coming together. We're going to add some placeholders for scores.


Here we have a div. It's styled using score and right. Inside it is a small header.
<div class="player left">
    <div class="score right"><small>You</small><br /></div>
    <div id="player_0" class="symbol right" style="background-image: url(symbol.jpg);"></div>
</div>
<div class="player right">
    <div id="player_1" class="symbol left" style="background-image: url(symbol.jpg);"></div>
</div>


We'll also have a span tag, with an id. The default value is 0.
<div class="player left">
    <div class="score right"><small>You</small><br /><span id="score_0">0</span></div>
    <div id="player_0" class="symbol right" style="background-image: url(symbol.jpg);"></div>
</div>
<div class="player right">
    <div id="player_1" class="symbol left" style="background-image: url(symbol.jpg);"></div>
</div>


Repeat this for the other side...
<div class="player left">
    <div class="score right"><small>You</small><br /><span id="score_0">0</span></div>
    <div id="player_0" class="symbol right" style="background-image: url(symbol.jpg);"></div>
</div>
<div class="player right">
    <div class="score right"><small>You</small><br /><span id="score_0">0</span></div>
    <div id="player_1" class="symbol left" style="background-image: url(symbol.jpg);"></div>
</div>


... but make sure the div is styled using left, instead. The span tag id should be different, and the header is for the computer.
<div class="player left">
    <div class="score right"><small>You</small><br /><span id="score_0">0</span></div>
    <div id="player_0" class="symbol right" style="background-image: url(symbol.jpg);"></div>
</div>
<div class="player right">
    <div class="score left"><small>Computer</small><br /><span id="score_1">0</span></div>
    <div id="player_1" class="symbol left" style="background-image: url(symbol.jpg);"></div>
</div>


In the CSS, score has a defined width and height, and font. We align the text center. The small tag within score will be half the font size.
.symbol
{
    width: 200px;
    height: 200px;
    border-radius: 50%;
    margin-top: 10px;
    background-size: cover;
    background-repeat: no-repeat;
    background-position: center center;
    outline: 15px solid rgb(255, 255, 255);
}

.score
{
    width: 150px;
    height: 50px;
    font-weght: bold;
    font-size: 2em;
    text-align: center;
}

.score small
{
    font-size: 0.5em;
}


hr
{
    clear: both;
}


Looks good. Let's do a dashboard next.


After the hr tag, add a div. Style it using the CSS class dashboard.
<hr />
<div class="dashboard">

</div>


Inside it, add a button. Style it using the CSS classes symbolBtn and hidden.
<hr />
<div class="dashboard">
    <button class="symbolBtn hidden"></button>
</div>


Add five more buttons.
<hr />
<div class="dashboard">
    <button class="symbolBtn hidden"></button>
    <button class="symbolBtn hidden"></button>
    <button class="symbolBtn hidden"></button>
    <button class="symbolBtn hidden"></button>
    <button class="symbolBtn hidden"></button>
    <button class="symbolBtn hidden"></button>
</div>


Now, we're going to be using more images I generated using AI. They are crude black-and-white drawings of hand signs - Paper, Rock, Scissors, Lizard and Spock.

symbol_paper.jpg

symbol_rock.jpg

symbol_scissors.jpg

symbol_lizard.jpg

symbol_spock.jpg

Add these images as content in the buttons. We'll limit the width to 80 pixels.
<hr />
<div class="dashboard">
    <button class="symbolBtn hidden"><img src="symbol_paper.jpg" width="80" /></button>
    <button class="symbolBtn hidden"><img src="symbol_rock.jpg" width="80" /></button>
    <button class="symbolBtn hidden"><img src="symbol_scissors.jpg" width="80" /></button>
    <button class="symbolBtn hidden"><img src="symbol_spock.jpg" width="80" /></button>
    <button class="symbolBtn hidden"><img src="symbol_lizard.jpg" width="80" /></button>
</div>


In the CSS, dashboard has a defined with and height, and is positioned in the middle via the margin property.
.score small
{
    font-size: 0.5em;
}

.dashboard
{
    width: 90%;
    height: 200px;
    margin: 0 auto 0 auto;
}


hr
{
    clear: both;
}


We style symbolBtn by making it a 100 pixel square, with rounded corners and a bit of spacing on the right.
.score small
{
    font-size: 0.5em;
}

.dashboard
{
    width: 90%;
    height: 200px;
    margin: 0 auto 0 auto;
}

.symbolBtn
{
    width: 100px;
    height: 100px;
    border-radius: 5px;
    margin-right: 5px;
}


hr
{
    clear: both;
}


You may also want to generally style buttons to have a grey background when hovered over.
.gameContainer
{
    width: 100%;
    height: 600px;
}

button:hover
{
    background-color: rgb(100, 100, 100);
}


.player
{
    width: 45%;
    height: 300px;
}


Now you can see the buttons! And what happens when you mouse over one of them.


Now add the hidden CSS class, and the buttons should disappear.
.symbolBtn
{
    width: 100px;
    height: 100px;
    border-radius: 5px;
    margin-right: 5px;
}

.hidden
{
    display: none;
}


hr
{
    clear: both;
}


Near the title, let's add some controls. First, we want a drop-down list with the id ddlRange. It determines if the user is playing with just Paper, Rock and Scissors, or the full extended version. The values are 3 and 5, representing the number of hand symbols available.
<h1>Paper, Rock, Scissors</h1>
<select id="ddlRange">
    <option value="3">Classic</option>
    <option value="5">Extended</option>
</select>


<div class="gameContainer">


Then we have a checkbox with the id cbAuto. This controls whether or not the computer plays against itself or the user.
<h1>Paper, Rock, Scissors</h1>
<select id="ddlRange">
    <option value="3">Classic</option>
    <option value="5">Extended</option>
</select>
<input type="checkbox" id="cbAuto">Auto

<div class="gameContainer">


We then have two buttons, ids btnStart and btnStop. btnStop is hidden, but both buttons are styled using the CSS class topBtn.
<h1>Paper, Rock, Scissors</h1>
<select id="ddlRange">
    <option value="3">Classic</option>
    <option value="5">Extended</option>
</select>
<input type="checkbox" id="cbAuto">Auto

<button id="btnStart" class="topBtn">Start</button>
<button id="btnStop" class="topBtn hidden">Stop</button>


<div class="gameContainer">


Styling for topBtn is just a matter of visuals. I'm sticking to the black-white theme.
.gameContainer
{
    width: 100%;
    height: 600px;
}

.topBtn
{
    width: 5em;
    height: 2em;
    background-color: rgb(0, 0, 0);
    color: rgb(255, 255, 255);
    font-weight: bold;
    border-radius: 3px;
}


button:hover
{
    background-color: rgb(100, 100, 100);
}


There be your controls!


That's it for the UI! Turn the red outline for divs off.
div { outline: 0px solid red; }


The paprocksc object

We're going to define some properties. First, symbols. This is a collection of all the hand symbols used in the game.
let paprocksc =
{
    symbols:
    [
                                                            
    ]

};


Each one is an object with the name property with values such as "paper", "rock", etc. The beats property is an array, and it defines which symbol the current symbol, well, beats. For example, Paper beats Rock and Spock.
let paprocksc =
{
    symbols:
    [
        {
            name: "paper",
            beats: ["rock", "spock"]
        }  
                                                      
    ]
};


And that's how we populate the rest of symbols.
let paprocksc =
{
    symbols:
    [
        {
            name: "paper",
            beats: ["rock", "spock"]
        },
        {
            name: "rock",
            beats: ["lizard", "scissors"]
        },
        {
            name: "scissors",
            beats: ["paper", "lizard"]
        },
        {
            name: "spock",
            beats: ["scissors", "rock"]
        },
        {
            name: "lizard",
            beats: ["spock", "paper"]
        }     
                                                       
    ]
};


range defines how many elements of symbols to use. The default is 3, which means we use the first 3 elements - Paper, Rock and Scissors. auto defines whether or not the computer is playing against itself, and by default it's false.
let paprocksc =
{
    symbols:
    [
        {
            name: "paper",
            beats: ["rock", "spock"]
        },
        {
            name: "rock",
            beats: ["lizard", "scissors"]
        },
        {
            name: "scissors",
            beats: ["paper", "lizard"]
        },
        {
            name: "spock",
            beats: ["scissors", "rock"]
        },
        {
            name: "lizard",
            beats: ["spock", "paper"]
        }                                                           
    ],
    range: 3,
    auto: false

};


Next we have score and round, both of which are arrays with two elements. score is an array of integers which keeps track of rounds won. round keeps track of who's using what symbol. In both cases, the first element pertains to the user, and the second to the computer.
let paprocksc =
{
    symbols:
    [
        {
            name: "paper",
            beats: ["rock", "spock"]
        },
        {
            name: "rock",
            beats: ["lizard", "scissors"]
        },
        {
            name: "scissors",
            beats: ["paper", "lizard"]
        },
        {
            name: "spock",
            beats: ["scissors", "rock"]
        },
        {
            name: "lizard",
            beats: ["spock", "paper"]
        },                                                            
    ],
    range: 3,
    auto: false,
    score: [0, 0],
    round: ["", ""]

};


Lastly, we have active and timer. active just determines if the buttons are clickable, and timer is relevant only during Auto mode.
let paprocksc =
{
    symbols:
    [
        {
            name: "paper",
            beats: ["rock", "spock"]
        },
        {
            name: "rock",
            beats: ["lizard", "scissors"]
        },
        {
            name: "scissors",
            beats: ["paper", "lizard"]
        },
        {
            name: "spock",
            beats: ["scissors", "rock"]
        },
        {
            name: "lizard",
            beats: ["spock", "paper"]
        },                                                            
    ],
    range: 3,
    auto: false,
    score: [0, 0],
    round: ["", ""],
    active: false,
    timer: null

};


Now, on to methods! We first have start() and stop(), and I hope these are self-explanatory.
let paprocksc =
{
    symbols:
    [
        {
            name: "paper",
            beats: ["rock", "spock"]
        },
        {
            name: "rock",
            beats: ["lizard", "scissors"]
        },
        {
            name: "scissors",
            beats: ["paper", "lizard"]
        },
        {
            name: "spock",
            beats: ["scissors", "rock"]
        },
        {
            name: "lizard",
            beats: ["spock", "paper"]
        },                                                            
    ],
    range: 3,
    auto: false,
    score: [0, 0],
    round: ["", ""],
    active: false,
    timer: null,
    start: function()
    {

    },
    stop: function()
    {

    }

};


showOptions() shows and hides symbol buttons according to which should be available. And from there, we have chooseSymbol(), which takes the choice, represented by the parameter symbol, and processes it.
    start: function()
    {
    
    },
    stop: function()
    {
    
    },
    showOptions: function()
    {
    
    },
    chooseSymbol: function(symbol)
    {
    
    }

};


Then we have what I like to call the other "show" methods. showSymnbol() takes index to determine which placeholder to show symbol in. showScore() displays the current score. showColor() changes the outline of the placeholder indicated by index, to the color determined by colorCode.
    showOptions: function()
    {
    
    },
    chooseSymbol: function(symbol)
    {
    
    },    
    showSymbol: function(index, symbol)
    {
    
    },    
    showScore: function()
    {
    
    },
    showColor: function(index, colorCode)
    {
    
    }

};


whowins() is the parent method that calls xbeatsy() to determine if one symbol beats the other.
    
    showSymbol: function(index, symbol)
    {
    
    },    
    showScore: function()
    {
    
    },
    showColor: function(index, colorCode)
    {
    
    },
    whowins: function()
    {
    
    },        
    xbeatsy: function(x, y)
    {
    
    }

};


I'd like to begin with some of the simpler "show" methods. Like this one, showSymbol(). We declare imgUrl as the string "symbol.jpg". If the value for symbol is not an empty string, we add an underscore and symbol to the filename, e.g, "symbol_scissors.jpg".
showSymbol: function(index, symbol)
{
    var imgUrl = "symbol" + (symbol === "" ? "" : "_" + symbol) + ".jpg";
},


Then we have player. It either references the div player_0 or player_1.
showSymbol: function(index, symbol)
{
    var imgUrl = "symbol" + (symbol === "" ? "" : "_" + symbol) + ".jpg";
    var player = document.getElementById("player_" + index);
},


This last line populates the desired div with the correct image background.
showSymbol: function(index, symbol)
{
    var imgUrl = "symbol" + (symbol === "" ? "" : "_" + symbol) + ".jpg";
    var player = document.getElementById("player_" + index);

    player.style.backgroundImage = "url(" + imgUrl + ")";
},


Now for showScore(). This is straightforward. We declare score0 and score1, and set them to the elements score_0 and score_1, respectively.
showScore: function()
{
    var score0 = document.getElementById("score_0");
    var score1 = document.getElementById("score_1");

},


We then populate those elements with the values of the score array. As mentioned previously, the first element is for the user and the second is for the computer.
showScore: function()
{
    var score0 = document.getElementById("score_0");
    var score1 = document.getElementById("score_1");

    score0.innerHTML = this.score[0];
    score1.innerHTML = this.score[1];

},


And then we have showColor(). colorCode will be a RGB value, and this is how we define it. First, we have an object, codes. Then we have the win, loss, draw and end properties, each of which is a RGB value. A win is green, a loss is red, a draw is grey, and the neutral state, end, is white.
showColor: function(index, colorCode)
{
    var codes =
    {
        win: "rgb(0, 255, 0)",
        loss: "rgb(255, 0, 0)",
        draw: "rgb(200, 200, 200)",
        end: "rgb(255, 255, 255)",
    };

},


As with showSymbol(), index defines the div to work on.
showColor: function(index, colorCode)
{
    var codes =
    {
        win: "rgb(0, 255, 0)",
        loss: "rgb(255, 0, 0)",
        draw: "rgb(200, 200, 200)",
        end: "rgb(255, 255, 255)",
    };

    var symbol = document.getElementById("player_" + index);
},


We will give that div's outline a color based on the RGB value that colorCode points to in codes.
showColor: function(index, colorCode)
{
    var codes =
    {
        win: "rgb(0, 255, 0)",
        loss: "rgb(255, 0, 0)",
        draw: "rgb(200, 200, 200)",
        end: "rgb(255, 255, 255)",
    };

    var symbol = document.getElementById("player_" + index);
    symbol.style.outline = "15px solid " + codes[colorCode];
},


Now for showOptions()! In here, we decide which of the symbol buttons in the dashboard to show or hide. Create an array, btns, by running getElementsByClassName() and passing in the class name symbolBtn. Then use a For loop to iterate through them.
showOptions: function()
{
    var btns = document.getElementsByClassName("symbolBtn");

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

    }

},


In the loop, check if i is less than the range property.
showOptions: function()
{
    var btns = document.getElementsByClassName("symbolBtn");

    for (var i = 0; i < btns.length; i ++)
    {
        if (i < this.range)
        {

        }
        else
        {

        }

    }
},


If it is, we ensure that the button is displayed. If not, it is hidden. That means that in Classic Mode, where range is 3, only the first 3 buttons will be shown.
showOptions: function()
{
    var btns = document.getElementsByClassName("symbolBtn");

    for (var i = 0; i < btns.length; i ++)
    {
        if (i < this.range)
        {
            btns[i].className = "symbolBtn";
        }
        else
        {
            btns[i].className = "symbolBtn hidden";
        }
    }
},


One last method!

We'll do xbeatsy(). This method determines if symbol x beats symbol y. For this, we first need to find information about symbol x. Use the filter() method on symbols, returning the element where name matches x. Assign the resultant array to the variable symbol.
xbeatsy: function(x, y)
{
    var symbol = this.symbols.filter((s)=> { return s.name === x});
}


Here, the return statement's value is either true or false. We ask if the beats array of the first (and only) element of symbol, contains y. If it does, x beats y. If not, then it returns false.
xbeatsy: function(x, y)
{
    var symbol = this.symbols.filter((s)=> { return s.name === x});
    return (symbol[0].beats.indexOf(y) > -1);
}


These methods don't seem to do much, or have anything to do with each other. But fear not; all shall be revealed shortly. Join us for the next part of this web tutorial!

Next

Running the game.

Monday, 7 April 2025

Fare Thee Well, Skype!

It was with a fair bit of wistfulness when I read the news that Skype would be officially decommissioned by Microsoft on the 5th of May, right next month. Hard to believe that just a few short years ago, I was between jobs and on Skype speaking to interviewers during the COVID-19 pandemic.

Skype is dead soon.

Skype first appeared in 2003 as a telecommunications software application, when I was still at my Desktop Support job. Back then, my Indian boss amusingly referred to it as "Sky Pee". The setup was smooth and easy, and with this software we could talk to people all over the world instead of paying some hideously expensive phone charges. The user experience just seemed to be one of the smoothest around. The installation process was simple, a joy to embark on. I remember thinking that even just the chat feature was a lot more pleasant to use than Microsoft Messenger's.

Somewhere along the way, in 2011, Skype was acquired by Microsoft. Fourteen years later, Microsoft has decided that it is no longer tenable to continue maintaining Skype.

Why did Skype fail?

Most people have heard of the Microsoft BSOD - the Blue Screen of Death. Well, it appears what we have here is the Microsoft Kiss of Death. It may seem a little unfair to Microsoft. After all, all they really did was acquire Skype. And yes, perhaps they tinkered with Skype's inner workings just a little. And... you know what, you're right, this is all on Microsoft.

I think COVID-19 provided an even greater environment for Skype to shine. In those dark days, due to the need to avoid close physical proximity, video calls became a necessity. However, COVID-19 ironically was also one of the factors hastening Skype's downfall, because it provided that very same environment for Skype's competitors to shine.

Left behind in the race.

And shine they did.

Google Hangouts, Oracle Zoom, Slack, Microsoft Teams. WhatsApp. Instagram. Even TikTok. All of them provided video conference features (Instagram and TikTok to a limited degree) and it was clear that Skype was being quickly outpaced. And since Microsoft was understandably more concerned with supporting Teams, Slack had become an afterthought. 

And like most afterthoughts, Skype eventually faded into obscurity. Software has to be relevant in order to survive. And in order to be relevant, software has to evolve. Under Microsoft, Skype was dead in the water. 

Goodbye, Skype!

We had great memories together. Countless memorable conversations were had in your pale blue-and-white interface. There should be a special placed reserved for Skype in the annals of software history - arguably the first of its kind.

The Skype's the limit,
T___T