0

I have built a rock/paper/scissors React app. The app is working fine, I just would like to add some delay when the CPU has to choose the weapon(rock/paper/scissors).

I would like that there is a window of time where the image selected by the CPU is not on the screen, while the user's choice is immediately appearing on screen.

I tried to add a setInterval() function inside my compounentDidMount() method but with no luck.

How can I add the delay only on the CPU part ?

https://codesandbox.io/s/nice-ardinghelli-96sum?file=/src/components/Main.js

Thank you very much in advance.

Main.js

import React from "react"
import Choice from "./Choice"
import TryAgain from "./TryAgain"

import paper from '../images/icon-paper.svg'
import rock from '../images/icon-rock.svg'
import scissors from '../images/icon-scissors.svg'

import './Main.css';

class Main extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            onScreen: true,
            choiceName: '',
            choiceImage: '',
            choiceBorderColor: '',
            choiceExtraBorderColor: '',
            houseChoice: '',
            results:'',
        }
        this.handleClick = this.handleClick.bind(this)
        this.handleTryAgainClick = this.handleTryAgainClick.bind(this)
    }
    
    /******************* setInterval() ******************************/
    componentDidMount() {
        // Call this function so that it fetch first time right after mounting the component
        this.handleClick();
    
        // set Interval
        this.interval = setInterval(this.handleClick, 5000);
    }

    /*function that handles the user choice*/
    handleClick = (choiceName, choiceImage, choiceBorderColor, choiceExtraBorderColor) => () => {
        this.setState({
            onScreen: false,
            choiceName,
            choiceImage,
            choiceBorderColor,
            choiceExtraBorderColor,
        })

        /*function that get a random number between 0 and 2, and set this number to the house index*/
        function getRandomInt(max) {
            return Math.floor(Math.random() * Math.floor(max));
        }
        const index = getRandomInt(3)
        this.setState({
            houseChoice: index
        })
        const results = this.getResults(choiceName, index).toUpperCase()
        this.setState({
            results: results,
        })

    
        if(results === "WIN") {
            this.props.setScore(1)
        } else if (results === "LOSE") {
            this.props.setScore(-1)
        }
        else {
            this.props.setScore(0)
        }
    }

    /*function that get the main logic and the results of the game*/
    getResults(choiceName, houseChoice) {
        if(choiceName === "paper" && houseChoice === 0) {
            return "draw"
        } else if(choiceName === "paper" && houseChoice === 1) {
            return "lose"
        } else if(choiceName === "paper" && houseChoice === 2) {
            return "win"
        }
        if(choiceName === "rock" && houseChoice === 0) {
            return "lose"
        } else if(choiceName === "rock" && houseChoice === 1) {
            return "win"
        } else if(choiceName === "rock" && houseChoice === 2) {
            return "draw"
        }
        if(choiceName === "scissors" && houseChoice === 0) {
            return "win"
        } else if(choiceName === "scissors" && houseChoice === 1) {
            return "draw"
        } else if(choiceName === "scissors" && houseChoice === 2) {
            return "lose"
        }
    }

    /*function that switches the screen and resets the index of the house*/
    handleTryAgainClick() {
        this.setState({
            onScreen: true,
            houseChoice: ''
        })
    }
zawrdo
  • 31
  • 7
  • By delaying the CPU choice what exactly you expect to happen? https://xyproblem.info/ – Dennis Vash Nov 19 '20 at 09:30
  • @DennisVash Yes sorry. I mean I would like that there is a window of time where the image selected by the CPU is not on the screen, while the user's choice is immediately appearing on screen. – zawrdo Nov 19 '20 at 09:34
  • So you need to **DELAY** the component render and not delaying the click. And btw you need to use `setTimeout` because `setInterval` doing something entirely different.... So read about conditional rendering. Your question is not minimal so Im not going to get over all this code for answering, hope you will get an answer. – Dennis Vash Nov 19 '20 at 09:38

5 Answers5

1

You need to add a state, which does not render anything. For example just set the cpu choice to 4 and update your render function. Then you can add a sleep funciton like here. I made some working example of what I think you want there
The main changes I made to your code are

this.sleep(1500).then(() => {
      const index = getRandomInt(3);
      this.setState({
        houseChoice: index
      });
      const results = this.getResults(choiceName, index).toUpperCase();
      this.setState({
        results: results
      });

      /*****************calling setScore()********************/
      if (results === "WIN") {
        this.props.setScore(1);
      } else if (results === "LOSE") {
        this.props.setScore(-1);
      } else {
        this.props.setScore(0);
      }
    });

in handleClick which performs the timeout.
In the result page I added

this.state.houseChoice === 2 ? (
             /*3*/
              <div
                className="elem-container result-container"
                style={{
                  borderColor: "hsl(349, 71%, 52%)",
                  color: "hsl(349, 70%, 56%)"
                }}
              >
                <img src={rock} className="choice-image" alt="img" />
              </div>
            ) : null
mark
  • 81
  • 4
  • Thank you very much for your answer! And is there a way to add a delay also on the YOU WIN/LOSE part ? I mean if there is a way that it gets displayed only when the CPU choice is appeared on screen too – zawrdo Nov 19 '20 at 10:15
  • Sure! Let's say we choose houseChoice === 4 to be our state when we are not yet ready to display something. We can then wrap the "result text" in a condition. I updated the example on codesandbox :) You can also replace the "null" part for example with a loading spinner or something like this – mark Nov 19 '20 at 10:29
  • Yes! It was perfectly! Thank you very much! Your answer is correct! – zawrdo Nov 19 '20 at 10:42
1

After the human player selects a move don't immediately let the CPU select a move. Implement the componentDidUpdate lifecycle function and move the CPU's selection logic there. This also necessitates moving the checking for the win logic as well.

/*function that handles the user choice*/
handleClick = (
  choiceName,
  choiceImage,
  choiceBorderColor,
  choiceExtraBorderColor
) => () => {
  this.setState({
    onScreen: false,
    choiceName,
    choiceImage,
    choiceBorderColor,
    choiceExtraBorderColor
  });
};

Once the human player has selected a move, get the random move for the house but don't immediately enqueue the state update, instead use a setTimeout into update state in a bit.

Only check for the win if both players have selected a move but the results haven't been computed and stored in state.

componentDidUpdate(prevProps, prevState) {
  if (
    prevState.choiceName !== this.state.choiceName &&
    this.state.choiceName
  ) {
    function getRandomInt(max) {
      return Math.floor(Math.random() * Math.floor(max));
    }
    const index = getRandomInt(3);

    setTimeout(() => {
      this.setState({
        houseChoice: index
      });
    }, 2000);
  }

  if (
    this.state.choiceName &&
    [0, 1, 2].includes(this.state.houseChoice) && // We want 0 to be truthy :)
    !this.state.results
  ) {
    const results = this.getResults(
      this.state.choiceName,
      this.state.houseChoice
    ).toUpperCase();
    this.setState({
      results: results
    });

    /*****************calling setScore()********************/
    if (results === "WIN") {
      this.props.setScore(1);
    } else if (results === "LOSE") {
      this.props.setScore(-1);
    } else {
      this.props.setScore(0);
    }
  }
}

Conditionally render a "waiting on the CPU" UI

<h4 className="result-title">
  {this.state.houseChoice === ""
    ? "THE HOUSE CHOOSING"
    : "THE HOUSE PICKED"}
</h4>

{this.state.houseChoice === "" ? (
  <div>...</div>
) : this.state.houseChoice === 0 ? (
  /*1*/
  <div
    className="elem-container result-container"
    style={{
      borderColor: "hsl(230, 89%, 62%)",
      color: "hsl(230, 89%, 65%)"
    }}
  >
    <img src={paper} className="choice-image" alt="img" />
  </div>
) : this.state.houseChoice === 1 ? (
  /*2*/
  <div
    className="elem-container result-container"
    style={{
      borderColor: "hsl(39, 89%, 49%)",
      color: "hsl(40, 84%, 53%)"
    }}
  >
    <img src={scissors} className="choice-image" alt="img" />
  </div>
) : (
  /*3*/
  <div
    className="elem-container result-container"
    style={{
      borderColor: "hsl(349, 71%, 52%)",
      color: "hsl(349, 70%, 56%)"
    }}
  >
    <img src={rock} className="choice-image" alt="img" />
  </div>
)}

Conditionally render the results and try again button.

<div className="final-result-container">
  {this.state.results && (
    <>
      <h1 className="bold">YOU {this.state.results}</h1>
      <TryAgain onClick={this.handleTryAgainClick} />
    </>
  )}
</div>

Don't forget to reset the all of the state when playing again

handleTryAgainClick() {
  this.setState({
    onScreen: true,
    choiceName: "",
    choiceImage: "",
    choiceBorderColor: "",
    choiceExtraBorderColor: "",
    houseChoice: "",
    results: ""
  });
}

Edit how-to-add-delay-on-particular-function-in-react

enter image description here

enter image description here

Drew Reese
  • 43,833
  • 5
  • 21
  • 43
  • Thank you very much for your answer ! And is there a way to add a delay also on the YOU WIN/LOSE part ? I mean if there is a way that it gets displayed only when the CPU choice is appeared on screen too – zawrdo Nov 19 '20 at 10:31
  • @zawrdo Sure. Updated the answer and codesandbox. I found and fixed a few other logical bugs I found in the code as well. – Drew Reese Nov 19 '20 at 10:50
0

I'm not sure if I get the question correct, but here goes, if the delay is to be set on the render then just do not render until your choice is made. If the app has to do something after an input from the player make the following action asynchronous with async await.

Dharman
  • 21,838
  • 18
  • 57
  • 107
LukaZ
  • 13
  • 3
0

If i get you right, maybe Debounce from lodash will be answer for you. I’ve used it on my tic tac toe app for delaying computer move. Simply add it from NPM

npm install lodash —save-dev

Then import in your file

import {debounce} from 'lodash';

Create constantly which will be debounce, which you’ve called from lodash. 500 is time,how much delay from move do you want. In this case it will be 0.5s.

const debounceComputerMove = debounce(()=>{
  computerMove();
},500);

Then when you want to call computerMove function, call debounceComputerMove instead of computerMove()

Pinncik
  • 179
  • 1
  • 8
0

const sleep = (milliseconds) made a var then defined time