9
\$\begingroup\$

I am new to Javascript, and I made a simple Rock Paper scissor game with some basic UI. We can play the game for how much longer we want and can check the score of both computer and our. It's a simple game that I made with basic knowledge that I had.

let compScore = 0 , playerScore = 0;

document.querySelector("#start").onclick = () => {
    document.querySelector("#startup").style.display = "none";
    document.querySelector("#choose").style.display = "flex";
}

document.querySelector("#check-score").onclick = () => {
    document.querySelector("#startup").style.display = "none";
    document.querySelector("#score-check-menu").style.display = "flex";
    document.querySelector("#comp-score").innerHTML = compScore;
    document.querySelector("#player-score").innerHTML = playerScore;
}

document.querySelector("#main-menue").onclick = () => {
    document.querySelector("#startup").style.display = "flex";
    document.querySelector("#score-check-menu").style.display = "none";
} 

const options = document.querySelectorAll(".opt");
options.forEach((option) => {
    option.onclick = function () {
        playerOption = this.value;
        console.log(playerOption);
        option.classList.add("active");
        options.forEach((option) => {
            option.disabled = true;
        })
        document.querySelector("#output").style.display = "flex";
        document.querySelector("#pl-choosen").innerHTML = playerOption;
        let compOption = compOpt();
        document.querySelector("#comp-choosen").innerHTML = compOption;
        let winner = roundWinner(playerOption, compOption)
        document.querySelector("#winner").innerHTML = winner;
        if (winner == "Player"){
            playerScore += 1;
        }
        else if (winner == "Computer"){
            compScore += 1;
        }
    }
})

document.querySelector("#restart").onclick = () => {
    document.querySelector("#startup").style.display = "flex";
    document.querySelector("#choose").style.display = "none";
    document.querySelector("#output").style.display = "none";
    options.forEach((option) => {
        option.disabled = false;
        option.classList.remove("active");
    })
}

function compOpt() {
    let compOpt
    do{
        if (Math.floor(Math.random()*3+1) == 1){
            compOpt = "Rock"
        }
        else if (Math.floor(Math.random()*3+1) == 2){
            compOpt = "Paper"
        }
        else if (Math.floor(Math.random()*3+1) == 3){
            compOpt = "Scissor"
        }
    } while (!(compOpt == "Rock" || compOpt == "Paper" || compOpt == "Scissor"))
    return compOpt;
}

function roundWinner(playerOption, compOption) {
    if (playerOption == "Rock" && compOption == "Scissor"){
        return "Player";
    }
    else if (playerOption == "Scissor" && compOption == "Paper"){
        return "Player";
    }
    else if (playerOption == "Paper" && compOption == "Rock"){
        return "Player";
    }
    else if (playerOption == compOption){
        return "No One";
    }
    else {
        return "Computer";
    }
}
*{
    padding: 0;
    margin: 0;
}
body{
    background: black;
    color: white;
    text-align: center;
}
h1{
    font-size: 48px;
    font-weight: bold;
}
h2{
    font-size: 24px;
    font-weight: bold;
}
.btn{
    padding: 10px 30px;
    background: black;
    color: white;
    border: 2px solid white;
    width: max-content;
    font-size: 16px;
    font-weight: bold;
    cursor: pointer;
    transition: 0.3s;
}
.btn.active{
    background: white;
    color: black;   
}
.btn:hover{
    background: white;
    color: black;
}
#container{
    height: 100vh;
    width: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 32px;
}
#startup{
    display: flex;
    flex-direction: column;
    gap: 32px;
    justify-content: center;
    align-items: center;
}
#score-check-menu{
    display: none;
    flex-direction: column;
    gap: 32px;
}
#choose{
    display: none;
    flex-direction: column;
    align-items: center;
    gap: 32px;
}
.options{
    display: flex;
    align-items: center;
    gap: 32px;
}
#output{
    display: none;
    flex-direction: column;
    align-items: center;
    gap: 32px;
}
#player-score, #pl-choosen{
    color: rgb(255, 78, 78);
}
#comp-score, #comp-choosen{
    color: aqua;
}
#winner{
    color: rgb(0, 255, 0);
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
    <div id="container">
        <h1>Rock, Paper, Scissor Game</h1>
        <div id="startup">
            <h2>You will play against the Computer</h2>
            <h2>To start click the Start button and to view score click the Check Score button</h2>
            <div class="options">
                <button class="btn" id="start">Start</button>
                <button class="btn" id="check-score">Check Score</button>
            </div>
        </div>
        <div id="score-check-menu">
            <h2>Computer Score: <span id="comp-score"></span></h2>
            <h2>Player Score: <span id="player-score"></span></h2>
            <button class="btn" id="main-menue">Back To Main Menue</button>
        </div>
        <div id="choose">
            <h2>Choose the option you want to play</h2>
            <div class="options">
                <button class="btn opt" value="Rock">Rock</button>
                <button class="btn opt" value="Paper">Paper</button>
                <button class="btn opt" value="Scissor">Scissor</button>
            </div>
        </div>
        <div id="output">
            <h2>You choosed <span id="pl-choosen"></span> and Computer choosed <span id="comp-choosen"></span></h2>
            <h2><span id="winner"></span> has won the match</h2>
            <h2>Would you like to play again? If yes then press the following button</h2>
            <button class="btn" id="restart">Play Again</button>
        </div>
    </div>

</body>
</html>

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Also some typos: choosed->chose, menue->menu, scissor->scissors \$\endgroup\$
    – tdy
    Commented Aug 13 at 21:43

2 Answers 2

12
\$\begingroup\$
let compOpt;
do{
    if (Math.floor(Math.random()*3+1) == 1){
        compOpt = "Rock"
    }
    else if (Math.floor(Math.random()*3+1) == 2){
        compOpt = "Paper"
    }
    else if (Math.floor(Math.random()*3+1) == 3){
        compOpt = "Scissor"
    }
} while (!(compOpt == "Rock" || compOpt == "Paper" || compOpt == "Scissor"))
return compOpt;

This is an overcomplicated way to make a random selection:

  1. Math.floor(Math.random()*3+1 is repeated three times. Whenever you see duplication like this, it's generally a good sign to use a variable.

  2. Adding 1 is superfluous. You could just compare Math.floor(Math.random()*3 with 0, 1, and 2.

  3. As discussed in the comments, this convoluted implementation leads to non-uniform selections. Each subsequent if branch only has a \$\frac{1}{3}\$ chance of the previous else chance, so instead of \$\approx 0.33\$ each, we have \$\approx 0.47, 0.32, 0.21\$:

    $$ \begin{aligned} R_{\mathrm{if}} &= \frac{1}{3} \Big(1\Big) &= \frac{9}{27} \\ P_{\mathrm{if}} &= \frac{1}{3} \Big(1 - R_{\mathrm{if}}\Big) &= \frac{6}{27} \\ S_{\mathrm{if}} &= \frac{1}{3} \Big(1 - R_{\mathrm{if}} - P_{\mathrm{if}}\Big) &= \frac{4}{27} \\ T_{\mathrm{if}} &= R_{\mathrm{if}} + P_{\mathrm{if}} + S_{\mathrm{if}} &= \frac{19}{27} \\ \\ R &= \frac{R_{\mathrm{if}}}{T_{\mathrm{if}}} &= \frac{9}{19} &\approx 0.47 \\ P &= \frac{P_{\mathrm{if}}}{T_{\mathrm{if}}} &= \frac{6}{19} &\approx 0.32 \\ S &= \frac{S_{\mathrm{if}}}{T_{\mathrm{if}}} &= \frac{4}{19} &\approx 0.21 \\ \end{aligned} $$

    In addition, since \$T_{\mathrm{if}} \neq 1\$, the loop has a non-zero chance of repeating. In the extreme case, a pathological sequence of numbers could lead to an infinite loop, as each successive call to Math.random() might result in a number which fails just the check for which it was called. On the other hand, if the random value had been stored in a variable which would then be checked, not only would each branch have an equal probability, but one of these branches would have been guaranteed to be selected, \$T_{\mathrm{if}}\$ would be 1, and the loop would have been unnecessary.

For a simpler implementation, notice that Math.floor(Math.random()*3 \$\in \{0, 1, 2\}\$, so you can just use the result as an array index:

const compOpts = ["Rock", "Paper", "Scissor"];
const index = Math.floor(Math.random() * compOpts.length);
return compOpts[index];

<h2>You will play against the Computer</h2>
<h2>To start click the Start button and to view score click the Check Score button</h2>
...
<h2>Computer Score: <span id="comp-score"></span></h2>
<h2>Player Score: <span id="player-score"></span></h2>
...
<h2>Choose the option you want to play</h2>
...
<h2>You choosed <span id="pl-choosen"></span> and Computer choosed <span id="comp-choosen"></span></h2>
<h2><span id="winner"></span> has won the match</h2>
<h2>Would you like to play again? If yes then press the following button</h2>

Don't use semantic tags for style/layout purposes. None of these h2 tags are actual headings in the semantic sense. Refactor these (and other) tags to align with HTML semantics:

  1. The h2 tags should be either p / span / div / label as appropriate.
  2. The game controls should be inside a form.
  3. The main div (#container) should be a main.
\$\endgroup\$
7
  • 5
    \$\begingroup\$ One note on the repetition of Math.random() - since it is called anew for each check, you could get into a pathological sequence where each call results in a number that doesn't match whatever it is being checked for (e.g., in Math.floor(Math.random()*3+1) == 1, Math.random() returns 0.8. you get false, then it moves to Math.floor(Math.random()*3+1) == 2, Math.random() -> 0.2 => false, then for Math.floor(Math.random()*3+1) == 3, with Math.random() -> 0.5 => false, and so on and on and on. This is a case where repeating yourself is not just a code smell, but can be a logic error. \$\endgroup\$
    – muru
    Commented Aug 14 at 5:42
  • 3
    \$\begingroup\$ @muru That is probably exactly what the author found, which is why they added the do {} while loop around it to get around that issue. \$\endgroup\$
    – Boude
    Commented Aug 14 at 8:45
  • 1
    \$\begingroup\$ @tdy thanks for your review and honestly i really liked your idea of using array index Instead of conditions i will implement it into the code \$\endgroup\$ Commented Aug 14 at 11:27
  • \$\begingroup\$ To extend what @muru said, out of suspicion I plugged the loop into a benchmark to test the probability spread of the different outcomes, and sure enough, over 10 million iterations it pretty consistently came back with 47%, 32%, and 21% for "Rock", "Paper", and "Scissor", respectively. So not only does the repetition produce a logic error, the loop "solution" heavily skews the outcome to pick "Rock" nearly 50% of the time. \$\endgroup\$
    – Abion47
    Commented Aug 14 at 14:45
  • \$\begingroup\$ @Abion47 It was a fun little probability problem to calculate the theoretical probabilities which are 9/19, 6/19, and 4/19! I needed to sum an infinite series, but there might be an easier way... \$\endgroup\$ Commented Aug 14 at 16:51
4
\$\begingroup\$

Another improvement would be to declare variable for the referenced DOM elements and use these for DOM manipulation and adding events. It would reduce the number of DOM queries and make the code more legible.

const startButton = document.getElementById('start'),
  startupEl = document.getElementById('startup'),
  chooseEl = document.getElement('choose')
  // etc etc ...

Reference like this:

start.addEventListener('click', () => {
  startupEl.style.display = "none";
  chooseEl.style.display = "flex";
});

Note a couple of other minor changes here are replacing querySelector with getElementById since all elements in this example are referenced by id and the latter method is slightly more performant. Additionally I have changed the onclick assignment with addEventListener. In this case it doesn't necessarily matter, however for other future projects you may find it more useful since it allows you to add multiple event listeners of the same type to an element, whereas onclick only allows for a single listener and can be easily overwritten.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for your review. Honestly i am confused as to whether i should use querySelector or getElementById cause on net i saw most people saying to use querySelector \$\endgroup\$ Commented Aug 14 at 11:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.