DEV Community

Cover image for Rock the Paper and scissors
Luc van Kerkvoort
Luc van Kerkvoort

Posted on • Edited on

Rock the Paper and scissors

Hi Everyone,

back for the third article in the series. Time for some fun! let's build Rock Paper Scissors...

again this was an actual exercise I had to go through and the interesting part was that I did not get the role even though the feedback I got was that it was the best explanation the interviewer had ever heard during a coding exercise.

ah well... you live and learn.

Approach

so there was a great freedom when it came to making rock paper scissors. There were no expectations for the UI other than the user can select an option.

the other requirements were:

  • When the user clicks an option, a computer option should be shown
  • There are 5 rounds to the game, each round should show who won the round
  • After all 5 rounds are played a message should showcase the winner.
  • After the game has been played a restart button should appear to restart the game
  • You have 45 minutes to complete this exercise

React implementation

Since the job was for a Senior React Developer the IDE was using React so the approach here was the one I used in the actual implementation.

I started off with commenting out the necessary items to complete the game:

  • options for the user, which I decided were going to be buttons
  • A computers choice, implementing a random option each time the player clicks one of the buttons
  • A check to see who won the round
  • Metrics for the game such as user selection, user score, computer selection, computer score, rounds and winning message.

Logic

I decided to start with creating a utils file that would hold the game logic.


 const [userScore, setUserScore] = useState(0);
  const [compScore, setCompScore] = useState(0);
  const [usersSelection, setUserSelection] = useState(null);
  const [compSelection, setCompSelection] = useState(null);
  const [message, setMessage] = useState(null);
  const [round, setRounds] = useState(5);

 const options = ["rock", "paper", "scissors"];


 const game = (userSelection) => {
    setRounds(round - 1);

    const random = Math.floor(Math.random() * options.length);

    const compChoice = options[random];
    setCompSelection(compChoice);
    setUserSelection(userSelection);

    const combinations = {
      rock: options[2],
      paper: options[0],
      scissors: options[1],
    };

    // Logic for a Tie game
    if (userSelection === compChoice) {
      setMessage("Tie");
      return;
    }

    // Logic for scoring
    if (combinations[userSelection] === compChoice) {
      setUserScore(userScore + 1);
      setMessage("You win this round");
    } else {
      setCompScore(compScore + 1);
      setMessage("You lose this round");
    }
  };
Enter fullscreen mode Exit fullscreen mode

We start with setting some state variables... scores, selections and rounds. The rounds start off with 5 so the first thing the logic will do is deduct 1.

Math.random is used to calculate a number between 0 and 2 to select an option for the computer.The state is set with the selections for both computer and user.

An object is used with the keys being the user selections and the value being the losing selection for the computer.

we then create the logic to decide who wins or when it is a tie. If the user selection is the same as the computer selection, it is a tie. If the user selection is the winning combination the user wins otherwise the computer wins the round and we set the message accordingly.

Then we needed to build out the rest of the logic which is deciding the winner and restarting the game.

const [restart, setRestart] = useState(false)

  useEffect(() => {
    if (round < 1) {
      winner();
    }
  }, [round]);

  const winner = () => {
    setRestart(true);

    if (userScore === compScore) return setMessage("its a Tie");
    return userScore > compScore
      ? setMessage("you are the winner")
      : setMessage("You lost");
  };


 const reset = () => {
    setUserScore(0);
    setCompScore(0);
    setCompSelection(null);
    setUserSelection(null);
    setRounds(5);
    setMessage(null);
    setRestart(false);
  };
Enter fullscreen mode Exit fullscreen mode

Let's start off with the useEffect. It checks the rounds whether it has reached 0, if it has, it runs the winner function to decide who won.

The winner function is a simple ternary statement returning the winner with the highest score. Otherwise if the scores are equal it returns early with the score being a Tie. It sets the state variable restart to true so the restart button becomes visible.

The restart function is simply resetting all the state variables to their original values.

I also created a React component that builds a UI for all the metrics we need to take care off.


  const Info = () => (
    <Scores>
      <div>Rounds Left: {round}</div>
      <div>
        User Score: {userScore}
        <br />
        Computer Score: {compScore}
      </div>
    </Scores>
  );
Enter fullscreen mode Exit fullscreen mode

here is the complete picture

import React, { useEffect, useState } from "react";
import { Scores } from "./styles";
const Logic = () => {
  const [userScore, setUserScore] = useState(0);
  const [compScore, setCompScore] = useState(0);
  const [usersSelection, setUserSelection] = useState(null);
  const [compSelection, setCompSelection] = useState(null);
  const [message, setMessage] = useState(null);
  const [round, setRounds] = useState(5);
  const [restart, setRestart] = useState(false);

  const options = ["rock", "paper", "scissors"];

  const reset = () => {
    setUserScore(0);
    setCompScore(0);
    setCompSelection(null);
    setUserSelection(null);
    setRounds(5);
    setMessage(null);
    setRestart(false);
  };
  const winner = () => {
    setRestart(true);

    if (userScore === compScore) return setMessage("its a Tie");
    return userScore > compScore
      ? setMessage("you are the winner")
      : setMessage("You lost");
  };

  const game = (userSelection) => {
    setRounds(round - 1);

    const random = Math.floor(Math.random() * options.length);

    const compChoice = options[random];
    setCompSelection(compChoice);
    setUserSelection(userSelection);

    const combinations = {
      rock: options[2],
      paper: options[0],
      scissors: options[1],
    };

    // Logic for a Tie game
    if (userSelection === compChoice) {
      setMessage("Tie");
      return;
    }

    // Logic for scoring
    if (combinations[userSelection] === compChoice) {
      setUserScore(userScore + 1);
      setMessage("You win this round");
    } else {
      setCompScore(compScore + 1);
      setMessage("You lose this round");
    }
  };

  useEffect(() => {
    if (round < 1) {
      winner();
    }
  }, [round]);

  const Info = () => (
    <Scores>
      <div>Rounds Left: {round}</div>
      <div>
        User Score: {userScore}
        <br />
        Computer Score: {compScore}
      </div>
    </Scores>
  );

  return {
    game,
    reset,
    message,
    restart,
    options,
    Info,
    usersSelection,
    compSelection,
  };
};

export default Logic;


Enter fullscreen mode Exit fullscreen mode

UI

I decided to use the logic to build the UI, we already have the array with the options which can be used to showcase the buttons for both the user as well as the computer.

import React from "react";
import { Game, Options, Option } from "./styles";
import Logic from "./utils";

export default function App() {
  const {
    Info,
    restart,
    reset,
    game,
    message,
    options,
    compSelection,
    usersSelection,
  } = Logic();

  const images = {
    rock: "https://cdn-icons-png.flaticon.com/512/3562/3562093.png",
    paper: "https://cdn-icons-png.flaticon.com/512/2949/2949963.png",
    scissors: "https://cdn-icons-png.flaticon.com/512/4151/4151732.png",
  };

  return (
    <>
      <Info />
      <Game>
        {message}
        <Options>
          {options.map((item, i) => (
            <Option
              id={item}
              key={i}
              disabled={restart && item !== usersSelection}
              onClick={() => game(item)}
            >
              <img src={images[item]} alt={item} height="100%" />
            </Option>
          ))}
        </Options>

        <Options>
          {options.map((item, i) => (
            <Option key={i} disabled={item !== compSelection}>
              <img src={images[item]} alt={item} height="100%" />
            </Option>
          ))}
        </Options>

        <div>{restart && <button onClick={() => reset()}>restart</button>}</div>
      </Game>
    </>
  );
}

Enter fullscreen mode Exit fullscreen mode

as you can see it is the info section that is being returned from the Logic hook. Then the options being mapped twice to create the different options for both the user as well as the computer.

Images to represent the hand gestures for Rock Paper and Scissors. disabled variable to show the computer selection and to showcase the users last selection after the game ends.

lastly a restart button that uses logical and to decide whether to show the button, which equates to the restart state in the hook.

Styled components

As you might know by now I have a preference for styled components, I just think they have multiple use cases that can come in handy at anytime. They are not necessary in this example but since we are using the library...

import styled from "styled-components";

export const Scores = styled.div`
  display: flex;
  justify-content: space-between;
`;

export const Game = styled.div`
  display: flex;
  justify-content: center;
  align-content: center;
  flex-direction: column;
  text-align: center;
`;

export const Options = styled.div`
  display: flex;
  justify-content: space-around;
  width: 50vw;
  margin: 5rem auto;
`;

export const Option = styled.button`
  height: 10rem;
  padding: 1rem;
  border: none;
  border-radius: 50%;
  transition: 0.5s;

  :disabled {
    background: black;
    border: 10px solid black;
  }

  :hover {
    box-shadow: 0px 0px 9px black;
  }
`;

Enter fullscreen mode Exit fullscreen mode

JavaScript Implementation

For the JavaScript implementation I have reused about 90% of the code of the React implementation since the logic doesn't change to much.

Here is the full implementation. I will be diving into the code that is separate from the React implementation after

<!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" />
    <title>Rock Paper Scissors</title>
  </head>
  <body>
    <div id="root"></div>
    <div id="score"></div>
    <div id="messages"></div>

    <script>
      let userScore = 0;
      let compScore = 0;
      let rounds = 5;
      let message = "";
      let restart = false;
      let compSelection;

      const root = document.getElementById("root");
      const score = document.getElementById("score");
      const messages = document.getElementById("messages");
      let comp = document.createElement("p");

      score.innerHTML = `rounds: ${rounds} , user score: ${userScore}, comp score = ${compScore}`;

      const options = ["Rock", "Paper", "Scissors"];

      const userBlocks = options.map((item, i) => {
        const button = document.createElement("button");
        button.innerHTML = item;
        button.addEventListener("click", () => game(item));

        root.append(button);
      });

      const game = (userSelection) => {
        if (restart) return;

        rounds--;

        const random = Math.floor(Math.random() * options.length);
        compSelection = options[random];
        comp.innerHTML = compSelection;

        const combinations = {
          Rock: "Scissors",
          Paper: "Rock",
          Scissors: "Paper",
        };

        if (userSelection === compSelection) {
          message = "its a Tie";
          messages.innerHTML = message;
          score.innerHTML = `rounds: ${rounds} user score: ${userScore}, comp score = ${compScore}`;
          return winner();
        }

        if (combinations[userSelection] === compSelection) {
          userScore++;
          message = "You won this round";
        } else {
          compScore++;
          message = "You lost this round, better luck next time";
        }

        messages.innerHTML = message;
        score.innerHTML = `rounds: ${rounds} user score: ${userScore}, comp score = ${compScore}`;
        return winner();
      };

      root.append(comp);

      const winner = () => {
        if (rounds > 0) return;

        restart = true;

        const restartButton = document.createElement("button");
        restartButton.id = "restartButton";
        restartButton.innerHTML = "restart";
        restartButton.addEventListener("click", (e) => {
          userScore = 0;
          compScore = 0;
          rounds = 5;
          message = "";
          restart = false;
          compSelection = null;

          comp.innerHTML = compSelection;

          messages.innerHTML = message;
          score.innerHTML = `rounds: ${rounds} user score: ${userScore}, comp score = ${compScore}`;
          restartButton.remove();
          return;
        });
        document.body.append(restartButton);

        if (userScore === compScore) {
          message = `It's a Tie`;
          return (messages.innerHTML = message);
        }

        userScore > compScore
          ? (message = "You Won!!!")
          : (message = "You Lose !!");
        return (messages.innerHTML = message);
      };
    </script>
  </body>
</html>

Enter fullscreen mode Exit fullscreen mode

Initializing

  const root = document.getElementById("root");
      const score = document.getElementById("score");
      const messages = document.getElementById("messages");
      let comp = document.createElement("p");

      score.innerHTML = `rounds: ${rounds} , user score: ${userScore}, comp score = ${compScore}`;

const userBlocks = options.map((item, i) => {
        const button = document.createElement("button");
        button.innerHTML = item;
        button.addEventListener("click", () => game(item));

        root.append(button);
      });
Enter fullscreen mode Exit fullscreen mode

since there isn't a possibility of embedding the JavaScript values in the JSX code in a vanilla approach, we have to setup some elements that can showcase the changes when they happen.

in this case the root element which will be holding the buttons for the user selection. A paragraph that showcases the computer selection is also used.

score is an element that will keep score on the metrics.

instead of setting the state and having the page restart reactively, we just set the innerHTML of the existing elements to represent the new metrics that happen after every round.

Restart Button

const restartButton = document.createElement("button");
        restartButton.id = "restartButton";
        restartButton.innerHTML = "restart";
        restartButton.addEventListener("click", (e) => {
          userScore = 0;
          compScore = 0;
          rounds = 5;
          message = "";
          restart = false;
          compSelection = null;

          comp.innerHTML = compSelection;

          messages.innerHTML = message;
          score.innerHTML = `rounds: ${rounds} user score: ${userScore}, comp score = ${compScore}`;
          restartButton.remove();
          return;
        });
        document.body.append(restartButton);
Enter fullscreen mode Exit fullscreen mode

The restart button does the same as the restart button on the react side, but the biggest difference is that we have to reset the elements as well as the variables.

after the restart button is clicked it is being removed by itself as well. until restart is set to true again and the cycle starts over.

Thank You

Thank you again for reading this and I hope this will help you in your next interview or coding exercise. I would love to hear your feedback or comments on the implementation and I encourage you to practice this when you are interviewing. It will help you with your chances.

Stay tuned for the next one tomorrow.

Top comments (0)