javascript function bug after a correct condition - javascript

i have finished the code of the popular snake game, it runs perfectly.
then i decided to add a level 2, so i put a condition that after the apples eaten by the snake become 10, there will be a level up and we will pass to level 2.
in level 2 there are 2 new lines drawn on the board, with the condition that if the snake hit these lines, the game will be over ( same for borders and the snake body )
here is the code:
here, game = setInterval(draw, gameSpeed), where gamespeed is the time in milliseconds and draw is the function that draws everything on the canvas
if(apples_eaten == 10){
clearInterval(game);
levelUp();
}
here is the levelup() function that should only run once in the whole game as there are only 2 levels.
function levelUp() {
lvl = 2;
apples_eaten = 0;
let display_lvl = document.getElementById("level");
display_lvl.innerHTML = "level: " + lvl;
snake.length = 1;
snake[0] = {
x : 15 * unit,
y : 10 * unit
};
apple = {
x : generate(1, w/unit),
y : generate(1, h/unit),
color : "red"
};
gameSpeed = 70;
game = setInterval(draw, gameSpeed);
}
the problem is that after reaching level 2, the game still runs correctly, except that if the snake eat a single apple, it starts bugging, moving in every direction and throwing the "you hit something" alert, that normally is thrown when you hit a border, line or itself

Related

How do you avoid the "RangeError: Maximum call stack size exceeded" error?

I'm currently working on a maze generating algorithm called recursive division. The algorithm is quite simple to understand: Step 1: if the height of your chamber is smaller than the width, divide your grid/chamber with a vertical line. If the height is greater than the width, then divide your chamber with a horizontal line. Step 2: Repeat step 1 with the sub-chambers that were created by the lines. You want to repeat these steps until you get a maze (until the width or height equals 1 unit).
The problem that I have with this algorithm is that JavaScript prints out a RangeError, meaning that I called the function that creates the maze too many times (I'm trying to implement this algorithm with a recursive function). Is there any way to avoid/prevent this from happening? Or am I missing something important in my code that makes the algorithm not work properly?
I have tried to implement a trampoline function, but since I'm a beginner I just don't understand it well enough to implement my self. I have also restarted my entire project ruffly 3 times with some hope that I will come up with a different approach to this problem, but I get the same error every time.
My code here:
//leftCord = the left most x coordinate of my chamber/grid, upCord = the upmost y coordinate of my
grid etc.
//(0, 0) IS POSITIONED IN THE LEFT TOP NODE OF MY GRID
function createMaze(leftCord, rightCord, upCord, downCord) {
var height = Math.abs(downCord - upCord);
var width = Math.abs(rightCord - leftCord);
if (height < 2 || width < 2) {
//The maze is completed!
return;
} else {
if (height < width) {
//cut the chamber/grid vertically
//Getting a random number that's EVEN and drawing the function x = 'random number' on the grid
var x = randomNum(leftCord / 2, rightCord / 2) * 2;
var lineX = [];
for (i = upCord; i < downCord; i++) {
lineX.push(grid[i][x]);
}
//Making a random door/passage and making sure it's ODD
var randomDoor = randomNum(0, lineX.length / 2) * 2 + 1;
lineX.splice(randomDoor, 1);
//Drawing the line
for (i = 0; i < lineX.length; i++) {
lineX[i].className = "wall";
}
//Making the same thing again, but with the left and right sub-chambers that were created by the line
createMaze(leftCord, x, upCord, downCord);
createMaze(x, rightCord, upCord, downCord);
} else {
//cut the chamber/grid horizontally
//Getting a random number that's EVEN and drawing the function y = 'random number' on the grid
var y = randomNum(0, downCord / 2) * 2;
var lineY = [];
for (i = leftCord; i < rightCord; i++) {
lineY.push(grid[y][i]);
}
//Making a random door/passage and making sure it's ODD
var randomDoor = randomNum(0, lineY.length / 2) * 2 + 1;
lineY.splice(randomDoor, 1);
//Drawing the line
for(i = 0; i < lineY.length; i++){
lineY[i].className = "wall";
}
//Making the same thing again, but with the upper and lower-chambers that were created by the line
createMaze(leftCord, rightCord, upCord, y);
createMaze(leftCord, rightCord, y, downCord);
}
}
}
This is happening because you never initialize i with var- it is sent into the global scope and is overwritten each function call.

How to prevent two canvas objects from appearing in the same location

I am building a Snake arcade game, and though the game is up and working well I am attempting to tweak the game so that the apple will never appear in the same location as one of the snake segments (for those who don't know Snake, it is a classic arcade game in which a snake slithers around the screen eating apples that randomly appear, trying to avoid running into itself or the wall and growing in length each time it eats an apple). Here is the code below that moves the apple around the screen:
Apple.prototype.move = function() {
var randomCol = Math.floor(Math.random()*(widthInBlocks-2)) +1;
var randomRow = Math.floor(Math.random()*(heightInBlocks-2)) +1;
this.position = new Block(randomCol, randomRow);
for (i = 0; i < Snake.segments; i++) {
if (this.position === Snake.segments[i].col && Snake.segments[i].row) {
this.move();
};
};
}
The constructor for the snake is as follows:
var Snake = function() {
this.segments = [
new Block(7, 5),
new Block(6, 5),
new Block(5, 5)
];
this.direction = "right";
this.nextDirection = "right";
};
And the code to draw the snake is as follows:
//function to toggle the colour of the snake's segments
function alternateColour(i) {
var colours = ["limegreen", "yellow"];
if (i % 2 === 0) {
return colours[0];
} else {
return colours[1];
};
};
//draw snake method
Snake.prototype.draw = function() {
this.segments[0].drawSquare("blue"); //snake head will always be blue
for (i = 1; i < this.segments.length; i++) {
this.segments[i].drawSquare(alternateColour(i));
};
};
To me this all looks right, but I am still a big newbie and still new to OOP. I am not sure if I have propertly written the Apple.prototype.move method so that it will never place the apple in the location of a segment of the snake's body. The probability of it happening is low, so in order to know for sure I would have to sit and play the game for hours potentially. Any input would be greatly appreciated.
Note: the game play area is divided into a grid system of rows and columns made up of 10x10 pixel areas using the following code:
var blockSize = 10;
var widthInBlocks = width/blockSize;
var heightInBlocks = height/blockSize;
Thank you.
It looks like you have a mistake in your Apple.prototype.move function when checking whether the apple position clashes with a snake segment.
You're calculating the apple position (randomCol, randomRow), but then not using these values when checking against each snake segment.
Also, you call this.move() when you expect to encounter a clash. But after that function has returned, you will continue to finish any remaining iterations in the previous for loop.
These issues can be resolved with the following changes:
for (i = 0; i < Snake.segments; i++) {
if (randomCol === Snake.segments[i].col && randomRow === Snake.segments[i].row) {
return this.move();
};
};

How to add a logic statement to an object entering a moving area

I am trying to make a game. The object of the game is to move the square across the screen without hitting a raindrop falling from the roof. How do i make it so that if one of the raindrops enters the square, the square returns to the beginning of the canvas or x =0. Here is the code:
var canvas = document.getElementById('game');
var ctx = canvas.getContext('2d');
var WIDTH = 1000;
var HEIGHT = 700;
var x = 0;
var y = HEIGHT-20;
var xPos = [0];
var yPos = [0];
var speed = [1];
var rainDrops = 50;
var rectWidth = 20;
var rectHeight = 20;
for (var i = 0; i < rainDrops; i++) {
xPos.push(Math.random()* WIDTH);
yPos.push(0);
speed.push(Math.random() * 5);
}
function rainFall () {
window.requestAnimationFrame(rainFall);
ctx.clearRect(0, 0, WIDTH, HEIGHT);
for (var i = 0; i <rainDrops; i++) {
//Rain
ctx.beginPath();
ctx.arc(xPos[i], yPos[i], 3, 0, 2 * Math.PI);
ctx.fillStyle = 'blue';
ctx.fill();
//Rain movement
yPos[i]+=speed[i];
//Box
ctx.fillStyle = 'red';
ctx.fillRect(x, y, rectWidth, rectWidth);
if (yPos[i] > HEIGHT) {
yPos[i]= 0;
yPos[i]+=speed[0];
}
//This is the part where I need the Help!!!!!!!!!
if (){
x = 0;
}
}
};
//Move Object
function move (e) {
if (e.keyCode === 37) {
ctx.clearRect (0, 0, WIDTH, HEIGHT);
x -=10;
}
if (e.keyCode === 39) {
ctx.clearRect (0, 0, WIDTH, HEIGHT);
x+=10;
}
canvas.width=canvas.width
}
//Lockl Screen
window.addEventListener("keydown", function(e) {
// Lock arrow keys
if( [37,39,].indexOf(e.keyCode) > -1) {
e.preventDefault();
}
}, false);
rainFall();
document.onkeydown = move;
window.addEventListener("load", doFirst, false);
Conditional statements
I am never too sure how to answer these types of questions. As you are a beginner I don't want to overwhelm you with code and techniques, but at the same time I don't want to give an answer that perpetuates some bad techniques.
The short answer
So first the simple answer from your code where you wrote
//This is the part where I need the Help!!!!!!!!!
// the test checks the center of the drop
// if drop is greater than > top of player (y) and (&&)
// drop x greater than > left side of player x and (&&) drop is less than <
// right side of player (x + rectWidth) then drop has hit player
if (yPos[i] > y && xPos[i] > x && xPos[i] < x + rectWidth ){
x = 0; // move player back
}
BTW you are drawing the player rectangle for each rain drop. You should move that draw function outside the loop.
The long answer
Hopefully I have not made it too confusing and have added plenty of comments about why I did this and that.
To help keep everything organised I separate out the various elements into their own objects. There is the player, rain, and keyboard handler. This is all coordinated via the mainLoop the is called once a frame (by requestAnimationFrame) and calls the various functions to do all that is needed.
The player object holds all the data to do with the player, and functions to draw and update the player (update moves the player)
The rain object holds all the rain in an array called rain.drops it has functions to draw and update the rain. It also has some functions to randomize a drop, and add new drops.
To test if the rain has hit the player I do it in the rain.update function (where I move the rain) I don`t know what you wanted to happen when the rain hits the player so I just reset the rain drop and added 1 to the hit counter.
I first check if the bottom of the rain drop drop.y + drop.radius is greater than the top of the player if(drop.y + drop.radius >= player.y){
This makes it so we dont waste time checking rain that is above the player.
The I test for the rain in the x direction. The easiest is the test the negative (if the rain is not hitting the player) as the logic is a little simplier.
If the right side of the drop is to the left of the left side of the player, or (use || for or) the left side of the drop is to the right of the right side of the player than the drop can not be hitting the player. As we want the reverse condition we wrap it in a brackets a put a not in front if(! ( ... ) )
The test is a little long so I break it into 2 lines for readability.
// drop is a single rain drop player is the player
if (drop.y + drop.radius >= player.y) {
if ( ! (drop.x + drop.radius < player.x ||
drop.x - drop.radius > player.x + player.width) ) {
// code for player hit by rain in here
}
}
The rain.update function also checks if the rain has hit the bottom of the canvas and resets it if so.
Demo
I copied your code from in the question and modified it.
addEventListener("load",function(){ // you had onload at the bottom after all the code that gets the canvas
// etc. Which kind of makes the on load event pointless. Though not needed
// in this example I have put it in how you can use it for a web page.
// Using onload event lets you put the javascript anywhere in the HTML document
// if you dont use onload you must then only put the javascript after
// the page elements you need eg Canvas.
var canvas = document.getElementById('gameCanvas');
var ctx = canvas.getContext('2d');
ctx.font = "20px arial";
var frameCount = 0; // counts the frames
var WIDTH = canvas.width;
var HEIGHT = canvas.height;
var currentMaxDrops = 5; // rain will increase over time
const numberFramesPerRainIncrease = 60 * 5; // long name says it all. 60 frames is one second.
const maxRainDrops = 150; // max number of rain drops
// set up keyboard handler
addEventListener("keydown", keyEvent);
addEventListener("keyup", keyEvent);
requestAnimationFrame(mainLoop); // request the first frame of the animation (get it all going)
//==========================================================================================
// Setup the keyboard input stuff
const keys = { // list of keyboard keys to listen to by name.
ArrowLeft : false,
ArrowRight : false,
}
function keyEvent(event){ // the key event argument event.code hold the named key.
if(keys[event.code] !== undefined){ // is this a key we want
event.preventDefault(); // prevent default browser action
keys[event.code] = event.type === "keydown"; // true if keydown false if not
}
}
//==========================================================================================
const player = { // object for everything to do with the player
x : 0, // position
y : HEIGHT - 20,
width : 20, // size
height : 20,
speed : 4, // speed per frame
color : "red",
showHit : 0, // when this is > 0 then draw the player blue to indicate a hit.
// This counter is counted down each frame so setting its value
// determins how long to flash the blue
hitCount : 0, // a count of the number of drops that hit the player.
status(){ // uses hit count to get a status string
if(player.hitCount === 0){
return "Dry as a bone.";
}
if(player.hitCount < 5){
return "A little damp.";
}
if(player.hitCount < 15){
return "Starting to get wet.";
}
return "Soaked to the core";
},
draw(){ // draw the player
if(player.showHit > 0){
player.showHit -= 1; // count down show hit
ctx.fillStyle = "blue";
}else{
ctx.fillStyle = player.color;
}
ctx.fillRect(player.x,player.y,player.width,player.height);
},
update(){ // this updates anything to do with the player
// Not sure how you wanted movement. You had it so that you move only when key down events
// so I have done the same
if(keys.ArrowLeft){
player.x -= player.speed; // move to the left
keys.ArrowLeft = false; // turn off the key. If you remove this line then will move left while
// the key is down and stop when the key is up.
if(player.x < 0){ // is the player on or past left side of canvas
player.x = 0; // move player back to zero.
}
}
if(keys.ArrowRight){
player.x += player.speed; // move to the right
keys.ArrowRight = false; // turn off the key. If you remove this line then will move right while
// the key is down and stop when the key is up.
if(player.x + player.width >= WIDTH){ // is the player on or past right side of canvas
player.x = WIDTH - player.width; // move player back to inside the canvas.
}
}
}
}
//==========================================================================================
const rain = { // object to hold everything about rain
numberRainDrops : 50,
drops : [], // an array of rain drops.
randomizeDrop(drop){ // sets a drop to random position etc.
drop.x = Math.random() * WIDTH; // random pos on canvas
drop.y = -10; // move of screen a little so we dont see it just appear
drop.radius = Math.random() *3 + 1; // give the drops a little random size
drop.speed = Math.random() * 4 + 1; // and some speed Dont want 0 speed so add 1
return drop;
},
createDrop(){ // function to create a rain drop and add it to the array of drops
if(rain.drops.length < currentMaxDrops){ // only add if count is below max
rain.drops.push(rain.randomizeDrop({})); // create and push a drop. {} creates an empty object that the function
// randomizeDrop will fill with the starting pos of the drop.
rain.numberRainDrops = rain.drops.length;
}
},
draw(){ // draw all the rain
ctx.beginPath(); // start a new path
ctx.fillStyle = 'blue'; // set the colour
for(var i = 0; i < rain.drops.length; i ++){
var drop = rain.drops[i]; // get the indexed drop
ctx.arc(drop.x, drop.y, drop.radius, 0, 2 * Math.PI);
ctx.closePath(); // stops the drops rendered as one shape
}
ctx.fill(); // now draw all the drops.
},
update(){
for(var i = 0; i < rain.drops.length; i ++){
var drop = rain.drops[i]; // get the indexed drop
drop.y += drop.speed; // move down a bit.
if(drop.y + drop.radius >= player.y){ // is this drop at or below player height
// checks if the drop is to the left or right of the player
// as we want to know if the player is hit we use ! (not)
// Thus the next if statement is if rain is not to the left or to the right then
// it must be on the player.
if(!(drop.x + drop.radius < player.x || // is the rigth side of the drop left of the players left side
drop.x - drop.radius > player.x + player.width)){
// rain has hit the player.
player.hitCount += 1;
player.showHit += 5;
rain.randomizeDrop(drop); // reset this drop.
}
}
if(drop.y > HEIGHT + drop.radius){ // is it off the screen ?
rain.randomizeDrop(drop); // restart the drop
}
}
}
}
function mainLoop () { // main animation loop
requestAnimationFrame(mainLoop); // request next frame (don`t need to specify window as it is the default object)
ctx.clearRect(0, 0, WIDTH, HEIGHT);
frameCount += 1; // count the frames
// when the remainder of frame count and long name var is 0 increase rain drop count
if(frameCount % numberFramesPerRainIncrease === 0){
if(currentMaxDrops < maxRainDrops){
currentMaxDrops += 1;
}
}
rain.createDrop(); // a new drop (if possible) per frame
rain.update(); // move the rain and checks if the player is hit
player.update(); // moves the player if keys are down and check if play hits the side of the canvas
player.draw(); // draw player
rain.draw(); // draw rain after player so its on top.
ctx.fillStyle = "black";
ctx.fillText("Hit " + player.hitCount + " times.",5,20);
ctx.setTransform(0.75,0,0,0.75,5,34); // makes status font 3/4 size and set position to 5 34 so I dont have to work out where to draw the smaller font
ctx.fillText(player.status(),0,0); // the transform set the position so text is just drawn at 0,0
ctx.setTransform(1,0,0,1,0,0); // reset the transform to the default so all other rendering is not effected
};
});
canvas {
border : 2px black solid;
}
<canvas id="gameCanvas" width=512 height=200></canvas>
Hope this was helpfull.

Handle multiple enemies firing bullets at the same time

I'm trying to make all my enemies simultaneously shoot bullets at the player. My code works for one enemy shooting at a time, but what I'm trying to do is to make them all fire simultaneously.
For brevity's sake I'm using this simple condition to trigger the fire_bullets function : if (player.x > thisEnemy.x) { //fire bullets. What I intended to do is, the farther the player moves to the right, the more enemies start shooting bullets. Problem is the condition is only working for the first enemy in the array, while the other ones simply won't fire any bullet. You can see demo here :
https://jsfiddle.net/Lau1989/796xr7vg/
Here is my fire_bullets() function :
function fire_bullets(thisEnemy) {
var HTMLbullets = document.querySelectorAll('.HTMLbullets');
for (var b = 0; b < HTMLbullets.length; b++) {
var time = new Date().getTime()/1000 - startTime;
if (bullets[b].isOut && time > 0.5) {
if (player.x > thisEnemy.x) {
bullets[b] = bullets_pool(thisEnemy.x, thisEnemy.y);
}
startTime = new Date().getTime()/1000;
}
if (!bullets[b].isOut) {
bullets[b].y -= 4;
if (bullets[b].y <= 50) { //check if bullet is out of range and recycle it
bullets[b].isOut = true;
var thisBullet = bullets.splice(b, 1)[b];
thisBullet.x = -100;
bullets.push(thisBullet);
}
}
HTMLbullets[b].style.left = bullets[b].x +"px";
HTMLbullets[b].style.top = bullets[b].y +"px";
}
}
My latest try was to add the fire_bullets function as a property to each enemy object and then call it whenever their x position is lower than the player x position, but it only made the last enemy being triggered to fire its bullets, while the other ones stopped shooting theirs.
How should I proceed to make all my enemies simultaneously fire bullets at the player ? Is there a good performance-wise solution to do it, considering that each level frequently has dozens of enemies firing bullets at the same time ?

Canvas Game: Collision Detection

For my education I have to make a basic game in HTML5 canvas. The game is a shooter game. When you can move left -> right and space is shoot. When I shoot the bullets will move up. The enemy moves down. When the bullet hits the enemy the enemy has to dissapear and it will gain +1 score. But the enemy will dissapear after it comes up the screen.
Demo: http://jordikroon.nl/test.html
space = shoot + enemy shows up
This is my code:
for (i=0;i<enemyX.length;i++) {
if(enemyX[i] > canvas.height) {
enemyY.splice(i,1);
enemyX.splice(i,1);
} else {
enemyY[i] += 5;
moveEnemy(enemyX[i],enemyY[i]);
}
}
for (i=0;i<bulletX.length;i++) {
if(bulletY[i] < 0) {
bulletY.splice(i,1);
bulletX.splice(i,1);
} else {
bulletY[i] -= 5;
moveBullet(bulletX[i],bulletY[i]);
for (ib=0;ib<enemyX.length;ib++) {
if(bulletX[i] + 50 < enemyX[ib] ||
enemyX[ib] + 50 < bulletX[i] ||
bulletY[i] + 50 < enemyY[ib] ||
enemyY[ib] + 50 < bulletY[i])
{
++score;
enemyY.splice(i,1);
enemyX.splice(i,1);
}
}
}
}
Objects:
function moveBullet(posX,posY) {
//console.log(posY);
ctx.arc(posX, (posY-150), 10, 0 , 2 * Math.PI, false);
}
function moveEnemy(posX,posY) {
ctx.rect(posX, posY, 50, 50);
ctx.fillStyle = '#ffffff';
ctx.fill();
}
Further to Ben's correction, for future reference it's better to do a line-line collision detection, rather than your point-box collision detection.
The reason for this is, say your enemy is small and your bullet is moving fast. There is always a chance the bullet will appear BEFORE the enemy in one frame, and then behind the enemy in the next, therefore never registering as a hit.
Testing for an intersect with the bullet path and an imaginary line on the enemy will be far more accurate.
ORI think I see a problem. You have the following code on your collision detection
if(bulletX[i] + 50 < enemyX[ib] ||
enemyX[ib] + 50 < bulletX[i] ||
bulletY[i] + 50 < enemyY[ib] ||
enemyY[ib] + 50 < bulletY[i])
This is straight ORs(||), and the +50 is on the less than side. That means that it should actually trigger as true whenever the bullet is not in the hitbox. I suspect that you instead want to have the +50s on the greater than side, and have the ORs(||) be ANDs(&&) instead.

Categories