Since my other bug got solved, I'm posting a new question for this bug.
I made a Snake canvas game, but my snake tends to eat itself when you press two buttons at the same time.. I'm not sure how to explain it properly, but this is what happens:
Say my snake is moving towards the left, and I press down + right, it'll eat itself and trigger a game over. Same when it goes to the right: down + left and bam, dead. I can't seem to reproduce the bug when the snake goes up and down though..
This is the code involved with changing directions:
bindEvents = ->
keysToDirections =
37 : LEFT
38 : UP
39 : RIGHT
40 : DOWN
$(document).keydown (e) ->
key = e.which
newDirection = keysToDirections[key]
if newDirection
setDirection newDirection
e.preventDefault()
setDirection = (newDirection) ->
# TODO: there's some bug here; try pressing two buttons at the same time..
switch Snake.direction
when UP, DOWN
allowedDirections = [LEFT, RIGHT]
when LEFT, RIGHT
allowedDirections = [UP, DOWN]
if allowedDirections.indexOf(newDirection) > -1
Snake.direction = newDirection
I thought there was something wrong with the compiled JS because my switch statement doesn't have a break on the last case; however, I tried adding else return to the coffee script file and this didn't change anything. I'm completely lost here so I hope someone will be able to spot where I'm going wrong.
It seems as if it takes the keydown events right, but they get overridden when you press too fast. If that makes sense? Like.. You'd press up when the snake is going right, but then you press left before it actually had a chance to go up and then it just goes left. Chaotic sentence right there, I suggest you play for a while and try to reproduce this if you're as intrigued as I am :(
I have no clue how to debug this properly.. The game loop tends to spam me with messages when I do console.log.
A live demo and link to my github repo can be found here
Thanks in advance.
The problem is that if you push the keys quickly enough, its possible to trigger the event callback multiple times during one frame. Thus, if the snake is going down, it can turn right and then up in the same frame, thus reversing direction and eating itself. I'll suggest two ways to solve this problem:
The first is to set a flag when the direction is changed, i.e.:
if allowedDirections.indexOf(newDirection) > -1 and !turnedThisFrame
Snake.direction = newDirection
turnedThisFrame = true
and then, in your code that runs every frame, set turnedThisFrame to false.
The second is to rework how you deal with keypresses. This is often the approach that I take. Keep a map of which keys are pressed (say, keysDown), and bind a function that sets keysDown[e.which] = true to keydown and another function which sets keysDown[e.which] = false to keyup. Then, you can check which keys are pressed in the code that runs each frame, and act accordingly.
Here's some details on how I implemented the second solution in a current project. The following code appears in my onload handler:
do ->
keysDown = []
$(document).keydown (e) ->
keysDown.push e.which
$(document).keyup (e) ->
keysDown = _.without keysDown, e.which
window.isPressed = (keyCode) -> keyCode in keysDown
The do -> construct is used to create a function and immediately calling it, which has the beneficial effect of keeping keysDown in closure for the handlers and isPressed, while avoiding polluting the main scope of the onload callback.
Then, at the beginning of my tick function (which runs once per frame and handles game logic, drawing the screen, etc.) I would have something like this:
switch Snake.direction
when UP, DOWN
allowedDirections = [LEFT, RIGHT]
when LEFT, RIGHT
allowedDirections = [UP, DOWN]
for direction in allowedDirections
if isPressed(directionToKey[direction])
Snake.direction = newDirection
break
The map directionToKey would just be the opposite of your keysToDirections.
Note that this means that keys listed first in allowedDirections will have priority, i.e. if you are going right and press both up and down in the same frame, up will occur regardless of which was pressed first.
Added advantage to this second method: you don't have to change the key handler callbacks when switching between, say, a menu screen and the game. You just have a different tick function checking for what is pressed.
You must retain the last direction is which the snake actually moved and determine allowedDirection based on that. Pressing a key only represents the intent to move in that direction, but it does not actually move when the key is pressed, but based on the speed of the game, i guess.
Your snake eating itself shows a problem with your hit detection (and hit detection handling) code. If you're hitting the snake, the game should end. A snake is not an apple! Nevermind, apparently I missed the part where the game is ending for you.
If you choose to allow pixel-granularity (I can't see your demo, my work's network is half-down..), you can't really make U turns "safe", like you could with a hex-granularity approach. Not saying your choice is bad, just telling you to pick your battles, some you just can't win.
I had the same problem, but I solved it by implementing a queue; each keypress sets the direction of the snake and that (new) direction is pushed to an array. Then, before I actually move the snake in the game loop, I check if there are any elements in my direction queue. If so, I shift it once via Array.shift. The return value is the first element of the queue and the new direction for my snake. Array.shift also removes that element from the queue, which is exactly what we want. If two keys are pressed almost simultaneously, the first and the second direction change is stored in our queue, and our aforementioned routine takes care of the rest, by first applying the first direction change in the next available tick and then applying the second direction change in the next tick thereafter.
Hope that makes sense? :-)
Related
Alright so I am currently making a PBBG based game. However, I have been having trouble with my function for a button click and two conditions in my if statement. I am actually not positive if this is at all possible, I searched everywhere I could think of but nothing gave me a definitive answer for getting this to work.
Basically, what I am trying to achieve, is that when a player presses the 'Attack' button, the player then receives the amount of experience points and gold they get from defeating that monster. Then, after that function runs, I am setting a delay of 6 seconds to where they can't press the button to attack again until the 6 seconds have passed.
I did get the function and onClick to work where when they win the fight, the game awards them the experience and gold from the kill. That all worked great and I made sure that was all working BEFORE I started adding in the time delay function and all.
Here is my code for the function with the time delay I am trying to add: Code
(Won't allow me to embed pictures yet so a link will have to do for now) and I am using just an HTML button with the onClick value set to SingleAttack(). The code with the problem appears to be in this part...
if (attackReady) || (currentExp >= NeededExp) {...}
What I have done here is I am holding a boolean, named attackReady and setting it to 'true'. When the player presses the Attack button, it then changes the 'true' value to 'false' and then adds a setTimeout() and period in miliseconds for delay. It then sets attackReady back to true and puts a 6 second time delay before the function can be called again.
You'll notice there is an if and an else if in my function. The if code runs only when a players current experience points are greater than or equal to the needed experience points. I think my problem is coming from having two conditions in the if statement. I am not entirely sure if that is possible, I have looked everywhere to find an answer and nothing for javascript specifically. I know C# allows it, does javascript allow it and if so, did I do it right or have I done it wrong?
When the Attack button is clicked and the function is called, it does nothing at all. Any insights?
As #Thomas noted, the entire test needs to also be enclosed in braces. You currently have:
if (attackReady) || (currentExp >= NeededExp) {...}
and what you want is either:
if ( (attackReady) || (currentExp >= NeededExp) ) {...}
or more simply:
if (attackReady || currentExp >= NeededExp) {...}
One more thing:
From your description, it might be the case that you want both tests to be true to execute that block of code. If that is the case you want to use an AND with && rather than the || OR test. OR is true if either the left or the right hand side is true.
so I am having troubles with automatic movement
The code I am using is
if (myGameArea.key && myGameArea.key == 40) {
snake.y ++;
}
which is moving but only when the key is hold, is there a way to let the object move in one direction automatically after just pressing the button. Sorry, if that's a stupid question, I really am not an experienced coder.
You should consider giving the snake object another attribute: velocity! Since you are dealing with two dimensions you can define vx and vy as the two components of the velocity, and when a key is pressed you can change the velocity to the appropriate value, for example:
if (myGameArea.key && myGameArea.key==40) {
snake.vx = 0;
snake.vy = 1;
}
As well as this, inside the snake object you should define the position by updating the current position. For this you just add the current velocity.
Hopefully this gets you on the path to a working snake game! If not, let me know and I can try to help out more.
I would use flags, and when you press a key set the flag to true for a while loop. When you press a key, go to a move function and have it move the direction of the flag until another key is pressed or you hit a wall.
Repro steps:
Go to http://playclassicsnake.com/Play
Click "Fast" (or "Slow" or even "Medium") on "Set Speed" under "Controls"
Hit left arrow key and wait until snake hits left wall and "Game Over!"
Look at JavaScript console
See something like
Uncaught TypeError: Cannot read property '0' of undefined
Refresh the page
Hit left arrow key and wait until snake hits left wall and "Game Over!"
Don't see same error in the console as before
My investigation:
The reason for the error is that the function move in the first case is being called after "Game Over!" and in the second case isn't. The second case is the correct behavior. In both cases a function endGame that starts out like
this.endGame = function ( msg )
{
this.inProgress = false;
clearInterval(this.mover);
is being called, but for some reason the interval is failing to clear in the first case. It makes no sense because the only difference between what happens in the first case and second case is that in the second case the speed is set with
SG.setSpeed($('#speed-form input[type="radio"]:checked').val());
on page load to start game, and in the first case it is set with that and then updated with
$('#speed-form input[type="radio"]').click(function ( )
{
SG.setSpeed($(this).val());
});
when you click one of the radio buttons. I've tested with
this.endGame = function ( msg )
{
console.log(this.mover); // TEST
this.inProgress = false;
clearInterval(this.mover);
that in both cases this.mover is defined. For reasons I don't understand and that may have to do with my problem, in the first case the the ID of the interval is always a high number like 68, 74, etc., while in the second case it is always 2!
I have no idea what's going on here. Any help is greatly appreciated.
Full code here.
The problem was that when you were calling clearInterval(this.mover);, you were only clearing the last interval set to this.mover, and all intervals before it were being left untouched. The solution I proposed (which seems to have worked) was to clear the previous interval every time a new one is set. This takes care of that issue because there is at most one interval at any given moment.
I am learning JavaScript and have been developing a simple game that is essentially a balloon that you can move around on the screen. I managed to do keypresses etc with a lot of help and the balloon moved about just perfectly.
I now want to simulate gravity, by having the balloon move one pixel down the screen if the balloon image was above a value, i tried to do this with the following do while statement:
var balloon = document.getElementById("balloon");
var bottom = parseInt (balloon.style.bottom, 10);
do {
balloon.style.bottom = bottom + 50 + 'px';
}
while (bottom = bottom > 600) // Gravity
What I want this to do, is to check the code is working by making the balloon move up the page 1 pixel if the bottom value is less than 600.
I have stripped out all the code I used to make the balloon move.
If I could just see the balloon move slowly up the page I would be very happy, because then at least I know I can just switch the values round when I've added the movement back in.
The other answers address the issue of attempting an animation with an explicit loop. As they have pointed out, you should use timers.
Because it seemed like fun, I made you a simple example of how to use a timer to animate a balloon falling:
http://jsfiddle.net/dmuu9w97/
The key code is the following:
// Make balloon fall 1px every 10ms
setInterval(function() {
var bottom = getBalloonBottom();
if (bottom > 0)
balloon.style.bottom = bottom - 1 + "px";
}, 10);
For your while loop condition should be (bottom>600) . No need for '='
You are loading the variable bottom outside the loop. It will never change. If it is 610 at the start of the loop it will remain 610 because it is assigned only in line 2 of your code
While loop should probably be ....bottom = (bottom - 1) + 'px';
If you write a while loop like this, it will execute 10 times immediately and your baloon will be always stuck in 600
To solve 'stuck at 600' problem, you should use a timer:
Think about "how fast should the balloon fall". Then you can come up with some number like "5 pixels in 100 milliseconds".
Then write a function... call that function on a timer.
Check the setTimeout function here...
setTimeout method
It's not impossible to do with a do loop but I think you ought to abandon this explicit loop in favor of javascript's timer/timeline. Look into how to use window.setTimeout() where the body of your do loop becomes the body of the callback function AND a trailing call to window.setTimeout() passing the callback again with a delay of 1000/your-chosen-framerate milliseconds. Then you can also process keypress events in their own handlers for intentional movement.
If you use an explicit loop, you'll only get gravity because the loop should never end (just as gravity never stops pulling) and therefore the browser will never have a chance to call the keypress event handler.
Your timeout callback runs once, queues itself again, and terminates. That gives control back to the browser's javascript engine to process events or, if nothing else, run the callback function again after the requested delay.
requestAnimationFrame may be more appropriate than setTimeout in modern JS implementations. It usually leads to a smoother result for animations.
I am working on a little HTML/JavaScript/CSS3 project for fun. I'm basically trying to make a wheel that rolls around in the browser window. To control the wheel I'm using keyup and keydown events for the cursor keys (left and right turn the wheel and up and down roll it forward or backward).
I've got it working pretty well so far, but there are two major glitches. Say I want to roll the wheel forward and without stopping I want to turn it a little to the right, then I would keep the up key pressed and press the right cursor key. When I do this there's a pause in the movement before it registers both events and keeps rolling.
That's one of the problems, the main problem is that, once I've performed the previous action and then wheel is at a desirable angle, if I let go of the right cursor key the browser registers both keys as released and the wheel comes to a stand still. Here is a jsFiddle of what it looks like: http://jsfiddle.net/UKqwu/1/. I know the code is a mess but it's a work in progress/learning experience and I've only been programming for a month or so.
Anyways thanks for any help. It only works in Chrome at the moment as far is I know. Haven't really been bothered fixing compatibility issues at this stage.
So, what is happening is essentially a limitation built in by your operating system, but there is a simple work-around. First I'll explain the limitation, and then the work-around.
If you were to (in a text box) hold down the "j" button, first one "j" would appear, and then after a short delay many "j"s would appear "jjjjjjjjjjjjjjjjjjjj"
This is the same problem your experiencing. The event fires once, waits for a moment, and then fires many more times.
The solution, however is simple. Instead of having your wheel move when the events are fired... have it update constantly, and separately keep track of what keys are up or down.
The Key Handler would look something like this...
function KeyHandler() {
this.left = false;
this.right= false;
...
function onKeyDown(e) {
if (e.keyCode == 37) {
this.left = true;
}
...
}
function onKeyUp(e) {
if (e.keyCode == 37) {
this.left = false;
}
...
}
}
(you'd attach the key handler to the body or whatever element you wish)
Your wheel would have an update function that looked like...
wheel.update = function() {
// the wheel is turning left
if (wheel.keyhandler.left) {
// make the appropriate adjustments
}
}
and then the game would have something like this...
wheel = new Wheel;
setInterval(function() {
wheel.update();
},100);
That way your wheel will always be updating based on the current state of the keys, and you wont have to rely on the limitations of events that are firing. :)
Here's a snippet of a simple game I once wrote
//key codes
var KEY_LEFT = 37;
var KEY_RIGHT = 39;
var KEY_A = 65;
var KEY_D = 68;
var KEY_SPACE = 32;
var keys = {};
function onKeyDown(e)
{
e = e || window.event;
var key = window.event.keyCode || e.which; // ie
keys[key] = true;
}
function onKeyUp(e)
{
var key = window.event.keyCode || e.which; // ie
delete keys[key];
}
This keeps track of all current key states. Then your game "tick" is on a setTimeout() rather than moving on key events and checks for appropriate keys.
function gameTick()
{
// update paddle angles
if (keys[KEY_LEFT])
{
// move left
}
if (keys[KEY_RIGHT])
{
// move right
}
}
Here's the game;
the problem you are facing is because your code is meant to detect single key press while your game needs 2 key press detection.
put this line in loop over size of e. that will set all pressed keys as 1. currently its detecting only one key press and processing for one at a time.
keys[e.keyCode] = 1;
check out this thread and u might get what you need. though its jquery, it might help conceptually. m also working on this with js... if anything new comes up will share...
Detect multiple keys on single keypress event in jQuery
your code and concept is cool if you are really one month old in programming.