Monday, 26 May 2025

Software Review: GitHub Desktop

Around 2020, GitHub sent me a notice that they were upping security standards for their users. I was using SourceTree at the time for Version Control, and I would have to implement a SSH key-pair in order for my existing setup to continue working. Installing and generating SSH just seemed more trouble than I was willing to go to, so I did took what I saw as the Path of Least Resistance - I uninstalled SourceTree and installed GitHub Desktop.


Years have passed, and I've never looked back.

The Premise

GitHub Desktop is exactly what it sounds like - https://github.com but in a desktop app instead of a web browser. Or, to look at it in another way, like doing version control on your machine without using the CLI (which can be problematic if you're typo-prone) or the web browser (which can be clunky).

The Aesthetics

The color scheme is mostly mauve, black and white, with shades of red and green for text highlighting. In Dark Mode, the same colors are used but in different proportions. No complaints here.


The layout is mainly column based, with very little wasted space. From afar it can look cluttered, but after some usage, I'm come to the conclusion that it is actually very optimized.


The Experience

I'm speaking as someone who knows how to push changes and the basic functions of Version Control, but doesn't key in the commands in the CLI enough to qualify as a savant. In this review, you will probably see me use the word "intuitive" a lot when describing the interface, because it is.


Using GitHub Desktop was easier than using the web interface, and definitely easier than using the CLI.

The Interface

The application is comforting in its simplicity. We have a bar at the top for branching and repository switching, and the rest of the window is dedicated to committing and pushing changes. That's for the Changes tab.


The History tab tracks commits and merges.

GitHub Desktop had enough of the common functionality in menu items and buttons, that I didn't have to search that hard for stuff I needed to do.

What I liked

Simple enough to use even for myself, and I wouldn't call myself an advanced user. Basic things like Commit, Push, Pull, Stash et al are readily available and very well-placed. I did not need to refer to any documentation to be able to find these features.

Branching is pretty intuitive as well. Functionality related to that are all here, in one place!


Needless to say, so are Merging and Pull Requests. I don't do those all that often, but on the rare occasion I did, it was carried off without a hitch.

Adding a new repository is intuitive (there's that word again) and just about idiot-proof. I also love that this application doesn't simply assume that I know shit just because I'm using it, and bothers to give me explanatory notes in the interface.


Installation is uncomplicated. I installed this for both Mac and Windows systems, and each time GitHub Desktop ran smooth as silk. It seems ridiculous to name this as a perk, but not when you have multiple laptops using multiple platforms.

Generally, GitHub Desktop doesn't try to be everything. It limits functionality to what's absolutely necessary for operations and nothing more.

What I didn't

Logging in and out wasn't as simple as I thought it should be. The functionality is right here, when you select Settings from the main menu, but I feel like something as fundamental as logging in and out should be right there on every screen instead of being hidden.


You won't see certain dashboards as you would in the web version, but perhaps that's for the best. Still, it would have been nice to have those views available if we really wanted them.

Conclusion

GitHub Desktop is one terrific piece of software if using the CLI is intimidating (or just depressing) to you. You don't even need to master every aspect of it to be able to start using it somewhat effectively. It's a neat little tool, available on multiple platforms and safe to use.

My Rating

9 / 10

Let's Pull for GitHub Desktop!
T___T

Tuesday, 20 May 2025

Spot The Bug: Restricting the Date Field

Hello, readers! I hope you're primed for this next episode of Spot The Bug!

These damn bugs
are everywhere...

Back in October, I encountered this one. This was on a certain web form. Customers would key in certain details on the form to place an order, among which was a date input which they used to state the expected date of delivery. However, they should only be allowed to key in a date that was three days from the current date, in order to give the Operations and Logistics Team time to get things ready.

This was simple enough. In the form, the HTML5 input occupied these couple lines.
<b>Delivery Date: </b>
<input type="date" id="dtDelivery" name="dtDelivery" />


The client-side JavaScript code calculated the correct date and set the min attribute to that value. The min attribute would only accept dates in the format YYYY-mm-dd, which was why I had to separate out month and date values before recombining them.
function adjustMinDate(id)
{
    var dt = document.getElementById(id);
    var today = new Date(2024,11,30);
    today.setDate(today.getDate() + 3);

    var dayStr = (today.getDate() < 10 ? "0" + today.getDate() : today.getDate());
    var monthStr = today.getMonth() + 1;
    var dtStr = today.getFullYear() + "-" + monthStr + "-" + dayStr;

    dt.min = dtStr;
}


So, on 5th October, the user was only able to select dates from 8th October onward!


Looked simple enough. It worked, didn't it? Until it didn't.

What Went Wrong

However, at the end of the year, I was alerted by a concerned Operations and Logistics Team that customers had started sending them impossible delivery dates, which meant that the date input was no longer restricting users! When I checked, I was able to select dates even before the current date, which was 30th December.


Why It Went Wrong

I inspected the code at run-time, and this is what I found. The month value was missing a preceding zero.


Now this would not have been a problem in December - because 12 is a two-digit number and does not require a preceding zero. But once January arrived, it needed to be "2025-01-02" instead of "2025-1-02". Otherwise, the format would be invalid and the min attribute would fail quietly.

How I Fixed It

It was just a matter of adding a preceding zero to any month value that was less than 10.
var monthStr = (today.getMonth() + 1 < 10 ? "0" + (today.getMonth() + 1) : today.getMonth() + 1);


And now, in January, it worked! I could not select any date before the 4th of January.


Moral of the Story

Some things are time-sensitive. Including, well, time. Therefore it's possible something that performed perfectly a few days ago could fail later on.

T00dles for n0w,
T___T

Tuesday, 13 May 2025

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

The game is started via the start() method. So make sure that clicking the Start button runs this.
<button id="btnStart" class="topBtn" onclick="paprocksc.start()">Start</button>


In the start() method, we set the range and auto properties depending on the values of the ddlRange drop-down list and the cbAuto checkbox.
start: function()
{
  var ddlRange = document.getElementById("ddlRange");
  this.range = ddlRange.value;

  var cbAuto = document.getElementById("cbAuto");
  this.auto = cbAuto.checked;

},


Then we hide btnStart by adding the hidden CSS class to it, and show btnStop by removing the hidden CSS class.
start: function()
{
  var ddlRange = document.getElementById("ddlRange");
  this.range = ddlRange.value;

  var cbAuto = document.getElementById("cbAuto");
  this.auto = cbAuto.checked;

  var btnStart = document.getElementById("btnStart");
  var btnStop = document.getElementById("btnStop");

  btnStart.className = "topBtn hidden";
  btnStop.className = "topBtn";

},


And set the active property to true.
start: function()
{
  var ddlRange = document.getElementById("ddlRange");
  this.range = ddlRange.value;

  var cbAuto = document.getElementById("cbAuto");
  this.auto = cbAuto.checked;

  var btnStart = document.getElementById("btnStart");
  var btnStop = document.getElementById("btnStop");

  btnStart.className = "topBtn hidden";
  btnStop.className = "topBtn";
  this.active = true;
},


We then have an If block to handle the scenario of auto being true. For now, we will leave that alone, If auto is not true, however, we run showOptions().
start: function()
{
  var ddlRange = document.getElementById("ddlRange");
  this.range = ddlRange.value;

  var cbAuto = document.getElementById("cbAuto");
  this.auto = cbAuto.checked;

  var btnStart = document.getElementById("btnStart");
  var btnStop = document.getElementById("btnStop");

  btnStart.className = "topBtn hidden";
  btnStop.className = "topBtn";
  this.active = true;

  if (this.auto)
  {

  }
  else
  {
    this.showOptions();
  }

},


Now let's do stop(). This is run every time the Stop button is clicked, and also when the values in ddlRange are changed, and cbAuto is clicked.
<h1>Paper, Rock, Scissors</h1>
<select id="ddlRange" onchange="paprocksc.stop()">
  <option value="3">Classic</option>
  <option value="5">Extended</option>
</select>
<input type="checkbox" id="cbAuto" onclick="paprocksc.stop()">Auto

<button id="btnStart" class="topBtn" onclick="paprocksc.start()">Start</button>
<button id="btnStop" class="topBtn hidden" onclick="paprocksc.stop()">Stop</button>

<div class="gameContainer">


In here, we set range to 0 and run showOptions(). This effectively hides all the symbol buttons.
stop: function()
{
  this.range = 0;
  this.showOptions();

},


After that, we hide the Stop button and display the Start button.
stop: function()
{
  this.range = 0;
  this.showOptions();

  var btnStart = document.getElementById("btnStart");
  var btnStop = document.getElementById("btnStop");

  btnStart.className = "topBtn";
  btnStop.className = "topBtn hidden";

},


Then we change both user and computer symbols to show a question mark, by running showSymbol() twice, passing in an empty string for the second argument for both calls. We then set score to have all zeroes in the array, and run showScore() to show the zeroes.
stop: function()
{
  this.range = 0;
  this.showOptions();

  var btnStart = document.getElementById("btnStart");
  var btnStop = document.getElementById("btnStop");

  btnStart.className = "topBtn";
  btnStop.className = "topBtn hidden";

  this.showSymbol(0, "");
  this.showSymbol(1, "");

  this.score = [0, 0];
  this.showScore();

},


And if timer is not null, we run clearInterval() on it.
stop: function()
{
  this.range = 0;
  this.showOptions();

  var btnStart = document.getElementById("btnStart");
  var btnStop = document.getElementById("btnStop");

  btnStart.className = "topBtn";
  btnStop.className = "topBtn hidden";

  this.showSymbol(0, "");
  this.showSymbol(1, "");

  this.score = [0, 0];
  this.showScore();

  if (this.timer !== null) clearInterval(this.timer);
},


Now let's try this. Click the start button. Do the three buttons appear? Does the Start button disappear, only to be replaced by the Stop button?


Now select "Extended" in the drop-down list. Does the Stop button disappear, only to be replaced by the Start button? If you click Start, do all 5 buttons appear?


Now that we have a scenario where the symbol buttons appear, let's handle them being clicked.
<div class="dashboard">
  <button class="symbolBtn hidden" onclick="paprocksc.chooseSymbol('paper')"><img src="symbol_paper.jpg" width="80" /></button>
  <button class="symbolBtn hidden" onclick="paprocksc.chooseSymbol('rock')"><img src="symbol_rock.jpg" width="80" /></button>
  <button class="symbolBtn hidden" onclick="paprocksc.chooseSymbol('scissors')"><img src="symbol_scissors.jpg" width="80" /></button>
  <button class="symbolBtn hidden" onclick="paprocksc.chooseSymbol('spock')"><img src="symbol_spock.jpg" width="80" /></button>
  <button class="symbolBtn hidden" onclick="paprocksc.chooseSymbol('lizard')"><img src="symbol_lizard.jpg" width="80" /></button>
</div>


And we'll work on the chooseSymbol() method. It has a parameter, symbol. This is a string such as "rock, "paper", etc. The first thing we should do is check if active is false, and exit early if so. This means that when active is false, clicking on the buttons accomplishes nothing.
chooseSymbol: function(symbol)
{
  if (!this.active) return;
},  


There will be a scenario where symbol is an empty string. For now, create an If block for it, and cater to the Else condition. If symbol has a value, set the first element of the round array to that value. Then recursively call chooseSymbol(), this time with an empty string.
chooseSymbol: function(symbol)
{
  if (!this.active) return;

  if (symbol === "")
  {

  }
  else
  {
    this.round[0] = symbol;  

    this.chooseSymbol("");              
  }

},  


Thus, if chooseSymbol() is called with an empty string, we declare ptr and assign a random number to it, between 0 and range, excluding range. And then use it to point to an element in symbols, and assign that value to the second element in the round array. In effect, the computer makes a random choice! Use the return statement at this point, because then we're done for that scenario.
chooseSymbol: function(symbol)
{
  if (!this.active) return;

  if (symbol === "")
  {
    var ptr = Math.floor(Math.random() * this.range);
    this.round[1] = this.symbols[ptr].name;
    return;

  }
  else
  {
    this.round[0] = symbol;
    
    this.chooseSymbol("");              
  }
},  


Then we run the showSymbol() method twice, once for the user and once for the computer. And after that, we run whowins().
chooseSymbol: function(symbol)
{
  if (!this.active) return;

  if (symbol === "")
  {
    var ptr = Math.floor(Math.random() * this.range);
    this.round[1] = this.symbols[ptr].name;
    return;
  }
  else
  {
    this.round[0] = symbol;
    
    this.chooseSymbol("");              
  }

  this.showSymbol(0, this.round[0]);
  this.showSymbol(1, this.round[1]);
  this.whowins();

},  


We begin whowins() by setting the active property to false, to ensure that the symbol buttons can't be clicked during this time.
whowins: function()
{
  this.active = false;
},


Then we declare winner0 and winner1, both Booleans. winner1 is defined by passing the first and second elements of the round array into the xbeatsy() method. Thus winner0 is true if the user beats the computer, and false if not. The reverse is true for winner1. Note that the values are still false if it's a draw.
whowins: function()
{
  this.active = false;

  var winner0 = this.xbeatsy(this.round[0], this.round[1]);
  var winner1 = this.xbeatsy(this.round[1], this.round[0]);

},


We next have an If-else block. The first condition checks for a draw - meaning winner0 and winner1 are both false. In the Else block, we then have cases for if user beats computer or computer beats user.
whowins: function()
{
  this.active = false;

  var winner0 = this.xbeatsy(this.round[0], this.round[1]);
  var winner1 = this.xbeatsy(this.round[1], this.round[0]);

  if (!winner0 && !winner1)
  {

  }
  else
  {
    if (winner0)
    {

    }

    if (winner1)
    {

    }
  }

},


If it's a draw, we run showColor() twice, ensuring that both user and computer's circles have a grey outline.
whowins: function()
{
  this.active = false;

  var winner0 = this.xbeatsy(this.round[0], this.round[1]);
  var winner1 = this.xbeatsy(this.round[1], this.round[0]);

  if (!winner0 && !winner1)
  {
    this.showColor(0, "draw");
    this.showColor(1, "draw");

  }
  else
  {
    if (winner0)
    {

    }

    if (winner1)
    {

    }
  }
},


In the case of there being a winner, we display the correct colors too. And update the score array accordingly. At the end of it, because the score array was updated, we also run showScore().
whowins: function()
{
  this.active = false;

  var winner0 = this.xbeatsy(this.round[0], this.round[1]);
  var winner1 = this.xbeatsy(this.round[1], this.round[0]);

  if (!winner0 && !winner1)
  {
    this.showColor(0, "draw");
    this.showColor(1, "draw");
  }
  else
  {
    if (winner0)
    {
      this.score[0] ++;
      this.showColor(0, "win");
      this.showColor(1, "loss");

    }

    if (winner1)
    {
      this.score[1] ++;
      this.showColor(0, "loss");
      this.showColor(1, "win");

    }

    this.showScore();
  }
},


After that, we use the setTimeout() function, resetting the circle outlines to white and the symbols to question marks, and active to true, after 1 second.
whowins: function()
{
  this.active = false;

  var winner0 = this.xbeatsy(this.round[0], this.round[1]);
  var winner1 = this.xbeatsy(this.round[1], this.round[0]);

  if (!winner0 && !winner1)
  {
    this.showColor(0, "draw");
    this.showColor(1, "draw");
  }
  else
  {
    if (winner0)
    {
      this.score[0] ++;
      this.showColor(0, "win");
      this.showColor(1, "loss");
    }

    if (winner1)
    {
      this.score[1] ++;
      this.showColor(0, "loss");
      this.showColor(1, "win");
    }

    this.showScore();
  }

  setTimeout
  (
    ()=>
    {
      this.showSymbol(0, "");
      this.showSymbol(1, "");
      this.showColor(0, "end");
      this.showColor(1, "end");
      this.active = true;
    },
    1000
  );

},


Time to test this!

Click on Start. Select Scissors. The computer will randomly select its own symbol. In this case, it also selects Scissors, and it's a draw. See the grey outline?


Now select Rock. As you can see, the computer selects Paper and it's a loss. One point to computer.


Now select Paper. As you can see, the computer selects Rock and it's a win. One point to user.


Try selected Extended Mode. You'll see that the game stops immediately. And then select Lizard. Computer also selects Lizard, and it's a draw.


Now select Spock. Computer selects Paper. It's a loss! One point to Computer.


Auto Mode

We're going to write the code for Auto Mode now! This will be fun.

Remember in the start() method we checked if auto was true? Well, if so, we use setInterval() to run chooseSymbol() with "auto" passed in as an argument, every 1.5 seconds. To ensure that this can be disabled later with clearInterval(), we set this to the timer property.
start: function()
{
  var ddlRange = document.getElementById("ddlRange");
  this.range = ddlRange.value;

  var cbAuto = document.getElementById("cbAuto");
  this.auto = cbAuto.checked;

  var btnStart = document.getElementById("btnStart");
  var btnStop = document.getElementById("btnStop");

  btnStart.className = "topBtn hidden";
  btnStop.className = "topBtn";
  this.active = true;

  if (this.auto)
  {
    this.timer = setInterval
    (
      () =>
      {
        this.chooseSymbol("auto");
      },
      1500
    );

  }
  else
  {
    this.showOptions();
  }
},


In chooseSymbol(), we take that part where the first element of the round array is set to symbol, and encase it in the Else portion of an If-else block. The If block checks if symbol is "auto".
chooseSymbol: function(symbol)
{
  if (!this.active) return;

  if (symbol === "")
  {
    var ptr = Math.floor(Math.random() * this.range);
    this.round[1] = this.symbols[ptr].name;
    return;
  }
  else
  {
    if (symbol === "auto")
    {

    }
    else
    {

      this.round[0] = symbol;
    }
    
    this.chooseSymbol("");              
  }

  this.showSymbol(0, this.round[0]);
  this.showSymbol(1, this.round[1]);
  this.whowins();
},  


If it is, we write the code to randomly select a symbol for the user.
chooseSymbol: function(symbol)
{
  if (!this.active) return;

  if (symbol === "")
  {
    var ptr = Math.floor(Math.random() * this.range);
    this.round[1] = this.symbols[ptr].name;
    return;
  }
  else
  {
    if (symbol === "auto")
    {
      var ptr = Math.floor(Math.random() * this.range);
      this.round[0] = this.symbols[ptr].name;

    }
    else
    {
      this.round[0] = symbol;
    }
    
    this.chooseSymbol("");              
  }

  this.showSymbol(0, this.round[0]);
  this.showSymbol(1, this.round[1]);
  this.whowins();
},  


Now try checking Auto Mode and then clicking Start. You should see the game play by itself! The buttons aren't shown because you don't need them in Auto Mode.

That's about what I wrote for the coding test back then. Hopefully I've improved since!


Rock on!
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 A.I 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 A.I. 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.