0

I am building a Simon game, modeled after the old handheld game from the 90s(?). I cannot figure out how to prevent the user from clicking on the 4 colored buttons when not their turn, and essentially, go out of turn and break my program. How can I prevent them from activating the mousedown jquery below while 'Simon' takes his turn? I really appreciate any help, thanks.

Codepen link: http://codepen.io/MindfulBell/pen/ZbgQym

Relevant code (I think, only been programming for 6 months)...

Simons action:

function simonSaysLv1(){   
    simonsMoves.push(Math.floor(Math.random() * (4-1+1)) + 1);
    doSimonMove(0);     
    playerTurn = true;    
    simonTurn = false;
  }

  function doSimonMove(index){ 

    if(index < simonsMoves.length){
      simonlightUp(simonsMoves[index]);
      setTimeout(function(){
        doSimonMove(index + 1);
      }, simonSpeed);
    }
  }

Mousedown player action:

$('.sim-button').mousedown(function(){

    if (playerTurn && started && gameOn) {
    var playerPress = '#' + this.id
    switch(playerPress){
      case grn:
        grn1Audio.play();
        $(grn).css('background-color', '#00ff00')
        playerMoves.push(1)       
        break;
      case red:
        red2Audio.play();
        $(red).css('background-color', '#ff1a1a')
        playerMoves.push(2)         
        break;
      case yel:
        yel3Audio.play();
        $(yel).css('background-color', '#ffffb3')
        playerMoves.push(3)         
        break;
      default:
        blue4Audio.play();
        $(blue).css('background-color', '#b3d1ff')
        playerMoves.push(4)          
        break;
    }    
       }  
      })
  //letting go of click
  $('.sim-button').mouseup(function(){
    if (playerTurn && started && gameOn) {
    var playerPress = '#' + this.id
    switch(playerPress){
      case grn:        
        $(grn).css('background-color', 'green')               
        break;
      case red:        
        $(red).css('background-color', '#cc0000')                 
        break;
      case yel:        
        $(yel).css('background-color', '#e4e600')                
        break;
      default:        
        $(blue).css('background-color', '#0052cc')                 
        break;
    }

      if (playerMoves.length === 21) {
        alert('You Win!')
      }
      //fail on strict
      else if (checkLoss() && strict) {
        reset();
        setTimeout(simonSaysLv1, 1200);
        $('#strict').css('border-color', '#99ff66')
        $('#count-num').text(simonsMoves.length+1)
      }
      //fail on non strict
      else if (checkLoss()) {
        playerMoves = [];
        setTimeout(function() {
          doSimonMove(0)}, 1200)        
      }
      //back to simon for his turn
      else if (playerMoves.length === simonsMoves.length) {
        speedCheck(simonsMoves.length)
        setTimeout(simonSaysLv1, 1200);
        $('#count-num').text(simonsMoves.length+1); //score
        playerMoves = [];
        simonTurn = true;
        playerTurn = false;      
      }
    }
  }) 
MindfulBell
  • 213
  • 2
  • 3
  • 7

3 Answers3

0

Here are a couple steps:

  1. Disable the buttons when they should not be clicked. See how to do that here.
  2. Add a conditional wrapper to the handler that will stop propagation of the method in the case that it is not a valid time to click.

The second option alone gets the job done in terms of not allowing a click to cause an action. The first doesn't allow a click to occur and (depending on the way you create your buttons) adds a visual component to their not being clickable. The first should only be done in conjunction with the second, however, because you need to be careful about the possibility that the user will manipulate the DOM to allow themselves to click the button.

Community
  • 1
  • 1
eshayer
  • 3
  • 2
  • Thanks for your advice. My follow up question would be that everything happens instantly so how can I set aside a 'time' when the player can't go. Maybe I am not understanding setTimeout...also, when you say conditional wrapper, would the if statement at the top of the mousedown function be OK? – MindfulBell Dec 06 '15 at 08:10
  • Answering out of order, yes, an if statement at the top would do it. On the first part, you can get the time at which the pause begins with `new Date()` and then check in your condition that the current time is sufficiently later than the time you had previously recorded by again getting the current time and comparing the difference. – eshayer Dec 06 '15 at 09:41
0

The main issue is that playerTurn will be set to true before the callback passed to setTimeout is returned. Javascript code executes asynchronously. The setTimeout callback will be called after the current call stack is finished executing, playerTurn's assignment to true being a part of the current call stack. Note that this would be true whether your timeout is 0 or 100000.

You will need to add the turn booleans inside the setTimeout callback to ensure the playerTurn variable is in the correct state when the user triggers the click event. For this program, it will just mean that nothing inside your click callback will execute because the if-block conditional will fail. There are several ways to do this, but an easy one would be to do this:

function simonSaysLv1(){   
  simonsMoves.push(Math.floor(Math.random() * (4-1+1)) + 1);
  doSimonMove(0);     
}

function doSimonMove(index){ 

  if(index < simonsMoves.length){
    simonlightUp(simonsMoves[index]);
    setTimeout(function(){
      doSimonMove(index + 1);
      if (index + 1 === simonsMoves.length) {
        playerTurn = true;    
        simonTurn = false;
      }
    }, simonSpeed);
  }
}
akosel
  • 1,133
  • 9
  • 14
  • Thanks! This worked well, I do have questions though and I am wondering if you can entertain them? Is playerTurn not setting to 'true' immediately because there are so many seconds (simonSpeed) that have to pass before it is set to true? i.e. "the setTimeout callback will be called after the current call stack is finished executing". So there are x items in simonsMoves to run through in: doSimonMove(index + 1), will they each have to finish before the next one does its thing? I am just trying to understand the timing/logic of playerTurn waiting to be 'true' I thought it was all instant. – MindfulBell Dec 07 '15 at 20:19
  • Think of it as a queue. `doSimonMove(0)` is executed. The `setTimeout` callback will be added to the main execution thread queue once the time has passed. Once it is its turn (after at least `simonSpeed` ms), the callback fires, which then immediately calls `doSimonMove(1)`. `doSimonMove(1)` in turn places its `setTimeout` callback to the end of the execution thread. `doSimonMove(0)` is then free to continue executing. This continues until we hit `index + 1 === simonMoves.length`. I made a fiddle that may help make these concepts concrete: https://jsfiddle.net/zcrszj20/3/ – akosel Dec 07 '15 at 23:48
0

My follow up question would be that everything happens instantly so how can I set aside a 'time' when the player can't go

set playerTurn = false while it isn't the player's turn. So set playerTurn = false before simon does it's stuff, then set playerTurn = true once simon is done doing its stuff (which would mean it is now the player's turn right? Not too familiar with the game).

when you say conditional wrapper, would the if statement at the top of the mousedown function be OK?

Yes, you can use the existing playerTurn - it seems that what it was created for in the first place actually. As long as playerTurn = false, the mousedown and mouseup event handlers will effectively do nothing, which is what you want.

Maybe I am not understanding setTimeout

Just to clarify, setTimeout will run the function you give it after the amount of time you tell it. It will not sit there and wait, it will continue executing the following lines of code in the meantime. In other words, it is asynchronous.

matmo
  • 1,239
  • 11
  • 16