3

I'm beginner at JS and I'm working on a project to create a game, which is in my case a Snake-game. Everything is going fine, except that if I quickly press multiple keys at once, the Snake dies, because (i think) it's a collision. That's why I want to disable multiple keypresses somehow, to try if it solves the problem. My code:

var Snake = function()
{
  //this is the direction table; UP, RIGHT, DOWN, LEFT
  this.directions = [[0, -1], [1, 0], [0, 1], [-1, 0]];
}

function onKeyDown(event)
{
  if (gameover)
    tryNewGame();
  else 
  {
    if (event.keyCode == 37 || event.keyCode == 65)
    {
      if (snake.direction != 1)  
        snake.direction = 3;
    } 
    else if (event.keyCode == 38 || event.keyCode == 87) 
    {
      if (snake.direction != 2) 
        snake.direction = 0;
    } 
    else if (event.keyCode == 39 || event.keyCode == 68) 
    {
      if (snake.direction != 3) 
        snake.direction = 1;
    } 
    else if (event.keyCode == 40 || event.keyCode == 83) 
    {
      if (snake.direction != 0)
        snake.direction = 2;
    }   
  }
}
Zsolti
  • 75
  • 6
  • It took me a minute to realize that you're asking about multiple *simultaneous* keypresses. That's important. – isherwood Apr 18 '19 at 18:52
  • You might lock input until `keyup` on the same key. – isherwood Apr 18 '19 at 18:54
  • 1
    Possible duplicate of [How to detect if multiple keys are pressed at once using JavaScript?](https://stackoverflow.com/questions/5203407/how-to-detect-if-multiple-keys-are-pressed-at-once-using-javascript) – isherwood Apr 18 '19 at 18:57

4 Answers4

1

The problem is probably that the direction changes twice before the snake's shape is updated, and so the first of those two direction changes is actually ignored.

A way to overcome this, is to buffer the direction changes in a queue (implemented as array).

So in your key event handler you would not do this:

if (snake.direction != 1)  
    snake.direction = 3;

But instead:

if ((snake.queue.length ? snake.queue[0] : snake.direction) != 1) 
    snake.queue.unshift(3);

This queue should be initialised in the Snake constructor:

this.queue = [];

Then when you update the snake's position (at a time interval), you would consume that queue if there is something in it:

if (snake.queue.length)
    snake.direction = snake.queue.pop();
// Now apply this direction:
//   ... this would be code you already have...

You could set a maximum to this queue size, as it can get awkward if the user keeps pressing keys faster than the snake updates.

trincot
  • 317,000
  • 35
  • 244
  • 286
0

you can use the setTimeout function on your key press functionality like this...

EDIT: 2000ms is just an example time. You can make it 500ms for every 1/2 second or any other number of ms you want

setTimeout(
function onKeyDown(event)
{
  if (gameover)
    tryNewGame();
  else 
  {
    if (event.keyCode == 37 || event.keyCode == 65)
    {
      if (snake.direction != 1)  
        snake.direction = 3;
    } 
    else if (event.keyCode == 38 || event.keyCode == 87) 
    {
      if (snake.direction != 2) 
        snake.direction = 0;
    } 
    else if (event.keyCode == 39 || event.keyCode == 68) 
    {
      if (snake.direction != 3) 
        snake.direction = 1;
    } 
    else if (event.keyCode == 40 || event.keyCode == 83) 
    {
      if (snake.direction != 0)
        snake.direction = 2;
    }   
  }
},
2000ms
);
jtylerm
  • 482
  • 4
  • 15
  • 1
    Seems like a 2 second lockout would render the game unplayable. Shorter timeouts would become unreliable. – isherwood Apr 18 '19 at 18:52
  • the 2000ms is arbitrary, you can make it any time limit you want – jtylerm Apr 18 '19 at 18:53
  • There is a similar stack question/answer here https://stackoverflow.com/questions/5203407/how-to-detect-if-multiple-keys-are-pressed-at-once-using-javascript – Vbudo Apr 18 '19 at 18:55
  • It doesn't matter what the time value is--it's going to be problematic. It either locks the game or it's not long enough to prevent key mashing (or both). – isherwood Apr 18 '19 at 18:57
0

I always recommend debounced or throttled function for such use case. Hands down, it is the best.

Lodash has a decent implementation of both _.debounce and _.throttle. Also make sure you read this article to fully understand the concepts.

Basic idea is after 1st call, you "freeze" the event handler for a short period of time. It's still called and receive events, just not having any effect.

hackape
  • 18,643
  • 2
  • 29
  • 57
0

As mentioned here, throttling the event would work well. Here's a simple class that could serve as your throttler:

class CooldownTimer {
  constructor(time) {
    this.cooldownTimeout = null
    this.cooldownTime = time
    this.startedAt = null
  }

  isReady = () => {
    return !this.cooldownTimeout
  }

  start = () => {
    if (!this.cooldownTimeout) {
      clearTimeout(this.cooldownTimeout)
    }

    this.startedAt = Date.now()
    this.cooldownTimeout = setTimeout(() => {
      this.cooldownTimeout = null
    }, this.cooldownTime)
  }
}

You would have to define a CooldownTimer somewhere, probably where you bind your events:

let keyPressCooldown = new CooldownTimer(200)

Then you can use it in your event code:

function onKeyPress(event) {
  if (keyPressCooldown.isReady()) {
    console.log('key pressed')
    keyPressCooldown.start() // Do not forget to start the cooldown here
  }
}
Fleezey
  • 156
  • 7