I am trying to understand this piece of Netsted objects in Javascript

Viewed 89

I am following a tutorial on YouTube about building projects in Javascript. And this piece of code is supposed to create a Rock, Paper & Scissors game. I understand most part of this code. But my challenge is how the decideWinner function works. How does the variable yourScore pass the value into the rpsDatabase ? and how are the functions being called ?

//Rock, Paper, Scissors

function rpsGame(yourChoice) {
    var humanChoice, botChoice, results, message;

    humanChoice = yourChoice.id;
    botChoice = numberToChoice(randToRpsInt());
    results = decideWinner(humanChoice, botChoice);
    console.log(botChoice);
}

function randToRpsInt () {
    return Math.floor(Math.random() * 3);
}

function numberToChoice (number) {
    return ['rock', 'paper', 'scissors'][number];
}

function decideWinner (yourChoice, computerChoice) {
    var rpsDatabase = {
        'rock': {'scissors': 1, 'rock': 0.5, 'paper': 0},
        'paper': {'rock': 1, 'paper': 0.5, 'scissors': 0},
        'scissors': {'paper': 1, 'scissors': 0.5, 'rock': 0},
    };

    var yourScore = rpsDatabase[yourChoice][computerChoice];
    var computerScore = rpsDatabase[computerChoice][yourChoice];

    return [yourScore, computerScore];
}

function finalMessage([yourScore, computerScore]) {
    if (yourScore === 0) {
        return {'message': 'You lost!', 'color': 'red'};
    } else if (yourScore === 0.5) {
        return {'message': 'You tied!', 'color': 'yellow'}; 
    } else {
        return {'message': 'You won!', 'color': 'green'};
    }
}
.container-game {
    border: 1px solid black;
    width: 75%;    
    margin: 0 auto;
    text-align: center;
}

.flex-box-weapons {
    display: flex;
    border: 1px solid black;
    padding: 10px;
    flex-wrap: wrap;
    justify-content: space-around;
}

.flex-box-weapons img:hover {
    box-shadow: 0px 10px 50px rgba(37, 50, 233, 1);
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <link rel="stylesheet" href="content/css/style.css">    
    <title>RPS</title>
</head>
<body>
    <div class="container-game">
        <h2>Challenge 3: Rock, Paper, Scissors</h2>
        <div class="flex-box-weapons" id="flex-box-weapons-div">
            <img id="Rock" alt="Rock" src="http://images.clipartpanda.com/rock-clipart-clipart-harvestable-resources-rock.png" width="150" height="150" onclick="rpsGame(this)">
            <img id="Paper" alt="Paper" src="
http://images.clipartpanda.com/paper-clipart-nexxuz-Loose-Leaf-Paper.png" width="150" height="150" onclick="rpsGame(this)">
            <img id="Scissors" alt="Scissors" src="https://thumbs.dreamstime.com/b/female-hand-sign-victory-sign-peace-sing-scissors-vector-color-flat-illustration-isolated-white-background-web-83128345.jpg" width="150" height="150" onclick="rpsGame(this)">
        </div>
    </div>

    <script src="content/Js/script.js">

    </script>
</body>
</html>

2 Answers

"How does the variable yourScore pass the value into the rpsDatabase ?"

It doesn't – yourScore is the result of looking up a nested value in the database.

You can use a variable to lookup a property of an object using [] notation -

const myObject = { hello: "world", foo: "bar" }
const key = "hello"
console.log(myObject[key])
// "world"

You can sequence many [][]... to lookup a nested property -

const myObject = { hello: { world: "earth" }}
const key1 = "hello"
const key2 = "world"
console.log(myObject[key1][key2])
// "earth"

"And how are the functions being called ?"

The rpsGame function is called when one of the images are clicked. This happens because each img has onclick="rpsGame(this)"


improvements

I would like to offer several quality improvements to the code.

1. The rpsDatabase is redefined each time the decideWinner is called. This is unnecessary as the values of rpsDatabase do not (should not) ever change -

const rpsDatabase = {
  rock: {scissors: 1, rock: 0.5, paper: 0},
  paper: {rock: 1, paper: 0.5, scissors: 0},
  scissors: {paper: 1, scissors: 0.5, rock: 0},
}

function decideWinner (yourChoice, computerChoice) {
  var yourScore = rpsDatabase[yourChoice][computerChoice]
  var computerScore = rpsDatabase[computerChoice][yourChoice]
  return [yourScore, computerScore]
}

2. By separating rpsDatabase, we can use it to reduce code duplication in randToRpsInt and numberToChoice. Instead we end up with a simple randomChoice function -

function randomChoice () {
  const rand = Math.floor(Math.random() * 3)
  // no need to redefine ["rock", "paper", "scissors"] on each call!
  return Object.keys(rpsDatabase)[rand]
}

3. The values chose to encode a win, loss, or tie are arbitrary: 1, 0, or 0.5. Consider this alternative -

outcome encoding
win 1
loss -1
tie 0

This new rpsDatabase would be written as -

const rpsDatabase = {
  rock: {scissors: 1, rock: 0, paper: -1},
  paper: {rock: 1, paper: 0, scissors: -1},
  scissors: {paper: 1, scissors: 0, rock: -1},
};

Now instead of testing for equality to arbitrary numbers, this allows you to write finalMessage in a more natural way using comparisons > and < -

function finalMessage ([yourScore, computerScore]) {
  if (yourScore > computerScore)
    return {message: 'You won!', color: 'green'}
  else if (computerScore > yourScore)
    return {message: 'You lost!', color: 'red'}
  else 
    return {message: 'You tied!', color: 'yellow'}
}

Or the equivalent using switch. Maybe you like this form more -

function finalMessage ([yourScore, computerScore]) {
  switch (true) {
    case yourScore > computerScore:
      return {message: 'You won!', color: 'green'}
    case computerScore > yourScore:
      return {message: 'You lost!', color: 'red'}
    default:
      return {message: 'You tied!', color: 'yellow'}
  }
}

4. Use addEventListener instead of onclick attribute. This creates cleaner separation of HTML and JavaScript, allowing greater portability of both -

<form id="game">
  <button type="button" value="rock"></button>
  <button type="button" value="paper"></button>
  <button type="button" value="scissors">✂️</button>
  <br>
  <output name="result"></output>
</form>
const game = document.forms.game

// attach `rpsGame` to `click` event on each `button`
for (const button of game.querySelectorAll("button"))
  button.addEventListener("click", rpsGame)

demo

Here's a functional demo that incorporates the suggestions above -

const game = document.forms.game

const rpsDatabase = {
  rock: {scissors: 1, rock: 0, paper: -1},
  paper: {rock: 1, paper: 0, scissors: -1},
  scissors: {paper: 1, scissors: 0, rock: -1},
}

function rpsGame (event) {
  const humanChoice = event.target.value
  const botChoice = randomChoice()
  const result = decideWinner(humanChoice, botChoice)
  game.result.value = JSON.stringify(finalMessage(result))
}

function randomChoice () {
  const rand = Math.floor(Math.random() * 3)
  return Object.keys(rpsDatabase)[rand]
}

function decideWinner (yourChoice, computerChoice) {
  const yourScore = rpsDatabase[yourChoice][computerChoice]
  const computerScore = rpsDatabase[computerChoice][yourChoice]
  return [yourScore, computerScore]
}

function finalMessage ([yourScore, computerScore]) {
  if (yourScore > computerScore)
    return {message: 'You won!', color: 'green'}
  else if (computerScore > yourScore)
    return {message: 'You lost!', color: 'red'}
  else 
    return {message: 'You tied!', color: 'yellow'}
}

for (const button of game.querySelectorAll("button"))
  button.addEventListener("click", rpsGame)
<form id="game">
  <button type="button" value="rock"></button>
  <button type="button" value="paper"></button>
  <button type="button" value="scissors">✂️</button>
  <br>
  <output name="result"></output>
</form>

From your code it is pretty clear that there are three clickable image, when user click either of these images, we are calling a method rpsGame and passing the whole image object as a parameter. This rpsGame method calls another method decideWinner to decide who is the winner by passing two parameters one selected by you(like rock, paper or scissors) and other selected by some random method like numberToChoice. Finally based on the the result returned by decideWinner method like 0, 0.5 or 1, you can call the finalMessage method and print whatever your results is, based on the condition given in finalMessage method.

Related