Showing posts with label game. Show all posts
Showing posts with label game. Show all posts

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 AI has rendered their fun little tests moot. In any case, I rewrote my code from scratch and hopefully my style now has improved.

The Stated Requirements

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

Let's begin!

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

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

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

        <script>
            let paprocksc =
            {

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

    </body>
</html>


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

    <div class="gameContainer">

    </div>

</body>


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

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

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

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


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

    div { outline: 1px solid red; }

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

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

    .left
    {
        float: left;
    }

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


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

div { outline: 1px solid red; }

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

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

.left
{
float: left;
}

.right
{
float: right;
}

hr
{
clear: both;
}

</style>


This is what it looks like!


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

symbol.jpg

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


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

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


hr
{
    clear: both;
}


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


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


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


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


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


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

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

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


hr
{
    clear: both;
}


Looks good. Let's do a dashboard next.


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

</div>


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


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


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

symbol_paper.jpg

symbol_rock.jpg

symbol_scissors.jpg

symbol_lizard.jpg

symbol_spock.jpg

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


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

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


hr
{
    clear: both;
}


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

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

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


hr
{
    clear: both;
}


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

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


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


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


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

.hidden
{
    display: none;
}


hr
{
    clear: both;
}


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


<div class="gameContainer">


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

<div class="gameContainer">


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

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


<div class="gameContainer">


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

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


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


There be your controls!


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


The paprocksc object

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

};


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


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


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

};


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

};


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

};


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

    },
    stop: function()
    {

    }

};


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

};


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

};


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

};


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


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


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

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


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

},


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

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

},


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

},


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

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


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

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


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

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

    }

},


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

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

        }
        else
        {

        }

    }
},


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

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


One last method!

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


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


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

Next

Running the game.

Saturday, 15 March 2025

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

I'm back with more Black Mirror! For those who are new to this, Black Mirror is an anthological series with standalone episodes around the theme of technology gone wrong. Sometimes it can be funny, but often it's bleak AF.


We're now in Series Five of Black Mirror, and there are three episodes to go through. Prepare to be shocked, disgusted and entertained in varying degrees.

Warning - spoilers ahead, important plot points discussed!

I try not to hold back where spoilers are concerned, and at this point, Black Mirror isn't exactly new. There's really no point in being coy about spoilers.

Also... if you don't approve of video games, stop watching. If you don't like bad language, stop watching. And if seeing two grown men kiss makes your brain implode, definitely fuck off and stop watching.

The Premise

Striking Vipers is the first episode, and it is centered around two old friends playing a fighting video game, but with VR Black Mirror style.


As both of them become obsessed with their time within the game, however, this results in their real lives suffering for it.

Generally, this episode warns against the dangers of getting too absorbed in a game. The tech used appears to be the same one used in USS Callister, and bears the same Aesop.

The Characters

Anthony Mackie as Danny Parker. He's the serious responsible type who's now married with a kid, but sex has become a bit of a chore. Mackie gives us a tremendous portrayal of a man who's not a cheater by nature, but gets caught up in something that doesn't feel like cheating until it's too late. I've been watching Mackie on-screen as Sam Wilson in the Marvel Cinematic Universe, and this isn't too far from what he portrays there.

Yahya Abdul-Mateen II as incorrigible manchild Karl Houghton with Peter Pan syndrome. He's finding it increasingly harder to relate to the girls he dates, because they're so much younger, even though the sex is presumably great. And the actor delivers with such earnestness lines like the one below.
"I fucked a polar bear and I still couldn't get you out of my mind."

Only in a series like Black Mirror would that quote make sense.

Anyway, I've seen the actor in superhero flicks like Aquaman and Aquaman and the Lost Kingdom. He gets a lot more to do here.

Nicole Beharie as Theo Parker. She has a role as Danny's wife and Tyler's mother, and while her portrayal is a little generic, Beharie does bring in some sassy charm into it.

Pom Klementieff is a breath of fresh air as Roxette. I loved seeing her in Mission Impossible: Dead Recknoning Part One and the Marvel Cinematic Universe, and she brings the same zany offbeat quality to the role, almost perfectly mirroring Karl offline.

Ludi Lin as Lance. Last saw him in a similar role as Liu Kang in Mortal Kombat. This time it felt a little flat, if I'm being honest. Nice abs, but I can't seem to buy him as Danny Parker's online alter-ego.

Fola Evans-Akingbola portrays Karl's girlfriend Mariella as this slightly bimbotic girl who's obsessed with her phone. Serves to show us that Karl gets great sex physically but he's missing that extra emotional connection. Someone who gets him.

August Muschett is Danny and Theo's son Tyler Parker. He's a cute little tyke, perfectly normal. And only serves to underscore how much Danny has going for him.

The Mood

Scenes of wedded bliss and suburban serenity are interspersed by nighttime landscapes within the game.


Unlike the usual Black Mirror fare, none of this is particularly unsettling, but there is a fair bit of drama and tension as we get into it.

What I liked

The banter between Danny and Karl, at Danny's birthday barbecue, is delightful. It really serves to illustrate what a vibe these two buddies had before things got weird.

I normally prefer Black Mirror episodes to be dark and creepy (maybe even violent) but this episode focused on the friendship between two men and it was glorious even though relationship drama isn't really my thing.

The name "Striking Vipers" is really such a sly nod to gay sex. Not that what's between Danny and Karl is 100% gay sex. When it happens, they're choosing to use avatars Roxette (a female) and Lance (a male).


The locations within the game are gorgeous. We can really see the effort they put into it. Also, the fighting moves. So outlandish. Love it! Especially that pancake slam by Roxette.


Could be just me, but I thoroughly enjoyed this over-the-top visual representation of Tetris.

What I didn't

I find it extremely hard to believe that the anatomical accuracy in the game would be so high as to facilitate sexual intercourse. And since this is a major plot point without which nothing else makes sense, it's pretty difficult to ignore.

The ending's also a little off, I think. So once a year Theo and Danny both get a license for infidelity? Looking at how obsessed Karl was, is once a year even enough? What does he do in between, fuck more polar bears?

Conclusion

This episode had so much promise despite the nagging little plot holes. The premise was decent, that's for sure. Thematically, it fits right in with the mythos. It was a fun watch.

My Rating

7 / 10

Next

Smithereens

Tuesday, 14 January 2025

App Review: Haunted Dorm

Think about all the tropes you've ever seen in Asian horror and even movies like the A Nightmare On Elm Street series and It. Think of all the times the characters triumphed over evil by virtue of their imaginations and teamwork.

And then download this game - Haunted Dorm, by China game company Mihuan, for a fun time.


This is a Tower Defense game with a single goal - to defeat the monster lurking the hallways before it comes for you. To that emd, you'll have all the classic tools for a Tower Defense; namely, missile weapons and resource generating items. The difference is that there is one main enemy to defeat. Sometimes there are multiple enemies, but only the demise of that one monster will win the game.

The Premise

The dorm is being haunted by a monster. You play as one kid in a group of children, going to a room and sleeping to gain enough resources. These resources will build your defences to hold off the monster's attacks, and eventually destroy it.


Once the monster breaks through the door of the room you choose, you're as good as dead and the game ends.

The Aesthetics

Haunted Dorm is rendered in what one would call "kiddy graphics". Everything looks hand-drawn and crude, like toddlers made this. It's a bold stylistic choice, though sometimes comes off as clunky.

The Experience

It's definitely a spooky experience, especially the first few days you play it. The atmosphere is haunting. The graphics are kiddy and this could end up either being comical or adding to the spookiness.


But there's also that sense of solidarity as you, the player, team up with other computer-controlled players to take down the monster.

The Interface

Drag your finger over the screen to move the avatar. Tap on buttons to perform actions. Select items to be built from menus.


It's not exactly complicated, though the execution feels clumsy.

There are also some items, like the machine gun, that you will need to tap and hold, and drag, in order to use. It's awesome, but not terribly intuitive.

What I liked

The music. It adds so much to the atmosphere, I can't rave enough about it.

Dream Hunter Mode. You get to play as the monster instead of the kids... and lemme tell you that this feature is fucking insane. Whereas in Normal Mode you had the aid of computer-controlled allies, in this mode it's the opposite - all these are working against you now. Still, what a feature!

Seven Monsters Mode. More interesting levels where you can't purchase Special Objects. This was pretty challenging.


Story Map Mode. Different rooms with different rules of engagement.

Ghost And Angels Mode. This is another mode where any allies who die, become a ghost as well. Good, chaotic fun!


The comments made when you steal stuff from other players, are amusing. Especially the awkward English translations of what was obviously Chinese text to begin with.


This solar beam is awesome beyond belief. Not only does it pinpoint the Monster's location, it deals damage and earns coins! And you can even upgrade it for bonus damage. Talk about overkill.


The entire game was ridiculously easy to play. Even with comically bad English translation, it was a breeze to just tap my way around and try shit out.



What I didn't

Unless you have the Trampoline, there really does not seem to be any way to escape what I call the Ghost Hole attack, or just spam-filling every square inch of the room, which can be super-tedious if you chose a large room.


The menu tends to go out of the screen sometimes depending on where you tapped the button. It's a layout issue and annoying AF. Do better!

Amy Can't Die Mode seemed like a waste of time. You basically sleep in one room with a bunch of other kids and you use your combined powers to hold of wave after wave of hostiles until you finally assemble a weapon strong enough to kill the Boss. It was an interesting concept, but the execution was so weak.



When there are objects appearing around the corridor, it's always a crapshoot as to what you'll end up grabbing before the others do. There's just so little time to react. At least give players and option to pick something else other than what they initially picked up!


I don't understand this game's obsession with frogs and toads. At all. It can be a little off-putting.




Conclusion

Haunted Dorm is simultaneously campy and goofy, and deliciously creepy. I found Normal Mode quite addictive and all the other features combined should provide great replayability. This is a game that doesn't take itself at all seriously, always a plus. One definitely could do worse than give Haunted Dorm a shot.

My Rating

6.5 / 10

Not spooktacular, but damn solid!
T___T