JavaScript game starts fast and slows down over time - javascript

My simple JavaScript game is a Space Invaders clone.
I am using the p5.js client-side library.
I have tried many things to attempt at speeding up the game.
It start off fast, and then over time it get slower, and slower, it isn't as enjoyable.
I do not mean to show every bit of code I have. I am not showing every class, but I will show the main class where everything is happening.
Could someone eyeball this and tell me if you see anything major?
I am new to JS and new to making games, I know there is something called update()
that people use in scripting but I am not familiar with it.
Thank you.
var ship;
var flowers = []; // flowers === aliens
var drops = [];
var drops2 = [];
function setup() {
createCanvas(600, 600);
ship = new Ship();
for (var i = 0; i < 6; i ++) {
flowers[i] = new Flower(i * 80 + 80, 60);
}
flower = new Flower();
}
function draw() {
background(51);
ship.show();
ship.move();
shipDrops();
alienDrops();
dropsAndAliens();
dropDelete();
drop2Delete();
}
// if 0 drops, show and move none, if 5, etc..
function shipDrops() {
for (var i = 0; i < drops.length; i ++) {
drops[i].show();
drops[i].move();
for (var j = 0; j < flowers.length; j++) {
if(drops[i].hits(flowers[j]) ) {
flowers[j].shrink();
if (flowers[j].r === 0) {
flowers[j].destroy();
}
// get rid of drops after it encounters ship
drops[i].evaporate();
}
if(flowers[j].toDelete) {
// if this drop remove, use splice function to splice out of array
flowers.splice(j, 1); // splice out i, at 1
}
}
}
}
function alienDrops() {
// below is for alien/flower fire drops 2
for (var i = 0; i < drops2.length; i ++) {
drops2[i].show();
drops2[i].move();
if(drops2[i].hits(ship) ) {
ship.shrink();
drops2[i].evaporate(); // must evap after shrink
ship.destroy();
if (ship.toDelete) {
delete ship.x;
delete ship.y;
} // above is in progress, deletes after ten hits?
}
}
}
function dropsAndAliens() {
var randomNumber; // for aliens to shoot
var edge = false;
// loop to show multiple flowers
for (var i = 0; i < flowers.length; i ++) {
flowers[i].show();
flowers[i].move();
// ******************************************
randomNumber = Math.floor(Math.random() * (100) );
if(randomNumber === 5) {
var drop2 = new Drop2(flowers[i].x, flowers[i].y, flowers[i].r);
drops2.push(drop2);
}
//**************** above aliens shooting
// below could be method, this will ensure the flowers dont
//go offscreen and they move
//makes whtever flower hits this space become the farther most
//right flower,
if (flowers[i].x > width || flowers[i]. x < 0 ) {
edge = true;
}
}
// so if right is true, loop thru them all again and reset x
if (edge) {
for (var i = 0; i < flowers.length; i ++) {
// if any flower hits edge, all will shift down
// and start moving to the left
flowers[i].shiftDown();
}
}
}
function dropDelete() {
for (var i = drops.length - 1; i >= 0; i--) {
if(drops[i].toDelete) {
// if this drop remove, use splice function to splice out of array
drops.splice(i, 1); // splice out i, at 1
}
}
}
function drop2Delete() {
for (var i = drops2.length - 1; i >= 0; i--) {
if(drops2[i].toDelete) {
// if this drop remove, use splice function to splice out of array
drops2.splice(i, 1); // splice out i, at 1
}
}
}
function keyReleased() {
if (key != ' ') {
ship.setDir(0); // when i lift the key, stop moving
}
}
function keyPressed() {
// event triggered when user presses key, check keycode
if(key === ' ') {
var drop = new Drop(ship.x, height); // start ship x and bottom of screen
drops.push(drop); // when user hits space, add this event to array
}
if (keyCode === RIGHT_ARROW) {
// +1 move right
ship.setDir(1);
} else if (keyCode === LEFT_ARROW) {
// -1 move left
ship.setDir(-1);
} // setir only when pressing key, want continuous movement
}

Please post a MCVE instead of a disconnected snippet that we can't run. Note that this should not be your entire project. It should be a small example sketch that just shows the problem without any extra code.
But to figure out what's going on, you need to debug your program. You need to find out stuff like this:
What is the length of every array? Are they continuously growing over time?
What is the actual framerate? Is the framerate dropping, or does it just appear to be slower?
At what point does it become slower? Try hard-coding different values to see what's going on.
Please note that I'm not asking you to tell me the answers to these questions. These are the questions you need to be asking yourself. (In fact, you should have all of these answers before you post a question on Stack Overflow!)
If you still can't figure it out, then please post a MCVE in a new question post and we'll go from there. Good luck.

Related

Why is my maze generator not detecting if a cell has been visited in p5.js?

I am trying to make a maze generator, and almost everything is working so far. I have been able to set my position to a random pos, and then I repeat the standard() function. In the function, I add pos to posList, and then I choose a random direction. Next, I check if the cell has been visited by running through all of the posList vectors in reverse. I haven't executed the code that backtracks yet. If visited = false then I move to the square and execute the yet-to-be-made path() function. However, for some reason, the mover just doesn't detect if a cell has been visited or not. I am using p5.js. What am I doing wrong?
var posList = [];
var pos;
var tempo;
var boole = false;
var direc;
var mka = 0;
function setup() {
createCanvas(400, 400);
//Set up position
pos = createVector(floor(random(3)), floor(random(3)));
frameRate(1)
}
//Choose a direction
function direct(dire) {
if(dire === 0) {
return(createVector(0, -1));
} else if(dire === 1) {
return(createVector(1, 0));
} else if(dire === 2) {
return(createVector(0, 1));
} else {
return(createVector(-1, 0));
}
}
/foLo stands fo forLoop
function foLo() {
//If we have checked less than three directions and know there is a possibility for moving
if(mka < 4) {
//tempoRARY, this is what we use to see if the cell has been visited
tempo = createVector(pos.x + direct(direc).x, pos.y + direct(direc).y);
//Go through posList backwards
for(var i = posList.length - 1; i >= 0; i --) {
//If the cell has been visited or the cell is off of the screen
if(tempo === posList[i]) {
//Change the direction
direc ++;
//Roll over direction value
if(direc === 4) {
direc = 0;
}
//Re-execute on next frame
foLo();
//The cell has been visited
boole = false;
//Debugging
console.log(direc)
mka++;
} else if(tempo.x < 0 || tempo.x > 2 || tempo.y < 0 || tempo.y > 2) {
direc ++;
if(direc === 4) {
direc = 0;
}
foLo();
boole = false;
console.log(direc)
mka++;
}
}
//If it wasn't visited (Happens every time for some reason)
if(boole === true) {
//position is now the temporary value
pos = tempo;
console.log("works")
mka = 0;
}
}
}
function standard() {
//Add pos to posList
posList.push(pos);
//Random direction
direc = floor(random(4));
//Convert to vector
direct(direc);
foLo();
//Tracks pos
fill(255, 255, 0);
rect(pos.x*100+50, pos.y*100+50, 50, 50)
}
function draw() {
background(255);
fill(0);
noStroke();
//draw grid
for(var i = 0; i < 4; i ++) {
rect(i*100,0,50,350);
rect(0, i*100, 350, 50);
}
standard();
boole = true;
console.log(pos)
console.log(posList);
}
Your issue is on the line where you compare two vectors if(tempo === posList[i]) {: This will never be true.
You can verify that with the following code (in setup() for example):
const v1 = new p5.Vector(1, 0);
const v2 = new p5.Vector(1, 0);
const v3 = new p5.Vector(1, 1);
console.log(v1 === v2) // false
console.log(v1 === v3) // false
This is because despite having the same value v1 and v2 are referencing two different objects.
What you could do is using the p5.Vector.equals function. The doc has the following example:
let v1 = createVector(10.0, 20.0, 30.0);
let v2 = createVector(10.0, 20.0, 30.0);
let v3 = createVector(0.0, 0.0, 0.0);
print(v1.equals(v2)); // true
print(v1.equals(v3)); // false
This might not give you a working algorithm because I suspect you have other logical errors (but I could be wrong or you will debug them later on) but at least this part of the code will do what you expect.
Another solution is to use a Set instead of your list of positions. The cons of this solution is that you will have to adapt your code to handle the "out of grid" position situation. However when you want to keep track of visited items a Set is usually a great solution because the access time is constant. That this means is that to define is a position has already been visited it will always take the same time (you'll do something like visitedSet.has(positionToCheck), whereas with your solution where you iterate through a list the more cells you have visited to longer it will take to check if the cell is in the list.
The Set solution will require that you transform your vectors before adding them to the set though sine, has I explained before you cannot simply compare vectors. So you could check for their string representation with something like this:
const visitedCells = new Set();
const vectorToString = (v) => `${v.x},{$v.y}` // function to get the vector representation
// ...
visitedCells.add(vectorToString(oneCell)); // Mark the cell as visited
visited = visitedCells.has(vectorToString(anotherCell))
Also has a general advice you should pay attention to your variables and functions name. For example
// foLo stands fo forLoop
function foLo() {
is a big smell: Your function name should be descriptive, when you see your function call foLo(); having to find the comment next to the function declaration makes the code less readable. You could call it generateMaze() and this way you'll know what it's doing without having to look at the function code.
Same for
//tempoRARY, this is what we use to see if the cell has been visited
tempo = createVector(pos.x + direct(direc).x, pos.y + direct(direc).y);
You could simply rename tempo to cellToVisit for example.
Or boole: naming a boolean boole doesn't convey a lot of information.
That could look like some minor details when you just wrote the code but when your code will be several hundred lines or when you read it again after taking several days of break, you'll thank past you for taking care of that.

Svg object modified with setAttributeNS not updating until after while loop

I'm creating a small game in javascript and I'm using svg for the graphics. Right now I'm having a problem with updating the game in the middle of a game tick. If I exit my loop directly after I update the fill attribute with "setAttributeNS", it's redrawn, but if I don't do that, it isn't updated until after "game_tick" is over. Even worse, if I call "game_tick" multiple times in a row, the svg objects aren't updated until after I've run all of the "game_tick"s instead of being updated after each one.
function game_tick(){
num_grid_copy = num_grid.slice();
for (var x = 0; x < num_squares_x; x += 1) {
for (var y = 0; y < num_squares_x; y += 1) {
var n = get_neighbors(x,y);
var isAliveInNextGen = next_gen(n, num_grid[x*num_squares_x+y]);
num_grid_copy[x*num_squares_x+y] = isAliveInNextGen;
if (isAliveInNextGen == 1){
rect_grid[x*num_squares_x+y].setAttributeNS(null, 'fill', '#0099ff');
}
else {
rect_grid[x*num_squares_x+y].setAttributeNS(null, 'fill', '#fff');
}
}
}
num_grid = num_grid_copy;
}
Thanks to valuable input from Robert I realized that javascript execution and page rendering are done in the same thread. I changed the function to the following:
function start() {
var inc = 0,
max = 25;
delay = 100; // 100 milliseconds
var repeat = setInterval(function() {
game_tick();
if (++inc >= max)
clearInterval(repeat);
},
delay);
}
This works fine. I can set the delay and the number of times it repeats.

Is there a javascript method that returns the state of a given Audio object?

I'm writing a simple puzzle game in javascript. You can play it here and the source code is here. It uses musical notes as its game pieces, and you can press a "Play" button to make it play the notes. The notes also light up when they are being played. The problem is that there seems to be a lag between the sound and the changing graphics.
For the audio playback method,this.playToneRow(), I used a global variable to iterate through all the notes, and then in the method that shows the pieces, this.drawNotes(), I just grabbed the value of that global variable to determine which game piece should light up. But I'm guessing this isn't the best way to do it, because there seems to be a delay between the .play() call and the actual sound.
So I was thinking that a better way to control the changing graphics would be some kind of method that tells which audio object is currently playing (if any) and then I can just change the corresponding image.
this.playToneRow = function()
{
var x = 0,
length = (this.notes.length + 2),
myArray = this.notes;
j = 0;
currentSound = this.player;
function runIteration () {
x = myArray[j];
setTimeout(function()
{
if (typeof x === 'number')
{
noteSound[x].play();
}
}, 500);
if (j === length)
{
j = -1;
currentSound = 0;
return;
}
j++;
setTimeout(runIteration, 500);
}
runIteration();
};
this.drawNotes = function()
{
this.noteText = "";
for (var i = 0; i < 12; i++)
{
this.noteText += noteNames[this.notes[i]];
this.noteText += ", ";
if (((j - 3) === i) && (currentSound === this.player))
{
ctx.drawImage(this.gamePieceHi, (i * 52) + 85, 275 - (this.notes[i] * 17.7));
}
else
{
ctx.drawImage(this.gamePiece, (i * 52) + 95, 285 - (this.notes[i] * 17.7));
}
}
};
It seems like this.playToneRow() is queuing a sound for 500ms from now via setTimeout(), but the part that draws the note is running immediately.

Javascript challenge - which basket contains the last apple?

I'm presented with the following challenge question:
There are a circle of 100 baskets in a room; the baskets are numbered
in sequence from 1 to 100 and each basket contains one apple.
Eventually, the apple in basket 1 will be removed but the apple in
basket 2 will be skipped. Then the apple in basket 3 will be removed.
This will continue (moving around the circle, removing an apple from a
basket, skipping the next) until only one apple in a basket remains.
Write some code to determine in which basket the remaining apple is
in.
I concluded that basket 100 will contain the last apple and here's my code:
var allApples = [];
var apples = [];
var j = 0;
var max = 100;
var o ='';
while (j < max) {
o += ++j;
allApples.push(j);
}
var apples = allApples.filter(function(val) {
return 0 == val % 2;
});
while (apples.length > 1) {
for (i = 0; i < apples.length; i += 2) {
apples.splice(i, 1);
}
}
console.log(apples);
My question is: did I do this correctly? What concerns me is the description of "a circle" of baskets. I'm not sure this is relevant at all to how I code my solution. And would the basket in which the remaining apple reside be one that would otherwise be skipped?
I hope someone can let me know if I answered this correctly, answered it partially correct or my answer is entirely wrong. Thanks for the help.
So, ... I got WAY too into this question :)
I broke out the input/output of my last answer and that revealed a pretty simple pattern.
Basically, if the total number of items is a power of 2, then it will be the last item. An additional item after that will make the second item the last item. Each additional item after that will increase the last item by 2, until you reach another item count that is again divisible by a power of 2. Rinse and repeat.
Still not a one-liner, but will be much faster than my previous answer. This will not work for 1 item.
var items = 100;
function greatestPowDivisor(n, p) {
var i = 1;
while(n - Math.pow(p, i) > 0) {
i++;
}
return Math.pow(p, (i - 1));
}
var d = greatestPowDivisor(items, 2)
var last_item = (items - d) * 2;
I believe Colin DeClue is right that there is a single statement that will solve this pattern. I would be really interested to know that answer.
Here is my brute force solution. Instead of moving items ("apples") from their original container ("basket") into a discard pile, I am simply changing the container values from true or false to indicate that an item is no longer present.
var items = 100;
var containers = [];
// Just building the array of containers
for(i=0; i<items; i++) {
containers.push(true);
}
// count all containers with value of true
function countItemsLeft(containers) {
total = 0;
for(i=0; i<containers.length; i++) {
if(containers[i]) {
total++;
}
}
return total;
}
// what is the index of the first container
// with a value of true - hopefully there's only one
function getLastItem(containers) {
for(i=0; i<containers.length; i++) {
if(containers[i]) {
return(i);
}
}
// shouldn't get here if the while loop did it's job
return false;
}
var skip = false;
// loop through the items,
// setting every other to false,
// until there is only 1 left
while(countItemsLeft(containers) > 1) {
for(i=0; i<containers.length; i++) {
if(containers[i]) {
if(skip) {
skip = false;
} else {
containers[i] = false;
skip = true;
}
}
}
}
// what's the last item? add one to account for 0 index
// to get a human readable answer
var last_item = getLastItem(containers) + 1;
Needs error checking, etc... but it should get the job done assuming items is an integer.

how to keep a object in array between other objects in array (canvas slider)

I am making a slider with kineticjs where you can can dynamically add more handles to gain more handles. Important is that the handles may not cross over eachother. Because it is important that the order of rank is still te same.
I have made what checks if the object what is higher in order has a lower x value than the object that is one lower in order. If I have made 5 handles, the last one works perfect. If I drop it on the left from the 4th it will put nicely right from the 4th. But if I put the 4th on the left of the 3th it will just go there and the 5th will be put close right to the 3th instead that the 4th does that.
How can it happen? Is there a solution for this problem?
container.addEventListener('mouseup', function(e) {
handles = layer.getChildren();
for(var m=handles.length; m--;)
{
if(m>0)
{
if (layer.get('#myRect'+m)[0].getAbsolutePosition().x < layer.get('#myRect'+(m-1))[0].getAbsolutePosition().x)
{
handles[m].setX(layer.get('#myRect'+(m-1))[0].getAbsolutePosition().x + 15);
}
}
}
layer.draw();
Update();
});
I have discovered that the layer.getChildren is quite messy. Each time he gives the objects an other rankorder. The layer.get('#myRect'+(m-1))[0] however is more reliable. I have kept the layer.getChildren for the counting.
EDIT: I came to discover that when you push the layer.getChildren in a selfmade array, it is more reliable while you have still the array options (layer.getChildren is unreliable for rank order).
The problem with my design is that you can have a moment where you have true on both if. Then he does always the if and thats not what you always want. So I have made a function where it calculates if your handle goes to the right or left and then looks if its across another one. Sorry for messy explanation and code.
var latestcoordinate = 0;
var newcoordinate = 0;
container.addEventListener('mousedown', function(e) {
latestcoordinate = 0;
for (var x = 0; x < array.length; x++) {
latestcoordinate += parseInt(array[x].getAbsolutePosition().x);
}
});
container.addEventListener('mouseup', function(e) {
newcoordinate = 0;
for (var x2 = 0; x2 < array.length; x2++) {
newcoordinate += parseInt(array[x2].getAbsolutePosition().x);
}
for(var m=array.length; m--;)
{
if(m<array.length-1 && newcoordinate < latestcoordinate)
{
if (array[m].getAbsolutePosition().x < array[m+1].getAbsolutePosition().x)
{
array[m].setX(array[m+1].getAbsolutePosition().x + 15);
}
}
if(m>0 && newcoordinate > latestcoordinate)
{
if (array[m].getAbsolutePosition().x > array[m-1].getAbsolutePosition().x)
{
array[m].setX(array[m-1].getAbsolutePosition().x - 15);
}
}
}
layer.draw();
Update();
});
Not that this is the solution, but I think you want to add an escape from your for loop or make it a 'for in' loop
for(var m=handles.length; m--;)
{
if(m>0) // <--- why isn't this clause in the for loop logic?
{
if (layer.get('#myRect'+m)[0].getAbsolutePosition().x < layer.get('#myRect'+(m-1))[0].getAbsolutePosition().x)
{
handles[m].setX(layer.get('#myRect'+(m-1))[0].getAbsolutePosition().x + 15);
}
}
else
break;
}

Categories

Resources