skip to Main Content

I’m trying to make a little game project in which you have to click really fast for a small window of time.

Using the setTimeOut() to control the time and also a setInterval() to display the time left.

Yet, for the life of me, I can’t seem to synchronize both timers. The timer display seems to be behind a second.

I already tried nesting the setInterval() inside the setTimeout(), but it just runs the setTimeout() and once its done then it starts the timer.

These are the function and the time variables I used:

    let time = 10000;
    let timer = time /1000;
    setTimeout(() => {
                gameButton.style.display = "none";
                score = 0;
                power = 0;
            }, time);

    const countdown = setInterval(() => {
                textTimer.textContent = "You have " + timer + " seconds!";
                timer--;
                if (timer < 0) {
                    clearInterval(countdown);
                    textTimer.textContent = "";
                }
            }, 1000); // Update every 1 second (1000 milliseconds)

2

Answers


  1. Okay. So you set a timeout callback after 10 seconds and you start an interval to count down from 10 to 0. Now, your interval starts after 1 second delay and starts from 10. When it is at that state, there are 9 seconds until the end, even though it claims 10 seconds…

    So on each second, it claims to have 1 more seconds. So, the solution is to create a function for the countdown, pass it in setInterval and call it separately as well, so it will start from the right count at the right moment and that will solve the discrepancy.

        let gameButton = document.getElementById("game-button");
        let textTimer = document.getElementById("text-timer");
        let time = 10000;
        let timer = time /1000;
        let score = 0;
        let power = 0;
        setTimeout(() => {
            gameButton.style.display = "none";
            score = 0;
            power = 0;
            clearInterval(countdown);
            textTimer.textContent = "";
        }, time);
        function finalCountDown() {
            textTimer.textContent = "You have " + timer + " seconds!";
            timer--;
        }
        const countdown = setInterval(finalCountDown, 1000); // Update every 1 second (1000 milliseconds)
        finalCountDown();
    <span id="text-timer"></span>
    <input id="game-button" value="click me" type="button">

    EDIT

    Now that I think of it, it makes sense to implement a mechanism in general that executes a function right away and creates an interval as well. Here it is:

    function setEagerInterval(c, t) {
        c();
        return setInterval(c, t);
    }
    
    let counter = 0;
    
    setEagerInterval(function() {
        console.log(++counter);
    }, 5000);

    And we can schedule an end to it too:

    function setEagerInterval(c, t, e) {
        c();
        let intervalID = setInterval(c, t);
        if (!isNaN(e)) {
            setTimeout(function() {
                console.log("aloha ended");
                clearInterval(intervalID);
            }, e);
        }
    }
    
    let counter = 0;
    
    setEagerInterval(function() {
        console.log(++counter);
    }, 1000);
    
    setEagerInterval(function() {
        console.log("aloha");
    }, 500, 2400);
    Login or Signup to reply.
  2. It seems that the relation between your interval and timeout is, that the timeout should run at the end of the interval.

    Therefore, I decided to clearly show this relation in-code using Promise#then:

    const cbInterval = /*...*/; // (interval callback)
    const cbReset = /*...*/; // (timeout callback)
    
    interval(cbInterval, 1000).then(cbReset);
    

    For this, the interval needs to be a promise; the custom interval function does this.

    This keeps the logic in sync as well as the relation obvious.

    "Full" example:

    /* Ignore; needed for code */
    const textTimer = document.getElementById("timer-text");
    const gameButton = document.getElementById("bt-game");
    
    let score = 0;
    let power = 0;
    
    /* SUGGESTED CHANGES HERE */
    let timer = 10; // seconds
    
    // Previous interval logic
    const cbInterval = (stop) => {
      textTimer.textContent = "You have " + timer + " seconds!";
      timer--;
      if (timer < 0) {
        stop();
      }
    };
    
    const cbReset = () => {
      // Previous interval-end logic
      textTimer.textContent = "";
    
      // Previous timeout logic
      gameButton.style.display = "none";
      score = 0;
      power = 0;
    };
    
    // Readable relation between previous interval and timeout logic
    interval(cbInterval, 1000).then(cbReset);
    
    // ---
    
    /**
     * Runs an interval as a promise.
     * @param callback - a callback; first argument is a function
     *                   to stop the interval and resolve the promise
     * @param ms - milliseconds to wait between calls
     */
    function interval(callback, ms) {
      return new Promise((resolve) => {
        let id;
        const fnStop = () => {
          clearInterval(id);
          resolve();
        };
    
        id = setInterval(() => {
          callback(fnStop);
        }, ms);
      });
    }
    <p id="timer-text"></p>
    <button id="bt-game">Play (placeholder)</button>

    Instead of this setInterval "analog", you may also want to look at Lajos Arpad’s setEagerInterval functions.

    The interval’s timing/stopping may have to be adjusted.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search