Help with Simon Says

Help with Simon Says
0

#1

I’m working on Simon Says and I’m stuck. I’m trying to write a function that simulates button clicks to use for the computer’s turn i.e. a series of increasingly complex turn moves.

I’m starting by just trying to figure out how to simulate a click event on the green button. I will later work out timing the event using setInterval and choosing random combinations of buttons for the turn via the Math.random function.

But for now I’m just trying to simulate the button click of the green button and save it to the computer’s array which is aiMoves[] in my program.

I’m simulating the click like this:

function simulateClick(){
    $('#green').on('click', function(){
        sound1();
        alert("Green button clicked");
    });
    $('#green').trigger('click');
  }

After simulating the click I want to pass it to the aiMoves array so I can compare it to the playerMoves array and determine if the player has the correct sequence of moves. I stored the players series of moves like this:

  function storeClicks(){
   $('.button').each(function() {
     $(this).click(function() {
        playerMoves.push($(this).attr('data-simonButton'));
        console.log(playerMoves);
        return playerMoves;
      });
    });
  }

I’ve hit a mental block and I’m probably overthinking this, but how would I do something similiar for the aiMoves array and then pass both to the validate function?

Here’s the codepen:


#2

Also, does the approach of using setInterval and the Math.random function to simulate the computer’s moves sound like a good approach or would there be a better way?


#3

I used math.random and modulated it to generate the clicks. I ran into the same mental roadblock you did, and I think instead of building a separate function to simulate a click as opposed to actually clicking, I had one function to handle a click. I used a boolean to keep track of whose turn it was (player1turn = true;). On the player’s turn, a button click would be added to the player’s array, until the player’s array length was the same as simon’s, at which point it was simon’s turn. The function that handled Simon’s turn generated a random number with Math.random, added it to the array, then clicked all the buttons held in simon’s array. Once Simon was done, it flipped the boolean (bool != bool) and made it player 1’s turn again.


#4

@Nicknyr I’ve not completed this project yet, but I wanted to share an idea I had about it that may be helpful. If it’s not, feel free to ignore it.

Would it be feasible to generate the AIs moves randomly before the game begins? Then, loop through those moves and keep track of how many the player gets correct, so you know where to end the loop?

So, for instance, the randomly generated AI moves might be:

aiMoves = ['red', 'green', 'blue', 'red', 'yellow', 'blue', 'green', 'blue', 'red', 'red', 'green', 'blue', 'yellow', 'green', 'blue', 'green', 'yellow, 'red', 'red', 'green', 'blue', 'red', 'yellow', 'blue', 'green', 'blue', 'red', 'red', 'green', 'blue', 'yellow', 'green', 'blue', 'green', 'yellow, 'red' ...]

For the AIs first move, it would be something like aiMoves[0] and then simulate the red button being pushed. If the player matches that move, then the next AI move would be something like aiMoves.slice[0, 2], which will return 'red', 'green', and so on.

Does this make sense? If so, do you think it’ll work?


#5

You don’t need to simulate clicks with this project, you can just change the CSS class (to something like active) for when the computer lights up a colour. Simulating clicks is pointless, it adds completely unecessary complexity. Simon is a single player challenge; there is no AI, a player does not play against anyone but themselves. The only aim of the program (bar rendering the UI) is to return an increasing sequence. All that has to happen is that the players clicks match the current sequence. If that happens, increase sequence by one, go again, keep doing that until game ends. For example:

Create empty array, eg `var sequence = []`.
Set max turns, eg `var maxTurns = 20`.

Assume there is a function for picking a random colour, eg:

`function randColour() { return ['r','g','b','y'][Math.round(Math.random()*3) + 1]; }`

1. `sequence.push(randColour())`
2. If `sequence.length > maxTurns`, EXIT, game is won.
3. Start round
4. Loop through `sequence` and highlight each matching UI element in turn
5. Copy current sequence and allow player input:
  a. If `copySequence.length == 0`, EXIT, round complete
  b. If input != first in sequence, EXIT, game over
  c. If input == first in sequence, `copySequence.shift()`
  d. GO TO a
6. GO TO 1 

#6

Why would you want to do it this way, though? You don’t know how good your player’s memory will be in advance. You might need an array of only 10 for someone with very poor memory skills, or 20,000 for someone with genius-level memory (yes, these people exist). It’d be a waste (of both processing power and memory) to randomly generate 20k values for every player, and in principle, there’s still no guarantee it’d be enough.

Sure, you could reasonably top out at around 100 and there’d be no noticeable performance drop either way, but this kinda seems like cheating just to avoid having to use .push(). And you’d still have to use .push() for the array recording the player’s moves, anyway.


#7

This is what I ended up with:

function aiTurns(randNum){
    if(level > 0) {
      for(var i = 0; i < aiMoves.length; i++) {
        if(aiMoves[i] === 1){
          //sound1();
          $('#green').addClass("active");
          setTimeout(function(){
            $('#green').removeClass("active");
          }, 500);
        }
        else if(aiMoves[i] === 2){
          //sound2();
          $('#red').addClass("active");
          setTimeout(function(){
            $('#red').removeClass("active");
          }, 500);
        }
        else if(aiMoves[i] === 3) {
          //sound3();
          $('#yellow').addClass("active");
          setTimeout(function(){
            $('#yellow').removeClass("active");
          }, 500);
        }
        else if(aiMoves[i] === 4){
          //sound4();
          $('#blue').addClass("active");
          setTimeout(function(){
            $('#blue').removeClass("active");
          }, 500);
        }
      }
    }
      level--;
      playerTurn = true;
  }

The problem I have now is that instead of showing each move individually it shows all moves up to that point simultaneously. For example if the move is 1, 2, 3 ( green, red, yellow) it lights up all three buttons at once.


#8

@Nicknyr You are on the right track. You are repeating yourself quite a bit though. Whenever you see repeated blocks of code it is generally a good idea to turn the block into its own function. Why not write a separate function that takes a color or move variable with your move function?

Maybe something like this:

function move(color) {
  //sound(color)
  $(`#{color}`).addClass("active");
  setTimeout(function(){
    $(`#{color}`).removeClass("active");
  }, 500);
}

This way you save lots of extra typing! :slight_smile:

As to your timing issue, it’s hard to say without seeing more code. I would guess that you don’t have any timeout on the loop going through your ai turn array. The one I see here only affects how long each tile has the ‘active’ class.

Keep at it. Just try to solve one problem at a time and you will have it in no time.


#9

setTimeout is asynchronous. You set the timeout, then your code continues executing (in this case, continues through the for loop, setting more timeouts). That’s why they all run at the same time.

You either need to set different timeouts each time based on which iteration of the loop you’re on or write it recursively such that each timeout is set only after the previous one has completed.


#10

That makes sense. Would using the map function be a good approach to doing this recursively?


#11

No, no map (or any of the iteration methods), they’re not useful in this case. If you’re using an array, then just a function that takes an array of things, do something to first item, call function again with the remaining of the arr, or uses a counter that increases each time to pick the right item. And exit once at end of array


#12

I’ve tried to iterate through it by incrementing the i variable from the for loop after it finds the match and executes the appropriate button press like this:

  function aiTurns(randNum){
      for(var i = 0; i < aiMoves.length; i++) {
      if(aiMoves[i] === 1){
          //sound1();
          $('#green').addClass("active");
          setTimeout(function(){
            $('#green').removeClass("active");
          }, 500);
          i++;
        }
        else if(aiMoves[i] === 2){
          //sound2();
          $('#red').addClass("active");
          setTimeout(function(){
            $('#red').removeClass("active");
          }, 500);
          i++;
        }
        else if(aiMoves[i] === 3) {
          //sound3();
          $('#yellow').addClass("active");
          setTimeout(function(){
            $('#yellow').removeClass("active");
          }, 500);
          i++;
        }
        else if(aiMoves[i] === 4){
          //sound4();
          $('#blue').addClass("active");
          setTimeout(function(){
            $('#blue').removeClass("active");
          }, 500);
          i++;
        }

        level--;
        playerTurn = true;
    }
  }

I’ve also tried incrementing i at the end of the for loop. Both are giving me some whacky results and I’m stumped.


#13

I’m only on the api weather machine, but my idea is to generate a random sequence like (quoting this)

aiMoves = ['red', 'green', 'blue', 'red', 'yellow', 'blue', 'green', 'blue', 'red', 'red', 'green', 'blue', 'yellow', 'green', 'blue', 'green', 'yellow, 'red', 'red', 'green', 'blue', 'red', 'yellow', 'blue', 'green', 'blue', 'red', 'red', 'green', 'blue', 'yellow', 'green', 'blue', 'green', 'yellow, 'red' ...]

Then when they click on a tile, push the name of whatever tile they click into a array. Then loop threw and compare the arrays. I still have no idea what the challenge is really about, just saying.


#14

I did it like this, the code is pretty rough and I haven’t tested it, but you can get the idea and fix its problems, if any. I had the same approach when coding my simon game and it worked just fine.

So first of all, you need 3 things
image

current for iteration, playerTurn speaks for itself and the sequence is just an example I wrote myself, but I’m sure you know how to generate it in your code. Also, I used numbers instead of color names just because I find it more simple.

In the html, each button have a class “button” and id “1”, “2”, “3” or “4”

‘Current’ variable is used both to play randomly generated sequence and for checking if the player is pressing the right button. You can use it for both since player won’t be clicking during sequence play and vice versa.

This is the code for button presses

And this is for sequence playing and animating

Inside the animateButton function you just do whatever you like to button with the passed id. Since the id and elements in the sequence are both numbers, this should be simple to implement.

I hope this is useful and my comments are easy enough to understand. Obviously there is a lot of room for improvements, but if you get the basic idea, you can do whatever you like.