Search code examples
javascriptcanvasrandom

JS Random Walk Randomness


I have this script in JS that implement a random walk with lerp, the problem is that with a pseudo-perfect randomness / distribution, my points always go to the top left. Can someone explain that ?
I know javascript randomness isn't perfect since it's based on the computer clock and calculations, however, with a certain amount of time, every points should stay around the center, because every probability cancels its opposite.

const canvas = document.querySelector('#myCanvas');
const ctx = canvas.getContext('2d');

const width = canvas.clientWidth;
const height = canvas.clientHeight;

canvas.width = width;
canvas.height = height;

// Amount of points
const AMOUNT = 15;

// Coordinate of the center of the screen
const centerX = width / 2;
const centerY = height / 2;

// Size of a point
const SIZE = 8;

// List containing all the point (placed by default at the center)
let points = Array.from({ length: AMOUNT }, () => [centerX, centerY]);
let pointsEnd = Array.from({ length: AMOUNT }, () => [centerX, centerY]);

// Keep count of the random distribution
let count = Array.from(new Array(4).keys());

function lerp(start, end, speed) {
  return start + (end - start) * speed;
}

function render() {
  // Clear the canvas
  ctx.fillStyle = 'rgba(255, 255, 255, 1)';
  ctx.fillRect(0, 0, width, height);

  // Loop through all the points
  for (let i = 0; i < AMOUNT; i++) {
    const point = points[i];
    const pointEnd = pointsEnd[i];

    // Draw the point
    ctx.fillStyle = 'black';
    ctx.fillRect(pointEnd[0] - SIZE / 2, pointEnd[1] - SIZE / 2, SIZE, SIZE);

    // Generate the random number
    const n = Math.floor(Math.random() * 4);
    count[n]++;

    // Get the sum of iterations
    const sum = count.reduce((a, b) => a + b);
    // Show the distribution of randomness
    console.log(count.map((val, i) => i + ':' + Math.round((val / sum) * 100) + '%').join(' | '));

    // Change coordinates depending on randomness
    if (n == 2) point[0] += 1;
    if (n == 3) pointEnd[0] -= 1;
    if (n == 1) point[1] += 1;
    if (n == 0) pointEnd[1] -= 1;
  }
}

setInterval(() => {
  requestAnimationFrame(render);
}, 1000 / 60);
<canvas id="myCanvas"></canvas>


Solution

  • The issue is how you are changing the coordinates based on randomness:

    if (n == 2) point[0] += 1;
    if (n == 3) pointEnd[0] -= 1;
    if (n == 1) point[1] += 1;
    if (n == 0) pointEnd[1] -= 1;
    

    This doesn't account for all the ways the point can change, you need to adjust the point and pointEnd in each case:

    if (n === 0) {
        point[0] += 1;
        pointEnd[0] += 1;
    } else if (n === 1) {
        point[0] -= 1;
        pointEnd[0] -= 1;
    } else if (n === 2) {
        point[1] += 1;
        pointEnd[1] += 1;
    } else if (n === 3) {
        point[1] -= 1;
        pointEnd[1] -= 1;
    }
    

    const canvas = document.querySelector('#myCanvas');
    const ctx = canvas.getContext('2d');
    
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    
    canvas.width = width;
    canvas.height = height;
    
    // Amount of points
    const AMOUNT = 15;
    
    // Coordinate of the center of the screen
    const centerX = width / 2;
    const centerY = height / 2;
    
    // Size of a point
    const SIZE = 8;
    
    // List containing all the point (placed by default at the center)
    let points = Array.from({ length: AMOUNT }, () => [centerX, centerY]);
    let pointsEnd = Array.from({ length: AMOUNT }, () => [centerX, centerY]);
    
    // Keep count of the random distribution
    let count = Array.from(new Array(4).keys());
    
    function lerp(start, end, speed) {
      return start + (end - start) * speed;
    }
    
    function render() {
      // Clear the canvas
      ctx.fillStyle = 'rgba(255, 255, 255, 1)';
      ctx.fillRect(0, 0, width, height);
    
      // Loop through all the points
      for (let i = 0; i < AMOUNT; i++) {
        const point = points[i];
        const pointEnd = pointsEnd[i];
    
        // Draw the point
        ctx.fillStyle = 'black';
        ctx.fillRect(pointEnd[0] - SIZE / 2, pointEnd[1] - SIZE / 2, SIZE, SIZE);
    
        // Generate the random number
        const n = Math.floor(Math.random() * 4);
        console.log(n);
        count[n]++;
    
        // Get the sum of iterations
        const sum = count.reduce((a, b) => a + b);
        // Show the distribution of randomness
        console.log(count.map((val, i) => i + ':' + Math.round((val / sum) * 100) + '%').join(' | '));
    
        // Change coordinates depending on randomness
        if (n === 0) {
            point[0] += 1;
            pointEnd[0] += 1;
        } else if (n === 1) {
            point[0] -= 1;
            pointEnd[0] -= 1;
        } else if (n === 2) {
            point[1] += 1;
            pointEnd[1] += 1;
        } else if (n === 3) {
            point[1] -= 1;
            pointEnd[1] -= 1;
        }
        /*
        if (n == 2) point[0] += 1;
        if (n == 3) pointEnd[0] -= 1;
        if (n == 1) point[1] += 1;
        if (n == 0) pointEnd[1] -= 1;
        */
      }
    }
    
    setInterval(() => {
      requestAnimationFrame(render);
    }, 1000 / 60);
    <canvas id="myCanvas"></canvas>