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.
Related
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.
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;
});
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) {
I'm trying to make a function that changes the value of the attribute x or y of the dot object four times, each time only if two conditions are met. The variable permissionSomething has to be 1, and x or y has to be more or less then a certain number, because in this case it is used for a moving dot on a canvas, which has to stay within the canvas. PermissionSomething is set to 1 if a certain key is pressed down, and changes back to the default value of -1 if that key goes up again.
var permissionLeft = -1;
var permissionRight = -1;
var permissionUp = -1;
var permissionDown = -1;
function update()
{
switch (true)
{
case permissionLeft = 1 && dot.x > 29:
dot.x--;
break;
case permissionRight = 1 && dot.x < 841:
dot.x++;
break;
case permissionUp = 1 && dot.y > 29:
dot.y--;
break;
case permissionDown = 1 && dot.y < 541:
dot.y++;
break;
}
}
If I run this, even without pressing any key, it starts to move in some strange way, stops and starts moving back and forth. If I don't press any keys permissionSomething should never be 1 so it should never move. The same thing happens if I write four if-statements like this:
if (permissionLeft = 1 && dot.x > 29) {
dot.x--};
if (.....
I am creating a minesweeper game for a javascript project and have run into a problem that I can't get my head round. When you click on an empty cell (one which does not have any mines around it and so does not display a number) in the minesweeper game for those who don't know, this will reveal the whole block of empty cells that are neighbouring eachother, stopping when the "wall of numbers" containing these empty blocks is found. Example below:
[1]http://datagenetics.com/blog/june12012/hole.png
This requires a recursive function to figure out which blocks are to be revealed. My code at the moment only reveals the block that is clicked on:
function revealGridContents()
{
switch (positionClickContents)
{
case 0:
ctx.drawImage(clickedImage, (xClick*20), (yClick*20));
break;
case 1:
ctx.drawImage(num1Image, (xClick*20), (yClick*20));
break;
case 2:
ctx.drawImage(num2Image, (xClick*20), (yClick*20));
break;
case 3:
ctx.drawImage(num3Image, (xClick*20), (yClick*20));
break;
case 4:
ctx.drawImage(num4Image, (xClick*20), (yClick*20));
break;
case 5:
ctx.drawImage(num5Image, (xClick*20), (yClick*20));
break;
case 6:
ctx.drawImage(num6Image, (xClick*20), (yClick*20));
break;
case 7:
ctx.drawImage(num7Image, (xClick*20), (yClick*20));
break;
case 8:
ctx.drawImage(num8Image, (xClick*20), (yClick*20));
break;
};
};
The number passed into the switch statement is the value of the data in the array grid[xClick][yClick]; eg a 4 symbolises a block with 4 mines around it, so the image for 4 will be displayed.
Case 0 is the case that a blank block is clicked and so the code for this needs modifying but I really can't think what to do.
From what I can understand, I will need to call the revealGridContents(); function from case 0, but passing in new values for xClick and yClick (the x and y values of the array position) for each square that I want to check.
Any help shedding the light on what I need to do next would be greatly appreciated!
Without knowing slightly more of your program it's hard to give you an exact solution. You'll probably need a separate function to do this, as just using the same function will reveal everything (which is obviously not how the game works). You'll also need some way of keeping track of revealed cells, otherwise you'll get into a loop (I'm assuming this is stored in another 2d array revealed[x][y]).
You probably want to do something like this (I haven't tested this so there may be errors in it - apologies):
function revealGridContents(){
switch (positionClickContents){
case 0:
ctx.drawImage(clickedImage, (xClick*20), (yClick*20));
checkAdjacentCells(xClick, yClick);
break;
...
}
}
function checkAdjacentCells(x,y){
var cellsToCheck = [
[x,y+1],
[x,y-1],
[x+1,y],
[x-1,y]];
var x,y;
for(var i=0; i<=cellsToCheck.length; i++){
x = cellsToCheck[i][0];
y = cellsToCheck[i][1];
if(!revealed[x][y] && grid[x][y] == 0){
ctx.drawImage(clickedImage, x*20, y*20);
checkAdjacentCells(x,y);
}
}
}
Just as a general advice, you need a better separation between the model of your game and the UI.
Here is the begining of my interpretation of the minesweeper game:
function init() {
var i,j; // indexes
map = []; // global map, because i'm lazy
for (i=0; i<10; i++) {
var row = [];
for (j=0; j<10; j++)
row.push({
bomb : Math.round(Math.random()-0.4), // set bombs randomly, change to your correct ratio
revealed : false, // nothing is visible at start
count : 0 // counts will be computed after all the map is created
});
map.push(row);
}
// set adjacent bomb counts
for (i=0; i<10; i++)
for (j=0; j<10; j++) {
if (map[i-1] && map[i-1][j-1] && map[i-1][j-1].bomb) map[i][j].count++;
if (map[i-1] && map[i-1][j] && map[i-1][j].bomb) map[i][j].count++;
if (map[i-1] && map[i-1][j+1] && map[i-1][j+1].bomb) map[i][j].count++;
if (map[i] && map[i][j-1] && map[i][j-1].bomb) map[i][j].count++;
if (map[i] && map[i][j+1] && map[i][j+1].bomb) map[i][j].count++;
if (map[i+1] && map[i+1][j-1] && map[i+1][j-1].bomb) map[i][j].count++;
if (map[i+1] && map[i+1][j] && map[i+1][j].bomb) map[i][j].count++;
if (map[i+1] && map[i+1][j+1] && map[i+1][j+1].bomb) map[i][j].count++;
}
}
function print() { // uses console to display instead of canvas
var output = '\n';
for (var i=0; i<10; i++) {
for (var j=0; j<10; j++) {
var item = map[i][j];
output += (item.revealed ? item.count : 'x') + ' ';
}
output += '\n';
}
console.log(output);
}
function click(x,y) {
reveal(x,y);
print(map);
}
function reveal(x,y) {
// break early if click is invalid (invalid clicks are generated)
if (x < 0 || x > 9 || y < 0 || y > 9 || map[x][y].revealed) return;
// mark the square as clicked
map[x][y].revealed = true;
if (map[x][y].bomb) { // losing click
console.log('You lost');
} else if (map[x][y].count === 0) { // click on 0 adjacent bombs
reveal(x-1, y);
reveal(x, y-1);
reveal(x, y+1);
reveal(x+1, y);
}
}
init();
console.log('First print');
print();
console.log('Click 1,3');
click(1,3);
The difficult part is in the click() function.
Try this demo (click 'run with JS' several times until you don't lose and hit a 0):
http://jsbin.com/iqeganU/1/edit