RMMV How to make an easy to understand multi-dimensional array in javascript

bishiba

Adept
Veteran
Joined
Apr 6, 2016
Messages
150
Reaction score
23
First Language
Swedish
Primarily Uses
N/A
If you are in need of making a multi-dimensional array but you are like me, and find the available suggestions if how to do it a bit, abstract(?), then go ahead and just use my little code snippet.

The only downside to it is that it needs to be set up with 2 lines. But other than that is more easy to understand, and works like any other array.

JavaScript:
grid.p[x + "," + y] = element;
So at first you declare z to be x and y separated with a comma. It essentially looks this way: grid.p["x,y"]. Imo it's even more straightforward than say grid.p[x][y], which is a real multi-dimensional array.

if you want to see it working you can just put the below into any javascript and run the makeNoise(density) function followed by the drawGrid() function.

JavaScript:
grid = {};
grid.size = 14;
grid.p = [];
tWall = "X";
tFloor = "O";

function makeNoise(density) {
   for (x = 0; x < grid.size; x++) {
      for (y = 0; y < grid.size; y++) {
         rand = Math.random()*100;
         if (density > rand) {
            grid.p[x + "," + y] = tWall;
         } else {
            grid.p[x + "," + y] = tFloor;
         }
      }
      rand = Math.random()*100;
      if (density > rand) {
         grid.p[x + "," + y] = tWall;
      } else {
         grid.p[x + "," + y] = tFloor;
      }
   }
}

function drawGrid() {
   let s = "";
   for (x = 0; x < grid.size; x++) {
      for (y = 0; y < grid.size; y++) {
         s += grid.p[x+"," + y];
      }
      s += grid.p[x + "," + y] + "\n";
   }  
console.log("grid:\n" + s);
}

I am no code master, but this felt really useful and I hope someone will find it helpful as well.

Should produce something like this in the console:
F61A4956-83C9-410A-B1BC-4DBF8F6679AA.jpeg
 
Last edited:

Nolonar

Veteran
Veteran
Joined
Feb 18, 2018
Messages
263
Reaction score
367
First Language
French, German
Primarily Uses
RMMZ
A few suggestions (remember: these are just suggestions; feel free to completely disregard them, especially if you feel confused):

Avoid declaring variables without var, let, or const. For example:
JavaScript:
const grid = {};
const tWall = "X";
const tFloor = "O";

Without these keywords, you are creating a global variable. Global variables are convenient, but they can also cause all sorts of bugs that are extremely difficult to fix, if you don't use them properly. You should never use global variables without a good reason.

The const keyword also serves as a kind of sanity check, as it throws an error if you try to change its value (because const denotes a constant, which should never change). Remember: errors help you find bugs! Avoiding or ignoring errors does not get rid of bugs; it only makes them much harder to find and fix.

Also, const prevents you from changing the value of the object, but it doesn't stop you from changing the value of its properties. So grid.p = [] is still possible, even if grid is a const.



Instead of:
JavaScript:
grid = {};
grid.size = 14;
grid.p = [];

You can do:
JavaScript:
const grid = {
    size: 14,
    p: []
};

This makes it more obvious that grid is supposed to have size and p. If you use an IDE (which I'd recommend, as it makes coding that much easier), this'll help your IDE tell you what properties your grid object has, and what type you should expect from these properties (like size: Number and p: Array).

Also, don't be afraid of using default values when you don't know the actual value yet. For example, if you don't know yet that size: 14, don't be afraid to initialize it with size: 0 and later set it with grid.size = 14, so your IDE at least knows that grid.size is a Number.



Instead of for (x = 0; /* bla */), use for (var x = 0; /* bla */), or if you can, for (let x = 0; /* bla */).

I've already explained why global variables can be dangerous, so I'll use this opportunity to explain why you should always use let over var, if possible.

Originally, there was no let keyword. This means that your code may run on an old browser that doesn't know what to do with it (such as the nw.js engine that ships with RPG Maker MV before 1.5.0). If that's the case, you have no other choice but to use var instead.

The key difference between the two keywords is the scope, which defines where in your code you can use the variable. var has a function scope, while let has the much more intuitive block scope. Example (shamelessly ripped off from https://stackoverflow.com/a/11444416/1169228):
JavaScript:
var funcs = [];

// let's create 3 functions
for (var i = 0; i < 3; i++) {
  // and store them in funcs
  funcs[i] = function() {
    // each should log its value.
    console.log("My value: " + i);
  };
}

for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();
}

You expect to see values 1, 2, and 3, but instead all three values are 3. That's because var i is valid throughout the function in which the for() loop is. Thus, its value is 3 for each function, by the time you execute the three functions. With let i, the variable would instead be valid only within the for() loop, and its value would therefore be different for each function.



In fact, using let y would've caused an error, with which you'd have noticed my next piece of advice:

Your makeNoise() function is creating a grid that's larger than expected (notice how your grid has 15 rows and columns, even though you defined grid.size = 14). This is the correct code:
JavaScript:
function makeNoise(density) {
   for (let x = 0; x < grid.size; x++) {
      for (let y = 0; y < grid.size; y++) {
         const rand = Math.random()*100;
         if (density > rand) {
            grid.p[x + "," + y] = tWall;
         } else {
            grid.p[x + "," + y] = tFloor;
         }
      }
   }
}

The same applies to your drawGrid() function:
JavaScript:
function drawGrid() {
   let s = "";
   for (let x = 0; x < grid.size; x++) {
      for (let y = 0; y < grid.size; y++) {
         s += grid.p[x+"," + y];
      }
      s += "\n";
   }
   console.log("grid:\n" + s);
}



Instead of letting your makeNoise() function write in your grid object, you might want to have a createGrid() function instead, which creates an independent array and returns it, then set grid.p = createGrid() (or p: createGrid() if you want to initialize it directly). This reduces coupling in your code (see: https://en.wikipedia.org/wiki/Coupling_(computer_programming)). Passing the grid.size as argument to the createGrid() function also reduces coupling.

The reason for reducing coupling, is that you can now use createGrid() without affecting your grid object. In this specific case it's not really that important, but as your project grows larger and larger, you'll eventually want to reuse existing functions, or rename certain objects or properties. High coupling hurts maintainability. It's always a good idea to keep this in mind and strive for low coupling, even in small projects.

Here's an example:
JavaScript:
function createGrid(size, density) {
   const data = [];
   for (let x = 0; x < size; x++) {
      for (let y = 0; y < size; y++) {
         data[x + "," + y] = density > Math.random() * 100 ? tWall : tFloor;
      }
   }
   return data;
}

grid.p = createGrid(grid.size, 50);

Notice how I'm creating and returning an independent data array, and how I'm replacing grid.p with the data array from createGrid(). Also note how my createGrid() function can retrieve the size regardless of the grid object. This allows me to easily do stuff like this:
JavaScript:
const grid1 = {
    size: 10,
    p: createGrid(10, 50)
};

const grid2 = {
    size: 14,
    p: createGrid(14, 50)
};
if I need multiple grids of different size.

Of course, you can further reduce coupling by having the drawGrid() function expect a grid argument, instead of using the global grid object. This would allow you to draw any grid, not just the global grid object.



Now that I've given you all this advice, I'd like to comment on your trick to more easily understand multi-dimensional arrays.

First of all, what you have here is not a multi-dimensional array; it's a dictionary. Without going too much into detail regarding the differences between an array and a dictionary, a dictionary requires more memory and is slower. Modern computers have plenty of memory and are plenty fast, so unless you have a huge grid or need to access it all the time (e.g. it's the grid for your game), this isn't really much of a problem.

Technically, JavaScript doesn't have "multi-dimensional arrays", so the closest you'll get are "jagged arrays" (arrays within arrays). While it may seem confusing to access a grid with grid[y][x] instead of grid[x][y], it's really just a matter of getting used to it.

There are also a few advantages (beyond performance) to properly using jagged arrays:
JavaScript:
const map = [
    ["+", "-", "-", "+"],
    ["|", "O", "O", "|"],
    ["|", "O", "O", "|"],
    ["+", "-", "-", "+"]
];
This is pretty convenient when you want to create predefined maps (as opposed to procedurally generating them). This would be much more difficult to do with a dictionary:
JavaScript:
const map = {
    "0,0": "+", "0,1": "|", "0,2": "|", "0,3": "+",
    "1,0": "-", // etc.
};

Your drawGrid() function could also be shorter if you used jagged arrays (the next code assumes that grid is a jagged array, rather than an object with properties size and p):
JavaScript:
function drawGrid(grid) {
   let s = "";
   for (const y in grid) {
      for (const c of grid[y]) {
         s += c;
      }
      s += "\n";
   }
   console.log("grid:\n" + s);
}

And even shorter if you use the Array.join() function:
JavaScript:
function drawGrid(grid) {
   let s = "";
   for (const y in grid) {
      s += grid[y].join("") + "\n";
   }
   console.log("grid:\n" + s);
}

And even shorter with Array.map():
JavaScript:
function drawGrid(grid) {
   const s = grid.map(row => row.join("")).join("\n");
   console.log("grid:\n" + s);
}

Doing the same with a dictionary would be quite a bit harder.


If you need help understanding jagged arrays, consider my map object earlier:
JavaScript:
const map = [
    ["+", "-", "-", "+"],
    ["|", "O", "O", "|"],
    ["|", "O", "O", "|"],
    ["+", "-", "-", "+"]
];

As you can see, it's an "array of rows" (where each row is itself an array).
Therefore, if you want to access a specific field, you must first access the row itself. Hence why you need map[y][x] to access each field, since each row is on a different y-coordinate.
 
Last edited:

bishiba

Adept
Veteran
Joined
Apr 6, 2016
Messages
150
Reaction score
23
First Language
Swedish
Primarily Uses
N/A
A few suggestions (remember: these are just suggestions; feel free to completely disregard them, especially if you feel confused):

Avoid declaring variables without var, let, or const. For example:
JavaScript:
const grid = {};
const tWall = "X";
const tFloor = "O";

Without these keywords, you are creating a global variable. Global variables are convenient, but they can also cause all sorts of bugs that are extremely difficult to fix, if you don't use them properly. You should never use global variables without a good reason.

The const keyword also serves as a kind of sanity check, as it throws an error if you try to change its value (because const denotes a constant, which should never change). Remember: errors help you find bugs! Avoiding or ignoring errors does not get rid of bugs; it only makes them much harder to find and fix.

Also, const prevents you from changing the value of the object, but it doesn't stop you from changing the value of its properties. So grid.p = [] is still possible, even if grid is a const.



Instead of:
JavaScript:
grid = {};
grid.size = 14;
grid.p = [];

You can do:
JavaScript:
const grid = {
    size: 14,
    p: []
};

This makes it more obvious that grid is supposed to have size and p. If you use an IDE (which I'd recommend, as it makes coding that much easier), this'll help your IDE tell you what properties your grid object has, and what type you should expect from these properties (like size: Number and p: Array).

Also, don't be afraid of using default values when you don't know the actual value yet. For example, if you don't know yet that size: 14, don't be afraid to initialize it with size: 0 and later set it with grid.size = 14, so your IDE at least knows that grid.size is a Number.



Instead of for (x = 0; /* bla */), use for (var x = 0; /* bla */), or if you can, for (let x = 0; /* bla */).

I've already explained why global variables can be dangerous, so I'll use this opportunity to explain why you should always use let over var, if possible.

Originally, there was no let keyword. This means that your code may run on an old browser that doesn't know what to do with it (such as the nw.js engine that ships with RPG Maker MV before 1.5.0). If that's the case, you have no other choice but to use var instead.

The key difference between the two keywords is the scope, which defines where in your code you can use the variable. var has a function scope, while let has the much more intuitive block scope. Example (shamelessly ripped off from https://stackoverflow.com/a/11444416/1169228):
JavaScript:
var funcs = [];

// let's create 3 functions
for (var i = 0; i < 3; i++) {
  // and store them in funcs
  funcs[i] = function() {
    // each should log its value.
    console.log("My value: " + i);
  };
}

for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();
}

You expect to see values 1, 2, and 3, but instead all three values are 3. That's because var i is valid throughout the function in which the for() loop is. Thus, its value is 3 for each function, by the time you execute the three functions. With let i, the variable would instead be valid only within the for() loop, and its value would therefore be different for each function.



In fact, using let y would've caused an error, with which you'd have noticed my next piece of advice:

Your makeNoise() function is creating a grid that's larger than expected (notice how your grid has 15 rows and columns, even though you defined grid.size = 14). This is the correct code:
JavaScript:
function makeNoise(density) {
   for (let x = 0; x < grid.size; x++) {
      for (let y = 0; y < grid.size; y++) {
         const rand = Math.random()*100;
         if (density > rand) {
            grid.p[x + "," + y] = tWall;
         } else {
            grid.p[x + "," + y] = tFloor;
         }
      }
   }
}

The same applies to your drawGrid() function:
JavaScript:
function drawGrid() {
   let s = "";
   for (let x = 0; x < grid.size; x++) {
      for (let y = 0; y < grid.size; y++) {
         s += grid.p[x+"," + y];
      }
      s += "\n";
   }
   console.log("grid:\n" + s);
}



Instead of letting your makeNoise() function write in your grid object, you might want to have a createGrid() function instead, which creates an independent array and returns it, then set grid.p = createGrid() (or p: createGrid() if you want to initialize it directly). This reduces coupling in your code (see: https://en.wikipedia.org/wiki/Coupling_(computer_programming)). Passing the grid.size as argument to the createGrid() function also reduces coupling.

The reason for reducing coupling, is that you can now use createGrid() without affecting your grid object. In this specific case it's not really that important, but as your project grows larger and larger, you'll eventually want to reuse existing functions, or rename certain objects or properties. High coupling hurts maintainability. It's always a good idea to keep this in mind and strive for low coupling, even in small projects.

Here's an example:
JavaScript:
function createGrid(size, density) {
   const data = [];
   for (let x = 0; x < size; x++) {
      for (let y = 0; y < size; y++) {
         data[x + "," + y] = density > Math.random() * 100 ? tWall : tFloor;
      }
   }
   return data;
}

grid.p = createGrid(grid.size, 50);

Notice how I'm creating and returning an independent data array, and how I'm replacing grid.p with the data array from createGrid(). Also note how my createGrid() function can retrieve the size regardless of the grid object. This allows me to easily do stuff like this:
JavaScript:
const grid1 = {
    size: 10,
    p: createGrid(10, 50)
};

const grid2 = {
    size: 14,
    p: createGrid(14, 50)
};
if I need multiple grids of different size.

Of course, you can further reduce coupling by having the drawGrid() function expect a grid argument, instead of using the global grid object. This would allow you to draw any grid, not just the global grid object.



Now that I've given you all this advice, I'd like to comment on your trick to more easily understand multi-dimensional arrays.

First of all, what you have here is not a multi-dimensional array; it's a dictionary. Without going too much into detail regarding the differences between an array and a dictionary, a dictionary requires more memory and is slower. Modern computers have plenty of memory and are plenty fast, so unless you have a huge grid or need to access it all the time (e.g. it's the grid for your game), this isn't really much of a problem.

Technically, JavaScript doesn't have "multi-dimensional arrays", so the closest you'll get are "jagged arrays" (arrays within arrays). While it may seem confusing to access a grid with grid[y][x] instead of grid[x][y], it's really just a matter of getting used to it.

There are also a few advantages (beyond performance) to properly using jagged arrays:
JavaScript:
const map = [
    ["+", "-", "-", "+"],
    ["|", "O", "O", "|"],
    ["|", "O", "O", "|"],
    ["+", "-", "-", "+"]
];
This is pretty convenient when you want to create predefined maps (as opposed to procedurally generating them). This would be much more difficult to do with a dictionary:
JavaScript:
const map = {
    "0,0": "+", "0,1": "|", "0,2": "|", "0,3": "+",
    "1,0": "-", // etc.
};

Your drawGrid() function could also be shorter if you used jagged arrays (the next code assumes that grid is a jagged array, rather than an object with properties size and p):
JavaScript:
function drawGrid(grid) {
   let s = "";
   for (const y in grid) {
      for (const c of grid[y]) {
         s += c;
      }
      s += "\n";
   }
   console.log("grid:\n" + s);
}

And even shorter if you use the Array.join() function:
JavaScript:
function drawGrid(grid) {
   let s = "";
   for (const y in grid) {
      s += grid[y].join("") + "\n";
   }
   console.log("grid:\n" + s);
}

And even shorter with Array.map():
JavaScript:
function drawGrid(grid) {
   const s = grid.map(row => row.join("")).join("\n");
   console.log("grid:\n" + s);
}

Doing the same with a dictionary would be quite a bit harder.


If you need help understanding jagged arrays, consider my map object earlier:
JavaScript:
const map = [
    ["+", "-", "-", "+"],
    ["|", "O", "O", "|"],
    ["|", "O", "O", "|"],
    ["+", "-", "-", "+"]
];

As you can see, it's an "array of rows" (where each row is itself an array).
Therefore, if you want to access a specific field, you must first access the row itself. Hence why you need map[y][x] to access each field, since each row is on a different y-coordinate.
Oh wow, thanks a lot for so much of this. My pride forced me to inform you that I knew some of this, like say how to declare variables, also that let and I think also const are more recent. A big reason for the lackluster function is that it is written in my phone lol...

But so much of what you wrote is both clear and amazing that you'd spend so much time on it. I just wanted to give a tiny advice for multidim arrays and I get a nice lesson :) Pretty happy I included the functions at this point :p

I haven't yet read everything, on my way out, but this is something I will go over in detail. Even the variables part I can learn more from, but... Doesn't the for function automatically set the declared variable as function specific? It does in java, which is where I am having some formal education. But there are quite a few differences...
I had issues with specifically creating global variables so I did start doing it, but I think I forgot it since again, it's written on the phone :)

And thanks for showing the problem with the makeNoise() function. I would've gotten it eventually, I mean I have learnt what I know primarily by backtracking the code available. But I'm not scared to ask for help, and I really appreciate that you showed the error in the code. Now it is quite obvious. For some reason I imagined that the loop would do the x row and then add the y value. Thinking about it, it obviously just adds one more value to the same row :)

But, I shall read more about this shortly as I've been out today. Will respond again later :)

Could you perhaps look into my other post as well? I've solved it another way but I want to know if it is possible the way I thought it worked?

https://forums.rpgmakerweb.com/index.php?threads/adding-properties-to-all-existing-and-potential-elements-in-array-using-prototype-or-similar-adding-objects-to-array.138859/
 

Nolonar

Veteran
Veteran
Joined
Feb 18, 2018
Messages
263
Reaction score
367
First Language
French, German
Primarily Uses
RMMZ
Doesn't the for function automatically set the declared variable as function specific? It does in java, which is where I am having some formal education. But there are quite a few differences...
Unfortunately not. At least not without putting var in front of the variable name.

You can easily check this yourself: In JavaScript all global variables are stored in the window object. So when you create a global variable, for example with testvar = 42, you'll see that window.testVar is also 42. And when you do for (x = 0; x < 100; x++) {} then window.x will also be 100 once the for loop has ended.

Could you perhaps look into my other post as well? I've solved it another way but I want to know if it is possible the way I thought it worked?
I had a look at it, though I'm not entirely sure I understand how you intended to use the code, so I'm not sure if my answer is helpful to you.
 

Latest Threads

Latest Posts

Latest Profile Posts

It makes sense that you try to focus on the squishy mages at the back of the party first, right? If the mage can out-DPS the enemy DPS before the latter can kill the tank in front, the fight becomes boring.
finally got footage of my main character running around the main hub world. I've fixed a few things tho even since this video unsurprisingly lol.
 …I think I might need to switch from standard ATK/DEF up/down buffs to parameter altering states. I just had an actor whose usual attack against an enemy does ~25 do a buffed attack against a debuffed enemy for ~600…
just saw that titan quest is free on steam until the 23rd

Forum statistics

Threads
115,227
Messages
1,088,238
Members
149,821
Latest member
thevoidyvoid
Top