Tuesday, 30 January 2024

Web Tutorial: Year of the Dragon Animation

The Dragon roars in 2024, and we have a web tutorial for it!

Today, we're going to use jQuery UI to create a simple CNY-themed animation. For this, we will use the antique Chinese character for "dragon", shown below, pronounced "long". This will only be used as a template, and you can get rid of it later.

dragon.png


For this, we have some HTML that will have a remote link to the appropriate jQuery libraries. The body's background is set to white for now, in the CSS.
<!DOCTYPE html>
<html>
  <head>
    <title>Year of the Dragon</title>

    <style>
      body
      {
        background-color: rgb(255, 255, 255);
      }
    </style>

    <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

    <script>

    </script>
  </head>

  <body>

  </body>
</html>


Here we add a div with the id container, within the body. In the CSS, we style container with a 500 pixel width and 650 pixels height (the exact size of the image), and some margins. Most importantly, we set the background image.
<!DOCTYPE html>
<html>
  <head>
    <title>Year of the Dragon</title>

    <style>
      body
      {
        background-color: rgb(255, 255, 255);
      }

      #container
      {
        background: url(dragon.png);
        width: 500px;
height: 650px;
        margin: 10px auto 0 auto;
      }

    </style>

    <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

    <script>

    </script>
  </head>

  <body>
    <div id="container">

    </div>

  </body>
</html>


There, an immediate impact.


We are now going to fill this div with tiny square divs, using JavaScript. In the script tag, create cny as an object.
<script>
  let cny =
  {

  };

</script>


From here, we create the grid array. At some point, it will be a two-dimensional array. But let's leave it as an empty array for now.
<script>
  let cny =
  {
    grid: []
  };
</script>


And then create the fillGrid() method.
<script>
  let cny =
  {
    grid: [],
    fillGrid: function()
    {

    }

  };
</script>


In here, we traverse the length of grid.
fillGrid: function()
{
  for(var i = 0; i < this.grid.length; i++)
  {

  }

}


We create a div, give it a class of row, and then append it to container. Pretty straightforward so far.
fillGrid: function()
{
  for(var i = 0; i < this.grid.length; i++)
  {
    var row = $("<div></div>");
    row.addClass("row");

    $("#container").append(row);

  }
}


In the CSS, we want row to be 10 pixels in height and fill the entire width. For now, let's also give it a red outline.
#container
{
  background: url(dragon.png);
  width: 500px;
  height: 650px;
  margin: 10px auto 0 auto;
}

.row
{
  height: 10px;
  width: 100%;
  outline: 1px red;
}


Now we're going to give grid 65 sub-arrays. The height of its parent is 650 pixels, and each div styled using row is 10 pixels... do the math!
grid:
[
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  []

],


And change the body tag to include an onload attribute that calls fillGrid().
<body onload = "cny.fillGrid();">


The code executes! Now you have 65 divs, styled using the CSS class row, within container!


Now we will fill the rows with squares. In the For loop, add another For loop that traverses the length of each current element. Right now all of them are empty arrays, of course, but we will change that soon.
fillGrid: function()
{
  for(var i = 0; i < this.grid.length; i++)
  {
    var row = $("<div></div>");
    row.addClass("row");

    for(var j = 0; j < this.grid[i].length; j++)
    {

    }


    $("#container").append(row);
  }
}


In here, again we create a div and assign it to the variable square. We then give square the CSS class of, well, square. And append square to row.
fillGrid: function()
{
  for(var i = 0; i < this.grid.length; i++)
  {
    var row = $("<div></div>");
    row.addClass("row");

    for(var j = 0; j < this.grid[i].length; j++)
    {
      var square = $("<div></div>");
      square.addClass("square");

      row.append(square);

    }

    $("#container").append(row);
  }
}


Now this is what the CSS class for square looks like. It has both height and width properties set to 10 pixels, is floated left, and we'll give it a red outline also.
#container
{
  background: url(dragon.png);
  width: 500px;
  height: 650px;
  margin: 10px auto 0 auto;
}

.row
{
  height: 10px;
  width: 100%;
  outline: 1px solid red;
}

.square
{
  width: 10px;
  height: 10px;
  outline: 1px solid red;
  float: left;
}


We change grid this way. Make the first element an array of 50 zeroes.
grid:
[
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  []
]


See the top row? It has 50 squares!


Now do the same for all of grid.
grid:
[
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
],


And this is what you end up with.


EDITOR'S NOTE: Due to my laptop finally giving up the ghost while I was in the middle of writing this web tutorial, subsequent screenshots will be taken from a MacBook.

We will next have more CSS classes! They will be shade0, shade1 and shade2. All of these have an orange background color, but with different opacity. shade3 will be almost transparent, shade2 will be half-opaque, and shade1 will be fully opaque.
.square
{
  width: 10px;
  height: 10px;
  outline: 1px solid red;
  float: left;
}

.shade1
{
  background-color: rgba(255, 155, 0, 1);
}

.shade2
{
  background-color: rgba(255, 155, 0, 0.5);
}

.shade3
{
  background-color: rgba(255, 155, 0, 0.2);
}


Add this line. Depending on the value inside grid (it's all 0 right now), either shade0, shade1, shade2 or shade3 will be the CSS class added to the div. If it's shade0, there'll be no change because there is no such class.
fillGrid: function()
{
  for(var i = 0; i < this.grid.length; i++)
  {
    var row = $("<div></div>");
    row.addClass("row");

    for(var j = 0; j < this.grid[i].length; j++)
    {
      var square = $("<div></div>");
      square.addClass("square");
      square.addClass("shade" + this.grid[i][j]);

      row.append(square);
    }

    $("#container").append(row);
  }
},


Make this change to the second array of the grid array.
grid:
[
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],


You should see the faint orange fill in the second row from the top of the grid.


Great, and now we fill up a couple more rows.
grid:
[
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,3,2,2,2,1,1,1,1,1,1,1,1,2,0,0,0,0,0,1,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],


Here you should have a pretty good idea where the 1s, 2s and 3s go.


Let's fill up more rows.
grid:
[
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,3,2,2,2,1,1,1,1,1,1,1,1,2,0,0,0,0,0,1,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,3,0,0,0,0,0,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,1,1,1,2,3,3,2,2,2,2,2,3,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,3,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,2,0,0,0,0,0,0,0,0],


And remove the red outline for the CSS classes row and square.
.row
{
  height: 10px;
  width: 100%;
  outline: 0px solid red;
}

.square
{
  width: 10px;
  height: 10px;
  outline: 0px solid red;
  float: left;
}


I think the picture is getting clearer...


OK, now fill up the rest of grid. You should have a pretty good idea what values to use. Then remove the image.
#container
{
  /*background: url(dragon.png);*/
  width: 500px;
  height: 650px;
  margin: 10px auto 0 auto;
}


And there you are.


Now to animate it!

What we want to do is run a method call when the page loads. Let's call it start(). It will accept a numeric value which determines the speed of the animation. Right now let's set it at 1 second, or 1000 milliseconds.
<body onload = "cny.fillGrid();cny.start(1000);">


Here's the method. The numeric value will be known as the parameter delay.
fillGrid: function()
{
  for(var i = 0; i < this.grid.length; i++)
  {
    var row = $("<div></div>");
    row.addClass("row");

    for(var j = 0; j < this.grid[i].length; j++)
    {
      var square = $("<div></div>");
      square.addClass("square");
      square.addClass("shade" + this.grid[i][j]);

      row.append(square);
    }

    $("#container").append(row);
  }
},
start: function(delay)
{

}


In here, we run the method changeColor(), and pass in delay as an argument.
start: function(delay)
{
  cny.changeColor(delay);
}


Create the changeColor() method. Make sure it has the parameter delay.
fillGrid: function()
{
  for(var i = 0; i < this.grid.length; i++)
  {
    var row = $("<div></div>");
    row.addClass("row");

    for(var j = 0; j < this.grid[i].length; j++)
    {
      var square = $("<div></div>");
      square.addClass("square");
      square.addClass("shade" + this.grid[i][j]);

      row.append(square);
    }

    $("#container").append(row);
  }
},
changeColor: function(delay)
{

},

start: function(delay)
{
  cny.changeColor(delay);
}


Now iterate from values 1 to 3 in a For loop.
changeColor: function(delay)
{
  for (var i = 1; i <= 3; i++)
  {
          
  }

},


In it, we use the each() method on all elements that have the CSS class shade1, shade2 or shade3.
changeColor: function(delay)
{
  for (var i = 1; i <= 3; i++)
  {
    $(".shade" + i).each(

    ); 
           
  }
},


Here, we'll create a value randomly smaller than delay, and call it newDelay.
changeColor: function(delay)
{
  for (var i = 1; i <= 3; i++)
  {
    $(".shade" + i).each(
      function( index )
      {
        var newDelay = delay - ((Math.floor(Math.random() * delay) + 1) / 2) ;
      }

    );            
  }
},


Then we'll call the changeColorForObj() method, passing in the current object, the value of i and newDelay as arguments.
changeColor: function(delay)
{
  for (var i = 1; i <= 3; i++)
  {
    $(".shade" + i).each(
      function( index )
      {
        var newDelay = delay - ((Math.floor(Math.random() * delay) + 1) / 2) ;
        cny.changeColorForObj(this, i, newDelay);

      }
    );            
  }
},


Create the changeColorForObj() method.
changeColor: function(delay)
{
  for (var i = 1; i <= 3; i++)
  {
    $(".shade" + i).each(
      function( index )
      {
        var newDelay = delay - ((Math.floor(Math.random() * delay) + 1) / 2) ;
        cny.changeColorForObj(this, i, newDelay);
      }
    );            
  }
},
changeColorForObj: function(obj, index, delay)
{

},

start: function(delay)
{
  cny.changeColor(delay);
}


In here, we use the animate() method on obj to change the background color, with an animation speed of delay.
changeColorForObj: function(obj, index, delay)
{
  $(obj).animate
  (
    {
      backgroundColor:
    },
    delay
  );

},


We determine the new background color by running the getColor() method. Pass in the value of index, and false, as arguments.
changeColorForObj: function(obj, index, delay)
{
  $(obj).animate
  (
    {
      backgroundColor: cny.getColor(index, false)
    },
    delay
  );
},


Create the getColor() method. It has two parameters - an integer, transparency, and a Boolean value, original.
changeColorForObj: function(obj, index, delay)
{
  $(obj).animate
  (
    {
      backgroundColor: cny.getColor(index, false)
    },
    delay
  );
},
getColor: function(transparency, original)
{

},

start: function(delay)
{
  cny.changeColor(delay);
}


In here, we define an array, t. It holds the opacity values for shade1, shade2 and shade3. At index position 0, we have 0 because it's required. But we won't ever use it because the value 0 will never be passed into getColor() as the value of transparency. Next, useT is defined as the element in t that transparency points to.
getColor: function(transparency, original)
{
  var t = [0, 1, 0.5, 0.2];
  var useT = t[transparency];

},


At the end of the method, return a CSS color string using the rgba() specification and useT as the opacity. But before that, we check if original is false and useT is greater than 0.
getColor: function(transparency, original)
{
  var t = [0, 1, 0.5, 0.2];
  var useT = t[transparency];

  if (!original)
  {
    if (useT > 0)
    {

    }
  }


  return "rgba(rgba(255, 155, 0, " + useT + ")";
},


If so, define subtract as a random number between 0.1 and 1. Subtract that value from useT, and set useT back to 0 if the value falls below that. What this does, is randomly decrease opacity of that element we are working on!
getColor: function(transparency, original)
{
  var t = [0, 1, 0.5, 0.2];
  var useT = t[transparency];

  if (!original)
  {
    if (useT > 0)
    {
      var subtract = (Math.floor(Math.random() * 10) + 1) / 10;
      useT -= subtract;
      if (useT < 0) useT = 0;

    }
  }

  return "rgba(rgba(255, 155, 0, " + useT + ")";
},


Back to the changeColorForObj() method. We use setTimeout() to introduce a delay using delay, but doubled.
changeColorForObj: function(obj, index, delay)
{
  $(obj).animate
  (
    {
      backgroundColor: cny.getColor(index, false)
    },
    delay
  );

  setTimeout(
    function()
    {

    },
    delay * 2
  );

},


Then we run the animate() method on obj again, this time passing in true as an argument, so that it resets the opacity value back to the original for shade1, shade2 and shade3! Again, we use delay doubled as the animation speed.
changeColorForObj: function(obj, index, delay)
{
  $(obj).animate
  (
    {
      backgroundColor: cny.getColor(index, false)
    },
    delay
  );

  setTimeout(
    function()
    {
      $(obj).animate
      (
        {
          backgroundColor: cny.getColor(index, true)
        },
        delay * 2
      );

    },
    delay * 2
  );
},


Run this! You should see the character appear in all its glory, fade out at random spots, then fade right back in!


To make this continuous, use setInterval() to run changeColor(), with with a triple delay! If you run this again, you'll see that there's a shimmering effect because the opacity is random every time! Brilliant, right?
start: function(delay)
{
  cny.changeColor(delay);

  setInterval(
    function()
    {
      cny.changeColor(delay);
    },
    delay * 3
  )

}


Final touches...

Now change the background to black.
body
{
  background-color: rgb(0, 0, 0);
}


Now the shimmering gets even more obvious!


It's time to add some text. In the body, add another div with id of text. In it, let's have some HTML, maybe a paragraph tag.
<body onload = "cny.fillGrid();cny.start(1000);">
  <div id="container">

  </div>

  <div id="text"><p>Year Of The Dragon<br />2024</p></div>
</body>


Here's the styling for text. We want it to fill the entirety of its parent, which is the body tag. For that, we also need the position property to be absolute, and have the left and top properties set to 0.
.shade1
{
  background-color: rgba(255, 155, 0, 1);
}

.shade2
{
  background-color: rgba(255, 155, 0, 0.5);
}

.shade3
{
  background-color: rgba(255, 155, 0, 0.2);
}

#text
{
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0;
}


Here's the styling for the paragraph tag within text. We set margin-top to move the paragraph tag down to the middle of the screen, and use text-align to center it. The rest of it is basically aesthetics - to make the text stand out.
#text
{
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0;
}

#text p
{
  font-family: georgia;
  font-weight: bold;
  font-size: 4em;
  color: rgb(200, 0, 0);
  text-shadow: 2px 2px rgba(200, 200, 200, 0.5);
  font-variant: small-caps;
  margin-top: 500px;
  text-align: center;
}


And that's your animation!


Happy Year of the Dragon!

I hope this bold funky animation is just the first of many good things this year for us. It's been a pleasure!

Live Long and prosper!
T___T

Tuesday, 23 January 2024

Five Phases of Programming I Went Through

As tech evolves, so does the growth of every software developer. Every programmer's evolution varies to a large extent dependent on their stack, their industry experiences and their personality.

My own journey has been no less.

Today I would like to chronicle, in philosophical phases, my own development from way back in 2015 as a student.

1. 1996 to late 2000s

This was me in school. I was a neat freak about code, at least where indentation and spacing were concerned. I tended to put a lot of code on one line, not because I was trying to look clever, but because I was lazy. And I had tons of comments because my lecturers trained me that way. When I got out into professional society, no one reviewed my code, so whatever habits I had, both good and bad, continued well into the mid-2000s.

Being very tidy.

At this point, too, I felt like knowing more programming languages increased my tech cred. That's complete rubbish, of course, but I was young. I was still hungrily learning, though, and that's always a good thing regardless of the source of my motivation.

Most of my expertise revolved around querying data from a database and displaying it on a web page, and perhaps some extremely rudimentary JavaScript manipulation. Nothing very groundbreaking, but it did constitute the majority of most use cases presented to me.

2. Late 2000s to early 2010s

The next phase of my career development manifested itself as a slogfest. I had rediscovered my passion for writing code after several years of wasting my life away in Desktop Support. I prided myself on how much I worked, and how hard. I didn't think of myself as a clever worker. I thought of myself as a hard worker. In fact, grinding became the focus of my existence.

I measured myself by lines of code produced. I was consistent for the sake of being consistent instead of tailoring approaches to individual cases. I wasn't really into code libraries; there was a serious case of Not Invented Here going on, and I really wanted to create every gadget, every component, myself.

Rudimentary building.

Was that wise?

Not really. In fact, in hindsight, it sounds pretty damn stupid. But was that neccessary? I suspect so. It was another phase of my development. Whatever else followed after this, might not have come without getting through this first.

On the other hand, putting that much time into trying to create every component myself, really upped my coding game, simply via repetition and research.

3. Early 2010s to late 2010s

Around this time, I started getting comfortable with the concepts of frameworks and libraries. Sure, it was good to have the skills to create those frameworks and libraries, but realistically, that would simply have the effect of slowing me down when there were so many more exciting things to learn. I came to terms with the fact that I would never be as good as I really wanted to be; simply because my time on this earth is limited.

Getting more education around that point helped me pick up more languages and technologies. This was the Specialist Diploma in Mobile Apps Development. I stopped trying to do everything in Notepad (or a plain text editor) and picked up the tools to make myself current. Why put myself at a disadvantage? Some sense of misplaced pride in being a caveman? This was tech, and I needed to get with the program.

The caveman way.

This was the period I picked up a whole lot of frameworks and programming languages, experimented madly, and dipped my toe in everything exciting about web and mobile development just so I could have a taste. This was probably where I had the most fun. After letting go of the need to excel, and learn everything thoroughly, I started learning just enough to do whatever I envisioned. Sometimes it was less than what was taught in school, but often, it was more. I had to do my own research to be able to accomplish the things I wanted, and it improved me immeasurably.

I learned things just for the fun of it, instead of doing it because I felt I needed to.

4. Late 2010s to early 2020s

A strange period, to be sure. Most of what I learned was in the form of JavaScript frameworks and libraries, but they were very disparate. I learned JavaScript frameworks. At the same time, I picked up data visualization libraries, and animation libraries. Around this time, I learned SVGs as well. Loads of fun.

More advanced construction.

This part seems more like an extension of the earlier phase, except that it had become less about the different number of platforms, but more about the different kinds of functionality. I had reached the point where learning new programming languages was cool and all, but what I really wanted to do was learn to do new things. As opposed to learning to do the same things in new ways. Which is valuable as well, don't get me wrong, but I was eager to move on.

Since I was working with animation and processing-heavy frameworks, much of my efforts were geared towards improving efficiency, as opposed to just getting things to work.

Professionally, as I was doing work for the Singapore Government at that time, I gained a whole new appreciation for REST APIs.

5. Early 2020s

This was the period of my life where I threw out all the stuff I'd obsessed over before, or just felt bad about not doing enough - code extensibility, code cleanliness, tidiness and so on - in favor of expediency. Yes, plenty of developers would turn their noses up at this, but they aren't the ones paying my bills, so, y'know, fuck 'em with an If-else block.

Doesn't have to be pretty,
but it does have to work.

This materialized because I was now working for a non-tech company where my employers didn't care how clean and clever my code was. My KPI was to deliver requirements, and deliver them yesterday. Now, we can argue forever about whether this is right or wrong, but requirements are requirements. And if I have a problem getting paid to deliver what programming purists would call subpar code, I have the absolute discretion to bugger off and work somewhere else.

But I didn't. Because the rise of AI has ensured that no one gives a rat's ass about the quality of your code, or even just your code period, when they can just ask ChatGPT for it.

Also, I wound up writing significantly less code than I was used to. Most of my code connected to REST endpoints that interfaced with other systems that did the bulk of the work. A lot of it was out of my hands. I was more like a facilitator and vendor coordinator than I was a hands-on programmer. There no longer was any one single platform I could reply on. I used whatever I was provided that would do what my employers needed.

Conclusion

This was an awfully simplistic look at my evolution as a software developer through the last couple decades. There is, of course, a whole lot of nuance that did not make it into this listicle because I detest being a windbag. The evolution continues... or at least, I really hope it does. There's so much left to learn, so many ways one could evolve.

Your growing software dev,
T___T

Friday, 19 January 2024

Branching With Switch Statements

Decision blocks constitute a large part of programming. Much of the time, when someone mentions a decision in programming, the first thing to come to mind is an If block. And indeed, If blocks are amazingly versatile, able to handle just about all decision-making requirements in programming.

However, there are other options. And I would like to talk about Switch statements, which, while not quite as versatile as If statements, are better in certain use cases.

The Anatomy of a Switch statement

We begin a Switch statement with the keyword. In the case of C++ (and JavaScript, and Java) it's "switch". It then includes the variable, x, in parentheses, and the curly braces.
switch (x)
{

}


Assuming that the value of x is an integer, we populate the Switch statement with cases using the case keyword.
switch (x)
{
    case 1:
    case 10:
    case 35:
}


Optionally, there's a default case in the case that the value of x is not 1, 10 or 35.
switch (x)
{
    case 1:
    case 10:
    case 35:
    default:
}


If x is 1, we run the function a(). If x is 10, we run the function b(). If x is 35, we run the function c(). If it's none of the above, we run the function a(). Now that I say it like that, it sounds like this could be a multiple If statement block. However...
switch (x)
{
    case 1:
        a();
    case 10:
        b();
    case 35:
        c();
    default:
        a();
}


... we need break statements if we just want this to operate like a multiple If statement block. We'll get back to this soon.
switch (x)
{
    case 1:
        a();
        break;
    case 10:
        b();
        break;
    case 35:
        c();
        break;
    default:
        a();
        break;
}


In the meantime, this is what the same code would look like in PHP. Other than the mandatory dollar sign in front of the variable name, very little has changed.
switch ($x)
{
    case 1:
        a();
        break;
    case 10:
        b();
        break;
    case 35:
        c();
        break;
    default:
        a();
        break;
}


In Python, switch has been replaced by match, with no parentheses needed. This being Python, no semicolons are needed either. And Python automatically assumes you want a break statement, which may or may not be such a great idea.
match x:
    case 1:
        a()

    case 10:
        b()

    case 35:
        c()

    case -:
        a()


The anatomy of the Switch statement, barring keyword and syntax differences across these languages, are astonishingly similar. Some languages, like Python, omit the break statement altogether, in favor of a break by default.

The Flow-through

The break statement tells the compiler (or interpreter) that after executing the code associated with that particular case, execution should resume outside of the Switch statement. For the vast majority of programmers, that is what is intended. However, when the break statement is excluded, execution goes onto the next case (whether or not the case is a match), and so on until a break statement is encountered.

This is what is known as a "Flow-through".

How useful is this? Well, in the case below, if you want the code a() and b() to execute when x is 1, and only b() to execute when x is 2, then yes this would work.
switch (x)
{
    case 1:
        a();
    case 2:
        b();
        break;
    case 3:
        c();
        break;
    default:
        a();
        break;
}


However, this case is so obscure and (in my opinion) unintuitive that doing this deliberately will not register immediately to someone reading the code. A programmer is far better off simply relying on the classic If-else.

Advantages over If blocks

In terms of switching execution for simple comparisons, especially in multiple simple comparisons, a Switch statement is far more readable. The following is a series of If blocks. Each of these is a simple comparison, with each If block containing multiple statements. The comparison is read three times.
if (x == 0)
{
    // multiple program statements
    // ...
    // ...
}

if (x == 1)
{
    // multiple program statements
    // ...
    // ...
}

if (x == 5)
{
    // multiple program statements
    // ...
    // ...
}


This can be condensed into a single Switch statement for the variable x. The cognitive load on the one reading this code is less. We understand the intent immediately; these are all the permutations of the variable x.

Also, the comparison is read one time in this scenario. The performance gain here is minimal; but what if there were ten, or fifty different values to cater for?
switch (x)
{
    case 0:
        // multiple program statements
        // ...
        // ...
        break;
    case 1:
        // multiple program statements
        // ...
        // ...
        break;
    case 5:
        // multiple program statements
        // ...
        // ...
        break;
    default:
        // multiple program statements
        // ...
        // ...
        break;
}


Disadvantages compared to If blocks

The Switch statement is meant for simple comparisons against enumerated or string values. For more complex cases, the If or If-else statement is still the way to go.

Something like this, for instance, is not possible in a Switch statement. This has two comparisons involving three variables - x against y and y against z.
if (x == y || (y < (z * 10.5)))
{
    // multiple program statements
    // ...
    // ...
}


We could use more complex comparisons using the Switch statement, but then one of the main advantages - readability - would be compromised.
switch (x)
{
    case (y * 10.5):
        // multiple program statements
        // ...
        // ...
        break;
    case (z / 10):
        // multiple program statements
        // ...
        // ...
        break;
    case "hello world":
        // multiple program statements
        // ...
        // ...
        break;
    default:
        // multiple program statements
        // ...
        // ...
        break;
}


In a nutshell

There are use cases for the Switch statement, and it's incredibly useful for simple comparisons against one variable. For everything else, use the If or If-else block as a fallback.

Your Iffy programmer,
T___T

Saturday, 13 January 2024

Film Review: Black Mirror Series Four (Part 3/3)

Onto the third episode!

The Premise

In order to cover up her involvement in a hit-and-run, Mia commits murder. Now exists a technology that can replay memories, and this leads to more murders being committed to cover up the earlier one.


But what about the tech? Well, a device known as a Corroborator comes into play here. You plug one end into someone's temple, turn on the machine, and there's your replay! This thing also plays a huge part in the story.

The Characters

Andrea Riseborough absolutely kills it (pun intended) as the reluctant serial murderer Mia Nolan. Gotta say, Riseborough was a superb choice to play this role. You can see every line on her face telling the story of the strain the murders take on her. The tears she sheds before and after each one, the cold fury every time a threat to her family or career surfaces. Riseborough's facial expressions just nailed the entire thing for me.

Andrew Gower as Mia's ex-boyfriend Rob. When he first see him he's frantic and panicked, and makes selfish and irresponsible decisions, and even pressures Mia into doing the same. Years later, guilt and remorse have changed him. He's a recovering alcoholic who wants to mitigate his crime years before, but his fate is tragic. It's hard to sympathize with the character, really. He started out disposing of the body of the man he accidentally killed, but by the first quarter of the show, it's his body that is being disposed of by Mia.

Kiran Sonia Sawar delivers a delightful performance as investigator Shazia Akhand. She's pretty as a picture but doesn't get sexualized in any way, which is refreshing. There's that moment where she gets in over her head while just trying to do her job, and it's powerful.

Anthony Welsh
as Shazia's's husband Anan Akhand. He has a certain puppy-dog presence which is supposed to get us in the feels when he gets murdered later on. It kind of works, except it all gets overshadowed by the kid getting murdered as well.

Jamie Michie as Simon Nicholls. Big bear of a man, jovial and kind, probably meant as a foil to the murderous character of Mia Nolan. Good casting choice here.

Joshua James as Gordy. He's the guy who gets knocked over by the self-driving pizza delivery vehicle, and is it just me, or does he actually looks more like a serial murderer than Andrea Riseborough? Might be the teeth.

Adelle Leonce as Nini Harper-Brown. Is it just me, or did she look more appealing in the memory replay than in-person? Genius if so, touching on how memories could be biased.

Brian Pettifer as dentist Willian Grange. Supposed to come across as some kind of creepy pervert. Wish he'd played it up a little more. Oh, well.

Armin Karina has a brief appearance as concierge Farshad. Hardly the picture of discretion, but I enjoyed the subtle look of mischief on his face.

Stefan Orn Eggertson as Finn Nicholls. Cute kid. The role was necessary for the purposes of showing the audience that Mia Nolan has a family that she wants to protect, though he didn't have much to do.

The Mood

It begins with a starkly beautiful scene of winter in the mountains, and then later on there's a tad more color than blue, grey and white. Tension fairly courses through the entire hour. Throughout it all, there's very little joy in this episode (other than the welcome scenes of Shazia and her family), and this is easily one of the bleakest stories Black Mirror has ever told.

What I liked

Did I mention that the scenery and backdrop were amazing?

The opening strains of that dang-blasted song play early on. Normally this makes me groan, but it went astonishingly well with the snowy mountain backdrop.


Wow, a mobile pizza delivery self-driving vehicle! These already kind of exist, but none of the pictures I saw on the net look remotely this cool. I also liked that this little baby was an integral part of the story.


Looks like in this episode, facial recognition seems to be a function one can access on their phone! So mundane, yet so many possibilities.


The Corroborator tech. This thing about plugging in one thumbtack-like piece on the side of someone's head is consistent with the interface used in USS Callister, and makes it possible to believe that it all exists in the same world.

I also like the thing Shazia did with the subjects, like using the scent of alcohol to trigger those memories. Just adds some dimension to the entire thing.

What I didn't

I don't understand the title at all. "Crocodile"? Like, what?

The murder of Rob was a bit puzzling. One moment Mia is trying to stop Rob from leaving, and next moment he's lying on his back with a nosebleed. I get that this was a murder, but how was he killed? Some clarity would be nice.

I feel like something as intrusive as a Corroborator would have consequences such as the one that happened to Shazia, far more commonplace, and the episode writers just didn't take that into account. It wasn't even mentioned.

At some point, the song gets a little overused, and starts being annoying. It's good in small, very sparing doses.

Conclusion

This wasn't a bad episode, though the plot left me a bit baffled at times.

Also, a baby gets murdered. And all for nothing. Depressing as all hell, this story. There were good bits here and there, but meh I wasn't feeling it all that much.

My Rating

8 / 10

Final thoughts on Black Mirror Series Four so far...

The first three episodes have been banging. Especially the first two. They certainly stuck to the spirit of the Black Mirror series. The third episode took things up a notch in terms of darkness, and that's a brave choice.

Looking forward to the next three episodes. I have a feeling it's going to get way darker than this.

To Infinity and Beyond!
T___T

Thursday, 11 January 2024

Film Review: Black Mirror Series Four (Part 2/3)

The second episode is Arkangel and it's a bit of a depressing ride, let me just warn you.

The Premise

Marie is an overprotective mother who goes to extreme means to protect her child from any distress. This leads to severe consequences once the child grows up and starts experiencing things she should have developed defence mechanisms against.

The Characters

Rosemarie DeWitt as Marie Sambrell, who works as a chiropractor. Her acting was top-notch here. The audience gets drawn into her parental panic, and actually roots for her when she does the sensible thing and disables Arkangel. But when when she starts it up again, we kind of get why. And there's a question lingering: would we have done the same thing?


Aniya Hodge as young Sara Sambrell. She's an adorable kid who doesn't need to do much other than be adorable.

Sarah Abbott as older Sara Sambrell. Now a bit more nuance is shown as Sara is perpetually confused over all the stuff she can't see that the others can. This is very well done.

Brenna Harding as teenage Sara Sambrell. She's beautiful and has an insatiable curiosity about her that ultimately proves to be her undoing.

Nicholas Campbell as grandfather Russ Sambrell. Wise old man with plenty of sensible advice and snark to go with it. I was sad when he died, but it was a necessary plot point.

Marie: You guys going to be all right?

Russ: I raised you all right, and you turned out OK. Took a while, but you did.


Nicky Torchia as young Trick, a.k.a Ryan Trebecky. Came off as a brat, but it was kind of adorable how he so enthustiastically became a bad influence for young Sara.

Owen Teague as teenage Trick. Sure he's a drug-pusher and shit, but this was a sympathetic figure that actually seemed to care for Sara. The sheer expression of horror on his face when he asks Amy "You've still got that system?" even seems to convey less fear for himself than horror on behalf of Sara. I can't even bring myself to feel anything negative about how he dumped Sara, it's not like the guy had any good options there. Owen Teague has a gift for playing creeps - I last saw him in the movie IT.

Abby Quinn as Sara's best friend Meryl. It's a bit-part that requires her to be chummy with Sara and little else.

The Mood

Initially, everything looks like the typical suburban scenario, childbirth scene in hospital notwithstanding. Soon, however, we're given a peek into Marie's paranoia. So far so good, until Arkangel comes into the picture and we get a good look at how extreme surveillance and censorship, facilitated by tech, harms the psyche. The effects are long-term and deep-rooted, made all the scarier by the fact that they aren't immediate and brutal.

Later on, there's an outburst of violence that marks the end of an excellent sequence of discovery of betrayal.

What I liked

The concept itself wasn't over-the-top, even though the specific technology isn't here yet. But with censorship and surveillance by governments, it's not too hard to point to modern-day parallels.

Russ's scenes with both mother and daughter are great. Seriously. Ribbing, laughter, lots of love...


...and even this scene depicting Russ Sambrell's heart attack from the viewpoint of Sara, is cleverly done, wringing out the first moment of drama where we see how Arkangel can have potentially fatal consequences.

The sequence where pre-teen Sara grows into teenage Sara, is really well done. I especially like the part where the dog starts getting used to Sara and even befriends her. That was sweet.


The Face Seeker app is just a plot device, but it is cool, man.


The soundtrack that plays when Sara discovers that her mother has been spying on her, is so effective at maintaining the tension. In fact, the entire sequence leading up to the confrontation, as well as the confrontation itself, is so well done.

What I didn't

Wait, the contraceptives that Amy put into Sara's smoothies terminated her pegnancy? Granted I'm no expert, and this is supposed to be science fiction, but I'm pretty sure that's not how contraception works.

The episode can be painfully slow at times, often out of neccessity. Still, I can't help but feel certain scenes could have been omitted. Like, what did the entire childbirth sequence really accomplish?

Also, when Arkangel is reactivated, we see that the censorship protocol has kicked in when Sara attacks Marie and her cortisol levels rise. So if Marie reactivated Arkangel a while back, how the hell did Sara not notice it? Are we expected to believe that no distressing events happened that would trigger the censorship protocol? How about Trick's breakup with Sara? FFS, Arkangel was active during that time. Marie was even watching Trick break up with her!

Conclusion

This episode was more about how human beings can always be counted on to use tech for questionable things, even with pure motives, than tech itself. There was nothing intrinsically sinister about the concept of Arkangel - it built to plant false images into someone's brain, for instance - but once its usage was taken to extreme levels, it damaged relationships beyond repair.

One could argue that teenage Sara had not exactly covered herself in glory with her experimentation with sex and drugs, but what teenager doesn't do stupid and reckless shit? This episode showed us how far a paranoid parent could go, even with the best of intentions, with the advancement of tech. And for that, it gets an absolute thumbs up from me.

Despite glaring plot holes, Arkangel was a good premise to build on.

My Rating

8.5 / 10

Next

Crocodile

Tuesday, 9 January 2024

Film Review: Black Mirror Series Four (Part 1/3)

At long last, it's time for a review of Series Four of Black Mirror! There are six episodes, and as with Black Mirror Series Three, I will take you through three harrowing episodes before covering the rest at a later date.


For those not in the know, Black Mirror is an anthological series about tech and the harmful ways in which people use it.

Warning

The twists within Black Mirror come thick and fast. It's possible to not have spoilers, but I'd rather have spoilers and give you this warning. Heavy, heavy spoilers.

The Premise

The series kicks off with USS Callister, a virtual reality skit that at first seems to be harmless fantasy, until what we're led to believe is the protagonist of the story, turns out to be the villain (well, this is Black Mirror, amirite?).


And then things get horrifying real quickly. Suffice to say, if the plight of cookies in previous seasons of Black Mirror made you uncomfortable, this takes it up another notch.

This episode appears to take place in the USA, if the title is any indication.

The Characters

Jesse Plemons plays the role of Robert Daly, the put-upon CTO of a gaming company. I last saw Plemons in Breaking Bad, where he was suitably sinister. In here, he's far campier and more sadistic, and the prime example of how power, even virtual power, can get to someone's head. Plemons provides a fascinating portrayal of an incel who gets increasingly petty the more his power trip fantasies play out.

Jimmi Simpson gives us a contrasting depiction of James Walton. As the CEO of Callister, Inc., he comes across as a charismatic but demanding boss and sleazy womanizer. It's only as the First Mate of USS Callister where we see his human side, where Daly's sadistic atrocities pile onto his tragedy.

Cristin Milioti
as new hire Nanette Cole. Another contrasting portrayal. As the new hire, we see her as a gushing fangirl of Daly. But as his virtual prisoner, we see other aspects - her defiance, leadership qualities, and even her salacious side! She carries a good part of this episode with her pluck, and is, to me, a believable heroine. Also, very easy on the eyes. Just sayin'.

Michaela Coel makes her second Black Mirror appearance as Shania Lowry. She looks remarkably different here, from when she first appeared during Nosedive in Black Mirror Series Three. Her character acts as exposition - out in the real world, advising Nanette on the landscape of the office, and in the virtual world. Lowry is also depicted as the closest person to Nanette in the office.

Osy Ikhile as intern Nate Packer. He makes coffee for people, and a botched order causes Daly to imprison him in the simulation. Seems harmless enough, if largely colorless.

Milanka Brooks as receptionist Elena Tulaska. She's unsmiling and taciturn, and very straight-laced. This is what causes Daly to gun for her - not smiling enough - which only serves to underline his pettiness.

Paul G. Raymond as programmer Kabir Dudani. Does not seem to have done much to warrant his imprisonment as he actually seems to respect Daly in the office. Serves as the techie in the simulation, at least until Nanette comes along.

Billy Magnussen as Valdack, who is also the gym bro in the office. He came across as a nice friendly guy in the office, and gave some hammy overacting in the simulation. Fun to watch, a tad underused.

Hammed Animashaun as pizza guy. A cameo, nothing more, but he made the most of it!

The Mood

It starts off as a cheerfully campy Star Trek knockoff, complete with clunky visual effects, cheesy dialogue science fiction technobabble. Somewhat predictably (again, this is Black Mirror, amirite?) it turns out to be a simulation minutes later but there's still nothing overtly wrong until we see how Daily really treats his crew.


The bright cheery colors somehow make his scenes of torture even more horrifying.

This does change, though, near the end, and become more akin to an inspiring tale of courage and freedom. And yes, sacrifice.

What I liked

In general the campiness of the whole Star Trek setup was a huge plus for me. Love it. Love it!

There's a funny background event as the unfortunate soul that Daly turned into an Arachajax, subsides and looks to be taking a break as Daly pauses the game.

And in that same scene, the hilariously over-the-top acting (which makes Nanette do an eye-roll) is just too good.

The reactions and remarks of the crew as they look over Nanette's racy pictures, are amusing.

That anguished fuck-you speech that Walton gives Daly is wrenching. He first admits his failings as a person, but also points out that Daly's retribution trumps everything that Walton ever did to him. This ends with a roar of defiance as Walton gets blown to smithereens. Maximum drama.

"Rannoch" and "Skillane" were the names of the destinations that USS Callister travelled to in the simulation. They seemed oddly familiar, and then it hit me - those names were used in White Bear of Black Mirror Series Two!

What I didn't

That little device where Daly feeds DNA samples into, to produce the virtual clones, is just a little too far-out. I get that this is supposed to be science fiction, but tech on this level seems to trump video game technology. This should be a bigger deal than is depicted in this story.


I'm not really sure the whole removal of genitals thing was necessary. It was mildly amusing, but I feel like it ultimately added very little to the plot while taking way too much time.


Wait, if all the characters in the simulation were reverted to their actual selves after escaping, whatever happened to Valerie from Marketing?!

Conclusion

A more than decent episode, albeit pretty predictable. Honestly, did anyone not see Robert Daly meeting a depressing end? This is Black Mirror, FFS.


On the other hand, this episode had plenty of funny bits, and a surprising amount of compelling acting. It's a great opening for Series Four! And if you're a Star Trek fan, this should totally be your jam.

My Rating

8.5 / 10

Next

Arkangel