JavaScript precent multiple keypress action - javascript

I made a simple snake game. It works like a charm but there is a bug with multiple keypressing.
For example, when you are going to top direction and press left and down or right and down at the same time, the snake just simply dies on the same line.
So here is the code for the keypress
document.body.addEventListener('keydown', keyDown);
function keyDown(event){
//up
if(event.keyCode == 38){
if(yVelocity == 1)
return;
yVelocity = -1;
xVelocity = 0;
}
//down
if(event.keyCode == 40){
if(yVelocity == -1)
return;
yVelocity = 1;
xVelocity = 0;
}
//left
if(event.keyCode == 37){
if(xVelocity == 1)
return;
yVelocity = 0;
xVelocity = -1;
}
//right
if(event.keyCode == 39){
if(xVelocity == -1)
return;
yVelocity = 0;
xVelocity = 1;
}
}
Is there any way to prevent multiple keypressing or any other way to prevent this?

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.

Related

How can I fix keydown delays in Javascript game?

I am creating a javascript snake game, and have run into a problem when certain keys are pressed too fast in order. For example, (while going right) hitting the up arrow and then the left arrow key too fast will make my snake turn around completely and run into itself, ignoring the up arrow key press. Is there any code that would make sure that any key press will always be rendered? Thanks in advance.
let d = "RIGHT";
document.addEventListener("keydown", direction);
function direction(event) {
let key = event.keyCode;
if (key == 37 && d != "RIGHT" && d != "LEFT") {
d = "LEFT";
} else if (key == 38 && d != "DOWN" && d != "UP") {
d = "UP";
} else if (key == 39 && d != "LEFT" && d != "RIGHT") {
d = "RIGHT";
} else if (key == 40 && d != "UP" && d != "DOWN") {
d = "DOWN";
}
}
In separate function:
if (d == "LEFT") snakeX -= box;
if (d == "UP") snakeY -= box;
if (d == "RIGHT") snakeX += box;
if (d == "DOWN") snakeY += box;
You can also see this problem by going to https://jssnake.glitch.me/ and playing around a bit.
I briefly looked into your code. You render 10x a second, so if you manage to press more than one key during that interval, the described issue will occur:
For example, (while going right) hitting the up arrow and then the left arrow key too fast will make my snake turn around completely and run into itself, ignoring the up arrow key press.
There are two possible solutions:
Run the render loop faster, so that no one can ever press two keys during that interval.
Do not store only the last key, but all keys that were pressed between since the last render call.
Avoid half-turns.
I think solution 1 is not ideal as you should never say never. So let's continue with number 3 (a hack) and then with number 2 (the correct and clean way).
Avoid half-turns (alternative 3)
This little hack does not solve the root of the problem, but it will make the snake behave kind-of correct. The snake can move in 4 directions, but it can always turn only in two directions. You could either use a two-keys control to trigger CW/CCW change, something like
let currentDir = "RIGHT"; //note I renamed your d to currentDir
let nextDir = undefined;
document.addEventListener("keydown", direction);
function direction(event) {
const key = event.keyCode;
while (~currentDir) {}; //wait until the control function is finished
switch (currentDir) {
case "LEFT": nextDir = (key === 37 ? "DOWN" : (key === 39 ? "UP" : nextDir)); break;
case "UP": nextDir = (key === 37 ? "LEFT" : (key === 39 ? "RIGHT" : nextDir)); break;
case "RIGHT": nextDir = (key === 37 ? "UP" : (key === 39 ? "DOWN" : nextDir)); break;
case "DOWN": nextDir = (key === 37 ? "RIGHT" : (key === 39 ? "LEFT" : nextDir)); break;
}
}
//and later in the movement control function:
currentDir = undefined; //avoid overwriting nextDir during this update,
// i.e. the while-loop inside of direction() will wait
switch (tmp) {
case "LEFT": snakeX -= box; break;
case "UP": snakeY -= box; break;
case "RIGHT": snakeX += box; break;
case "DOWN": snakeY += box; break;
}
currentDir = nextDir;
nextDir = undefined;
The four-keys version would work in a similar way, you can easily intergrate it to your code. The key is to use the pair of currentDir and nextDir and keeping currentDir constant over the whole 0.1s time between the render calls. But your problem would kind-of stay. A snake heading right would only continue up if you would press ↑ and ← immediately after each other.
let currentDir = "RIGHT";
let nextDir = undefined;
document.addEventListener("keydown", direction);
function direction(event) {
const key = event.keyCode;
while (~currentDir) {}; //wait until the control function is finished
switch (currentDir) {
case "LEFT":
case "RIGHT":
nextDir = (key === 38 ? "UP" : (key === 40 ? "DOWN" : nextDir)); break;
case "UP":
case "DOWN":
nextDir = (key === 37 ? "LEFT" : (key === 39 ? "RIGHT" : nextDir)); break;
}
}
Keys buffer (alternative 2)
The correct solution is even easier, but requires an array. It stores all keys pressed since the last render call in a queue.
keysPressed = [];
document.addEventListener("keydown", event =>
keysPressed.push(event.keyCode); //enqueues the key pressed
Having two or three keys pressed, you could virtually update the snake position inside of the 0.1s interval applying one valid turn in each frame. This could lead to delayed snake movement if you would be able to fill the buffer quickly with commands. It can be interesting to try out as a fun excercise. The movement function for the four-keys control would look like this:
{
if (keysPressed.length > 0 {
const key = keysPresses.shift(); //dequeues the oldest key
//if there are more keys in the queue, they have to wait until next time
switch (d) {
case "LEFT":
case "RIGHT":
d = (key === 38 ? "UP" : (key === 40 ? "DOWN" : d)); break;
case "UP":
case "DOWN":
d = (key === 37 ? "LEFT" : (key === 39 ? "RIGHT" : d)); break;
}
}
switch (d) {
case "LEFT": snakeX -= box; break;
case "UP": snakeY -= box; break;
case "RIGHT": snakeX += box; break;
case "DOWN": snakeY += box; break;
}
}
What you need here is to delay the effect of a key press. You can achieve it by storing the last pressed key in a variable and only reading the key when snake is ready to turn.
let pressedKey;
document.addEventListener("keydown", event => {
pressedKey = event.keyCode;
});

Algorithm Not Properly Killing Go Pieces When Air To Bottom Right

I've created an algorithm to determine which pieces have been killed in a game of Go. For my algorithm, pieces are stored in a 2D array of rows and columns (from 0-8, as I am using a 9x9 board). Each piece is also an array, where:
piece[0] is the color of the piece, either "empty", "black", or "white".
piece[1] is irrelevant.
piece[2] is a boolean of whether or not it has liberty (initially set to true if an empty square and false if there is a piece there).
piece[3] is whether or not it has been iterated over in the algorithm yet. All pieces start with this as true.
Here is the algorithm for discerning which pieces have liberty:
let oldBoard = 0;
while (board != oldBoard) {
oldBoard = board;
for (let row = 0; row < 9; row++) {
for (let col = 0; col < 9; col++) {
if (board[row][col][2] && !board[row][col][3]) {
board[row][col][3] = true;
if (row != 0 && (board[row][col][0] == "empty" || board[row][col][0] == board[row-1][col][0])) { board[row-1][col][2] = true; }
if (row != 8 && (board[row][col][0] == "empty" || board[row][col][0] == board[row+1][col][0])) { board[row+1][col][2] = true; }
if (col != 0 && (board[row][col][0] == "empty" || board[row][col][0] == board[row][col-1][0])) { board[row][col-1][2] = true; }
if (col != 8 && (board[row][col][0] == "empty" || board[row][col][0] == board[row][col+1][0])) { board[row][col+1][2] = true; }
}
}
}
}
Afterwards, it removes all pieces that are marked as not having liberty (piece[2] == false).
Everything works fine, unless the air spaces are to the bottom/right. For instance, if you put down this configuration:
Then the X-ed out piece will be captured, which it shouldn't be. But, if you put down this configuration:
Then no piece will be captured, which is the expected result.
As best as I can tell from my investigations of the bug, it seems to be that any piece that was marked as having liberty from a piece to its bottom or right is unable to give any adjacent pieces liberty.
You can find the full repository here.
I might be late on this one, but here's my idea.
As I commented, you should look at the stones as a group, instead of looking at it individually.
#maraca 's comment was very helpful. Since adjacent stones to the updated stone is affected, you could check all four adjacent pieces from the updated stone, then check each of those stones' adjacent stones and so on... until it ends. The function can look something like this.
function isGroupAlive(x, y) {
let stack = [];
let alive = false;
const check = (x, y) => {
stack.push([x, y].toString());
let cell = board[x][y];
if (x !== 0 && board[x-1][y][0] === null) alive = true;
else if (x !== 0 && board[x-1][y][0] === cell[0] && stack.includes([x-1, y].toString)) check(x-1, y);
if (x !== size-1 && board[x+1][y][0] === null) alive = true;
else if (x !== size-1 && board[x+1][y][0] === cell[0] && stack.includes([x+1, y].toString)) check(x+1, y);
if (y !== 0 && board[x][y-1][0] === null) alive = true;
else if (y !== 0 && board[x][y-1][0] === cell[0] && stack.includes([x, y-1].toString)) check(x, y-1);
if (y !== size-1 && board[x][y+1][0] === null) alive = true;
else if (y !== size-1 && board[x][y+1][0] === cell[0] && stack.includes([x, y+1].toString)) check(x, y+1);
}
check(x, y);
return alive;
}
I used a stack to keep track of already iterated stones' coordinates, but you could do something else too.

How to Make Time Buffering for keyPressed() Function in P5.js?

I made a simple Snake game in the P5.js environment,
and I have one thing that I want to fix.
So, I have this code for keyPressed() function
function keyPressed(){
if(keyCode === LEFT_ARROW) {
if(snake.yspeed == 1 || snake.yspeed == -1) {
snake.yspeed = 0;
snake.xspeed = -1;
}
}else if(keyCode === RIGHT_ARROW) {
if(snake.yspeed == 1 || snake.yspeed == -1) {
snake.yspeed = 0;
snake.xspeed = 1;
}
}else if(keyCode === UP_ARROW) {
if(snake.xspeed == 1 || snake.xspeed == -1) {
snake.xspeed = 0;
snake.yspeed = -1;
}
}else if(keyCode === DOWN_ARROW) {
if(snake.xspeed == 1 || snake.xspeed == -1) {
snake.xspeed = 0;
snake.yspeed = 1;
}
}
}
yspeed is the vertical movement, and xspeed is the horizontal movement.
So, if the snake is moving to the right, even if I pressed the LEFT_ARROW , the snake won't turn to the left. But, if I pressed UP_ARROW or DOWN_ARROW and quickly press the LEFT_ARROW, the snake will immediately turn left and hit itself.
So, I want to add a buffering time for the keyPressed() function after one of the arrow keys being pressed.
How can I do this in P5.js?
I also made a snake game and encountered this exact problem.
I wrote it in vanilla javascript and had set the fps to 12, which give the player enough time to change direction twice between two cycles.
Solution
My solution was to have a buffer variable that stores the last pressed direction. in your case, it could look like so
let dir = '';
function keyPressed(){
if(keyCode === LEFT_ARROW) dir = 'LEFT';
if(keyCode === RIGHT_ARROW) dir = 'RIGHT';
if(keyCode === UP_ARROW) dir = 'UP';
if(keyCode === DOWN_ARROW) dir = 'DOWN';
}
And actually change its direction only before I called the snake's update() method in draw():
function draw() {
...
snake.direction(dir);
snake.update();
snake.draw();
...
}
You only need to check whether or not the move is valid within the direction() method.
this.direction = function(d) {
switch(d) {
case 'LEFT':
if (this.xv != 1) {this.xv = -1; this.yv = 0;}
break;
case 'RIGHT':
if (this.xv != -1) {this.xv = 1; this.yv = 0;}
break;
case 'UP':
if (this.yv != 1) {this.xv = 0; this.yv = -1;}
break;
case 'DOWN':
if (this.yv != -1) {this.xv = 0; this.yv = 1;}
break;
default:
this.xv = this.yv = 0;
}
}
(I copied this from my code, but you get the idea)
How it works
Lets take your example.
In your game, what is happening is that it first changes the velocity of the snake to point UP, then to the LEFT, all before the next snake.update() is called.
Now all it does is change the dir variable to UP, then LEFT before the next snake.update() is called. So the next snake.direction() gets a dir argument containing LEFT, which is ignored (because the snake is pointing RIGHT) and the snake doesn't go backward on itself.
I hope this made some sense, you can check my code here if it helps.
You could try to add a time limit on the key presses, by doing something like checking that draw() was called at least once between key presses. Something like this:
var canPress = true;
function keyPressed(){
if(canPress){
// respond to key press
}
canPress = false;
}
function draw(){
// draw the frame
canPress = true;
}
But my guess is that this will make your game feel clunky and unresponsive.
Instead of restricting user input based on time, there are two general solutions for this:
Option 1: Move the snake immediately when the user presses the corresponding key. You'd do this by calling something like an update() function, both at the beginning of each frame, and when you press a key.
Option 2: Prevent the snake from moving in a direction that would kill itself immediately. You'd do this by checking whether the square you're about to go in is occupied, and ignoring the key press if so.
Which approach you take depends on how you want your game to work. You could even take a hybrid approach and do both.

Javascript canvas, collision detection via array not working

Basically, I have a program that makes a square, and stores the left, right, top and bottoms in an array. When it makes a new square, it cycles through the array. If the AABB collision detection makes the new square overlap with another square, it should make sure that the square is not displayed, and tries again. Here is a snippet of the code I made, where I think the problem is:
var xTopsBotsYTopsBotsSquares = [];
//Makes a randint() function.
function randint(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function checkForOccupiedAlready(left, top, right, bottom) {
if (xTopsBotsYTopsBotsSquares.length == 0) {
return true;
}
for (i in xTopsBotsYTopsBotsSquares) {
if (i[0] <= right || i[1] <= bottom ||
i[2] >= left || i[3] >= top) {/*Do nothing*/}
else {
return false;
}
}
return true;
}
//Makes a new square
function makeNewsquare() {
var checkingIfRepeatCords = true;
//DO loop that checks if there is a repeat.
do {
//Makes the square x/y positions
var squareRandomXPos = randint(50, canvas.width - 50);
var squareRandomYPos = randint(50, canvas.height - 50);
//Tests if that area is already occupied
if (checkForOccupiedAlready(squareRandomXPos,
squareRandomYPos,
squareRandomXPos+50,
squareRandomYPos+50) == true) {
xTopsBotsYTopsBotsSquares.push([squareRandomXPos,
squareRandomYPos,
squareRandomXPos+50,
squareRandomYPos+50]);
checkingIfRepeatCords = false;
}
}
while (checkingIfRepeatCords == true);
}
Any help is much appreciated.
Your loop is incorrect I think, since you use i as a value, whereas it is a key:
for (i in xTopsBotsYTopsBotsSquares) {
if (i[0] <= right || i[1] <= bottom ||
i[2] >= left || i[3] >= top) {/*Do nothing*/}
else {
return false;
}
}
Could become:
for (var i = 0, l < xTopsBotsYTopsBotsSquares.length; i < l; i++) {
var data = xTopsBotsYTopsBotsSquares[i];
if (data[0] <= right || data[1] <= bottom ||
data[2] >= left || data[3] >= top) {/*Do nothing*/}
else {
return false;
}
}

jQuery if statement triggered twice

I'm attempting to write a read only values in a html input using jQuery, and I've run into a problem where a single if statement fires twice.
Basically the input starts with a default value in the html [this is the readonly value]:
<input id="main-field" type="text" value="dan" >
Then, a jQuery 'keypress keydown' function checks the index of the pressed key in relation to the readonly word and if the index is before or after the word it returns 'true' which will add the characters, otherwise it will return false which will prevent adding chars. The problem is that if I type before the word it increases the index of the readonly word twice, where it should be increased by one (since the readonly word has moved by one index for each char).
Here's the 'keypress keydown' function; hopefully it's easy to understand (let me know if not, I want to get better at that as well):
var readOnlyEnd = $('#main-field').val().length,
readOnlyStart = 1;
$('#main-field').on('keypress keydown', function(event) {
var character = String.fromCharCode(event.keyCode).toLowerCase();
// note: using the jquery caret plugin
var pos = $('#main-field').caret();
// handling new character between 'a' and 'z' and the delete char.
if (((character >= 'a') && (character <= 'z')) || (event.which == 8)) {
// if delete is pressed:
if (event.which == 8) {
if (pos == readOnlyEnd) return false;
else if (pos < readOnlyStart) {
if (pos == 0) return true;
if (pos > 0) {
console.log('reudce index!!');
// Will reduce indexes.
readOnlyStart -= 1;
readOnlyEnd -= 1;
return true; // Will delete.
}
}
else if ((pos >= readOnlyStart) && (pos < readOnlyEnd)) return false;
}
// within the word.
else if ((pos >= readOnlyStart) && (pos < readOnlyEnd)) return false;
// before the readonly word. - HERE IS THE PROBLEM, INCREASING TWICE.
else if (pos < readOnlyStart) {
readOnlyStart += 1;
readOnlyEnd += 1;
return true; // Will add character
}
else {
return true;
}
}
else {
// In case something that doesn't affect the input was pressed (like left/right arrows).
return true;
}
});
Note: I'm using the jQuery caret plugin for the cursor place.
Please let me know if you have any ideas or suggestions, or if the solution to my problem is similar to a solution to another problem on here
You should use only one event. Either keypress or keydown in following statement:
$('#main-field').on('keypress keydown', function(event) {
This will fire event twice on a single key press.
So, change your statement to:
$('#main-field').on('keypress', function(event) {

Categories