How to represent entities in a grid - javascript

I'm attempting to simulate a bunch of simple entities moving around on a grid.
Individual entities will have specific properties and the grid will be a two dimensional array of square cells that may at any time contain anywhere from zero to multiple entities.
What type of data structures would be appropriate here given that I don't want to over engineer things but at the same time see problems with the obvious simple solutions?
If I have an array of entities where each entity has it's coordinates, then this is fine for populating the grid each step. But this makes it so if I want to know something about a given location, then I have to loop through the entire entity array to see if there's an entity at that location. (or adjacent to it if I needed to know something like that)
And the opposite (having the grid array keeping track of everything) runs into the reverse problem where if I want to know something specific about an entity (like it's name or fitness) I need to search through the whole grid array to find the entity I'm looking for.
And finally there's my third approach which seems over engineered: Have an array of all the entities and the grid array be a bunch of arrays of pointers to whatever entities are in that cell. In this third one I think the cells would need to know which entities were in them and the entities would need to know which cell they were in. And I'm not even sure if I want entities to know their coordinates. It might make more sense for them to only recognize their surroundings and then move, relative to their own location. Also, I'm doing this in javascript, so pointers are probably only logically possible with some hack and I'm trying to not over engineer things.

Are these objects, grid and entity, in a system where they each only have relevance when connected to the other? What I mean is will your entity objects only ever be used in this grid? And will this grid object only ever hold entity objects? If so, then you can link them together not just from the grid's perspective, but also from the entity.
I assume that your grid is made up of a main grid object, which likely consists of a collection of rows, and each row has a collection of cells. Each cell of course being equivalent one [x][y] location in an array. Each of these cells will have a collection of entity objects.
If you create your objects to be "hierarchically referential" it will be easy to move through the list. The grid may have a collection of rows, but each row should have a parent property that references to the grid it's apart of. The rows may have a collection of cells, but each cell should have a parent property that refers to its parent row. And each entity object should have a cell property that refers to the cell of which it is apart.
When you create your move() function for the entities, it will need to 1) remove itself from its current cell entity collection, 2) add itself to the new cell collection, and 3) update the entity.cell property to refer to the new cell.
This is an untested mock-up of the kind of structures I'm talking about:
function grid(height, width) {
this.rows = [];
this.cellAt = function(row, cell) {
return this.rows[row].cells[cell];
};
this.entitiesAt = function(row, cell) {
return this.cellAt(row, cell).entities;
};
this.addRow = function() {
var r = new row(this);
this.rows.push(r);
return r;
};
//create the grid in the default size.
if (height != null && width != null) {
var i, j, r;
for (i = 0; i <= height; i++) {
r = this.addRow();
for (j = 0; j <= width; j++)
r.addCell();
}
}
}
function row(parentGrid) {
this.grid = parentGrid;
this.cells = [];
this.getIndex = function() {
return this.grid.rows.indexOf(this);
};
this.addCell = function() {
var c = new cell(this);
this.cells.push(c);
return c;
};
}
function cell(parentRow) {
this.row = parentRow;
this.entities = [];
this.getIndex = function() {
return this.row.cells.indexOf(this);
};
this.addEntity = function(entity) {
this.entities.push(entity);
entity.cell = this;
};
this.removeEntity = function(entity) {
var i = this.entities.indexOf(entity);
if (i >= 0) this.entities.splice(i, 1);
entity.cell = null;
};
this.removeEntityAt = function(index) {
if (this.entities.length < index && index >= 0) {
e = this.entities[index];
this.entities.splice(index, 1);
e.cell = null;
}
};
}
function entity() {
this.cell = null;
this.getLocation = function() {
return {
"row" : this.cell.row.getIndex(),
"cell" : this.cell.getIndex()
};
};
this.move = function(row, cell) {
var g = this.cell.row.grid;
this.cell.removeEntity(this);
g.cellAt(row, cell).addEntity(this);
};
}
Note: Careful with indexOf(). It's helpful, but not fully cross browser.
Actual usage would look something like this.
Create the new grid
var grid1 = new grid(100, 100);
Create a new entity and add it to the grid:
var e = new entity();
grid1.cellAt(12, 23).addEntity(e);
Move an entity:
e.move(53, 23);
There is potentially a lot more here. For instance, if this grid, row, cell, and entity objects are going to represent actual HTML elements, in the creation script you can have it create those HTML elements. For example, you might want to create a div element when a new row and/or cell is created. One way to link the array to the div so that you have a connection between the logical grid and the "physical" element:
<div data-row="12" data-cell="23"><div>
Bottom line is that I think creating these structured and "hierarchically referential" objects will make it easier for you to move around in the gird. Also, it would be super easy to modify this to support multiple grids in one document.

Related

Optimized the color render function

I have a big data to handle.
They need to be classified into 4 colors, and render to the SVG.
My function is:(parameter A B C are used to do something....)
function mapRender(dataArray,A,B,color1,color2,color3,color4,C){
//......do somthing.......
var colorPlusAry = $.grep(dataArray, function(item,key){
var vote_percentage = parseInt(item.vote_percentage);
var result = vote_percentage>=0 && vote_percentage <50;
return result;
});
//......do somthing.......
}
I use grep to generate the new array which the item has same color, and render to SVG.
function colorDistrict(colorArray,color){
var village = '';
var fillColor = 'fill:'+color;
for(var item in colorArray) {
village = colorArray[item].village;
$('svg').find('path').each(function(){
var id = $(this).attr('id');
if(village) {
if(id.substring(6) === village){
$(this).attr('style',fillColor);
}
}
});
}
}
colorDistrict(colorPlusAry,color1); //Render 4 array
It works successfully, but the data is too large and make render slowly, when I trigger the function, it takes several seconds to react...
How can I optimize this function to render the color?
Optimization is a difficult business without the real data, and without knowing the precise DOM structure. I can only give some hints from what I see:
The costly process is interacting with the DOM. Looking at the colorDistrict() function, the two loops seem to be independent. It would then make sense to run the .each() loop only once, and the loop over the colorArray as the nested one. The latter only contains precomputed values and should run much faster.
Looking at what these loops really do, you can write them much more semantic. You compare the collection of all paths and the colorArray for intersection, and then assign a style to a filtered list of paths.
function colorDistrict (colorArray, color){
var fillColor = 'fill:' + color;
// filter the paths to find those mentioned in colorArray
var relevantPaths = $('svg').find('path').filter(function () {
var village = $(this).attr('id').substring(6);
// boolean to indicate the village is part of the colorArray
var included = colorArray.some(function (item) {
return item.village === village;
});
return included;
});
relevantPaths.attr('style', fillColor);
}
If I understand correctly what you are doing, the colorDistrict() function is executed multiple times, once for every color you assign. Do the <path> elements change between rendering the different colors? If not, you should execute $('svg').find('path') only once and cache the found paths for reuse inside that function.
var paths = $('svg').find('path');
function colorDistrict (colorArray, color){
var fillColor = 'fill:' + color;
var relevantPaths = paths.filter(function () {
...
});
}

javascript / jQuery: most efficient way to search in and update an Array of Objects

I am currently working on a private side/fun-project which is a small browser-game based on javascript and jQuery.
for this game i am saving data to an array of objects:
this.map.push(
{
"id" : id,
"x" : pos_x,
"y" : pos_y
}
);
now i need to find and/or update data in this array quite frequently (in "real time").
for example when i need to find the array-index at a position of my coords-system, i am using jQuery's $.each() function:
this.getIndexAtPos = function(x, y)
{
var index = false;
$.each(this.map, function(key, obj){
if(obj.x == x && obj.y == y)
{
index = key;
return false;
}
});
return index;
}
and another example: when i need to update data for an object with a specific id, i am using jQuery's $.map() function:
this.updateCoordsById = function(id, x, y)
{
this.map = $.map(this.map, function(obj, i)
{
if(obj.id == id)
{
//only update x, y!
obj.x = x;
obj.y = y;
return obj;
}
else
{
return obj;
}
});
}
it's all working fine for me so far, but i am on a rather fast computer and as the game is extending there will be more and more of these actions. like let's say a couple of hundred $.each and $.map calls per second.
that's why i am worried about performance issues, specially on slower computers.
MY QUESTIONS:
i know that $.each and $.map are (partly or fully) iterating my array on each call. that's why i used return false; inside my $.each function as soon as the entry is found to speed up things a little.
1. can i improve the performance by using other jQuery functions or changing the way i use them?
2. are there better ways (performance wise) to solve this by using native javascript?
3. should i use other datatypes/structures to boost performance?
note: the procedures i need to implement always are quite similar: find objects in the array by coords(x, y); find them by id; update objects by coords(x, y); update objects by id, and so on.
i would appreciate any help, opinions and suggestions!
OK as this is a grid it makes sense to store it as a grid in memory.
A 2-dimensional array will give you much faster access than a one-dimensional one, and also lets you access object directly by their coordinates instead of checking the coordinates of each object.
You can also have an container object which contains all your objects, with their ID as the property, which gives you fast lookup by ID.
You can put these together by storing the ID of the object in the grid, then looking up the object in the container by the ID.
I've created a fiddle at http://jsfiddle.net/d75zkvnb/1/ which shows a simple example of this. The structure of the Map object is below:
var Map = {
init: function(){
this.Width = 10;
this.Height = 10;
// grid stores the coordinates and the IDs of the object at that coordinate
// note this is stored by y then x.
this.grid = [];
// stored the actual objects, indexed by id
this.objects = {};
// set up the grid
for(var y = 0; y < this.Height; ++y)
{
this.grid.push(new Array(this.Width));
}
},
// gets the object at (x, y)
GetAtCoord: function(x, y)
{
// find the id
var id = this.grid[y][x];
// directly access it from the array
return this.objects[id];
},
// gets the object with the ID
GetById: function(objId)
{
// direct access to the object
return this.objects[objId];
},
// add an object at its stored coordinates
AddObject: function(obj){
this.grid[obj.y][obj.x] = obj.id;
this.objects[obj.id] = obj;
},
// Move an object in the grid
MoveObject: function(objId, newX, newY)
{
// get the objct to move
var theObj = this.objects[objId];
// reove it from the grid
this.grid[theObj.y][theObj.x] = null;
// updates its stored position
theObj.x = newX;
theObj.y = newY;
// add it back to the grid
this.grid[newY][newX] = objId;
}
};

Store a table row index as an array index

There a simple function:
selected_row = []; // global scope
function toggleRowNumber(rowIndex) {
if(selected_row[rowIndex]) selected_row.splice(rowIndex, 1);
else selected_row[rowIndex] = 1;
}
usage
toggleRowNumber(50000); // click the row - write the index
toggleRowNumber(50000); // click the row again - remove the inxed
alert(selected_row.length);
50001
OK
Delightful feature!
So is there a way to direct write|read an index without any searchin/looping? And without this huge feat as decribed above.
Thanks.
If I understoold correctly, you want to store and index where you can check/set whether an item is selected or not. If that is the case, you are looking for a "key - value" data structure. Then, why not use a map?
var selected_row = {};
function toggleRowNumber(rowIndex) {
if(selected_row[rowIndex]) selected_row[rowIndex] = 0; //or = undefined;
else selected_row[rowIndex] = 1;
}
That is better because hash map will save you time and space.
Space becuase you are not storing hundreds of 'undefined' values in a vector.
Time because, hash function used to access elements is pretended to hit the right position in many cases.

Can't work out why array updating when it shouldn't be?

I'm building Conway's game of life with Javascript and HTML5 Canvas.
The code here is within the context of a gameOfLife object:
this.cells = [];
this.nextCellState = [];
After populating this.cells with my cell objects, I populate this.nextCellState like so:
this.nextCellState = this.nextCellState.concat(this.cells);
On mouse click, the corresponding cell object property isAlive is turned true:
function clickAlive(x, y) {
for (var i in this.cells) {
if (x.between(this.cells[i].x, this.cells[i].x + cellsize) && y.between(this.cells[i].y, this.cells[i].y + cellsize)) {
this.cells[i].isAlive = true;
console.log('Breakpoint');
}
}
}
The problem is, having a look at cells and nextCellState arrays at the breakpoint, both of them have the clicked cell activated to true.
What is causing this?
When you copy the contents of cells into nextCellState, you are making a shallow copy of the array. The objects themselves are now aliased by the two arrays (that is, cells[0] and nextCellState[0] refer to the same object).
You need to create new objects in nextCellState to be able to change the objects' internal states independently. The easiest would be if your cell objects had a copy constructor function. Then you could do something like this:
this.nextCellState = this.nextCellState.concat(
this.cells.map(function(cell) {
return cell.copy(); // or whatever your copy constructor is
})
);

Javascript sort string or number

EDIT: Pete provided a really good solution that works when the fields contain numbers, however I need to be able to sort strings too - any ideas?
I'm trying to write a javascript sorting algorithm that will sort a table based on the column clicked - I know this is semi-reinventing the wheel but the design is too complex for me to try and insert some other plugin etc.
Some columns are text, some columns are numbers.
Clicking a column calls: sort(X,Y). X is the column number so we know which cells to compare for the sort. Y is the mode, i.e. ascending or descending.
The code for the sort function is:
function sort(field, mode) {
var tabrows = 0;
$(".data tr").each(function() { if($(this).hasClass("hdr")) { } else {tabrows++;} });
var swapped;
do {
swapped = false;
for (var i=0;i< tabrows; i++) {
var j = i + 3;
var k = i + 4;
var row1 = $(".data tr:nth-child("+j+")");
var row2 = $(".data tr:nth-child("+k+")");
var field1 = row1.find("td:eq("+field+")").text();
var field2 = row2.find("td:eq("+field+")").text();
if(shouldswap(field1, field2, mode)) {
swaprows(row1, row2);
swapped = true;
}
}
} while (swapped);
}
The shouldswap function is as follows:
function shouldswap(field1, field2,mode) {
if(field1 > field2) {
if(mode==1) {
return true;
} else {
return false;
}
}
return false;
}
Code for swaprows function:
function swaprows(row1, row2) {
row2.insertBefore(row1);
}
Can anyone see why this would cause the browser to freeze/lockup. I've been working on this for quite a while so I think a fresh pair of eyes may point out something silly! Any help is appreciated :)
The problem might be that you're calling the jQuery constructor a bunch of times and doing heavy operations on it (e.g. using .find() with complex selectors). Therefore, your function is just slow and that's probably the issue.
The good news is that JavaScript has a native implementation of QuickSort (a very fast sorting function) that will probably take care of your needs. When combined with a reduction in expensive calls, your code should end up being enormously more efficient. I'd change your code to look like this:
var sortByField = function(field, mode) {
var numExp = /^-?\d*\.?\d+$/;
var $rows = $(".data tr:not(.hdr)"), $table = $(".data");
$rows.each(function () {
this.fieldVal = $(this).find("td:eq("+field+")").text();
if(numExp.test(this.fieldVal)) { //if field is numeric, convert it to a number
this.fieldVal = +this.fieldVal;
}
}).sort(function (a, b) {
if (mode === 1) {
return (a.fieldVal > b.fieldVal) ? -1 : 1;
}
return (a.fieldVal < b.fieldVal) ? -1 : 1;
}).detach().each(function () {
$(this).appendTo($table);
});
};
This won't work well with multiple tables on one page (because it assumes everything is on the same table). So if you want to do that, you should pass in the table or table selector as a parameter. But that's an easy fix to make. You can see my solution in action here:
http://jsfiddle.net/r8wtK/ (updated)
It should be far more efficient than your code and should reduce "freezing" by quite a bit (ore even entirely).
UPDATE:
The OP noted that some fields may contain strings. Doing a string comparison on numbers is bad because it returns a lexicographical ordering (e.g. "10" < "2"). So I added a test to see if the data appear to be numeric before doing the sort.
Could it be that you're adding 3 and 4 to i in order to get your row indices? So when i gets to (tabrows-1), it appears that it will be trying to access rows with index of (tabrows+2) and (tabrows+3). If I understand your logic correctly, these are out of bounds, so row1, row2, field1 and field2 will be empty. Therefore, if you're in mode==1, I think this will make it so that your algorithm attempts to swap these two non-existent rows and keeps comparing for infinity. Does this make sense, or am I misunderstanding your logic?
If that's the case, I think you just need to change your for loop to:
for (var i=0;i< tabrows-4; i++) {
// your code
}
What is the purpose of adding 3 to j and 4 to k anyway? Do you have 3 rows of data at the top that you don't want to compare?

Categories