Site icon FSIBLOG

How to Fix a Game Error in a Nested Array with JavaScript

How to Fix a Game Error in a Nested Array with JavaScript

How to Fix a Game Error in a Nested Array with JavaScript

Uncaught TypeError Cannot read property ‘x’ of undefined, if you’ve seen this error while working with nested arrays in JavaScript, you’re not alone. I ran into this same issue while trying to create a simple game with bouncing circles using the <canvas> element. It was frustrating at first, but once I understood the problem, the fix was actually quite straightforward.

The Problem

In my original code, I tried to draw several animated circles on a canvas. Each circle was stored inside a nested array called circles. But when I ran the project, I got this error:

Uncaught TypeError: Cannot read property 'x' of undefined

And it pointed to this line:

ctx.arc(circles[i].x, circles[i].y, circles[i].r, 0, Math.PI * 2, false);

This really puzzled me. I had defined the array properly. So what was going on?

Why This Happen

After some debugging, I realized the problem was with how I was using the for loop and variable i.

for (var i = 0; i < circles.length; i++) {
// All logic and drawing functions declared here
setTimeout(draw, 10); // <- here's the trap
}

The issue is that I wrapped everything including function definitions and setTimeout(draw, 10) inside a for loop and assumed i would still be valid when the draw() function was called.

But by the time setTimeout(draw, 10) ran, the loop had already completed, and i was out of bounds (equal to circles.length). So when the draw function tried to access circles[i], it was undefined hence the error.

Loop Dynamically Inside the Draw Function

What I needed to do was restructure the code:

The Final, Working Code

<!DOCTYPE html>
<html>
<head>
<title>Canvas Game: Bouncing Circles</title>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
<script type="text/javascript">
// Define your circles
const circles = [
{x: 200, y: 150, r: 40, direction: 1, speedX: 1, speedY: 2, color: "yellow"},
{x: 100, y: 100, r: 30, direction: 2, speedX: 2, speedY: 1, color: "red"},
{x: 300, y: 200, r: 20, direction: 3, speedX: 1.5, speedY: 1.5, color: "blue"}
];

function moveCircle(circle) {
// Move based on direction
switch (circle.direction) {
case 1: circle.x += circle.speedX; circle.y += circle.speedY; break;
case 2: circle.x += circle.speedX; circle.y -= circle.speedY; break;
case 3: circle.x -= circle.speedX; circle.y -= circle.speedY; break;
case 4: circle.x -= circle.speedX; circle.y += circle.speedY; break;
}

// Bounce off edges
if (circle.y > 300 - circle.r && circle.direction === 1) {
circle.direction = 2;
} else if (circle.x > 400 - circle.r && circle.direction === 2) {
circle.direction = 3;
} else if (circle.y <= circle.r && circle.direction === 3) {
circle.direction = 4;
} else if (circle.x <= circle.r && circle.direction === 4) {
circle.direction = 1;
} else if (circle.y <= circle.r && circle.direction === 2) {
circle.direction = 1;
} else if (circle.x >= 400 - circle.r && circle.direction === 1) {
circle.direction = 4;
} else if (circle.y >= 300 - circle.r && circle.direction === 4) {
circle.direction = 3;
} else if (circle.x <= circle.r && circle.direction === 3) {
circle.direction = 2;
}
}

function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

// Clear canvas
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);

// Move and draw each circle
for (let i = 0; i < circles.length; i++) {
const circle = circles[i];
moveCircle(circle);
ctx.fillStyle = circle.color;
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.r, 0, Math.PI * 2);
ctx.fill();
}

requestAnimationFrame(draw); // Smooth animation
}

window.onload = draw;
</script>
</head>
<body>
<canvas id="canvas" width="400" height="300"></canvas>
</body>
</html>

Improvement I Made

To go beyond just fixing the bug, I decided to improve the whole project:

Ideas for More Practice

Once I got this working, I started thinking of ways to build on it:

Let me know if you want me to help with any of these ideas I’d be happy to break those down in future posts!

Final Thought

This experience was a great reminder that understanding variable scope and timing in JavaScript is essential, especially when dealing with asynchronous operations like setTimeout() or animations. What seemed like a bug in my data (circles[i]) was actually a problem with how and when that data was accessed.

Exit mobile version