1. Overview

Challenge & starter files: Advent of JS

Full codes: nilstarbb/advent-of-js/12-rock-paper-scissors

Live Demo: 12 - Rock, Paper, Scissors || Advent of JavaScript

Preview:

rock-paper-scissors.gif

2. Details

Users should be able to:

  • play the game and see the results

3. Coding

Setup React template

Please be aware: this approach is fine for learning and creating simple demos but is not suitable for production.

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    ...
    <link rel="stylesheet" href="./styles.css" />

    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  </head>

  <body>
    <div class="wrapper menu" id="wrapper"></div>
    <script type="text/babel" src="./script.js"></script>
  </body>
</html>

script.js:

const { useState, useEffect } = React;

const App = () => {
  return (
    <>
			...
    </>
  );
};

ReactDOM.render(<App />, document.getElementById("wrapper"));

Add styles for tie

This game has three results: win, loss, and tie. The starter files only have styles for win and loss, so we’d better create one for the tie.

Add a title for tie:

<div class="your-pick">
  <h1 class="you-win">you win</h1>
  <h1 class="tie">tie</h1>
  <img src="./images/rock.png" alt="Rock" />
</div>

Then use CSS style to hide those titles on different results:

/* Winner Page */
body.computer-wins h1.you-win,
body.computer-wins h1.tie,
body.you-win h1.computer-wins,
body.you-win h1.tie,
body.tie h1.you-win,
body.tie h1.computer-wins {
  visibility: hidden;
}

Play rock, paper, scissors

Rock, paper, scissors is pretty simple. Here we create three options for the user and computer to pick, then compare the picks with the winning pairs to get our result.

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

const pickWins = {
  rock: "scissors",
  paper: "rock",
  scissors: "paper",
};

...

const handlePick = (userPick) => {
  const computerPick = picks[Math.floor(Math.random() * picks.length)];
  const result = {
    userPick: userPick,
    computerPick: computerPick,
    result:
      userPick == computerPick
        ? "tie"
        : pickWins[userPick] == computerPick
        ? "you-win"
        : "computer-wins",
  };
  setPlayResult(result);
};

Build UI

Since the CSS file use body classes outside components to change the appearance, we need to use document.body.classList.add()/remove() to modify those classes.

You can use React Router here to switch between play and result, but as this is just a small practice project, we won’t go that far.

script.js:

const { useState, useEffect } = React;

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

const pickWins = {
  rock: "scissors",
  paper: "rock",
  scissors: "paper",
};

const Winner = ({ result, handlePlayAgain }) => {
  useEffect(() => {
    switch (result.result) {
      case "computer-wins":
        document.body.classList.add("winner", "computer-wins");
        document.body.classList.remove("you-win", "tie");
        break;
      case "you-win":
        document.body.classList.add("winner", "you-win");
        document.body.classList.remove("computer-wins", "tie");
        break;
      case "tie":
        document.body.classList.add("winner", "tie");
        document.body.classList.remove("computer-wins", "you-win");
      default:
        break;
    }
  }, []);

  return (
    <>
      <div className="your-pick">
        <h1 className="you-win">you win</h1>
        <h1 className="tie">tie</h1>
        <img
          src={"./images/" + result.userPick + ".png"}
          alt={result.userPick}
        />
      </div>
      <div className="computer-pick">
        <h1 className="computer-wins">computer wins</h1>
        <img
          src={"./images/" + result.computerPick + ".png"}
          alt={result.computerPick}
        />
      </div>
      <button className="play-again" onClick={() => handlePlayAgain()}>
        play again?
      </button>
    </>
  );
};

const PickOption = ({ option, handleClick }) => {
  return (
    <li className="pick-one">
      <button onClick={() => handleClick(option)}>
        <img src={"./images/" + option + ".png"} alt={option} />
        {option}
      </button>
    </li>
  );
};

const Pick = ({ handleClick }) => {
  useEffect(
    () => document.body.classList.remove("winner", "you-win", "computer-wins"),
    []
  );

  return (
    <>
      <h1>pick one</h1>
      <ul>
        {picks.map((pick) => (
          <PickOption key={pick} option={pick} handleClick={handleClick} />
        ))}
      </ul>
    </>
  );
};

const App = () => {
  const [isPlaying, setIsPlaying] = useState(true);
  const [playResult, setPlayResult] = useState(null);

  useEffect(() => {
    if (playResult) {
      setIsPlaying(false);
    }
  }, [playResult]);

  const handlePick = (userPick) => {
    const computerPick = picks[Math.floor(Math.random() * picks.length)];
    const result = {
      userPick: userPick,
      computerPick: computerPick,
      result:
        userPick == computerPick
          ? "tie"
          : pickWins[userPick] == computerPick
          ? "you-win"
          : "computer-wins",
    };
    setPlayResult(result);
  };

  const handlePlayAgain = () => {
    setIsPlaying(true);
    setPlayResult(null);
  };

  return (
    <>
      {isPlaying ? (
        <Pick handleClick={handlePick} />
      ) : (
        <Winner result={playResult} handlePlayAgain={handlePlayAgain} />
      )}
    </>
  );
};

ReactDOM.render(<App />, document.getElementById("wrapper"));