I am struggling with the error of which throw me once I am trying to recursively change a colour. The error is: “Uncaught TypeError Cannot read property ‘map’ of undefined (sketch: line 18)” and reference to this piece of code: this.color.levels.map(x => x * 0.9) .
I supposed that’s because of a recursive and problem with “this” context. The “right” function which I’ve created executing just one time until above error is thrown.
Any ideas how to make this work or how to recursively change the colour referencing to the same object which I’ve been created?
My code: https://editor.p5js.org/grzegorz.kuguar#gmail.com/sketches/Syc1qQmnQ
<code> class Branch {
constructor(begin, end, strokeW, color, angle) {
this.begin = begin;
this.end = end;
this.angle = angle;
this.strokeW = strokeW;
this.color = color;
}
display() {
stroke(this.color);
strokeWeight(this.strokeW);
line(this.begin.x, this.begin.y, this.end.x, this.end.y);
}
right(angle) {
let direction = p5.Vector.sub(this.end, this.begin);
direction.rotate(angle);
let nextPoint = p5.Vector.add(direction, this.end);
let right = new Branch(this.end, nextPoint, this.strokeW*0.7, this.color.levels.map(x => x * 0.9)); //this line of code throw an error once I am trying to manipulate on the array
return right;
}
}
let tree = [];
let trunk;
let something; //just for check how looks like a p5.color object
function setup() {
createCanvas(400, 400);
background(20);
something = color(100, 230, 100);
console.log(something);
let x = createVector(width/2, height);
let y = createVector(width/2, height-100);
trunk = new Branch(x,y, 7, color(255,100, 100));
tree[0] = trunk;
tree.push(trunk);
}
function draw() {
for(let i = 0; i < tree.length; i++) {
tree[i].display();
}
}
function mousePressed() {
for(let i = tree.length-1; i >= 0; i--) {
tree.push(tree[i].right(Math.PI/4, 0.66));
}
}
The problem is in your use of map. You are doing:
this.col.levels.map(x => x * 0.9)
which returns an array, not a p5.Color object.
To create the new color object, instead use:
color.apply(this, this.col.levels.map(x => x * 0.9))
You can see the full sketch here
Related
I'm trying to have a few circles drawn on the screen that do not move after initialization. Right now it is constantly drawing them to the screen instead of keeping them there. Here's the code:
for (let i = 0; i < 1; i++) {
//location
const r = random(100, 900);
const r2 = random(900, 100);
//size
const rS = random(50, 250);
const rS2 = random(250, 50);
//draw the ellipse with parameters
ellipse(r, r2, rS, rS2);
}
(This is with the p5.js library)
It sounds like your code is in the draw() function, which is called multiple times a second. Since you call random() every single time, it creates new parameters every single time. Instead, you should assign parameters to a variable somewhere else (like in the setup function) and then use those in the draw function. Something like:
var ellipses = [];
function setup() {
createCanvas(640, 480);
for (let i = 0; i < 1; i++) {
ellipses.push({
r: random(100, 300),
r2: random(300, 100),
rS: random(50, 250),
rS2: random(250, 50)
});
}
}
function draw() {
clear();
//location
//draw the ellipse with parameters
ellipses.forEach(function (e) {
ellipse(e.r, e.r2, e.rS, e.rS2);
})
}
<script src="https://unpkg.com/p5#1.1.9/lib/p5.min.js"></script>
I'd like to create a potentially unlimited amount of instances of a certain class within my javascript document:
let trees;
const treespawn = () => {
let x = random(50, windowWidth - 50);
let y = random(windowHeight /22, windowHeight / 1.13);
for (let i = 0; i < 20; i++) {
trees[i] = new Trees(x, y);
return trees[i];
}
}
function draw() {
background(0, 111, 10);
trees.trunk();
trees.leaves();
trees.shudder();
}
class Trees {
constructor(x, y) {
stuff--stuff--stuff
}
trunk() {
stuff--stuff--stuff
}
leaves() {
stuff--stuff--stuff
}
shudder() {
stuff--stuff--stuff
}
}
Some points:
the trees variable is declared globally because I can't think of a way to keep my class function calls in scope.
I'm using p5 hence the function draw. I've created all my other classes in the setup() function.
Basic explanation:
I'd like to create my Trees class object many times and have them appear on my canvas at the start of each level of the game I'm making. This would require around 20 trees per level inside the parameters set to let x and let y. In higher levels of my game I may want to increase the amount of trees.
You can see how simply writing out 20-30 variable declarations, creating those classes and calling all of the functions inside each class would be impractical.
I realise this must be done with some sort of array methodology.
Can anyone help me here?
Thanks in advance!
Update:
This is as far as I've gotten and no console error message but I see no trees on the canvas.
function draw() {
background(0, 111, 10);
() => {
let x = random(50, windowWidth - 50);
let y = random(windowHeight /22, windowHeight / 1.13);
for (let i = 0; i < 20; i++) {
trees[i] = new Trees(x, y);
trees[i].trunk();
trees[i].leaves();
trees[i].shudder();
}
}
}
Update
random is not a native Javascript function, so if you're not getting it from a library or from elsewhere, you might need to define it:
function random(lowerBound, upperBound) {
var range = upperBound - lowerBound + 1;
return Math.floor(Math.random() * range);
}
Try this:
class Trees {
constructor() {
stuff--stuff--stuff
}
trunk() {
stuff--stuff--stuff
}
leaves() {
stuff--stuff--stuff
}
shudder() {
stuff--stuff--stuff
}
}
const treespawn = () => {
let x = random(50, windowWidth - 50);
let y = random(windowHeight /22, windowHeight / 1.13);
return new Trees(x, y);
}
let trees = [];
for (let i = 0; i < 20; i++) {
trees.push(treespawn());
}
function draw() {
background(0, 111, 10);
trees.forEach(tree => {
tree.trunk();
tree.leaves();
tree.shudder();
});
}
I am currently trying to build a snake game from a tutorial in JavaScript and Canvas. Currently I am a receiving an error on this line: grid.set(FOOD, randPos.x, randPos.y); which is part of the setFood function towards the bottom. I dont understand where the .x is coming from because I dont see a X or Y defined within that scope. Would really appreciate it if someone could point me in the right direction so I can finish my first game!!! Thanks.
<script>
//CONSTANTS
var COLS=26;
var ROWS=26;
//ID's
var EMPTY=0;
var SNAKE=1;
var FOOD=2;
//Direction
var LEFT=0;
var UP=1;
var RIGHT=2;
var DOWN=3;
var grid = {
width: null,
height: null,
_grid: null,
init: function(d,c,r){
this.width = c;
this.height = r;
this._grid = [];
for(var x=0; x<c; x++){
this._grid.push([]);
for(var y=0; y < r; y++){
this._grid[x].push(d);
}
}
},
set: function(val,x,y){
this._grid[x][y] = val;
},
get: function(x,y){
this._grid[x][y];
}
}
var snake = {
direction: null,
last: null,
_queue: null,
init: function(d,x,y){
this.direction = d;
this._queue = [];
this.insert(x,y);
},
insert: function(x,y){
this._queue.unshift({x:x, y:y});
this.last = this._queue[0];
},
remove: function(){
return this._queue.pop();
}
}
function setFood(){
var empty = [];
for(var x=0; x<grid.width; x++){
for(var y=0; y<grid.height; y++){
if(grid.get(x,y)===EMPTY){
empty.push({x:x, y:y});
}
}
}
var randPos = empty[Math.floor(Math.random()*empty.length)];
console.log("working");
grid.set(FOOD, randPos.x, randPos.y);
console.log("working???")
}
//Game objects
var canvas, ctx, keystate, frames;
function main(){
canvas = document.createElement('canvas');
canvas.width = COLS*20;
canvas.height = ROWS*20;
ctx = canvas.getContext('2D');
document.body.appendChild(canvas);
frames = 0;
keystate = {};
init();
loop();
}
function init(){
grid.init(EMPTY, COLS, ROWS);
var sp = {x:Math.floor(COLS/2), y:ROWS-1};
snake.init(UP, sp.x, sp.y);
grid.set(SNAKE, sp.x, sp.y);
setFood();
}
function loop(){
update();
draw();
window.requestAnimationFrame(loop, canvas);
}
function update(){
frames++;
}
function draw(){
var tw = canvas.width/grid.width;
var th = canvas.height/grid.height;
for(var x=0; x<grid.width; x++){
for(var y=0; y<grid.height; y++){
switch(grid.get(x,y)){
case EMPTY:
ctx.fillStyle = "#fff";
break;
case SNAKE:
ctx.fillStyle = "#0ff";
break;
case FOOD:
ctx.fillStyle = "#f00";
break;
}
ctx.fillRect(x*tw, y*th, tw, th);
}
}
}
main();
</script>
Take a look at the setFood function. Few lines above the line where You get the error, there is a loop that fills the empty collection with objects containing both x and y fields. One of those objects is than assigned to randPos variable.
The error is caused probably by invalid assignment of the value to randPos variable. It's undefined, so it does not contain required x field.
UPDATE
It seems that You just missed the return keyword in Your grid.get(x,y) function, so it should look like this:
get: function(x,y){
return this._grid[x][y];
}
Please verify if adding it solves Your issue.
Let me help you debug:
grid.set(FOOD, randPos.x, randPos.y); is giving you an error saying you're trying to get the x property from something that's undefined.
Okay, so randPos is undefined.
randPos comes from empty[Math.floor(Math.random()*empty.length)];.
empty is an array created inside a nested for loop: empty.push({x:x, y:y});. Do you see where x is coming from?
As Lukasz points out, there's only a specific case where an array dereference like that is undefined. What if you try doing something like
var a = [];
console.log(a[0]);
?
And the reason empty is empty? No grid cell === EMPTY.
get: function(x,y){
this._grid[x][y];
}
Because of a missing return.
New answer. Check out your function
get: function(x,y){
this._grid[x][y];
}
and compare it with the one in the video you linked
get: function(x,y){
return this._grid[x][y];
}
You need to add the return part, otherwise the function will return undefinedand the array called empty won't get any content so your random number generator will suggest you use empty[0], which is undefined.
Last answer was replacing randPos.x with randPos["x"] but as informed by bbill in the comments below, that's not it.
I've spent about 12 hours looking through this code, and fiddling with it, trying to find out where there's a recursion problem because I'm getting the, "maximum call stack size exceeded," error, and haven't found it. Someone smarter than me please help me!
so far, all I found was that when I make the object, spot, a circle, object, the problem disappears, but when I make it a, 'pip', I get this stack overflow error. I've gone over the pip class with a friggin' microscope, and still have no idea why this is happening!
var canvas = document.getElementById('myCanvas');
//-------------------------------------------------------------------------------------
// Classes
//-------------------------------------------------------------------------------------
//=====================================================================================
//CLASS - point
function point(x,y){
this.x = x;
this.y = y;
}
//=====================================================================================
// CLASS - drawableItem
function drawableItem() {
var size = 0;
this.center = new point(0,0);
this.lineWidth = 1;
this.dependentDrawableItems = new Array();
}
//returns the size
drawableItem.prototype.getSize = function getSize(){
return this.size;
}
// changes the size of this item and the relative size of all dependents
drawableItem.prototype.changeSize = function(newSize){
var relativeItemSizes = new Array;
relativeItemSizes.length = this.dependentDrawableItems.length;
// get the relative size of all dependent items
for (var i = 0; i < this.dependentDrawableItems.length; i++){
relativeItemSizes[i] = this.dependentDrawableItems[i].getSize() / this.size;
}
// change the size
this.size = newSize;
// apply the ratio of change back to all dependent items
for (var i = 0; i < relativeItemSizes.length; i++){
this.dependentDrawableItems[i].changeSize(relativeItemSizes[i] * newSize);
}
}
//moves all the vertices and every dependent to an absolute point based on center
drawableItem.prototype.moveTo = function(moveX,moveY){
//record relative coordinates
var relativeItems = new Array;
relativeItems.length = this.dependentDrawableItems.length;
for (var i = 0; i < relativeItems.length; i++){
relativeItems[i] = new point;
relativeItems[i].x = this.dependentDrawableItems[i].center.x - this.center.x;
relativeItems[i].y = this.dependentDrawableItems[i].center.y - this.center.y;
}
//move the center
this.center.x = moveX;
this.center.y = moveY;
//move all the items relative to the center
for (var i = 0; i < relativeItems.length; i++){
this.dependentDrawableItems[i].moveItemTo(this.center.x + relativeItems[i].x,
this.center.y + relativeItems[i].y);
}
}
// draws every object in dependentDrawableItems
drawableItem.prototype.draw = function(ctx){
for (var i = 0; i < this.dependentDrawableItems.length; i++) {
this.dependentDrawableItems[i].draw(ctx);
}
}
//=====================================================================================
//CLASS - circle
function circle(isFilledCircle){
drawableItem.call(this);
this.isFilled = isFilledCircle
}
circle.prototype = new drawableItem();
circle.prototype.parent = drawableItem.prototype;
circle.prototype.constructor = circle;
circle.prototype.draw = function(ctx){
ctx.moveTo(this.center.x,this.center.y);
ctx.beginPath();
ctx.arc(this.center.x, this.center.y, this.size, 0, 2*Math.PI);
ctx.closePath();
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.outlineColor;
if (this.isFilled === true){
ctx.fill();
}else {
ctx.stroke();
}
this.parent.draw.call(this,ctx);
}
//=====================================================================================
//CLASS - pip
function pip(size){
circle.call(this,true);
}
pip.prototype = new circle(false);
pip.prototype.parent = circle.prototype;
pip.prototype.constructor = pip;
//----------------------------------------------------------------------
// Objects/variables - top layer is last (except drawable area is first)
//----------------------------------------------------------------------
var drawableArea = new drawableItem();
var spot = new pip();
spot.changeSize(20);
drawableArea.dependentDrawableItems[drawableArea.dependentDrawableItems.length] = spot;
//------------------------------------------
// Draw loop
//------------------------------------------
function drawScreen() {
var context = canvas.getContext('2d');
context.canvas.width = window.innerWidth;
context.canvas.height = window.innerHeight;
spot.moveTo(context.canvas.width/2, context.canvas.height/2);
drawableArea.draw(context);
}
window.addEventListener('resize', drawScreen);
Here's the demo: http://jsfiddle.net/DSU8w/
this.parent.draw.call(this,ctx);
is your problem. On a pip object, the parent will be circle.prototype. So when you now call spot.draw(), it will call spot.parent.draw.call(spot), where this.parent is still the circle.prototype…
You will need to explicitly invoke drawableItem.prototype.draw.call(this) from circle.prototype.draw. Btw, you should not use new for the prototype chain.
Why would you write code like that? It's so difficult to understand and debug. When I'm creating lots of classes I usually use augment to structure my code. This is how I would rewrite your code:
var Point = Object.augment(function () {
this.constructor = function (x, y) {
this.x = x;
this.y = y;
};
});
Using augment you can create classes cleanly. For example your drawableItem class could be restructured as follows:
var DrawableItem = Object.augment(function () {
this.constructor = function () {
this.size = 0;
this.lineWidth = 1;
this.dependencies = [];
this.center = new Point(0, 0);
};
this.changeSize = function (toSize) {
var fromSize = this.size;
var ratio = toSize / fromSize;
this.size = toSize;
var dependencies = this.dependencies;
var length = dependencies.length;
var index = 0;
while (index < length) {
var dependency = dependencies[index++];
dependency.changeSize(dependency.size * ratio);
}
};
this.moveTo = function (x, y) {
var center = this.center;
var dx = x - center.x;
var dy = y - center.y;
center.x = x;
center.y = y;
var dependencies = this.dependencies;
var length = dependencies.length;
var index = 0;
while (index < length) {
var dependency = dependencies[index++];
var center = dependency.center;
dependency.moveTo(center.x + dx, center.y + dy);
}
};
this.draw = function (context) {
var dependencies = this.dependencies;
var length = dependencies.length;
var index = 0;
while (index < length) dependencies[index++].draw(context);
};
});
Inheritance is also very simple. For example you can restructure your circle and pip classes as follows:
var Circle = DrawableItem.augment(function (base) {
this.constructor = function (filled) {
base.constructor.call(this);
this.filled = filled;
};
this.draw = function (context) {
var center = this.center;
var x = center.x;
var y = center.y;
context.moveTo(x, y);
context.beginPath();
context.arc(x, y, this.size, 0, 2 * Math.PI);
context.closePath();
context.lineWidth = this.lineWidth;
context[this.filled ? "fill" : "stroke"]();
base.draw.call(this, context);
};
});
var Pip = Circle.augment(function (base) {
this.constructor = function () {
base.constructor.call(this, true);
};
});
Now that you've created all your classes you can finally get down to the drawing:
window.addEventListener("DOMContentLoaded", function () {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var drawableArea = new DrawableItem;
var spot = new Pip;
spot.changeSize(20);
drawableArea.dependencies.push(spot);
window.addEventListener("resize", drawScreen, false);
drawScreen();
function drawScreen() {
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
spot.moveTo(width / 2, height / 2);
drawableArea.draw(context);
}
}, false);
We're done. See the demo for yourself: http://jsfiddle.net/b5vNk/
Not only have we made your code more readable, understandable and maintainable but we have also solved your recursion problem.
As Bergi mentioned the problem was with the statement this.parent.draw.call(this,ctx) in the circle.prototype.draw function. Since spot.parent is circle.prototype the this.parent.draw.call(this,ctx) statement is equivalent to circle.prototype.draw.call(this,ctx). As you can see the circle.prototype.draw function now calls itself recursively until it exceeds the maximum recursion depth and throws an error.
The augment library solves this problem elegantly. Instead of having to create a parent property on every prototype when you augment a class augment provides you the prototype of that class as a argument (we call it base):
var DerivedClass = BaseClass.augment(function (base) {
console.log(base === BaseClass.prototype); // true
});
The base argument should be treated as a constant. Because it's a constant base.draw.call(this, context) in the Circle class above will always be equivalent to DrawableItem.prototype.draw.call(this, context). Hence you will never have unwanted recursion. Unlike this.parent the base argument will alway point to the correct prototype.
Bergi's answer is correct, if you don't want to hard code the parent name multiple times you could use a helper function to set up inheritance:
function inherits(Child,Parent){
Child.prototype=Object.create(Parent.prototype);
Child.parent=Parent.prototype;
Child.prototype.constructor=Child;
};
function DrawableItem() {
this.name="DrawableItem";
}
DrawableItem.prototype.changeSize = function(newSize){
console.log("changeSize from DrawableItem");
console.log("invoking object is:",this.name);
}
function Circle(isFilledCircle){
Circle.parent.constructor.call(this);
this.name="Circle";//override name
}
inherits(Circle,DrawableItem);
Circle.prototype.changeSize = function(newSize){
Circle.parent.changeSize.call(this);
console.log("and some more from circle");
};
function Pip(size){
Pip.parent.constructor.call(this,true);
this.name="Pip";
}
inherits(Pip,Circle);
var spot = new Pip();
spot.changeSize();
For a polyfill on Object.create look here.
I'm fairly new to object orientated stuff so this may very well be the wrong way to be going about getting this done.
This is a very slimmed down version of what I currently have, but the concept is essentially the same. When the user clicks on a canvas element on my page, I create 20 instances of the particle object below, append them to an array whilst at the same time updating the canvas at 30FPS and drawing circles based on the x property of the instances of each object. Once a particle is off the screen, it's removed from the array.
var particle = function()
{
var _this = this;
this.velocity = 1;
this.x = 0;
this.updateVelocity = function(newVelocity)
{
_this.multiplier = newVelocity;
}
var updateObject = function()
{
_this.x += velocity;
}
}
I would like the user to be able to control the velocity of new particles that are created using an input element on the page. When this is updated I have an event listener call
particle.updateVelocity(whateverTheUserEntered);
However I get the error "particle has no method updateVelocity". After a bit of reading up on the subject I understand that to call that function I need to create an instance of the object, but this will only update the velocity value of that instance which isn't going to work for me.
My question is, is there a way to achieve what I'm doing or have I approached this in completely the wrong way? As I said, I'm still getting to grips with OOP principles so I may have just answered my own question...
Try this:
var particle = new (function()
{
var _this = this;
this.velocity = 1;
this.x = 0;
this.updateVelocity = function(newVelocity)
{
_this.multiplier = newVelocity;
}
var updateObject = function()
{
_this.x += velocity;
}
})();
Your's is creating a function and then setting the variable particle to that value. particle will not have any special properties because of this. My example above, however, by using new and the function as a constructor assigns particle an instance of a (now anonymous) class.
I think what you want is:
// define a particle "class"
function Particle() {
var _this = {};
_this.velocity = 1;
_this.x = 0;
_this.multiplier = 1;
_this.updateVelocity = function(newVelocity)
{
_this.multiplier = newVelocity;
}
_this.updateObject = function()
{
_this.x += velocity;
}
return _this;
}
// make 1 particle
var myParticle = new Particle();
myParticle.updateVelocity(100);
// make a bunch of particles
var myParticles = [];
for (var i=0; i < 100; i++) {
var p = new Particle();
p.updateVelocity(Math.random * 100);
myParticles.push(p);
}
If you change it to
var particle = new function () {
}
The 'new' will cause creation of an instance.
So create a function that builds new particle instances for you.
Make velocity static and have a static method to update it. This way, you can still make instances of particle and update the velocity for all of them.
var particle = function() {
// particle stuff
}
particle.velocity = 1;
particle.updateVelocity = function(newVelocity) {
this.velocity = newVelocity
}