Saturday, 5 September 2026

Great Expectations and how to manage them, redux

Almost exactly a year ago, I spoke about great expectations, and managing them. The examples I provided were in a professional context. Often, however, the most relatable examples aren't professional, but personal.

It was 2024. I had just moved house, and that was another big item off my list. Actually, that was the final big item off my list. I went back to the WhatsApp groups that I had muted over the past year so I could concentrate on doing my thing, and found that in one of those groups, only a handful of them were left. Which was weird because I distinctly recalled them being close friends. What had happened in all the time I'd been minding my own business?

Spilling the tea.

Now that I had (relatively) more time on my hands, I made it a point to find out. Reaching out to those who were no longer part of the group, we met up for food and coffee, and with each of them, spoke at length about all the drama that had transpired during my absence.

The deets, sort of

Before I begin, let's get one thing out of the way. Aside from certain facts, everyone's view of the entire thing diverged in some wild way or other. And I have no reason to suspect that any of them was outright lying to me - after all, it's not like my opinion of them affected anything at all. Like I always say, no one is the villain in their own story. Not even the villains. Very few people have the honesty or humility to think of themselves as the bad guy.

Anyway...

One friend had put another friend up at his place, rent-free, when she was in need a few years ago. He complained to me that she was a nightmare - leaving the airconditioning on and racking up huge bills, not doing the housework, and so on. And I'm not saying that his complaints were illegitimate - if someone's living at your place for free, they should be considerate about racking up huge electricity bills and cleaning up after themselves.

At the same time, though, what the hell was this guy thinking? Why put himself in this position in the first place?

Look, being friends is one thing. But we all need to be conscious of the flaws of others (and ourselves, while we're at it) even if - especially if - these people are your friends. And consider the distance between you and those people carefully.

...your friends are my friends,
and my friends are
your friends...

Distance is healthy.

I'll be the first to admit that I can be an asshole with severe personality flaws. At the same time, I'm not putting anyone in a position where they have to put up with those flaws daily. (Except Mrs TeochewThunder... sucks to be her, I guess.) I recognize that it is far easier for people to stay friends with me if there's a healthy distance between us. My friends constantly annoy me by giving me unsolicited (and worse, garbage unqualified) advice, self-righteously pontificating about ridiculous causes I couldn't give less of a shit about, projecting their insecurities onto me... and I'm sure I annoy them in equal measure in a myriad of ways.

However, with distance, and freedom from expectations, we're all afforded the space to be ourselves without getting in anyone's way.

The lesser the distance is, the greater expectations become. In a way, this makes perfect sense. You wouldn't for example, feel betrayed by someone who wasn't a friend because you would have zero expectations of that person. You would never accuse a total stranger of "selling you out", because that is a deeply personal thing, and personal is the last thing you would associate with a stranger. If you were in a pinch, you would turn to friends or relatives for help, especially those who'd helped you before. It's human nature.

I never put my friends in a position where they can let me down. In that sense, I expect them to let me down given half a chance, so I don't give them the opportunity.

What all this means

Expectations were unrealistic.

Remember I said before, employers sometimes have unrealistic expectations? That they hire you to do one thing, but out of the blue they suddenly decide that they need you for some unrelated task? And then have the sheer audacity to be outraged when you don't magically become good at that unrelated task just because they expect you to be?

For example, if you were hired to be a software developer but your employer needed a lightbulb changed and decided you were the person for the job. Because there's probably this line in your employment contract that says your duties aren't limited to those stated, and that your employer can decide if there are new duties they can add. Or some bullshit like that.

And then when you ended up breaking the lightbulb, they acted like you just killed their entire family.

Oops!

Well, that's because there are their expectations, and then there is reality. And sometimes they just don't meet. If you hire someone to develop software and but ask them to screw in lightbulbs instead, and they end up breaking the lightbulb, whose fault is it, really? If a screwdriver got damaged because you tried to use it like a hammer, whose fault is it? Yours. Don't look elsewhere. You're the idiot in this story.

My buddy had expectations of our mutual friend, but she just wasn't the person he wanted her to be. As reasonable as he thought those expectations were, perhaps the reality is that she's a dog that shits everywhere it pleases.

You meet people where they are, and stop trying to change them. 

Permission to be Imperfect

Some say my standards are too low. I disagree. In fact, my standards, both for myself and others, are ridiculously high. But my expectations aren't. The problem isn't standards. The problem is that I think I'm better than everyone else. None of you reading this will ever be half as awesome as I think myself to be.

See, I have standards that I strive to live up to. But I often fail because, as mentioned before, my standards are high to an absurd degree. Recognizing that I'm only human, I often forgive myself for that failure and try to do better. I expect to fail because I'm not perfect and I've made my peace with that. But if I'm not perfect, and I still consider myself better than the rest of you, it would be monstrously unfair of me to expect better from everyone else. If I'm not perfect, nobody has any business being perfect.

A flawless diamond.

Therefore, you are all excused for being imperfect. His Teochewness has decreed it.

The only time one can expect perfection is when writing a program. And even then, unless it's overly simple, it can only be perfect to a point. The more moving parts a programmer introduces into any given system, the greater the likelihood that the system will break in an extreme use case.

Guess what else is full of moving parts? Human beings.

People expect software to be perfect given that it has no feelings and that it's basically ones and zeroes. What they forget is that software is made by imperfect human beings. Even if, in the future, software is created by Artificial Intelligence, don't forget where AI learned all that from - imperfect human beings.

Oh wow, look at that. I managed to insert a rant about AI in here. Way to go, old man.

The Expectant Conclusion

Know when to stop. That's a lesson that can be applied to almost everything in life. Where friends are concerned, it's not a matter of "the closer the better". Understand that there is such a thing as too close, because people suck.

In relation to the above, understand when expectations are being unreasonably raised. Every time you declare that someone's your bro and you'll take a bullet for him, you raise expectations that you'll do so when the occasion arises. Every time someone asks for a favor and you oblige, you raise expectations that you'll help again when needed. You have to think about whether or not you can afford to raise those expectations. Remember previously I told you that how people treat you is a matter of how you train them to treat you? Well, start training others... and yourself. Establish that distance. Enforce those boundaries.

And yes... temper those expectations.

Permission to suck, granted!
T___T

Tuesday, 1 September 2026

A Baffling Encounter with a Tech Poseur

Social Media has never held all that much attraction for me. At some point, the novelty of interacting with people online turns into the drudgery of interacting with people, period. It was mid 2022 when this random encounter happened on the Clubhouse app, with some dude from Canada.

I introduced myself as a software developer, which seemed to signal to this fella that I needed to be challenged. He asked what I knew about Kubernetes, and when I honestly answered "not a lot", he then openly questioned how I could call myself a software developer and followed up by asking what I knew about SQL.


Again, seeing no reason to even attempt to impress a total stranger online, I said I wouldn't call myself an expert and just used as much as I needed to. This was totally subjective, of course. I know how to perform CRUD operations and database joins, and normalize database structures even if I've long forgotten the formal definition of BCNF. To some people, that would absolutely count as expertise, though I feel obligated to hold myself to higher standards.

In response, he started questioning my dedication to my craft, and this made me suspect that I wasn't speaking to someone who actually worked in tech... because genuine tech workers, even if they lacked humility, would probably have the experience to avoid simplistic (and frankly not terribly clever) snap judgments like these.

Why that was completely stupid

A couple reasons. First of all, Kubernetes isn't what I would call a mainstream tech thing. It's more of a DevOps thing, which still makes it a software tech discipline, just not first on everyone's to-do list.

A DevOps engineer would totally learn it.

A back-end engineer? Almost definitely.

A full-stack developer? Maybe.

A Data Analyst, Front-end engineer or Network Manager? Doubtful.

So acting like a techie is somehow deficient for not knowing much about it, tells me more about the person making that statement, than the one being judged. This would be as absurd as me mocking back-end engineers for not knowing HTML and CSS. Because, really, why would they? What next? You aren't a real musician if you can't play the zither? You're not a real doctor if you can't extract a tooth?

Real doctors can pull teeth!

Now SQL would be a more mainstream tool than Kubernetes. Regardless of which layer of the stack a tech worker is operating on currently, they would almost be guaranteed to have been taught SQL in school. And even then, plenty of tech workers don't deal with SQL professionally. Even I only really use raw SQL when I'm performing joins in database consoles - for actual software development, it's too much of a security risk and the accepted tools abstract away SQL for library functions.

Also, can I just mention that using Kubernetes and SQL together as a flex, is just kind of odd? Not pineapple-on-pizza weird. Maybe spinach-on-pizza weird.

Genuine Questions...

My industry isn't a circus, so why are we attracting these clowns? We were known as nerds in school. Why are people now so anxious to be be associated with us?

Why these clowns?

Oh, well. It's now 2026. With all the tech layoffs going on, perhaps my industry would have lost enough mystique to no longer produce these groupies.

Also, why does every tech poseur I encounter, feel the need to namedrop Kubernetes? Does it even sound that impressive?

I'm dead serious. Every other guy who tried to impress me with his tech cred, has cited Kubernetes. One even took the pains to explain Kubernetes was a upscaler in cloud infrastructure, as if we couldn't just read that up on its Wikipedia page. I'm not even that offended by the posturing. It's more the low-effort posturing, like I was some noob who can't tell when someone's full of hot air.

There are three situations where Kubernetes might be brought up casually as a subject.

One; DevOps engineers dissecting a scaling problem. Two; backend engineers deep in infra discussion. Three; tech performer using tech buzzwords as a big stick to hammer home the illusion of being technically experienced.

As far as I could tell, this did not fall under the first two categories. The irony is, in his eagerness to perform tech experience, he ended up demonstrating something that few actual software engineers would exhibit - ignorance of tech stacks.

So... what is it about Kubernetes specifically? This stumps me.

Finally

Of course, I don't mind being told I'm not much of a tech. I already don't consider myself exceptional. Although, the judgement might have stung more coming from someone who obviously knew his shit. As it was, it left me somewhat bemused. As a generalist, I didn't feel personally disrespected. But someone who actually spent the sweat and sleepless nights mastering Kubernetes and working with it, might feel quite differently.

It's been four years. Why am I even talking about this now? In truth, I wanted to talk about it way back, but more interesting stuff kept coming up. Plus, I am one hell of a procrastinator. It's a slow news week, so there you go.

How's your Java and JavaScript?
T___T

Tuesday, 25 August 2026

Web Tutorial: Pill Puzzle (Part 4/4)

We've developed the ability to place pills on either side of the scale, and return them to their original positions if required. Time to work on using the scale. For that, we ensure that the weigh() method is invoked when the button is clicked.
<div><button id="btnWeigh" onclick="pillgame.weigh()">WEIGH</button></div>


Then we work on this method. We begin with that return statement at the end. Then we exit early if playerStatus is not "ill".
weigh: function()
{
  if (this.playerStatus != "ill") return;

  return;

},


We also run setWeightTimes(), as we should.
weigh: function()
{
  if (this.playerStatus != "ill") return;
  this.setWeightTimes(this.weighTimes + 1);

  return;
},


Next, we define leftWeight. What we do is use the map() method to iterate through all the values of pills_left, which are indexes into the pills array, in effect making a new array based on all the weights of the pills that are in pills_left.
weigh: function()
{
  if (this.playerStatus != "ill") return;
  this.setWeightTimes(this.weighTimes + 1);

  const leftWeight = this.pills_left
  .map((x) => this.pills[x])


  return;
},


Then we use the reduce() method, totalling all the values.
weigh: function()
{
  if (this.playerStatus != "ill") return;
  this.setWeightTimes(this.weighTimes + 1);

  const leftWeight = this.pills_left
  .map((x) => this.pills[x])
  .reduce((sum, weight) => sum + weight, 0);

  return;
},


We do something similar for rightWeight. And after that, we have three If blocks to handle the various outcomes.
weigh: function()
{
  if (this.playerStatus != "ill") return;

  this.setWeightTimes(this.weighTimes + 1);
  const leftWeight = this.pills_left
  .map((x) => this.pills[x])
  .reduce((sum, weight) => sum + weight, 0);

  const rightWeight = this.pills_right
  .map((x) => this.pills[x])
  .reduce((sum, weight) => sum + weight, 0);

  if (leftWeight == rightWeight)
  {
    return;
  }

  if (leftWeight > rightWeight)
  {
    return;
  }

  if (leftWeight < rightWeight)
  {
    return;
  }


  return;
},


If leftWeight is heavier than rightWeight, pnlWeighPillsContainerLeft should go down and pnlWeighPillsContainerRight should go up, thus the justify-content properties of both need to be adjusted accordingly - pnlWeighPillsContainerLeft to flex-end (to sink to the "bottom") and pnlWeighPillsContainerRight to flex-start (to float to the "top"). The opposite is true for if rightWeight is heavier than leftWeight. And if both are equally heavy, both pnlWeighPillsContainerLeft and pnlWeighPillsContainerRight sink to the bottom.
weigh: function()
{
  if (this.playerStatus != "ill") return;
  this.setWeightTimes(this.weighTimes + 1);

  const leftWeight = this.pills_left
  .map((x) => this.pills[x])
  .reduce((sum, weight) => sum + weight, 0);

  const rightWeight = this.pills_right
  .map((x) => this.pills[x])
  .reduce((sum, weight) => sum + weight, 0);

  if (leftWeight == rightWeight)
  {
    $("#pnlWeighPillsContainerLeft").css("justifyContent", "flex-end");
    $("#pnlWeighPillsContainerRight").css("justifyContent", "flex-end");


    return;
  }

  if (leftWeight > rightWeight)
  {
    $("#pnlWeighPillsContainerLeft").css("justifyContent", "flex-end");
    $("#pnlWeighPillsContainerRight").css("justifyContent", "flex-start");


    return;
  }

  if (leftWeight < rightWeight)
  {
    $("#pnlWeighPillsContainerLeft").css("justifyContent", "flex-start");
    $("#pnlWeighPillsContainerRight").css("justifyContent", "flex-end");


    return;
  }

  return;
},


Let's try this! Place pill 1 on the left and pill 2 on the right.

Then click WEIGH button. See that it now indicates that the scale has been used once! In all likelihood, both pills are equal in weight, so both scale bowls should sink.

Refresh. Now place pill 1 on the left and pill 2 on the right... and then add pills 3 and 4 on the left.

Now weigh. The left side will sink and the right side will rise! And now the display says you have used the scale once.

Now place the rest of the pills on the right side...

...and weigh. The right side should sink and the left should float. The display should say you have used the scale twice, and the button should disappear.

Now for the final part!

Feeding any given pill to the mouth. For this, add a click action to the face. It should call placePill(), with "mouth" passed in as an argument.
<div id="playerFace" onclick="pillgame.placePill('mouth')"></div>


There are only two scenarios here - success or failure. That depends on the weight of the pill selected. We derive that by using the value of pill_selected as an index into pills. And this leads into the two If blocks.
if (slot == "mouth")
{
  if (this.pills[this.pill_selected] == 1)
  if (this.pills[this.pill_selected] == 10)


  return;
}


If the pill is light, we use setPlayerState() to set the player state to "well". If it's a heavy pill, then it's poison and you just killed the patient. Either way, we run gameOver() because the game is effectively over at this point.
if (slot == "mouth")
{
  if (this.pills[this.pill_selected] == 1) this.setPlayerState("well");
  if (this.pills[this.pill_selected] == 10) this.setPlayerState("dead");
  this.gameOver();


  return;
}


Now for the final method, gameOver(). We basically return stuff to factory settings, visually.
gameOver: function()
{
  $("#btnWeigh").hide();
  $(".pnlWeighPillsContainer").css("justifyContent", "center");
  this.pills_middle = [];
  this.pills_left = [];
  this.pills_right = [];
  this.renderPills();

  return;

}


Now remove the red lines and we're all set.
div {outline: 0px solid red;}


Final test!

It's easy to fail. Select any pill at random and click on the face. There's an 87.5% chance that you kill the patient.

But to cure the patient is the crux of how this puzzle is solved. Place pills 1, 2 and 3 on the left. Pills 4, 5 and 6 go on the right.

Weigh. You see that Pills 1, 2 and 3 are lighter, so the cure is definitely in that group.

Return pills 4, 5 and 6.

Return pills 2 and 3.

Place pill 2 on the right side of the scale, and weigh. Looks like pill 2 is still heavier.

Which makes pill 1 the cure. Feed it to the face, and you win!


We're done! Not too shabby, eh?

Fare "well",
T___T

Saturday, 22 August 2026

Web Tutorial: Pill Puzzle (Part 3/4)

All right! Now that we have a mechanism by which to display pills, we need to enable placing and returning them via the interface. For this, let us add a click action to the DOM. It will run placePill(), with an argument describing which location to place the pill in.

<div id="pnlWeighPillsContainerLeft" class="pnlWeighPillsContainer">
  <div class="pnlWeighPills" id="pnlWeighPills_left" onclick="pillgame.placePill('left')">
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div><div class="bowl"></div></div>
  </div>        
</div>

<div class="scaleContainer">
  <div class="pnlScale">⚖</div>
  <div>Times Used: <span id="pnlTimesUsed"></span></div>
  <div><button id="btnWeigh" onclick="pillgame.weigh()">WEIGH</button></div>
</div>

<div id="pnlWeighPillsContainerRight" class="pnlWeighPillsContainer">
  <div class="pnlWeighPills" id="pnlWeighPills_right" onclick="pillgame.placePill('right')">
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div><div class="bowl"></div></div>
  </div>        
</div>


We'll work on the placePill() method next. It accepts a parameter, slot. We have the default return statement, and a guard clause that says if playerStatus is anything but "ill", nothing happens. Which makes sense; in those cases the game is over and nothing needs to happen.
placePill: function(slot)
{
  if (this.playerStatus != "ill") return;

  return;

},


Now let's have a few cases. The first case is for if no pill is selected; that's when we return the pill rather than placing it. The second case is for if slot is "mouth", which means a pill has been selected and we're about to feed it to the face at the bottom. The last two are "left" and "right", and those don't have curly brackets because they're one-line operations.
placePill: function(slot)
{
  if (this.playerStatus != "ill") return;

  if (this.pill_selected == -1)
  {
    return;
  }

  if (slot == "mouth")
  {
    return;
  }

  if (slot == "left")
  if (slot == "right")


  return;
},


Let's handle the last two cases first. If pill_selected is not -1, that means there is a pill selected. If that's the case, we run the unshift() method on the appropriate array to place the selected pill at the beginning of the array.
placePill: function(slot)
{
  if (this.playerStatus != "ill") return;

  if (this.pill_selected == -1)
  {
    return;
  }

  if (slot == "mouth")
  {
    return;
  }

  if (slot == "left") this.pills_left.unshift(this.pill_selected);
  if (slot == "right") this.pills_right.unshift(this.pill_selected);

  return;
},


After that, declare index, and check if pills_middle still has the pill selected. If it does, remove it using the splice() method. Then set pill_selected to -1 and run renderPills().
placePill: function(slot)
{
  if (this.playerStatus != "ill") return;

  if (this.pill_selected == -1)
  {
    return;
  }

  if (slot == "mouth")
  {
    return;
  }

  if (slot == "left") this.pills_left.unshift(this.pill_selected);
  if (slot == "right") this.pills_right.unshift(this.pill_selected);

  const index = this.pills_middle.indexOf(this.pill_selected);

  if (index !== -1) this.pills_middle.splice(index, 1);

  this.pill_selected = -1;
  this.renderPills();


  return;
},


Let's test this! Click START. Click on pill 3 and then on the left side. Does pill 3 appear on the left?

Now click on pill 4 and then on the right side. Does pill 4 appear on the right?

What if you then clicked on pill 7 and then on the left side?

Go on, add more pills on the left and right side of the scale. Notice how the pills disappear from the middle when you place them.

Now let's handle replacing the pills. If no pills are selected (meaning pill_selected is -1), running placePill() should result in running returnPill(). But I'm getting ahead of myself; let's begin with the first If block in the placePill() method. We establish two more If blocks in those, and an early return statement after. These check for the value of slot, and ensure that there are actually elements in the relevant array before taking any action.
if (this.pill_selected == -1)
{
  if (slot == "left" && this.pills_left.length > 0)
  {

  }

  if (slot == "right" && this.pills_right.length > 0)
  {

  }

  return;

}


Here, you set pill_selected as the first element of pills_left. Then you remove it from pills_left using the shift() method.
if (this.pill_selected == -1)
{
  if (slot == "left" && this.pills_left.length > 0)
  {
    this.pill_selected = this.pills_left[0];
    this.pills_left.shift();

  }

  if (slot == "right" && this.pills_right.length > 0)
  {

  }

  return;
}


With that, you run the returnPill() method (we'll build that soon) and then renderPills() to update the display.
if (this.pill_selected == -1)
{
  if (slot == "left" && this.pills_left.length > 0)
  {
    this.pill_selected = this.pills_left[0];
    this.pills_left.shift();
    this.returnPill();
    this.renderPills();

  }

  if (slot == "right" && this.pills_right.length > 0)
  {

  }

  return;
}


We perform the analogous operations for the opposite side.
if (this.pill_selected == -1)
{
  if (slot == "left" && this.pills_left.length > 0)
  {
    this.pill_selected = this.pills_left[0];
    this.pills_left.shift();
    this.returnPill();
    this.renderPills();
  }

  if (slot == "right" && this.pills_right.length > 0)
  {
    this.pill_selected = this.pills_right[0];
    this.pills_right.shift();
    this.returnPill();
    this.renderPills();

  }

  return;
}


Now, what does returnPill() do?
returnPill()
{
  return;
},


It places the selected pill back in pills_middle via the push() method. Once that is done, pill_selected becomes -1 again.
returnPill()
{
  this.pills_middle.push(this.pill_selected);
  this.pill_selected = -1;


  return;
},


Go on, try it. Reproduce what we did in the previous screenshot, then click on the left side of the scale. Does pill 7 get sent back to the middle?

We've been using a fair amount of JavaScript array methods here. If you feel like reading up further, here are some references.
- shift()
- unshift()
- push()
- splice()

Next

Weighing the pills and delivering the cure.

Wednesday, 19 August 2026

Web Tutorial: Pill Puzzle (Part 2/4)

Now that the HTML is largely out of the way, it's time to lay out the JavaScript skeleton.

This is pillgame. It's the overall game object that contains the properties and methods we'll be using.
<script>
  let pillgame =
  {

  };

</script>


Let's have some properties. playerStatus is a string (default value "ill") which acts as an index into the object playerStates. pill_selected tracks which pill is currently in selection, and a negative number just means that no pill is selected. pills, pills_middle, pills_left and pills_right are arrays. weighTimes is an integer that defaults to 0, and tracks how many times the scale has been used.
<script>
  let pillgame =
  {
    playerStatus: "ill",
    playerStates:
    {
  
    },
    pill_selected: -1,
    pills: [],
    pills_middle: [],
    pills_left: [],
    pills_right: [],
    weighTimes: 0

  };
</script>


Now you see what I mean by playerStatus being an index into playerStates. playerStates is an object that has three different objects - ill, well and dead. Each one has face, which is a HTML symbol, color and message.
<script>
  let pillgame =
  {
    playerStatus: "ill",
    playerStates:
    {
      "ill":
      {
        "face": "&#9785;",
        "color": "#004400",
        "message": "Slowly dying..."
      },
      "well":
      {
        "face": "&#9786;",
        "color": "#FFFF00",
        "message": "Cured! Congratulations!"
      },
      "dead":
      {
        "face": "&#9760;",
        "color": "#404040",
        "message": "Dead. Too bad!"
      }

    },
    pill_selected: -1,
    pills: [],
    pills_middle: [],
    pills_left: [],
    pills_right: [],
    weighTimes: 0
  };
</script>


Now for the arrays. pills is the array of 8 pills. The figures show their weights. They're all a 10. 10 pounds, 10 grams - it doesn't matter. It's just a number, and the point is that right now they all have the same number. pills_middle has 8 elements. They are the index references to the elements in pills. When the game starts, by default, the pills are in this slot. pills_left and pills_right are also placement arrays, but at the start of the game, they are empty.
<script>
  let pillgame =
  {
    playerStatus: "ill",
    playerStates:
    {
      "ill":
      {
        "face": "☹",
        "color": "#004400",
        "message": "Slowly dying..."
      },
      "well":
      {
        "face": "☺",
        "color": "#FFFF00",
        "message": "Cured! Congratulations!"
      },
      "dead":
      {
        "face": "☠",
        "color": "#404040",
        "message": "Dead. Too bad!"
      },
    },
    pill_selected: -1,
    pills: [10, 10, 10, 10, 10, 10, 10, 10],
    pills_middle: [0, 1, 2, 3, 4, 5, 6, 7],
    pills_left: [],
    pills_right: [],
    weighTimes: 0
  };
</script>


And after this, we have 9 methods.
<script>
  let pillgame =
  {
    playerStatus: "ill",
    playerStates:
    {
      "ill":
      {
        "face": "☹",
        "color": "#004400",
        "message": "Slowly dying..."
      },
      "well":
      {
        "face": "☺",
        "color": "#FFFF00",
        "message": "Cured! Congratulations!"
      },
      "dead":
      {
        "face": "☠",
        "color": "#404040",
        "message": "Dead. Too bad!"
      },
    },
    pill_selected: -1,
    pills: [10, 10, 10, 10, 10, 10, 10, 10],
    pills_middle: [0, 1, 2, 3, 4, 5, 6, 7],
    pills_left: [],
    pills_right: [],
    weighTimes: 0,
    weigh: function()
    {

    },
    setWeighTimes: function(times)
    {

    },
    setPlayerState: function(state)
    {

    },
    start: function()
    {

    },
    renderPills: function()
    {

    },
    selectPill: function(index)
    {

    },
    placePill: function(slot)
    {

    },
    returnPill()
    {

    },
    gameOver: function()
    {

    }

  };
</script>


We are going to start building with the method start(). First, set this method to call when the button is clicked. Make sure you add the id, btnStart, as well.
<button id="btnStart" onclick="pillgame.start()">START</button>


And here's the styling for that. I just made it ultra-big.
.bowl
{
  width: 60px;
  height: 20px;
  background-color: rgb(200, 200, 200);
  border-top: none;
  border-radius: 0 0 10px 10px;
  margin: 0 auto 0 auto;
}

#btnStart
{
  width: 10em;
  height: 3em;
  border-radius: 3px;        
  font-size: 2em;
}


We begin with a return statement at the end, which is probably unnecessary but I like to form the habit of. Then we run the methods setPlayerState() with the argument "ill" and setWeighTimes() with the argument 0. Because these are the beginning states.
start: function()
{
  this.setPlayerState("ill");
  this.setWeighTimes(0);

  return;

},


setPlayerState() takes state and sets playerStatus to that value. Then it populates playerFace with the face property value of the element in playerStates pointed to by state, and sets the appropriate color. It also similarly sets the content of playerMessage.
setPlayerState: function(state)
{
  this.playerStatus = state;
  $("#playerFace").html(this.playerStates[state].face);
  $("#playerFace").css("color", this.playerStates[state].color);
  $("#playerMessage").text(this.playerStates[state].message);

  return;

},


As for setWeighTimes(), we take times and set weighTimes to that value. Then we ensure that the content of pnlTimesUsed is times. If times is equal to 2, which means the scale can no longer be used, we hide btnWeigh,
setWeighTimes: function(times)
{
  this.weighTimes = times;
  $("#pnlTimesUsed").text(times);
  if (times == 2) $("#btnWeigh").hide();

  return;

},


Now what are pnlTimesUsed and btnWeigh? Right here. For btnWeigh, we should even make sure it runs weigh() when clicked.
<div class="scaleContainer">
  <div class="pnlScale">⚖</div>
  <div>Times Used: <span id="pnlTimesUsed"></span></div>
  <div><button id="btnWeigh" onclick="pillgame.weigh()">WEIGH</button></div>
</div>


The style for btnWeigh is here. It's mostly cosmetic, except that it's set to be invisible by default using the display property.
.bowl
{
  width: 60px;
  height: 20px;
  background-color: rgb(200, 200, 200);
  border-top: none;
  border-radius: 0 0 10px 10px;
  margin: 0 auto 0 auto;
}

#btnWeigh
{
  width: 5em;
  height: 2em;
  border-radius: 3px;
  display: none;
}


#btnStart
{
  width: 10em;
  height: 3em;
  border-radius: 3px;        
  font-size: 2em;
}


See what happens here. The START button is huge. The WEIGH button is missing.

Click the START button, and the deep green face comes on because we ran setPlayerState().

We continue! We'll carry on by displaying the btnWeigh button, and changing the text on btnStart to read "RESTART". And we use the css() method to ensure that the divs styled by pnlWeighPillsContainer have the justify-content property reset to center.
start: function()
{
  this.setPlayerState("ill");
  this.setWeightTimes(0);
  $("#btnWeigh").show();
  $("#btnStart").html("RESTART");
  $(".pnlWeighPillsContainer").css("justifyContent", "center");


  return;
},


Here, we reset pills to its original state... and then we randomly select one of these to be 1 instead of 10, making it the lightest (and the cure!).
start: function()
{
  this.setPlayerState("ill");
  this.setWeightTimes(0);
  $("#btnWeigh").show();
  $("#btnStart").html("RESTART");
  $(".pnlWeighPillsContainer").css("justifyContent", "center");

  this.pills = [10, 10, 10, 10, 10, 10, 10, 10];
  this.pills[Math.floor(Math.random() * 8)] = 1;


  return;
},


Then we reset the values for pills_middle, pills_left and pills_right, and pill_selected. We also run the renderPills() method, which we will get to next.
start: function()
{
  this.setPlayerState("ill");
  this.setWeightTimes(0);
  return;
  $("#btnWeigh").show();
  $("#btnStart").html("RESTART");
  $(".pnlWeighPillsContainer").css("justifyContent", "center");

  this.pills = [10, 10, 10, 10, 10, 10, 10, 10];
  this.pills[Math.floor(Math.random() * 8)] = 1;

  this.pills_middle = [0, 1, 2, 3, 4, 5, 6, 7];
  this.pills_left = [];
  this.pills_right = [];
  this.renderPills();
  this.pill_selected = -1;


  return;
},


We start by making sure all div tags styled with the CSS class pillSlot, are cleared. And of course, a return statement.
renderPills: function()
{
  $(".pillSlot").html("");

  return;

},


And then we iterate through pills_middle using a forEach loop.
renderPills: function()
{
  $(".pillSlot").html("");

  this.pills_middle.forEach
  (

  );


  return;
},


The callback uses x, which is the element, and i, which is the index.
renderPills: function()
{
  $(".pillSlot").html("");

  this.pills_middle.forEach
  (
    (x, i) =>
    {

    }

  );

  return;
},


In here, we declare pill as a div element, and style it using the CSS classes pill and topView. We also ensure that the text within has a number. Since we want this to be user-friendly, we're going to avoid index 0 and just add 1 to everything.
renderPills: function()
{
  $(".pillSlot").html("");

  this.pills_middle.forEach
  (
    (x, i) =>
    {
      const pill = $("<div></div>");
      pill.addClass("pill topView");
      pill.text(x + 1);

    }
  );

  return;
},


Remember each div within pillsContainer was styled using pillSlot? Well, right now, these are the slots for the elements in pills_middle. We reference each one, declaring slot, then append the div element pill to it.
renderPills: function()
{
  $(".pillSlot").html("");

  this.pills_middle.forEach
  (
    (x, i) =>
    {
      const pill = $("<div></div>");
      pill.addClass("pill topView");
      pill.text(x + 1);

      const slot = $("#pillsContainer .pillSlot")[i];

      $(slot).append(pill);

    }
  );

  return;
},


And we also add a click action to pill, calling selectPill() and passing in x as an argument.
renderPills: function()
{
  $(".pillSlot").html("");

  this.pills_middle.forEach
  (
    (x, i) =>
    {
      const pill = $("<div></div>");
      pill.addClass("pill topView");
      pill.text(x + 1);
      pill.on("click", () => {
        this.selectPill(x);
      });

      const slot = $("#pillsContainer .pillSlot")[i];

      $(slot).append(pill);
    }
  );

  return;
},


Here's some CSS for pill. Generally, we're setting font size and color.
.pnlWeighPills
{
  display: grid;
  grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
  gap: 1px;
}  

.pill
{
  font-family: sans-serif;
  font-size: 1em;
  font-weight: bold;
  color: rgba(0, 0, 0, 0.8);
}


.bowl
{
  width: 60px;
  height: 20px;
  background-color: rgb(200, 200, 200);
  border-top: none;
  border-radius: 0 0 10px 10px;
  margin: 0 auto 0 auto;
}


topView is the one with more meat. We want a circular div with a grey linear gradient background with a solid light grey outline. height is 15 pixels lower than width because we have a 15 pixel padding at the top.
.pnlWeighPills
{
  display: grid;
  grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
  gap: 1px;
}  

.pill
{
  font-family: sans-serif;
  font-size: 1em;
  font-weight: bold;
  color: rgba(0, 0, 0, 0.8);
}

.topView
{
  width: 50px;
  height: 35px;
  outline: 2px solid rgb(200, 200, 200);
  border-radius: 50%;
  background: linear-gradient(45deg, rgb(100, 100, 100), rgb(200, 200, 200));
  text-align: center;
  margin: 0 auto 0 auto;
  padding-top: 15px;
}


.bowl
{
  width: 60px;
  height: 20px;
  background-color: rgb(200, 200, 200);
  border-top: none;
  border-radius: 0 0 10px 10px;
  margin: 0 auto 0 auto;
}


There you go, 8 pills! All numbered, too!

Now, let's set the numbers to highlight if moused over. And turn to red if selected.
#pillsContainer
{
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
  gap: 5px;
  height: 100px;
}

#pillsContainer div:hover
{
  color: rgb(255, 255, 255);
}

#pillsContainer div.selected
{
  color: rgb(255, 0, 0);
}


.pillSlot
{
  cursor: pointer;
}


And for that, we continue on with working on the selectPill() method. The method accepts a parameter, index. Start with a return statement, then use the removeClass() method to remove the CSS class selected from all elements styled using the pill CSS class.
selectPill: function(index)
{
  $(".pill").removeClass("selected");

  return;

},


Set pill_selected to index. Then declare searchVal and set it to the value of index, plus 1. This is the value to search for in the DOM.
selectPill: function(index)
{
  $(".pill").removeClass("selected");
  this.pill_selected = index;
  var searchVal = index + 1;


  return;
},


Now we go through all elements that are styled using the CSS class pill, and return the one where its value from the text() method is the same as searchVal.
selectPill: function(index)
{
  $(".pill").removeClass("selected");
  this.pill_selected = index;
  var searchVal = index + 1;

  $(".pill").filter
  (
    function ()
    {
      return $(this).text().trim() == searchVal;
    }
  )


  return;
},


And use addClass() to add the class "selected".
selectPill: function(index)
{
  $(".pill").removeClass("selected");
  this.pill_selected = index;
  var searchVal = index + 1;

  $(".pill").filter
  (
    function ()
    {
      return $(this).text().trim() == searchVal;
    }
  )
  .addClass("selected");

  return;
},


Try mousing over pill number 4.

Now click on it! It turns red!

We may need to place pills on the left and right sides of the scale as well. Declare starting_index.
renderPills: function()
{
  $(".pillSlot").html("");

  var starting_index = 0;

  this.pills_middle.forEach


Because of the law of gravity, pills will always be rendered from the bottom of the pile rather than at the top. Unfortunate, in the collection of divs styled using the CSS class pillSlot within pnlWeighPills_left (which also corresponds to the array pills_left), the number starts at the top. So what do we do? We set starting_index as 8 (which is the total number of pill slots) less the actual number of pills in pills_left. Then we iterate through pills_left the same way we iterated through pills_middle.
renderPills: function()
{
  $(".pillSlot").html("");

  var starting_index = 0;

  starting_index = (8 - this.pills_left.length);
  this.pills_left.forEach
  (
    (x, i) =>
    {

    }
  );


  this.pills_middle.forEach
  (
    (x, i) =>
    {
      const pill = $("<div></div>");
      pill.addClass("pill topView");
      pill.text(x + 1);
      pill.on("click", () => {
        this.selectPill(x);
      });
      const slot = $("#pillsContainer .pillSlot")[i];

      $(slot).append(pill);
    }
  );

  return;
},


We create the div and insert it into slot. Except that now slot is defined as that div styled using CSS class pillSlot within pnlWeighPills_left, and we start at starting_index, plus whatever value i is at the moment. i is, of course, the index of the current element of pills_left. So if you're processing the first element of pills_left and pills_left has 3 elements, it will render inside the fifth element of pnlWeighPills_left. The second element of pills_left will render inside the sixth element of pnlWeighPills_left. And so on.
renderPills: function()
{
  $(".pillSlot").html("");

  var starting_index = 0;

  starting_index = (8 - this.pills_left.length);
  this.pills_left.forEach
  (
    (x, i) =>
    {
      const pill = $("<div></div>");
      pill.addClass("pill sideView");
      pill.text(x + 1);
      const slot = $("#pnlWeighPills_left .pillSlot")[starting_index + i];

      $(slot).append(pill);

    }
  );

  this.pills_middle.forEach
  (
    (x, i) =>
    {
      const pill = $("<div></div>");
      pill.addClass("pill topView");
      pill.text(x + 1);
      pill.on("click", () => {
        this.selectPill(x);
      });
      const slot = $("#pillsContainer .pillSlot")[i];

      $(slot).append(pill);
    }
  );

  return;
},


We'll do pretty much the same for pills_right and pnlWeighPills_right. Notice that in both cases, we use CSS class sideView rather than topView.
renderPills: function()
{
  $(".pillSlot").html("");

  var starting_index = 0;

  starting_index = (8 - this.pills_left.length);
  this.pills_left.forEach
  (
    (x, i) =>
    {
      const pill = $("<div></div>");
      pill.addClass("pill sideView");
      pill.text(x + 1);
      const slot = $("#pnlWeighPills_left .pillSlot")[starting_index + i];

      $(slot).append(pill);
    }
  );

  starting_index = (8 - this.pills_right.length);
  this.pills_right.forEach
  (
    (x, i) =>
    {
      const pill = $("<div></div>");
      pill.addClass("pill sideView");
      pill.text(x + 1);
      const slot = $("#pnlWeighPills_right .pillSlot")[starting_index + i];

      $(slot).append(pill);
    }
  );


  this.pills_middle.forEach
  (
    (x, i) =>
    {
      const pill = $("<div></div>");
      pill.addClass("pill topView");
      pill.text(x + 1);
      pill.on("click", () => {
        this.selectPill(x);
      });
      const slot = $("#pillsContainer .pillSlot")[i];

      $(slot).append(pill);
    }
  );

  return;
},


If you're wondering where the heck pnlWeighPills_right and pnlWeighPills_left came from, they're the ids for the parents of those divs.
<div class="pnlWeighPillsContainer">
  <div class="pnlWeighPills" id="pnlWeighPills_left">
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div><div class="bowl"></div></div>
  </div>
</div>

<div class="scaleContainer">
  <div class="pnlScale">⚖</div>
  <div>Times Used: <span id="pnlTimesUsed"></span></div>
  <div><button id="btnWeigh" onclick="pillgame.weigh()">WEIGH</button></div>
</div>

<div class="pnlWeighPillsContainer">
  <div class="pnlWeighPills" id="pnlWeighPills_right">
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div class="pillSlot"></div>
    <div><div class="bowl"></div></div>
  </div>
</div>


This is the styling for sideView.
.topView
{
  width: 50px;
  height: 35px;
  outline: 2px solid rgb(200, 200, 200);
  border-radius: 50%;
  background: linear-gradient(45deg, rgb(100, 100, 100), rgb(200, 200, 200));
  text-align: center;
  margin: 0 auto 0 auto;
  padding-top: 15px;
}

.sideView
{
  width: 50px;
  height: 20px;
  outline: 2px solid rgb(200, 200, 200);
  background: linear-gradient(90deg, rgb(100, 100, 100), rgb(200, 200, 200), rgb(100, 100, 100));
  margin: 0 auto 0 auto;
}


.bowl
{
  width: 60px;
  height: 20px;
  background-color: rgb(200, 200, 200);
  border-top: none;
  border-radius: 0 0 10px 10px;
  margin: 0 auto 0 auto;
}


We can test this with some temporary code here...
{
  this.setPlayerState("ill");
  this.setWeightTimes(0);

  $("#btnWeigh").show();
  $("#btnStart").html("RESTART");
  $(".pnlWeighPillsContainer").css("justifyContent", "center");

  this.pills = [10, 10, 10, 10, 10, 10, 10, 10];
  this.pills[Math.floor(Math.random() * 8)] = 1;

  this.pills_middle = [0, 1, 2, 3, 4, 5, 6, 7];
  this.pills_left = [];
  this.pills_right = [];
  this.pills_left = [0, 1, 5];
  this.pills_right = [2, 3, 6, 7, 4];

  this.renderPills();
  this.pill_selected = -1;

  return;
},


See?

Comment out the code we just wrote, or erase it. We've done what we set out to do - display pills.

Next

Placing and returning pills.