0

So I have built a circular random colour picker utility in javascript and HTML5 Canvas and all the components are dynamic, the size of the objects adjusts to the size of the screen and the spacing also adjusts to the size of the screen. Furthermore, if the user resizes the display the utility also dynamically resizes.

I am using an array to store the colours for the circles. When the circles are generated they use the first colour in the array, delete that colour from the array, and then shuffle the array.

The problem is that when the user resizes the display the colour array does not have enough colours left to draw all the circles, this is because the code removes used colours so that there are no duplicates. However, i tried to fix this by declaring a constant array of the colours called origColours and setting the colours array equal to the origColours array.

Below is the code I have written. I cannot see how or why the origColours array is being manipulated, hopefully you can help

:)

//########//SETUP
var canvas = document.getElementById("myCanvas");
var c = canvas.getContext("2d");

canvas.height = innerHeight;
canvas.width = innerWidth;

document.documentElement.style.overflow = 'hidden';  // firefox, chrome
document.body.scroll = "no"; // ie only


//########//COLORS
const origColours = ["#1c2133", "#2b6ea8", "#5d99bf", "#333968", "#000000", "#b000b0", "#0000aa", "#ff0000", "#00aaaa", "#7CFC00", "#00FF7F", "#8B0000", "#F0E68C"];
var colours = ["#1c2133", "#2b6ea8", "#5d99bf", "#333968", "#000000", "#b000b0", "#0000aa", "#ff0000", "#00aaaa", "#7CFC00", "#00FF7F", "#8B0000", "#F0E68C"];



//########//VARIABLES
var backgroundColour = 0;

var mouse = {
    x: undefined,
    y: undefined,
}; 

var key = {
    keyCode: undefined,
}

var mainRadius = 0;
var smallRadius = 0;

var pointerCircle;
var circles = [];



//########//EVENTS
window.addEventListener("mousemove", function(event) {
    mouse.x = event.x;
    mouse.y = event.y;
})

window.addEventListener("keypress", function(event) {
    key.keyCode = event.keyCode;
    if (key.keyCode == 32) {
        switchBg();
    }
})

window.addEventListener('resize', function(event) {
    canvas.width = innerWidth
    canvas.height = innerHeight

    setup();
})



//########//OBJECTS
function Circle(x, y, radius, colour) {
    this.x = x;
    this.y = y;
    this.radius = radius;
    //this.n = Math.floor(Math.random()*colours.length);

    if (colour == undefined) {
        //this.fill = colours[this.n];
        this.fill = colours[0];
        this.orignalFill = this.fill;
        colours.shift();
        colours = shuffleArray(colours);
    } else {
        this.fill = colour;
        this.orignalFill = this.fill;
    } 


    this.draw = function() {
        c.fillStyle = this.fill;
        c.strokeStyle = this.colour;
        c.beginPath();
        c.arc(this.x,this.y,this.radius,0,Math.PI*2);
        c.fill();
    }

    this.update = function() {

        //Bounce off the edges
//        if (this.x + this.radius > innerWidth || this.x - this.radius < 0) {
//            this.dx = -this.dx;
//        }
//        if (this.y + this.radius > innerHeight || this.y - this.radius < 0) {
//            this.dy = -this.dy;
//        }

        //Move circle
//        this.x += this.dx;
//        this.y += this.dy;



        //Draw the circle after all calculations have been made
        this.draw();

    }
}



//########//UTILITY FUNCTIONS
function shuffleArray(arr) {
    var j, x, i;
    for (i = arr.length - 1; i > 0; i--) {
        j = Math.floor(Math.random() * (i + 1));
        x = arr[i];
        arr[i] = arr[j];
        arr[j] = x;
    }
    return arr;
}

function checkCollisions(obj1, objs) {
    for (var i = 0; i < objs.length; i++) {
        if (checkCollision(obj1, objs[i])) {
            return objs[i]
        }
    }

}
function renderCircles(arr) {
    for (var i = 0; i < arr.length; i++) {
        arr[i].update();
    }
}

function checkCollision(object1, object2) {
    var obj_s = getDistance(object1.x, object1.y, object2.x, object2.y);

    if (obj_s < object1.radius + object2.radius) {
        return true;
    } else {
        return false;
    }
}

function getDistance(x1, y1, x2, y2) {
    xs = x2 - x1;
    ys = y2 - y1;

    return Math.sqrt(Math.pow(xs, 2) + Math.pow(ys, 2));
}

function switchBg() {
    if (backgroundColour == 0) {
        document.body.style.backgroundColor = "black"
        backgroundColour = 1
    } else if (backgroundColour == 1) {
        document.body.style.backgroundColor = "white"
        backgroundColour = 0
    }
}



//########//ANIMATION
function animate() {
    requestAnimationFrame(animate);
    c.clearRect(0,0,innerWidth,innerHeight);

    pointerCircle.x = mouse.x;
    pointerCircle.y = mouse.y;

    var result = checkCollisions(pointerCircle, circles);

    if (result != undefined) {
        circles[0].fill = result.fill;
    } else {
        circles[0].fill = circles[0].orignalFill;
    }

    pointerCircle.update();
    renderCircles(circles);

}

//########//RUNNING CODE


function setup() {
    if (innerHeight >= innerWidth) {
        mainRadius = innerWidth/6;
    } else {
        mainRadius = innerHeight/6;
    }

    smallRadius = mainRadius/2;

    c.clearRect(0,0,innerWidth,innerHeight);

    circles = [];
    colours = origColours

    pointerCircle = new Circle(0,0,1, "rgba(0,0,0,0)");
    circles.push(new Circle(innerWidth/2, innerHeight/2, mainRadius, "white"));

    circles.push(new Circle((innerWidth/2)-mainRadius*2, innerHeight/2, smallRadius));
    circles.push(new Circle((innerWidth/2)+mainRadius*2, innerHeight/2, smallRadius));
    circles.push(new Circle((innerWidth/2), (innerHeight/2)-mainRadius*2, smallRadius));
    circles.push(new Circle((innerWidth/2), (innerHeight/2)+mainRadius*2, smallRadius));

    var angCoE = mainRadius / 2 * 3;

    circles.push(new Circle((innerWidth/2)+angCoE, (innerHeight/2)-angCoE, smallRadius));
    circles.push(new Circle((innerWidth/2)+angCoE, (innerHeight/2)+angCoE, smallRadius));
    circles.push(new Circle((innerWidth/2)-angCoE, (innerHeight/2)-angCoE, smallRadius));
    circles.push(new Circle((innerWidth/2)-angCoE, (innerHeight/2)+angCoE, smallRadius));

}

setup();
animate();
Cake Hook
  • 25
  • 5
  • I haven't read your code, but I'm guessing that you assume `const` means that there's deep immutability, and/or you assume that assignment makes a deep copy –  May 12 '18 at 15:08
  • Possible duplicate of [Why does changing an Array in JavaScript affect copies of the array?](https://stackoverflow.com/questions/6612385/why-does-changing-an-array-in-javascript-affect-copies-of-the-array) ... https://stackoverflow.com/q/9024709/2437417 ... https://stackoverflow.com/q/14170205/2437417 –  May 12 '18 at 15:10
  • Possible duplicate of [Why can I change value of a constant in javascript?](https://stackoverflow.com/q/23436437/1048572) – Bergi May 12 '18 at 16:18

4 Answers4

6

Note: So I was a little too hasty and didn't read your question thoroughly enough. The real solution was posted by Hey24sheep and pooyan below -- I'm leaving this here to explain a different facet of the question.

Declaring a variable as const means you cannot change its value. If the variable in question holds a reference to an object (such as an array), this means you cannot make the variable refer to a different object.

For example, if you tried this:

const colors = [ 'red', 'green', 'blue' ];
colors = [ 'yellow', 'cyan', 'magenta' ];

This would fail, because you're trying to change what colors refers to. However, the array itself is a separate entity from your variable, and its properties are still free to be manipulated.

What you're looking for in this case is Object.freeze():

const colors = Object.freeze([ 'red', 'green', 'blue' ]);

Now you will find that you cannot add, remove, or change any elements of the array. And, since you delcared it with const, you cannot reassign the variable colors either.

Further info:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const

Máté Safranka
  • 4,081
  • 1
  • 10
  • 22
  • 1
    Thanks, Your answer is quite different and really something which we miss to think. – Hey24sheep May 12 '18 at 15:19
  • Thank you for your input, it was greatly appreciated however I implemented the advice about using slice and that seemed to work. Thanks anyway! – Cake Hook May 12 '18 at 15:25
5

In JavaScript objects are passed and assigned by reference (more accurately the value of a reference), so colours is a reference to the same object.

Because you are doing this in your Setup function.

colours = origColours

You need to create a copy if you need to modify one and not the other. Basically, the slice() operation clones the array and returns the reference to the new array

colours = origColours.slice();
Hey24sheep
  • 1,172
  • 6
  • 16
  • Although your solution is correct but this is not what exactly `slice()` does. The `slice()` method selects the elements starting at the given start argument, and ends at, but does not include, the given end argument and then returns a new array object (shallow copy) of selected items. Since in your example there is no argument passed to the `slice()` method, it just simply selects all of the elements and then returns them in a new array. (Sort of clone indeed :p) – Tushar Shukla Oct 03 '18 at 13:27
1

you should clone your array instead of colours = origColours. one way to clone your array is colours = origColours.slice(0); otherwise when you change your colours array, your origColours would be effected too.

pouyan
  • 3,445
  • 4
  • 26
  • 44
1

you can make a copy of the array and this will leave the original array untouched theres a few ways you can copy an array

colours = origColours.slice();

or if you're using es7 polyfills

colours = [...origColours]

const means you can't change the assignment but you can change the insides of the assignmeant

//you can do this
const a = [1, 2]; // [1]
a.pop()
console.log(a)

// but you cant do this
const i = 5;
i = 4; // erro
Joe Warner
  • 3,335
  • 1
  • 14
  • 41