laszlo
laszlo

Reputation: 624

prevent snake from going in reverse direction

I am making a classic snake remake in JavaScript just for weekend fun, and I ran into this problem that if I press buttons quite fast - the snake (caterpillar in my case) is able to change direction to opposite and run into itself.

The way to recreate this situation is as follows:

direction is for example 'left'

press up(or down) and press right quickly after

now the caterpillar goes backwards. And my goal is it should make U-turn

I made the checks for opposite dir, but this doesn't prevent this

update = function() {
    if (cat.direction != 'right' && key[37] === true) {
        cat.direction = 'left';
    }
    if (cat.direction != 'left' && key[39] === true) {
        cat.direction = 'right';
    }
    if (cat.direction != 'down' && key[38] === true) {
        cat.direction = 'up';
    }
    if (cat.direction != 'up' && key[40] === true) {
        cat.direction = 'down';
    }
};

the full code

I was using normal addEventListener for the key listening, but I changed it for another approach (found somewhere), where I do update on keys very often and caterpillar moving is happening only from time to time, as I thought it might be a problem to directly associate drawing, changing direction and moving in the same time interval. I hope I am understandable, sorry if something isn't clear - I would be happy to provide more info if so.

Upvotes: 1

Views: 1803

Answers (2)

trincot
trincot

Reputation: 351039

One solution is to not process more than one key per move, but to allow more responsiveness you could implement a key buffer, instead of maintaining the key states as you do know. You would only collect arrow key presses in that buffer, and not push any repetitions of the same key into it.

Here are the relevant changes to the code:

Initialise the key buffer:

var keyBuffer = [];

Push arrow keys into the buffer when pressed:

var keyDown = function(e) {
    var keyCode = e.which ? e.which : e.keyCode;
    // *** Queue the arrow key presses
    if (keyCode >= 37 && keyCode <= 40 && 
            keyCode !== keyBuffer[keyBuffer.length-1] && ) {
        keyBuffer.push(keyCode);
    }
};

Process one key from the buffer at a time:

var update = function() {
    // *** Extract next key from buffer, and only treat that one
    // -- could be `undefined`, but that is not a problem:
    var key = keyBuffer.shift();
    if(cat.direction != 'right' && key === 37){
        cat.direction = 'left';
    } else if(cat.direction != 'left' && key === 39){
        cat.direction = 'right';
    } else if(cat.direction != 'down' && key === 38){
        cat.direction = 'up';
    } else if(cat.direction != 'up' && key === 40){
        cat.direction = 'down';
    }
};

Only process next key when about to move:

function loop() {
    board.resetCanvas();
    if(counter > 1000){
        update(); // ***only process buffered keys when moving
        cat.move();
        counter = 0;
    }
    cat.draw();
    counter += 5*cat.multiplier;
};

That's it. See fiddle below:

var canvas = document.getElementById("board");
var context = canvas.getContext("2d", {alpha:false});
var pieceSideLength = canvas.width / 40;
var key = [];
var keyBuffer = [];

window.addEventListener('keyup', function(e) {
    this.keyUp.call(this, e);
}, false);
window.addEventListener('keydown', function(e) {
    this.keyDown.call(this, e); 
}, false);
				
function Piece(x,y){
		this.x = x;
		this.y = y;
}

board = {
	
    leftBound: 0,
    rightBound: canvas.width / pieceSideLength,
    topBound: 0,
    bottomBound: canvas.height / pieceSideLength,
   
	drawPiece: function(x, y, color){
		context.fillStyle = color;
		context.fillRect(x*pieceSideLength,y*pieceSideLength,pieceSideLength,pieceSideLength);
		context.strokeStyle = 'white';
		context.strokeRect(x*pieceSideLength,y*pieceSideLength,pieceSideLength,pieceSideLength);
	},
	
	resetCanvas: function(){
		context.clearRect(0,0,canvas.width,canvas.height);
	}
	
};
//cat as for caterpillar
cat = {

	x: canvas.width/pieceSideLength/2, //initial x 
	y: canvas.height/pieceSideLength/2, //initial y
	pieces: [],
	direction: 'up',
	color: '#5da03c',
	shouldGrow: false,
	multiplier: 5,
   
    init: function(){
		cat.pieces.push(new Piece(this.x, this.y));
    },
   
    move: function(){
		if(cat.pieces.length <= 10){
			cat.shouldGrow = true;
		}
        var newX = cat.pieces[cat.pieces.length-1].x;
        var newY = cat.pieces[cat.pieces.length-1].y;
		if(cat.direction=='up'){
			cat.makeNewHeadAt(newX,newY-1);
		  }
		if(cat.direction=='down'){
			cat.makeNewHeadAt(newX,newY+1);
		  }
		if(cat.direction=='left'){
			cat.makeNewHeadAt(newX-1,newY);
		  }
		if(cat.direction=='right'){
			cat.makeNewHeadAt(newX+1,newY);
		  }
		  cat.grow();
		},
   
    makeNewHeadAt: function(x,y){
		cat.pieces.push(new Piece(x,y));
    },
   
    grow: function(){
		if(cat.shouldGrow == false){
			cat.pieces.shift();
		} else {
			cat.shouldGrow = false;
		}
    },
   
    draw: function(){
		for(i=0;i<cat.pieces.length;i++){
			var p = cat.pieces[i];
			board.drawPiece(p.x,p.y,cat.color);
		}
    }
   
   
   
   
   
   
   
};
cat.init();
update = function() {
    // *** Extract next key from buffer, and only treat that one
    // -- could be `undefined`, but that is not a problem:
    var key = keyBuffer.shift();
	if(cat.direction != 'right' && key === 37){
		cat.direction = 'left';
	} else if(cat.direction != 'left' && key === 39){
		cat.direction = 'right';
	} else if(cat.direction != 'down' && key === 38){
		cat.direction = 'up';
	} else if(cat.direction != 'up' && key === 40){
		cat.direction = 'down';
	}
};
keyUp = function(e) {
    var keyCode = e.which ? e.which : e.keyCode;
    this.key[keyCode] = false;
};
keyDown = function(e) {
    var keyCode = e.which ? e.which : e.keyCode;
    // *** Queue the key presses
    if (keyCode >= 37 && keyCode <= 40 && 
            keyCode !== keyBuffer[keyBuffer.length-1]) {
        keyBuffer.push(keyCode);
    }
    this.key[keyCode] = true;
};

var counter = 0;
function loop() {

    board.resetCanvas();
	if(counter > 1000){
        update(); // ***only process buffered keys when moving
		cat.move();
		counter = 0;
	}
	cat.draw();
    
    counter += 5*cat.multiplier;

};
setInterval(loop, 1);
body { margin: 0px }
<div>
    <canvas id="board" width="300" height="200" style="display: block; margin: 0 auto; background-color: #553300; border-style: solid; border-color: green;"></canvas>
</div>

Limiting the buffer size

You can limit the buffer size by replacing this:

keyBuffer.push(keyCode);

with:

keyBuffer = keyBuffer.slice(-2).concat(keyCode);

This will limit the size to 3. Adjust the slice argument as desired.

Upvotes: 3

Bert
Bert

Reputation: 2244

You can keep track of whether the snake has 'moved'. If you receive keyboard input, don't react to another keypress until the snake has moved. This way you're only allowing 1 key for each movement, so you can't change direction and run into yourself.

Modified example: link

update = function() {
  if (moved = true) {
      if(cat.direction != 'right' && key[37] === true){

and so forth

Upvotes: 1

Related Questions