2

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>
StaFa
  • 29
  • 4
  • [How can I access and process nested objects, arrays or JSON?](https://stackoverflow.com/questions/11922383/how-can-i-access-and-process-nested-objects-arrays-or-json) – Andreas Jul 10 '21 at 15:47
  • 1
    The `rpsGame` function is called when one of the images are clicked. This happens because each `img` has `onclick="rpsGame(this)"` – Mulan Jul 10 '21 at 15:47
  • 1
    There is nothing special here, both players states hold a single word, and first it picks the list of responses the other player can have based on your input, then it selects the value in that list by using the opponents value. It's nothing more then first selecting an object inside a list, then a value inside that object based on the keys provided. Calling it a 'database' is a bit... much. It's more like a `map` of results. Or a `table`. Think of it as a table. – somethinghere Jul 10 '21 at 15:52
  • 1
    be careful, `humanChoice = yourChoice.id;` is related to the `id` attribute of ``. you are using uppercase `id` attribute values, `Rock`, `Paper`, and `Scissors`, but the `rpsDatabase` uses lowercase `rock`, `paper`, `scissors` – Mulan Jul 10 '21 at 15:57
  • Okay, thank you all so much. I believe that I am not asking the right question. And sorry, I didn't make the question clear. What i want to understand is how **var yourScore = rpsDatabase[yourChoice][computerChoice]; var computerScore = rpsDatabase[computerChoice][yourChoice];** has an effect on the rpsDatabase above it. @somethinghere – StaFa Jul 10 '21 at 16:05
  • It has no 'effect' on it, it is just sorting through the first object based on your input, and then sorts the results to find the final score that matches the input on the opponent. All it's doing is "What did the player do?", "Rock" (...searches for rock instruction..), "Okay, now what did the opponent do?", "Scissors", "Okay, let me see..." (..follows listing on instruction sheet..) "Then your score is 1!". – somethinghere Jul 10 '21 at 20:44

2 Answers2

2

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

It doesn'tyourScore 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>
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • That explains it really well for me. I couldn't understand why he didn't just type in **console.log(myObject[hello][world])** right away, but instead had to use the variables that held the exact strings like the values of the Key1 and key2 variable. Thank you so much. – StaFa Jul 10 '21 at 16:18
  • 1
    You're very welcome! note `myObject["hello"]["world"]` is not the same as `myObject[hello][world]`, notice the absence of quotes. When no quotes are used, `hello` and `world` are seen as variables, and the _value_ of the variables are used as the keys to lookup. However, if you use `.` syntax, you can lookup values without quotes, `myObject.hello.world` will return `"earth"`, as the `.` accepts a property literal, and does not treat `hello` or `world` as variables. Only `[]` can be used to lookup a _variable_ property on an object. – Mulan Jul 10 '21 at 16:24
  • @StaFa I made an update to this answer that provides many qualitative improvements to the code in your question. I hope you find them useful. Let me know if you have any questions :D – Mulan Jul 10 '21 at 16:49
  • 1
    Yoooo, that's crazy. Because your improvement brought to my attention that the second parameter (computerScore) of the finalMessage function is not being used in the original code. While in your case, you've made use of it and also a lot easier. Thank you so much. The use of const is now something that's firmly imprinted in my brain. – StaFa Jul 10 '21 at 17:37
  • I have no questions now, but I may have questions in the future, so if it won't be a bother, I'd appreciate you being my mentor. – StaFa Jul 10 '21 at 17:39
  • Happy to help :D You can feel free to message me whenever. I'm usually on the site several times per week – Mulan Jul 10 '21 at 20:03
1

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.

Dharman
  • 30,962
  • 25
  • 85
  • 135
  • Yes, it helps. I am pretty new to the whole object and especially accessing its key and value. That's why I am confused by all these. – StaFa Jul 10 '21 at 16:13