Slowdown in Game of Life with Objects - Why? - javascript

I am working on recreating Game of Life in different ways, taking Coding Train’s video as the starting point. What I am trying to add is ‘color inheritance’: A newborn cell ‘inheriting’ the color from the average of the neighbouring cells’ colours.
I have made two versions: The first one has a 2D grid array for cells, and each cell is an array with RGB values.
grid[i][j] = [255,0,100]
A for loop iterates through the grid array and draws boxes of the right colour on the right position on the canvas. It then checks if the cell should be alive or dead next generation. This works fine without any slowdown, no matter the size of the grid.
https://editor.p5js.org/umutreldem/sketches/1qCmEzHlY
The second version fills each element of the grid array with Cell objects. This object receives its coordinates on the grid and its size. The color is given relative to its position on the grid.
function Cell(x, y, size) {
this.x = x * size;
this.y = y * size;
this.size = size;
//...
this.r = x * 10;
this.g = y * 10;
this.b = x+y;
The Cell object has functions for determining how many alive neighbours it has, inheriting colour from neighbours, etc. It is the same process as the first version. Then in draw() the grid is looped twice: once for determining the next status of each cell, and once for changing the cells to their new state.
This version experiences massive slowdown, especially at higher resolutions.
https://editor.p5js.org/umutreldem/sketches/2skO6-2Cm
I would like to add more features to this, such as keeping count of the age of each cell (for how long it is alive,) and using making an object for each cell makes more sense to me.
With my limited experience with p5.js / JavaScript in general, I cannot understand why this is the case. The function of both versions is the same (apart from one extra loop in the second version), so why does the second version tax the computer this much?

In your second example (using objects) you draw a every square, even if it is "empty".
In your first example, you only draw a square when the cell is active, and thus save computation by not drawing black squares on a black background :)
You can simply comment out the else statement in the "object version" to get the same computational savings.
this.show = function() {
if (this.status == 1) {
fill(color(this.r, this.g, this.b));
stroke(0);
square(this.x, this.y, this.size);
}
// else {
// fill(20);
// stroke(0);
// square(this.x, this.y, this.size);
// }
}
Here is a modified version, which seems to work reasonably fast.

Related

Is there a way to draw hundreds of points faster (p5.js)

I am making a program to test out my attempt at a Perlin Noise generating algorithm. The Perlin noise itself seems fine, however I've found that drawing that noise on the canvas is very slow. This is probably because for every single point in the canvas I have to call the stroke() function to change the color of the next pixel, then draw that pixel. This is done over a 400*400 pixel canvas, so I am changing the color with stroke() 160,000 times and calling point() 160,000 times.
This takes some time to do. I was wondering if there is any way of making this faster. Perhaps if I could turn the Perlin noise into an image, then load that image instead of drawing all 160,000 points individually?
The code of my draw loop is below
function draw() {
background(220);
strokeWeight(1);
for(var row = 0; row < height; row ++)
{
for(var column = 0; column < width; column ++)
{
//takes a noise value from the myNoise array whose elements have a range of [-1,1] and turns it into a value from [0,256], and makes that the color of the next point
stroke((myNoise[row][column]+1)*128);
point(column,row)
}
}
noLoop();
}
Edit: I used the following code to create and load an image. Credit to Samathingamajig for the tip.
function draw() {
background(220);
img = createImage(width,height);
img.loadPixels();
for(var row = 0; row < height; row ++)
{
for(var column = 0; column < width; column ++)
{
//takes a noise value from the myNoise array whose elements have a range of [-1,1] and turns it into a value from [0,256], and makes that the color of the next pixel in the image
img.set(row,column,color((myNoise[row][column]+1)*128))
}
}
img.updatePixels();
image(img,0,0)
noLoop();
}
Also Samathingamajig pointed out that 400*400 is 160,000, not 1,600, which I have changed above.
My original code took about 4 seconds to run the draw loop. This new version takes about 0.75 seconds.
I also tested using the createGraphics() method as suggested by rednoyz. This was not as fast as using the image methods because it still requires me to call stroke() 160,000 times.
Both of these solutions gave me an image that I could very quickly draw, however createImage() allowed me to create the image in much less time than createGraphics() did.
Just to add a bit of nuance to the existing suggestion:
use pixels[] instead of set(x, y, color): it's less intuitive to think of a 1D index that takes into account [r,g,b,a,...] pixels order, and (pixelDensity on retina displays), but it is faster.
The documentation mentions:
Setting the color of a single pixel with set(x, y) is easy, but not as
fast as putting the data directly into pixels[]. Setting the pixels[]
values directly may be complicated when working with a retina display,
but will perform better when lots of pixels need to be set directly on
every loop.
In your case that would roughly look like this:
img.loadPixels();
let numPixels = width * height;
for(let pixelIndex = 0; pixelIndex < numPixels; pixelIndex ++)
{
//takes a noise value from the myNoise array whose elements have a range of [-1,1] and turns it into a value from [0,256], and makes that the color of the next pixel in the image
// index of red channel for the current pixel
// pixels = [r0, g0, b0, a0, r1, g1, b1, a1, ...]
let redIndex = pixelIndex * 4;
// convert 1D array index to 2D array indices
let column = pixelIndex % width;
let row = floor(pixelIndex / width);
// get perlin noise value
let grey = (myNoise[row][column]+1) * 128;
// apply grey value to R,G,B channels (and 255 to alpha)
img.pixels[redIndex] = grey; // R
img.pixels[redIndex + 1] = grey; // G
img.pixels[redIndex + 2] = grey; // B
img.pixels[redIndex + 3] = 255; // A
}
img.updatePixels();
(also looping once instead of nested looping will help).
Regarding point(), it might use something like beginShape(POINTS);vertex(x,y);endShape(); behind the scenes which means something like this would be slightly more efficient:
let numPixels = width * height;
beginShape(POINTS);
for(let pixelIndex = 0; pixelIndex < numPixels; pixelIndex ++)
{
//takes a noise value from the myNoise array whose elements have a range of [-1,1] and turns it into a value from [0,256], and makes that the color of the next pixel in the image
// convert 1D array index to 2D array indices
let column = pixelIndex % width;
let row = floor(pixelIndex / width);
// get perlin noise value
stroke(color((myNoise[row][column]+1) * 128));
// apply grey value to R,G,B channels (and 255 to alpha)
vertex(column, row);
}
endShape();
That being said, it might not work as intended:
AFAIK this won't work with createCanvas(400, 400, WEBGL) as currently you can't set an independent stroke to each vertex in a shape.
With the typical Canvas 2D renderer this may still be very slow to render this many vertices using beginShape()/endShape()
Although a more advanced topic, another option that should be fast is shader(). (Might find some perlin noise inspiration on shadertoy.com btw).
p5.js is great to learn with but it's main goal is not to have the most performant canvas renderers. If shaders are a bit too complex at this stage, but you're comfortable with javascript in general, you can look at other libraries such as pixi.js (maybe pixi particle-emitter could be handy ?)
I've tried a lot of millisecond tests, and the image approach is by FAR the best. The problem is really just the amount of pixels you're trying to process as #Samathinamajig pointed out.
testing: https://editor.p5js.org/KoderM/sketches/6XPirw_98s

Way to see if whole canvas has been painted in one color. Javascript + processing.js

I'm still a beginner at javascript, and I'm making a game about dying the whole screen white while the paint brush becomes smaller and smaller until in completely disappears.
I wanted to know, is there a simple way to figure out if the whole canvas has been painted, so I can put a winning screen?
I'm using the processing.js library, here is my code, if it's of any use:
background(255,0,0);
var eight = 100;
var draw = function(){
strokeWeight(eight);
point(mouseX,mouseY);
eight -= 0.2;
if(eight<0){
noStroke();
}
Here's a modestly efficient way of determining if the user has whited every pixel
Create an array where each canvas pixel is represented by an array element.
var pixels=new Array(canvas.width*canvas.height);
Initially fill the array with all zeros.
Create a variable that hold the # of unique pixels whited out so far.
var whited=0;
When the user passes over a pixel, see if the pixel has already been whited. If it hasn't been whited, change its array value to 1 and increment the whited variable.
var n = mouseY * canvas.width + mouseX
if(pixels[n]=0){
pixels[n]=1;
whited++;
}
You have a winner if the value of whited equals the number of pixels on the canvas.
if(whited==pixels.length){
alert('You have won!');
}
A thought: Instead of making the user find every (tiny) missed pixel, you might consider making a grid so the user has an easier time finding that 1 (larger) missed grid cell instead of finding one missed pixel in a sea of white.
You can go over all the pixels and check if they are not white
for (var i=0;i<imgData.data.length;i+=4)
{
if(imgData.data[i]==0&&imgData.data[i+1]==0&&imgData.data[i+2]==0&&imgData.data[i]+3==0){alert("white pixel")}
}
http://www.w3schools.com/tags/canvas_getimagedata.asp
Since you're using Processing, just walk over the pixels:
void setup() {
...
}
void draw() {
...
}
void yourCheckFunction() {
loadPixels();
boolean allWhite = true;
for(int c: pixels) {
if(brightness(c) < 255) {
// we found a not-white pixel!
allWhite = false;
break;
}
}
if (allWhite) {
// the paint surface is entirely white.
} else {
// there are non-white patches left
}
}
There are lots of ways to optimize this (like chopping up the surface into distinct areas with their own administrative true/false value so you can first check if they were all-white on a previous run, and if so, you don't need to recheck them) but this covers the basics:
assume the canvas is all white pixels
try to invalidate that assumption by finding a not-white pixel
immediately stop checking if you do
if there are none, your loop will end "naturally"
Alternatively, you can track how many pixels your user's action have painted. Once that number of pixels is equal to width*height, all pixels must necessarily be white (see markE's answer for that)

Text Collision Detection

I am building a web application that draws a set of letters in different fonts on an HTML 5 Canvas using fillText. The user will click somewhere on that canvas and I need to check which letter they clicked on (or if they clicked on a letter at all).
I think I will need to:
Get the vector path for each letter (I have no clue how to do this).
Check if the click point is inside the letter path using some simple collision-detection algorithm.
Is there some easy function to do this that I am missing? Or maybe a library for things like this? If there aren't any libraries, how do I get the path for the letter in a specific font to do the checking myself?
I need to use the actual shape of the letter and not just its bounding box as I don't want the user to be able to click in the middle of an O and it register as a hit.
Any hints in this direction would be appreciated.
Logic
You can't handle separate letters on canvas without providing custom logic for it. Everything drawn to the canvas is merged to a soup of pixels.
And unfortunately you can't add text as pure path so you will have to check pixel values. Otherwise you could simply add the text to a new path and use the isPointInPath method for each letter.
One approach
We can't provide full solutions here on SO but here is a basis you can hopefully built on top of to provide basic logic to click single letters on a canvas:
Each letter is stored as object incl. its position, size, font and char, but also with a rectangle hit region (see below)
Define an array with these objects and then pass them to a render function
When you register a click iterate through the array and test against the rectangular hit-region and if inside check the pixel (*)
*) To distinguish between overlapping letters you need to check by priority. You can also render this char onto a separate canvas so you get pixels of only this char. I am not showing this in the demo but you'll get the idea.
Demo
var ltrs = []; /// stores the letter objects
/// Create some random objects
for(;i < 20; i++) {
/// build the object
var o = {char: alpha[((alpha.length - 1) * Math.random())|0],
x: ((w - 20) * Math.random())|0,
y: ((h - 20) * Math.random())|0,
size: (50 * Math.random() + 16)|0,
font: fonts[((fonts.length - 1) * Math.random())|0]};
/// store other things such as color etc.
/// store it in array
ltrs.push(o);
}
Then we have some function to render these characters (see demo).
When we then handle clicks we iterate through the object array and check by boundary first to check what letter we are at (picking just a pixel here won't enable us to ID the letter):
demo.onclick = function(e) {
/// adjust mouse position to be relative to canvas
var rect = demo.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top,
i = 0, o;
/// iterate
for(;o = ltrs[i]; i++) {
/// is in rectangle? "Older" letters has higher priority here...
if (x > o.x && x < (o.x + o.rect[2]) &&
y > o.y && y < (o.y + o.rect[3])) {
/// it is, check if we actually clicked a letter
/// This is what you would adopt to be on a separate canvas...
if (checkPixel(x, y) === true) {
setLetterObject(o, '#f00')
return;
}
}
}
}
The pixel check is straight forward, it picks a single pixel at x/y position and checks its alpha value (or color if you use solid backgrounds):
function checkPixel(x, y) {
var data = ctx.getImageData(x, y, 1, 1).data;
return (data[3] !== 0);
}
CLICK HERE FOR ONLINE DEMO
Updated check pixel function:
This updated check is capable of checking letters even if they are overlapping in the same region.
We create a separate canvas to draw the letter in. This isolates the letter and when we pick a pixel we can only get a pixel from that specific letter. It also doesn't matter what background color is as we our off-screen canvas only set pixels for the letter and not background during the check. The overhead is minimal.
function checkPixel(o, x, y) {
/// create off-screen canvas
var oc = document.createElement('canvas'),
octx = oc.getContext('2d'),
data,
oldX = o.x,
oldY = o.y;
/// default canvas is 300x150, adjust if letter size is larger *)
//oc.width = oc.height = 200;
/// this can be refactored to something better but for demo...
o.x = 0;
o.y = 0;
setLetterObject(octx, o, '#000');
o.x = oldX;
o.y = oldY;
data = octx.getImageData(x - oldX, y - oldY, 1, 1).data;
return (data[3] !== 0);
}
*) When we create a canvas the default size is 300x150. To avoid re-allocating a new bitmap we just leave it as it is as the memory is already allocated for it and we only need to pick a single pixel from it. If letters has larger pixel size than the default size we will of course need to re-allocate to make the letter fit.
In this demo we temporary override the x and y position. For production you should enable the setLetterObject method to somehow override this as that would be more elegant. But I'll leave it as-is here in the demo as the most important thing is to understand the principle.
I'd say that the best option is to actually use pixels that by the way are the most accurate thing you can do (remember that the user is seeing pixels when clicking, nothing more).
Since you cannot just use the color directly (because there can be many text objects with the same color (and may be also other primitives with the same color) you could use instead a separate "pick" canvas for that.
Basically when you draw your objects on the main canvas on the repaint function you also draw them in another hidden canvas with the exact same size, but you draw them using a unique color for each entity.
You can therefore have up to 16 millions entity (24-bit) on the canvas and know instantly which one was clicked by keeping a map between color code and the entity itself. By the way this sort of map is often used in CAD applications exactly to speed up picking.
The only somewhat annoying part is that there's no portable way to disable antialiasing when drawing on a canvas so it's possible that the color you'll get back from the pick canvas is not one of the colors you used or, even worse, it's possible that by clicking on the border of an entity a different unrelated entity is considered selected.
This should be a very rare event unless your display is really really crowded and picking is basically random anyway.

Javascript: Simple Particle Motion, Particle Elastically Bouncing Off Other Particle

I've created this rather simple javascript; balls or 'molecules' moving around the screen. I was hoping to add to the functionality that when one ball comes into contact with another, they swap velocities. We don't need to worry about any angles, just when they come into contact with each other, the velocities swap. (Instead of changing the velocities though, in the code linked I've just coded a colour change)
I've been trying to call the function 'someplace' to recognise when the molecules touch, but I've had no luck with that. I don't really understand why.
Link to code:
http://jsbin.com/arokuz/5/
There seems to be three main problems:
The molecules seem to be randomly changing, rather than when two molecules touch.
When one sets the array to have say, 3 molecules, only two appear, the first is actually there, but unresponsive to .fillstyle changes, so invisible against the canvas
With the function method I would only be able to recognise when molecules in series (1 and 2 or 4 and 5) in the array touch...how could I check all the molecules?
You are only comparing a molecule with 2 other ones, which in fact might be anywhere.
Collision detection is a topic quite hard to solve, but if you want to have your idea
working quickly you might go for a n^2 algorithm with 2 nested for loops.
the code is quite expected :
// collision
for(var t = 0; t < molecules.length-1; t++)
for(var tt = t+1; tt < molecules.length; tt++) {
var p1 = molecules[t];
var p2 = molecules[tt];
if (sq(p1.x-p2.x) +sq(p1.y-p2.y) < sq(p1.radius+p2.radius) )
{
p1.collided = 8; // will diplay for next 8 frames
p2.collided = 8; // .
}
}
the fiddle is here :
http://jsbin.com/arokuz/10
The reason only two appear when three are made isn't because the first one doesn't render it is rather the last one doesn't, this is because of how you draw them by comparing its distance with the next one in the list - as it is the last there is no next and thus throws a null error and continues (check the console).
The reason why they seem to "randomly" detect collisions or not is because they are not checking against all other molecules - only the next in the list, unfortunately the only simply way to do it would be to go through all other balls for every ball and checking.
To get the molecules to detect distance you could use the pythagorean theorem, I typically use it such as:
var distx = Math.abs(molecule1.x - molecule2.x);
var disty = Math.abs(molecule1.x - molecule2.y);
var mindist = molecule1.radius + molecule2.radius;
return Math.sqrt(distx*distx+disty*disty) < mindist;

adding a margin to a fillRect with JavaScript canvas

I am making a snake game for fun, to practice JavaScript/canvas, and what I have so far is a while loop that says while i is < 100, then add a square and the square is added by using fillRect(10, 10, xCoord, yCoord) and in the while loop I have xCoord = xCoord + 11; so it moves the square to the right with a space in between... here is a link:
http://brycemckenney.com/snake_canvas
And the relevant code:
while(i < 100) {
// fillRect(x, y, width, height)... x is horizontal and y is vertical...
context.fillRect(xCoord, yCoord, sW, sH);
// We want each squere to have one pixel in between them, so we increment by 11, not 10.
xCoord = xCoord + 11;
// When i (0) gets to numOfSquares (3), then make the rest white so that you can't see them...
if (i >= numOfSquares) {
context.fillStyle = "#FFFFFF";
}
//Increment by 1 every loop, so that the squares keep going
i++;
}
I am trying to get the snake to animate, and I have tried many different options. Now I am trying to just add a margin-right the the 4 squares, so it looks like it's moving... is it possible to add margin to those? Here is a snapshot of what I have:
Well, since you're making the game of snake, we know what you want to do and I think it would be more useful if we tried to get you on the right track instead of modifying your existing code.
Lets suppose our logical game grid is 60x60.
So snake-pieces can be anywhere in this grid, having X and Y values between 0 and 59. This means:
A piece of snake in the top-left corner is at [0, 0]
A piece of snake in the top-right corner is at [59, 0]
Let us further suppose that a snake is made up of a number of segments. How about 4 segments to start. This means that we need to keep an array of the 4 positions:
[position1, position2, position3, position4]
We have to pick a front, so lets say that the end of the array is the "front" of the snake. If we pick the top left 4 positions the snake would be:
var mySnake = [[0, 0], [1,0], [2,0], [3,0]]
Which on the board looks like this:
++++OOOOOOOO
OOOOOOOOOOOO
OOOOOOOOOOOO
That means its 4 snake-pieces going from left to right, just like you have so far. The thing is, by saving these locations we've gone and added some persistent state to our program.
Now snake is a funny game, because "moving" the snake really means two things:
Taking the last (tail) piece away
Adding a new piece to the snake
So we should make a function for moving the snake that does both of these. We'll make one based on direction:
// modifies a snake's array
function moveSnake(snake, direction) {
// First we must remove a piece of the snake's tail, which is at the start of the array
// There's a built in command in JavaScript for this called shift()
snake.shift();
// Now we must add the new piece!
// To tell where we need to go we must look at the last location:
var lastLoc = snake[snake.length - 1];
// Just to be a little more clear:
var lastX = lastLoc[0];
var lastY = lastLoc[1];
switch (direction) {
case 'up':
snake.push([lastX, lastY-1]);
break;
case 'down':
snake.push([lastX, lastY+1]);
break;
case 'left':
snake.push([lastX-1, lastY]);
break;
case 'right':
snake.push([lastX+1, lastY]);
break;
}
// redraw after every move!
drawSnake(ctx, mySnake);
}
With this method we could do:
var mySnake = [[0, 0], [1,0], [2,0], [3,0]];
moveSnake(mySnake, 'down');
// mySnake is now [[1,0], [2,0], [3,0], [3,1]];
The snake now looks like this:
O+++OOOOOOOO
OOO+OOOOOOOO
OOOOOOOOOOOO
Now this method is pretty dumb, and in a real game of snake we'd need to add some conditions. Typically:
If the new piece of snake is on top of a piece of food, the tail piece is NOT removed, instead the snake is +1 block longer
If the new piece is outside of out 60x60 grid, the user loses the game
If the new piece is at a location that any of the other snake pieces already exist, the user loses the game
Alas those are not done here
We still need to draw it all, but since we are keeping track of the snake's location that's easy peasy. We can just draw one square for each piece of the snake:
function drawSnake(context, snake) {
// Remember to clear the board!
ctx.clearRect(0, 0, 600, 600);
var length = snake.length;
for (var i = 0; i < length; i++) {
context.fillStyle = 'teal';
// position is its own array and looks like: [x, y]
var position = snake[i];
// our logical snake board is 60x60, but the real canvas is 600x600,
// so we multiply x and y by 10.
// We also use "9" for the width and height so theres a tiny gap
context.fillRect(position[0]*10, position[1]*10, 9, 9);
}
}
See the completed demo here.
http://jsfiddle.net/FsqXE/
Note that it allows arrow keys to control the snake to show off the moveSnake method, but the regular snake game has the movement controlled by a timer, and the user can usually only change the next-possible-direction.
The canvas element is a 2D drawing surface, so it doesn't support (higher level) things like CSS margins.
To do animation, you'll need to continually clear the canvas and redraw the scene, each time slightly adjusting the position of moving objects (so that they appear to move). In your case, this means redrawing the squares, each time with a starting position 4 pixels away from the last time you redrew the frame.
You may want to have a look at the Basic animations page at MDN (part of their canvas tutorial).

Categories