Referencing a previous index in a 2D generated array - Javascript - javascript

I am trying to build an application that generates a random map using square tiles. To do this my plan is to use a 2D array that randomly selects the first tile in a row then, when randomly selecting the second tile, it references the right edge value of the previous tile and filters the possible random tiles it can pick from down to an array of only those that match. This process repeats till the row is complete, then when starting a subsequent row the filter will apply to both the right edge of the previous tile and the bottom edge of the one above.
I have gotten to the point where I can generate an array filled with random tiles but I can't get the filtering process to work. I have tried using a .filter in the random selection function but when it tries to reference the previous index that index is undefined.
The tiles are an array of objects like this:
1 means a connected edge, 0 means no connection. Essentially the tiles just have to match 1's with 1's and 0's with 0's.
[
{
"top": 1,
"right": 0,
"bottom": 1,
"left": 0,
"img": "../public/images/1.png"
},
{
"top": 0,
"right": 1,
"bottom": 0,
"left": 1,
"img": "../public/images/2.png"
}
]
My current code looks like this:
// Setting the number of row and columns and creating the empty array
const rows = 5;
const columns = 5
const map = [];
// Populate the empty array with a 2D array of random tile objects.
function generateMap() {
for (let y = 0; y < rows; y++) {
map[y] = [];
for (let x = 0; x < columns; x++) {
map[y][x] = randomlyPopulateMap(x, y);
}
}
}
function randomlyPopulateMap(x, y) {
const randNum = Math.floor(Math.random() * tiles.length);
const randTile = tiles[randNum];
return randTile;
}
I've tried what feels like a dozen solutions but I'm still new to coding and I'm uncertain if I'm even taking the right approach to this problem. Any guidance is greatly appreciated.

I can imagine randomlyPopulateMap first filtering the tiles based on whether they match left-to-right with the tile to the left and top-to-bottom with the tile above. I'm using the condition that if there is no tile above to restrict then any tile will do. You can decide if the boundary itself imposes any additional restrictions.
function randomlyPopulateMap(x, y) {
const candidates = tiles.filter(tile =>
(x == 0 || tile.left == map[y][x-1].right) &&
(y == 0 || tile.top == map[y-1][x].bottom))
and then choose a random tile from among the remaining candidates.
const randNum = Math.floor(Math.random() * candidates.length);
const randTile = candidates[randNum];
return randTile;
}

Related

Javascript double nested for loop traversing

I am fairly new to JavaScript and I am trying to find a way to write this without using a double for loop by either using higher order function or data structures.
I have these two defined type here
List = {
'start' ? : Position
'end' ? : Position
}
Position = {
row?: number;
column?: number;
}
This is the for loop that I believe can be optimized or avoid using for loop for.
for (let row = list.start.row; row <= list.end.row; row++) {
for (let col = list.start.column; col <= list.end.column; col++) {
console.log(`row: ${row}, col: ${col}`);
}
}
So if the I have the list defined as
list.start.row = 2, list.start.column = 4
list.end.row = 3, list.start.column = 6
I should have (2,4) (2,5) (2,6) (3,4) (3,5) (3,6) output as the console messages and order does not matter. What would be the most optimal way of doing this? Thank you in advance!
You can absolutely do this with a single loop. In fact its a very common way to navigate multi dimensional arrays in memory. For example, you have a chunk of raw image data, and you want to get the pixel information at point(80,120). You'd have to be able to convert the (x,y) coordinates to some offset. This is trivial if you know the width of a row. To get the pixel information, assuming each pixel is 8 bits simply calculate i = y * width + x. You can use this same technique to create a multi dimensional array using one loop. You'd have to apply the above formula in reverse to find the (x,y) points from (i, width). Computing the width is easy, its the end.column - start.column and calculating the number of elements would be the height * width. The height is obviously the difference in rows. You would then loop through your entire linear domain and project the i back to (x,y) in your array.
It might be easier to just look at the sample code.
const list = {
start: { row: 2, column: 4 },
end: { row: 3, column: 6 }
};
const x0 = list.start.column;
const y0 = list.start.row;
const x1 = list.end.column;
const y1 = list.end.row;
const width = x1 - x0 + 1; // range is inclusive so [2,2] has a width of 1.
const height = y1 - y0 + 1; // same here.
const results = Array.from({ length: width * height }, (_, n) => {
const row = (n - n % width) / width + y0;
const column = n % width + x0;
return [row, column];
});
console.log(results);
Should you do it instead of a nested loop? Probably not in javascript. Complexity is still O(mn) and so its no faster algorithmically then a nested loop. The compiler/vm will likely optimize a [x,y] nested loop into something like this for you anyway.
It always will be a nested loop if you want to print all values of x for all values of y. You can use different syntax, such as forEach, but it'll always be the same concept - and the solution you have is actually the most optimal and efficient (as there'll always be xy combinations for x and y values; so if your time complexity is O(mn), it's fine).

Is there a way to see if a value in a 2D array matches any value in another 2D array?

I'm building a battleship game in Javascript and React and I've been stuck on this issue for a while now even after much Googling and StackOverflowing.
Basically my board is a 2D array, with 10 arrays inside of one array. I'm trying to randomly place ships and I'm having difficulties checking if a ship intersects another ship.
Here's what I have for my ships:
placeShips = () => {
// Logic to place boats randomly below
// Checks required before placing a boat:
// 1. Does the boat go off the board
// 2. Does the boat overlap another boat
// 3. If checks above pass then place boat
let placedPosition = []
let board = this.state.board.slice()
let i
for (i = 0; i < this.state.ships.length; i++) {
// First randomly select coordinates for where the boat will start
let xcoord = Math.floor(Math.random() * 10)
let ycoord = Math.floor(Math.random() * 10)
// Get positions in array where a boat will be
let potentialBoat = []
let newCoords
let j
for (j = 0; j < this.state.ships[i].getLength(); j++) {
newCoords = [xcoord, ycoord + j]
potentialBoat.push(newCoords)
The first for loop repeats for each ship left in my state to place and the second for loop takes a ship's length, gets the intended coordinate ([[0, 1], [0,2]] for a 2 length ship for example) and stores it in the potentialBoat array.
My idea is to use this potentialBoat array and see if there's any [xcoordinate, ycoordinate] that exists already in the placedPosition array and if so, to loop again for the current boat and get new coordinates until they don't intersect.
Is this possible? Or should I rethink my entire implementation? Thanks!
Inside the inner loop, when in the process of creating a ship, consider creating a string representing the coordinates. Eg, for newCoords of 1, 3, create a string 1_3. To validate the location, check to see if that string exists in an array (or Set) of the locations of the validated ships. At the end of the inner loop, once all positions for the length of the ship have been validated, combine the possible-locations into the validated-locations array:
placeShips = () => {
const placedPosition = [];
const board = this.state.board.slice();
const validatedPositionStrings = []; // <---- Create this array
for (const ship of this.state.ships) {
const thisShipLength = ship.getLength();
tryShip:
while (true) {
const thisBoatPossiblePositionStrings = [];
// Generate ship positions until valid
const xcoord = Math.floor(Math.random() * 10);
const ycoord = Math.floor(Math.random() * 10);
const potentialBoat = [];
for (let j = 0; j < thisShipLength; j++) {
// Then check to see if the below position is already in it
const thisCoordinateString = `${x}_${y}`;
if (validatedPositionStrings.includes(thisCoordinateString)) {
// Invalid
continue tryShip;
}
thisBoatPossiblePositionStrings.push(thisCoordinateString);
// If this point is reached, then this particular coordinate is valid
// do whatever you need to do:
const newCoords = [xcoord, ycoord + j];
potentialBoat.push(newCoords);
}
// All positions for ship are valid
// do something with potentialBoat here?
// push positions to placedPosition?
validatedPositionStrings.push(...thisBoatPossiblePositionStrings);
break;
}
}
}
It could be made less computationally complex by using a Set instead of an array, but that probably doesn't matter unless there are a very large number of iterations.
It would also be possible to search your array of arrays to see if the position has already been placed, but that would require an unnecessary amount of code IMO.
If possible, you might consider changing your data structure around so that rather than an array of arrays, you have just a single object representing the coordinates, whose values indicate the ship at that position (and possibly other attributes needed for a particular point), eg:
{
1_3: { ship: 'destroyer', 'attackedYet': 'false' }
// ...
Such an object would probably be easier to look up and work through than an array of arrays of X-Y pairs.

Iterate over 2D array of booleans and return an incremented value based on true/false

I'm trying to return a "matrix" or a 2D array where the boolean value turns into a number from 1-4 based on how many "true" values are next to it. I tried a different approach previously, represented by the current code below.
The Problem:
When matrix = [[true, false, false], [false, true, false],[false, false, false]]
the output should be [[1, 2, 1],[2, 1, 1],[1, 1, 1]]
My Code:
function minesweeper(matrix) {
for( var i =0; i < matrix.length; i++){
for(var j = 0; j < matrix.length; j++){
if(matrix[i] && matrix[i][j] == true){
matrix[i][j] = 2;
}else {
matrix[i][j] = 1;
}
}
}
return matrix;
}
My Errors/results:
Input matrix: [[true,false,false],[false,true,false],[false,false,false]]
Output: [[2,1,1], [1,2,1], [1,1,1]]
Expected Output: [[1,2,1], [2,1,1], [1,1,1]]
Input matrix: [[false,false,false], [false,false,false]]
Output:[[1,1,1], [1,1,1]]
Expected Output:[[0,0,0], [0,0,0]]
Input matrix: [[true,false,false,true], [false,false,true,false], [true,true,false,true]]
Output:[[2,1,1,2], [1,1,2,1], [2,2,1,2]]
Expected Output:[[0,2,2,1], [3,4,3,3], [1,2,3,1]]
Update: It wasn't clear from your question whether you wanted to check 4 directions (e.g. north, west, south and east) or 8 directions (north, northwest, west, southwest, south, southeast, east and northeast). My initial answer was for 4 directions. However, I know see from your expected results that you likely wanted 8 directions, so I have re-written my answer for that scenario.
There is a problem with the way you have asked the question. You talk about changing the original matrix, rather than, e.g., returning a new matrix with the results. If you actual change the matrix while you are still processing it, then you will probably end up changing some values before you've actually analyzed them. For instance, if you analyze the top left cell, find out that it's true, and then increment the cell to the right in the same original table, then the 2nd cell will no longer be the true or false value that it originally had, but rather will now be whatever you assigned to that cell (??? false plus 1 ??? or whatever). Thus, you really should leave your original matrix untouched and return a new table with the addition results from your analysis. (This touches on the issue of data immutability, but that's a discussion for another day.)
In any case, one approach to solving this is start with a results table the same size as the original matrix table, but with all values initially set to zero. Then you can iterate through all cells in the input table, adding 1 to positions in the results table that are to the right of, below, to the left of, and above the initial corresponding cell in the input table. However, you have to make sure that the results table position you're trying to add one to is actually in the table, i.e. not off the edge (e.g. not above or to the left of the upper left cell).
function minesweeper(matrix) {
const numRows = matrix.length, numCols = matrix[0].length; // determine matrix size
const dirs = [[1,0],[1,1],[0,1],[-1,1],[-1,0],[-1,-1],[0,-1],[1,-1]];
// coordinate changes for all 8 directions
const results = matrix.map(row => row.map(cell => 0)); // initiate results table with 0s
matrix.forEach((rowOfCells, matrixRowNum) => { // for each row
rowOfCells.forEach((cell, matrixColNum) => { // for cell in each row
if (cell) { // if that cell contains a true value
dirs.forEach(dir => { // iterate through all dir'ns
const resultsRowNum = matrixRowNum + dir[0]; // vertical position in results table
const resultsColNum = matrixColNum + dir[1]; // horizontal position in results table
if (
resultsRowNum >= 0 &&
resultsRowNum < numRows &&
resultsColNum >= 0 &&
resultsColNum < numCols
) { // if this is a valid position in the results table, i.e. not off the edge
results[resultsRowNum][resultsColNum] += 1; // then increment the value found there
}
});
}
});
});
return results;
}
let matrix;
matrix = [[true,false,false],[false,true,false],[false,false,false]];
console.log(JSON.stringify(matrix));
console.log(JSON.stringify(minesweeper(matrix)));
console.log('');
matrix = [[false,false,false], [false,false,false]];
console.log(JSON.stringify(matrix));
console.log(JSON.stringify(minesweeper(matrix)));
console.log('');
matrix = [[true,false,false,true], [false,false,true,false], [true,true,false,true]];
console.log(JSON.stringify(matrix));
console.log(JSON.stringify(minesweeper(matrix)));
function minesweeper(matrix) {
var solution=[];
for( var i =0; i < matrix.length; i++){
var inner=[];
solution.push(inner);
for(var j = 0; j < matrix[i].length; j++){
var count=0;
if(matrix[i] && matrix[i][j]) count++;//at this position
if(matrix[i] && matrix[i][j-1]) count++;//one left
if(matrix[i] && matrix[i][j+1]) count++;//one right
if(matrix[i-1] && matrix[i-1][j]) count++;//one above
if(matrix[i+1] && matrix[i+1][j]) count++;//one below
inner.push(count);
}
}
return solution;
}
You need to create another array to resolve to your values.
http://jsbin.com/siquxetuho/edit?console

Make sure that my for loop generates exactly 8 unique number pairs

It's a minesweeper game. The objective here is to generate the exact number of mines. In this case I have an 8x8 grid of boxes with 10 mines (see at the bottom). So, the nested for loop generates 64 objects with x and y coordinates (for later drawing the grid) and a state property to indicate whether the field is mined. Then, in generateBombs I generate 10 objects with state:mined with random x and y to overwrite 8 of the 64 objects in the boxes array randomly and thus plant the mines. The problem with my approach here is that there is a possibility of 2 non-unique pairs of x and y objects to be generated, and this way I'll end up with less than the original number of mines, because the same object will be overwritten twice. What is a good approach here?
Also, one of my requirements is for the generator to use a helper function for the mines, but they take the same arguments, the need might be defeated.
var minesweeper = {
boxes: [],
//rows
boxesNum: 0,
bombsNum: 0,
//creates a 8x8 grid
generateMap: function (width, height, bombsNum) {
for (i = 1; i < height; i++) {
this.boxes.push({
x: i,
y: 1,
state: "safe"
});
for (j = 1; j < width; j++) {
this.boxes.push({
x: 1,
y: j,
state: "safe"
});
}
}
this.generateBombs(width, height, bombsNum)
},
//mines random fields from the grid
generateBombs: function (width, height, bombsNum) {
for (k = 0; k < bombsNum; k++) {
this.boxes.push({
x: Math.floor(Math.random() * width + 1),
y: Math.floor(Math.random() * height + 1),
state: "mined"
});
}
}
}
minesweeper.generateMap(8, 8, 10);
You'd be better off working on the array of boxes itself, rather than generating bombs first.
generateBombs: function (width, height, bombsNum) {
var bombCount = 0; // Count how many bombs we planted,
while(bombCount < 10){ // Loop until we have 10 bombs,
var index = parseInt(Math.random() * this.boxes.length + 1); // Get a random box id,
if(this.boxes[index].state === "safe"){ // If the box is safe, plant a bomb.
this.boxes[index].state = "mined";
bombCount++; // Increase bomb count with 1.
}
}
}
This method will guarantee that you have 10 bombs planted at 10 different locations. It could select the same box twice, but if it does so, it just tries again.
In the best case scenario, you have 10 iterations of the loop, compared to other methods that require arrays of coordinates to be checked for every bomb you want to generate.
However, there's one problem with this method of randomly picking a bomb position:
If you increase the bomb count, the method will hit more and more boxes where bombs already have been planted, resulting in an exponential increase of iterations required to plant as many bombs as you want. Basically, the denser the field is, the more likely the function is to randomly select a cell that already has a bomb, so it'd have to try again.
I don't expect this to be noticeable at bomb counts of, say, 50% or lower, though.
Your generateMap function is also broken. Try this instead:
generateMap: function (width, height, bombsNum) {
for (var i = 0; i < width; i++) {
for (var j = 0; j < height; j++) {
this.boxes.push({
x: (i + 1),
y: (j + 1),
state: "safe"
});
}
}
this.generateBombs(width, height, bombsNum)
},

How to find selected elements within a javascript marquee selection box without using a loop?

I am writing my own drag and drop file manager. This includes a javascript marquee selection box which when active calculates the elements (files) that are intersected and selects them by adding a class to them.
I currently perform the check during a mousemove handler, loop through an array of element coordinates and determine which ones are intersected by the drag and drop selection box.
The function currently looks like this:
selectItems : function(voidindex){
var self = this;
var coords = self.cache.selectioncoords;
for(var i=0, len = self.cache.items.length; i<len; i++){
var item = self.cache.items[i];
var itemcoords = item.box_pos;
if(coords.topleft.x < (itemcoords.x+201) && coords.topright.x > itemcoords.x && coords.topleft.y < (itemcoords.y+221) && coords.bottomleft.y > itemcoords.y){
if(!item.selected){
item.selected = true;
item.html.addClass('selected').removeClass('activebutton');
self.cache.selecteditems.push(i);
self.setInfo();
}
}
else{
if(item.selected){
item.selected = false;
if(!voidindex || voidindex !== i){
item.html.removeClass('selected');
}
var removeindex = self.cache.selecteditems.indexOf(i);
self.cache.selecteditems.splice(removeindex, 1);
self.setInfo();
}
}
}
},
There is lots of dirty logic in the code above which ensures that the DOM is only manipulated when the selection changes. This is not relevant to the question and can be exluded. The important part is the intersection logic which checks the coordinates of the element versus the coordinates of the marquee selection box.
Also please note that the item dimensions are fixed at 201px width by 221px height.
I have tested this and all works perfectly, however I have the need to support potentially thousands of files which would mean that at some point we will start seeing UI performance decrease.
I would like to know if there is anyway to perform intersection detection without looping through the coordinates of each element.
The coordinates of the marquee box are defined as follows at any given time:
selectioncoords : {
topleft : {
x : 0,
y : 0
},
topright : {
x : 0,
y : 0
},
bottomleft : {
x : 0,
y : 0
},
bottomright : {
x : 0,
y : 0
},
width : 0,
height : 0
}
And the coordinates of each item, stored in the self.cache.items array are defined as follows:
item : {
box_pos : {
x : 0,
y : 0
},
grid_pos : {
row : 1,
column : 1
}
}
So the information available will always be the actual grid position (row/column) as well as the physical item position (left and top offsets in pixels within the grid).
So to summarize, the question is, is there anyway to detect item intersection from a set of marquee selection box coordinates as defined above without looping through the whole array of item coordinates every time the mousemove event fires?
Thanks in advance for any help.
The following depends upon a locked grid with the dimensions as described.
You are comparing a mouse-defined rectangle against a grid with static edge sizes. Thus, given an x coordinate or a y coordinate, you should be able to derive pretty easily which column or row (respectively) the coordinate falls into.
When the user starts the select box, grab that x and y, and find the row/column of the start. When the mouse moves while pulling the select box, you find (and then update) the row/column of the finish. anything that is both within the rows defined by that box and within the columns defined by that box (inclusive) is selected. If you then keep your selectable elements in a two-dimensional array according to rows and columns, you should be able to just grab the ones you want that way.
Mind, how much more (or less) efficient this is depends on the size of your expected selection boxes as compared to the total size, and the degree to which you expect the grid to be populated. Certainly, if the average use case is selecting half or so of the objects at a time, there's not a whole lot you can do to cut down efficiently on the number of objects you have to look at each time.
Also, though it is kludgy, you can have the mousemove handler not fire every time. Letting it pause a bit between updates will reduce the responsiveness of this particular function a fair bit, but it'll cut down significantly on the amount of resources that are used.
There are several ways you could approach this. Here's one. First you need the items in some kind of organized structure that you can look up quickly by row and column. You could use a two-dimensional array, or for simplicity I'm going to use a hash table. You could do this at the same time that you create the self.cache.items, or later, something like this:
var cacheLookup = {};
function initCacheLookup() {
var items = self.cache.items;
for( var i = 0, n = items.length; i < n; i++ ) {
var item = items[i];
var key = [ item.grid_pos.row, item.grid_pos.column ].join(',');
cacheLookup[key] = item;
}
}
Then when you want to get the items intersecting the rectangle, you could do something like this:
var itemWidth = 201, itemHeight = 221;
var tl = selectioncoords.topleft, br = selectioncoords.bottomright;
var left = Math.floor( tl.x / itemWidth ) + 1;
var right = Math.floor( br.x / itemWidth ) + 1;
var top = Math.floor( tl.y / itemHeight ) + 1;
var bottom = Math.floor( br.y / itemHeight ) + 1;
var selecteditems = [];
for( var row = top; row <= bottom; row++ ) {
for( var col = left; col <= right; col++ ) {
var key = [ row, col ].join(',');
var item = cacheLookup[key];
if( item ) {
selecteditems.push( item );
}
}
}
// Now selecteditems has the items intersecting the rectangle
There's probably an off-by-one error or two here, but this should be close.
Well, as I said, that is one way to do it. And it has the possibly interesting property that it doesn't depend on the order of items in the self.cache.items array. But that cacheLookup hash table smells like it might not be the most efficient solution.
Let me take a guess: isn't that array already in the correct order by rows and columns (or vice versa)? For example, if your grid is four wide, then the top row would be array elements 0-3, the second row 4-7, the third row 8-11, etc. Or it could be a similar arrangement going down the columns.
Assuming it's in row-by-row order, then you don't need the hash table at all. That initCacheLookup() function goes away, and instead the search code looks like this:
var nCols = 4/*whatever*/; // defined somewhere else
var itemWidth = 201, itemHeight = 221;
var tl = selectioncoords.topleft, br = selectioncoords.bottomright;
var left = Math.floor( tl.x / itemWidth );
var right = Math.floor( br.x / itemWidth );
var top = Math.floor( tl.y / itemHeight ) * nCols;
var bottom = Math.floor( br.y / itemHeight ) * nCols;
var items = self.cache.items;
var selecteditems = [];
for( var iRow = top; iRow <= bottom; iRow += nCols ) {
for( var col = left; col <= right; col++ ) {
var index = iRow + col;
if( index < items.length ) {
selecteditems.push( items[index] );
}
}
}
// Now selecteditems has the items intersecting the rectangle
This code will be a little faster, and it's simpler too. Also it doesn't depend at all on the item.box_pos and item.grid_pos. You may not need those data fields at all, because they are easily calculated from the item index, grid column count, and item height and width.
Some related notes:
Don't hard code 201 and 221 in the code. Store those in variables once, only, and then use those variables when you need the item height and width.
There is a lot of duplication in your data structures. I recommend that you ruthlessly eliminate all duplicated data unless there is a specific need for it. Specifically:
selectioncoords: {
topleft: {
x: 0,
y: 0
},
topright: {
x: 0,
y: 0
},
bottomleft: {
x: 0,
y: 0
},
bottomright: {
x: 0,
y: 0
},
width: 0,
height: 0
}
More than half the data here is duplicated or can be calculated. This is all you need:
selectioncoords: {
left: 0,
right: 0,
top: 0,
bottom: 0
}
The reason I bring this up is that was a bit confusing when working on the code: "I want the left edge. Do I get that from topleft.x or bottomleft.x? Are they really the same like they seem? How do I pick?"
Also, as mentioned above, the item.box_pos and item.grid_pos may not be needed at all if the items are stored in a sequential array. If they are needed, you could store just one and calculate the other from it, since there's a direct relationship between the two:
box_pos.x === ( grid_pos.column - 1 ) * itemWidth
box_pos.y === ( grid_pos.row - 1 ) * itemHeight
You can limit the scope of your checks by indexing each item in a grid, as often as necessary and no more often. You can use the grid to give you a list of elements near an X, Y coordinate or that might be in an X1, Y2, X1, Y2 range.
To get you started ...
var Grid = function(pixelWidth, pixelHeight, boxSize) {
this.cellsIn = function(x1, y1, x2, y2) {
var rv = [];
for (var x = x1; x < x2; x += boxSize) {
for (var y = y1; y < y2; y += boxSize) {
var gx = Math.ceil(x/boxSize);
var gy = Math.ceil(y/boxSize);
rv.push(this.cells[gx][gy]);
}
}
return rv;
} // cellsIn()
this.add = function(x1, y1, x2, y2, o) {
var cells = this.cellsIn(x1, y1, x2, y2);
for (var i in cells) {
cells[i].push(o);
}
} // add()
this.get = function(x1, y1, x2, y2) {
var rv = [];
var rv_index = {};
var cells = this.cellsIn(x1, y1, x2, y2);
for (var i in cells) {
var cell = cells[i];
for (var oi in cell) {
if (!rv_index[cell[oi]]) {
rv_index[cell[oi]] = 1;
rv.push(cell[oi]);
}
}
}
return rv;
} // get()
this.cells = [];
for (var x = 0; x < Math.ceil(pixelWidth/boxSize); x++) {
this.cells[x] = [];
for (var y = 0; y < Math.ceil(pixelHeight/boxSize); y++) {
this.cells[x][y] = [];
}
}
};
So, rather than iterating through all possible objects, whatever they may be, you iterate over all the objects that are near or potentially in the given coordinates.
This requires that you maintain/re-index the grid as item coordinates change. And you'll likely want to add some functionality to the above (or similar) Grid class to modify/move existing objects. But, to the best of my knowledge, an index of this sort is the best, if not only, way to index objects "in space."
Disclaimer: The code above isn't tested. But, I have similar code that is. See the DemoGrid function class here: http://www.thepointless.com/js/ascii_monsters.js
The functionality of my DemoGrid is similar (as far as I remember, it's been awhile), but accepts x, y, radius as parameters instead. Also notable, my mouse events don't touch the grid every time the event fires. Checks are rate-limited by a game/main loop.
If the system is set up such that
self.cache.items is ordered from left to right and top to bottom
(0,0),(1,0),(2,0),(0,1),(1,1),(1,2),(0,2),(1,2),(2,2)
There is an item in each space
GOOD - (0,0),(1,0),(2,0),(0,1),(1,1),(1,2),(0,2),(1,2),(2,2)
BAD - (0,0),(2,0)(1,2),(1,3),(2,1),(2,3)
We need to know the total number of columns.
So the code to get you started.
// Some 'constants' we'll need.
number_of_columns = 4;
item_width = 201;
item_height = 221;
// First off, we are dealing with a grid system,
// so that means that if given the starting x and y of the marquee,
// we can determine which element in the cache to start where we begin.
top_left_selected_index = Math.floor(selectioncoords.topleft.x / item_width) + (Math.floor(selectioncoords.topright.y / item_height) * number_of_columns );
// Now, because the array is in order, and there are no empty cache points,
// we know that the lower bound of the selected items is `top_left_selected_index`
// so all we have to do is walk the array to grab the other selected.
number_columns_selected = (selectioncoords.bottomright.x - selectioncoords.topleft.x) / item_width;
// if it it doesn't divide exactly it means there is an extra column selected
if((selectioncoords.bottomright.x - selectioncoords.topleft.x) % item_width > 0){
number_columns_selected += 1;
}
// if it it doesn't divide exactly it means there is an extra column selected
number_rows_selected = (selectioncoords.bottomright.y - selectioncoords.topleft.y) / item_height;
if((selectioncoords.bottomright.y - selectioncoords.topleft.y) % item_height > 0){
number_rows_selected += 1;
}
// Outer loop handles the moving the pointer in terms of the row, so it
// increments by the number of columns.
// EX: Given my simple example array, To get from (1,0) to (1,1)
// requires an index increase of 3
for(i=0; i < number_rows_selected; i++){
// Inner loop marches through the the columns, so it is just one at a time.
// Added j < number_of_columns in case your marquee stretches well past your content
for(j=0; j < number_columns_selected && j < number_of_columns; j++){
// Do stuff to the selected items.
self.cache.items[top_left_selected_index + (i * number_of_columns) + j];
}
}

Categories

Resources