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.

Sunday, 16 August 2026

Web Tutorial: Pill Puzzle (Part 1/4)

During some audio Social Media dialogue on the Clubhouse app, I overheard someone describe a puzzle. Something along the lines of - you've been poisoned and about to die. There are eight pills, and any one could be the cure. One is deadly poison. It's the lightest pill, but only by a fraction. You have a weighing scale you can use twice.

I thought about it, and I wanted to make this game. Except that I would switch things around a bit. One of the pills is the cure and the rest are poison. Now that would make things more deadly. And exciting!

We will be using jQuery for this, because I've come a long way the last ten years and sometimes I can't be arsed to deal with vanilla JavaScript. This is one of those times.

We start off with this HTML, ensuring that the jQuery library is included, and setting font size and background color to black. div tags will be outlined in red while we built the interface.
<!DOCTYPE html>
<html>
  <head>
    <title>Pill Puzzle</title>

    <style>
      body
      {
        font-size: 12px;
        font-family: verdana;
        text-align: center;
        background-color: rgb(0, 0, 0);
        color: rgb(200, 200, 200);
      }

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

    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

    <script>

    </script>
  </head>

  <body>

  </body>
</html>


We'll divide the interface into three divs and style them accordingly.
<body>
    <div class="topContainer">

    </div>

    <div class="middleContainer">

    </div>

    <div class="bottomContainer">

    </div>

</body>


The top is 400 pixels wide, the middle 600, and the bottom 500. All of them use the margin property to center themselves.
<style>
body
{
    font-size: 12px;
    font-family: verdana;
    text-align: center;
    background-color: rgb(0, 0, 0);
    color: rgb(200, 200, 200);
}

div {outline: 0px solid red;}

.topContainer
{
    width: 400px;
    margin: 0 auto 0 auto;
}

.middleContainer
{
    width: 600px;
    margin: 0 auto 0 auto;
}      

.bottomContainer
{
    width: 500px;
    margin: 0 auto 0 auto;
}

</style>


Part of this exercise is my current flirtation with grid and flexbox, as you'll soon see. I want this div to contain three columns, the middle one widest.
<div class="topContainer">
    <div>

    </div>

    <div>

    </div>

    <div>

    </div>

</div>


And if we do this, we ensure the middle column is the biggest, with a 20 pixel space between all three.
.topContainer
{
    display: grid;
    grid-template-columns: 1fr 2fr 1fr;
    gap: 20px;

    width: 400px;
    margin: 0 auto 0 auto;
}


In the first div, we go to its center div and add three more divs inside. In the first one, we have the scale symbol. The second one is used to display how many times the scale has been used. And the last one contains a button used for weighing.
<div class="topContainer">
  <div>
  
  </div>

  <div>
    <div>&#x2696;</div>
    <div>Times Used: </div>
    <div><button>WEIGH</button></div>

  </div>

  <div>
  
  </div>
</div>


This is just the start.

Now for the sides. Each of these will contain another div, and in turn will contain 9 more divs! These are placeholders for the pills. There are going to be 8 pills, so theoretically, there's a maximum of 8 placeholders on each side. The ninth one is the scale bowl at the bottom!
<div class="topContainer">
  <div>
    <div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
    </div>

  </div>

  <div>
    <div>&#x2696;</div>
    <div>Times Used: </div>
    <div><button>WEIGH</button></div>
  </div>

  <div>
    <div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
    </div>

  </div>
</div>


To really hammer home the visuals, we're going to do some styling here. The left and right divs will be styled using pnlWeighPillsContainer. The first immediate div within will be styled using pnlWeighPills. The middle div is styled using scaleContainer, and that first div within is styled using pnlScale.
<div class="topContainer">
  <div class="pnlWeighPillsContainer">
    <div class="pnlWeighPills">
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
    </div>
  </div>

  <div class="scaleContainer">
    <div class="pnlScale">&#x2696;</div>
    <div>Times Used: </div>
    <div><button>WEIGH</button></div>
  </div>

  <div class="pnlWeighPillsContainer">
    <div class="pnlWeighPills">
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
    </div>
  </div>
</div>


Now for the CSS. scaleContainer is a grid container that divides its content into three rows, with the top row getting the most space. pnlScale neccessitates that because we've set the font size to a large one. pnlWeighPillsContainer is a flexbox container where the alignment is vertical and by default, content is set to the middle. However, its immediate child, pnlWeightPills, is a grid with nine rows, each having equal billing.
body
{
  font-size: 12px;
  font-family: verdana;
  text-align: center;
  background-color: rgb(0, 0, 0);
  color: rgb(200, 200, 200);
}

div {outline: 1px solid red;}

.topContainer
{
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
  gap: 20px;
  width: 400px;
  margin: 0 auto 0 auto;
}

.middleContainer
{
  width: 600px;
  margin: 0 auto 0 auto;
}      

.bottomContainer
{
  width: 500px;
  margin: 0 auto 0 auto;
}  

.scaleContainer
{
  display: grid;
  grid-template-rows: 8fr 1fr 1fr;
  gap: 0px;
}

.pnlScale
{
  font-size: 12em;
}

.pnlWeighPillsContainer
{
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.pnlWeighPills
{
  display: grid;
  grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
  gap: 1px;
}


Here you see the scale! The left and right divs have a mess of divs bunched right in the middle. We'll work on that next.

In the last div of the divs inside each div styled using pnlWeighPills, insert a div with the CSS class bowl.
<div class="topContainer">
  <div class="pnlWeighPillsContainer">
    <div class="pnlWeighPills">
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div><div class="bowl"></div></div>
    </div>
  </div>

  <div class="scaleContainer">
    <div class="pnlScale">&#x2696;</div>
    <div>Times Used: </div>
    <div><button>WEIGH</button></div>
  </div>

  <div class="pnlWeighPillsContainer">
    <div class="pnlWeighPills">
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div><div class="bowl"></div></div>
    </div>
  </div>
</div>


This is bowl. It's a light grey div that has rounded corners at the bottom, with a fixed width and height.
.pnlWeighPills
{
  display: grid;
  grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
  gap: 1px;
}  

.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;
}


And now that one of the divs in the grid contains content that has fixed height and width, the rest of the divs follow suit!

We can leave the top portion alone for now. Let's work on the middle portion. We first have one div with the id pillsContainer. It in turn contains 8 div tags, one for each pill.
<div class="middleContainer">
  <div id="pillsContainer">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
  </div>

</div>


After that, have two more divs on new lines - ids playerFace and playerMessage.
<div class="middleContainer">
  <div id="pillsContainer">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
  </div>
  <br />
  <div id="playerFace"></div>
  <br />
  <div id="playerMessage"></div>

</div>


pillsContainer is another grid display, with 8 columns all with equal weight. For playerFace and playerMessage, I specified font size. For playerFace, the cursor property has been set to pointer because this is clickable.
.pnlScale
{
  font-size: 12em;
}

#pillsContainer
{
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
  gap: 5px;
  height: 100px;
}

#playerFace
{
  font-size: 7em;
  cursor: pointer;
}

#playerMessage
{
  font-size: 3em;
}


.pnlWeighPillsContainer
{
  display: flex;
  flex-direction: column;
  justify-content: center;
}


Speaking of clickable, let's also get this out of the way. All the div tags inside pillsContainer should be styled using the pillsSlot CSS class.
<div id="pillsContainer">
  <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>


Same for these inside the divs styled using the pnlWeighPills CSS class. Just the first eight, not the last one.
<div class="pnlWeighPills">
  <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>


In the CSS, just set pillSlot to be clickable.
#pillsContainer div.selected
{
  color: rgb(255, 0, 0);
}

.pillSlot
{
  cursor: pointer;
}


#playerFace
{
  font-size: 7em;
  cursor: pointer;
}


In any case, your middle portion should be taking shape now.


Now for the bottom part! This part is the easiest. You add some instructions, and then add a button that the user clicks when they're ready to play.
<div class="bottomContainer">
  <p>You are slowly dying. There are eight pills. One of them is the cure. The rest are lethal poison and will kill you instantly. They are all identical in size and shape, and color. However, the cure is slightly lighter than the rest. You may use the scale exactly two times before it breaks.</p>

  <p>Once you have decided which pill to take, select the pill and click on the face at the bottom. Good luck!</p>

  <button>START</button>

</div>


And here we go, that's basically the UI. There's more to it, of course, but we have the skeleton upon which the rest can be filled in.


Next

Populating with pills.

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

Monday, 3 August 2026

Working has different goals from working out

The last time I spoke about swimming laps in relation to software development, it was to discuss Brooks' Law. I've had a couple more insights recently, though those are more about work in general.

See, I'd gotten a bit smug about how little effort it took me to swim from Point A to Point B. I was averaging one stroke for every three that others were doing, but travelling at the same speed, or better. Then I took a closer look at the metrics I was using.

Getting smug in the water.

That's when I knew I'd lost sight of the big picture, and allowed work philosophy to cloud my judgement. Here I was, patting myself on the back for becoming a more efficient swimmer when it was never even the goal.

Working

Larry Wall summed it up elegantly when he said that laziness was a virtue in programmers. He could have meant it as a joke, but philosophically, this cuts to the heart of the matter. Work for the sake of work is not admirable, at least not in the corporate context. You are not paid to work. You are paid to get work done. You are paid to add value through your work. If you think otherwise, you're basically a (admittedly very industrious, good on ya mate) buffalo in the shape of a human being.

Beast of burden.

Thus it stands to reason that at work, you want to produce the most output for the least amount of effort. Both current and future effort. Which is why software developers try to write good code - not because writing clean elegant code is a virtue unto itself, but because code that conforms to those standards minimizes the amount of effort needed to maintain it down the road.

Therefore because I was travelling through water at the same speed as others while putting in less effort, I thought I was nailing it. I forgot that I was working out, not working.

Working out

What are the metrics for working out? You want to put on more muscle. You want to lose weight. You want better cardio-vascular conditioning.

Unless being an athlete is literally your job, you're not cycling primarily because you want to be a better cyclist, or swimming because you want to be a more efficient swimmer. No, when you're working out, your goals are very different. Burn calories. Get your heart rate up. When working out, doing more work is precisely the goal.

Now, at my age, putting on more muscle isn't a goal. Decades of staring at my abs have made the prospect less than interesting. Losing weight isn't that big a deal; as long as I can see my toes when standing, I'm good.

Heart health.

But cardio-vascular conditioning? Oh, absolutely. That's what I was swimming for in the first place. And because I was going at the same speed I've been doing for years, without putting in much effort, my heart rate wasn't going up at all. Which effectively meant that while I was in the water congratulating myself for having become better at swimming, I was pretty much wasting my time.

Conclusion

Swimming faster was never the goal. Swimming more efficiently was never the goal. Putting in more physical effort was always the goal. Not only did I take my eyes off the prize, I forgot what it looked like.

Mission Swimpossible?
T___T

Thursday, 30 July 2026

A Software Developer's Vacation In Singapore: Coast-to-Coast Trail Edition (Part 4/4)

In the weeks that followed, I would constantly revisit these sub-routes. I would do them again, and in reverse. The point was to get myself familiar with the entire route. Prepare myself for the day I attempted to the entire thing in one go.

And then the day came. I scheduled it, right on Good Friday, 3rd of April, and prayed it wouldn't rain.

In the morning, I had a McDonald's breakfast at Jurong East Mall, then headed off to Chinese Garden MRT where I prepped two bottles of water and a sandwich, and a Snickers bar from the Cheers store. Minimal prep, I know. That's me - minimal to a fault.

There, the Anywheel bike was rented, and I started off from Checkpoint One. Things were smooth-going much of the way; I was pretty familiar with the route by now, after having gone back and forth over it obsessively.

This time, I rode on the other side of Bukit Timah Road just for a more varied experience. This took me right past the Rochor River, and this was surprisingly beautiful. I might have caught it at a good time.

Rochor River was
quite the eyeful.

It was just before 12 noon when I arrived at Checkpoint Four. So far so good. I was feeling fine, and barely even breathing hard.

The First Break

I sat down at the Kheam Hock Park to eat my sandwich. The sky was overcast and I was concerned it might rain and cockblock my entire day. It was maybe ten minutes past noon.

Kheam Hock Park, right at the
end of a boardwalk.

I hadn't had time to really take a look at the tiny park. Charming. Good for a dog walk.

Once I reached the overhead bridge at Lornie Highway, I got over the bridge, passed MacRitchie just like the last time, and audibly groaned when I arrived at the construction going on at Bishan and Ang Mo Kio. Damn, I really hate this part.

Once I got that part behind me, the best part of the journey began - starting from Ang Mo Kio Linear Park to. the entire Waterway stretch at Punggol. I took some time to settle in and eat my Snickers bar.

Huddling under this bridge
for my Snickers break.

Once done, I was on my way again, passing Checkpoint Ten at Coney Island without bothering to stop. The sun was blazing but I soldiered on out of the beautiful cycling paths and onto the hellish landscape of Punggol Tingkat.

By this time, my ass was killing me and my right leg had gone numb. I was conscious of a light but persistent burning sensation on the back of my neck. When I finally crossed from Punggol Barat back to the mainland and was greeted by the sight of the entrance to Rower's Bay Park, it was like water in a desert.

Rower's Bay Park entrance.

The sun was high in the sky, and it was making the water sparkle. The beauty of the sight was indescribable. I'd just completed the Coast-to-Coast Trail, and it was glorious.

The alternative route from Checkpoint Seven

On a separate occasion, just out of curiosity, I returned to Checkpoint Seven at Sengkang Swimming Complex, to explore the alternate route to Checkpoint Ten. This was actually the Sengkang Floating Wetland, a patch of swamp that was somehow eerily beautiful in the day. I had to dismount from the bicycle and push.

The bridge over the wetland.

Nice park after the wetland.

The route led on to Sengkang Riverside Park, which I largely skirted to embark on a long stretch of road leading west, parallel to the Tampines Expressway.

Road leading out of the park
and under a flyover.

Long stretch ahead
next to TPE.

After some riding, I arrived at the Seletar Aerospace Flyover, and headed north again along Seletar Aerospace Way, right up until I came to this idyllic green patch called The Oval

Very nicely manicured
infrastructure at The Oval.

I was pretty much winging it at this point, with a rough idea as to which direction to my destination, but no real clue as to the paths I needed to travel to get there.

That final stretch!

Aerospace greenery. Pretty fly!

The next twenty minutes saw me meander around the aerospace until I discovered Seletar West Link, after which I simply rode the green stretch all the way to the welcoming entrance of Rower's Bay Park.

Thoughts on this route

Now this was underwhelming, and I suspect this was because I really didn't know the official route. In fact, part of the problem might be that this part of the Coast-to-Coast Trail simply wasn't meant for cycling. It was akin to trying to find semicolons in a Python code base. Sure, you'll probably be able to find them, but they won't mean the same thing to you especially if you cut your teeth on Java.


I don't feel like revisiting this part anytime soon, though the other parks around the area do look promising for exploration.

Epilogue

This little foray into cycling in Singapore, would soon expand into a more extended exploration of similar trails in Singapore. The fact that I had also quit smoking at this point, also meant that I was able to spend more time in these parks.

This was a side of Singapore I hadn't appreciated up to now. And I intend to remedy that.

Where there's a wheel, there's a way!
T___T