By modifying arr[1][0] multiple cells are modified - javascript

I'm trying to make a match-3 game (candy crush like). I have an object level which has tiles property which is a 2d array. After I do some manipulations I want to change the type of a specific element to -1 using this simple line (I'll be using for, but for now I've made it simple for demonstrative purposes)
level.tiles[1][0].type = -1;
Here is the code
var level = {
x: 250, // X position
y: 113, // Y position
columns: 8, // Number of tile columns
rows: 8, // Number of tile rows
tilewidth: 40, // Visual width of a tile
tileheight: 40, // Visual height of a tile
tiles: [], // The two-dimensional tile array
selectedtile: {selected: false, column: 0, row: 0}
};
var tileTypes = [
{
type: "red",
colors: [255, 128, 128]
},
{
type: "green",
colors: [128, 255, 128]
},
{
type: "darkBlue",
colors: [128, 128, 255]
},
{
type: "yellow",
colors: [255, 255, 128]
}
];
function createLevel() {
for (var i = 0; i < level.columns; i++) {
level.tiles[i] = [];
}
for (var i = 0; i < level.columns; i++) {
for (var j = 0; j < level.rows; j++) {
level.tiles[i][j] = getRandomTile();
}
}
}
function getRandomTile() {
return tileTypes[Math.floor(Math.random() * tileTypes.length)];
}
createLevel();
level.tiles[1][0].type = -1;
Unfortunately not only tiles[1][0] is modified, but multiple cells. The interesting part is that every time random cells are affected

This occurs because getRandomTile() returns a reference to a tile type, not a copy of it.
I.e. to simplify this case:
var a = {x: 1};
var b = [a, a, a, a];
b[0].x = 2;
console.log(a, b);
will output
{x: 2} [{x: 2}, {x: 2}, {x: 2}, {x: 2}]
If you want the tiles to be modifiable, have getRandomTile return a copy – a shallow copy in this case, so colors is still a reference, not a copy – of the randomly chosen tile type.
function getRandomTile() {
const tileType = tileTypes[Math.floor(Math.random() * tileTypes.length)];
// Idiom for shallow copy, i.e. assign all properties of tileType
// into a new, unique object.
return Object.assign({}, tileType);
}

The problem is you modify the type object, instead of linking to another type. A solution would be to clone it when creating the tiles:
function getRandomTile() {
var srcType = tileTypes[Math.floor(Math.random() * tileTypes.length)];
return {type:srcType.type, colors:srcType.color};
}
Another one (depending on your goal) would be to have Tile objects, each one having a reference to a Type object (not just an integer). At this point some classes might be helpful:
class TileType {
constructor(colors){
this.colors = colors;
}
}
let tileTypes = [...]
class Tile {
constructor(){
this.type = tileTypes[Math.random()*tileTypes.length|0];
}
setNewType(type){
this.type = type;
}
}
etc.

This is caused by getRandomTile which returns the same reference of the object defined in tileTypes if the index passed in is the same. You can print tileTypes to help your understand what happens.

Related

Array algorithm too slow

i have written an algorithm that finds each line of a hexagon in a huge object-array structure.
the array consits of about 80.000 - 100.000 elements (line coordinates from start to end).
A hexagon cosists of 6 lines points. So the array has the information of about 15.000 hexagons.
The structure of the object (UNSORTED!!!) looks like this:
const stamps = [
{
vertices: [
{x: 114.5116411118, y: 342.9815785601},
{x: 115.6663416502, y: 344.9815785601}
]
},
{
vertices: [
{x: 115.6663416502, y: 340.9815785601},
{x: 114.5116411118, y: 342.9815785601}
]
},
{
vertices: [
{x: 122.6663416502, y: 364.9815785601},
{x: 147.9757427269, y: 314.9815785601},
]
},
{
vertices: [
{x: 117.9757427269, y: 340.9815785601},
{x: 115.6663416502, y: 340.9815785601},
]
},
{
vertices: [
{x: 119.1304432653, y: 342.9815785601},
{x: 117.9757427269, y: 340.9815785601},
]
},
{
vertices: [
{x: 117.9757427269, y: 344.9815785601},
{x: 119.1304432653, y: 342.9815785601},
]
},
{
vertices: [
{x: 115.6663416502, y: 344.9815785601},
{x: 117.9757427269, y: 344.9815785601},
]
},
];
To find each line hexagon, my idea was that there has to be 2 elements that have to same coordinate. If this is the case, i'm jumping to the index of this element and repeat that process untill i have all 6 lines of the hexagon.
It works like this, but its really, really slow. For an array with 80.000 elements its about 3 minutes.
The algorithm:
function findHexPolyPoints() {
const hexCoordinates = [];
let activeArrayPos = 0;
let position = 0;
while (1) {
let foundPair = false;
if (stamps.length < 6) break;
for (let k = 0; k < stamps.length; ++k) {
if (k === position) continue;
if (stamps[position].vertices[0].x === stamps[k].vertices[1].x && stamps[position].vertices[0].y === stamps[k].vertices[1].y) {
if (hexCoordinates[activeArrayPos]) {
hexCoordinates[activeArrayPos].push(stamps[k].vertices[0].x, stamps[k].vertices[0].y);
} else {
hexCoordinates.push([stamps[position].vertices[0].x, stamps[position].vertices[0].y, stamps[k].vertices[0].x, stamps[k].vertices[0].y]);
}
foundPair = true;
} else if (stamps[position].vertices[1].x === stamps[k].vertices[0].x && stamps[position].vertices[1].y === stamps[k].vertices[0].y) {
if (hexCoordinates[activeArrayPos]) {
hexCoordinates[activeArrayPos].push(stamps[k].vertices[1].x, stamps[k].vertices[1].y);
} else {
hexCoordinates.push([stamps[position].vertices[1].x, stamps[position].vertices[1].y, stamps[k].vertices[1].x, stamps[k].vertices[1].y]);
}
foundPair = true;
}
if (foundPair) {
stamps.splice(position, 1);
if (k > position) {
position = k - 1;
} else {
position = k;
}
if (hexCoordinates[activeArrayPos].length < 12) break;
}
if (hexCoordinates[activeArrayPos] && hexCoordinates[activeArrayPos].length === 12) {
if (k > position) stamps.splice(k - 1, 1);
else stamps.splice(k, 1);
activeArrayPos += 1;
position = 0;
break;
}
if (k === stamps.length - 1) {
stamps.splice(position, 1);
break;
}
}
}
sortHexagons(hexCoordinates);
}
Is there any way to speed up my algorithm? I have read that a simple for loop is still faster that some js sort functions like .map .filter or similar.
The following O(n) algorithm assumes
two different hexagons do not have overlapping vertices
there are no more than two same vertices in the array (meaning, there could be an orphan that doesn't belong to any hexagon, but its coordinates should not equal any of the hexagons vertices)
there is no floating point inaccuracy in coordinates (meaning two vertices that are supposed to be equal, are === exactly equal)
6 connected vertices are assumed to form an hexagon... (there is no barycenter calculation and checks to ensure it's actually an hexagon)
(if the points 1. and 2. are not true, the algo would need to be worked more, to try all possibilities (in overt[x_y] array, see below) in order to avoid non-hexagon or overlapping coords, depending on the expectancy to find overlapping hexagons, or orphans, the complexity could go beyond O(n))
Using the concept of map (get an object value from a key), which is considered O(1).
In order to conveniently use the vertices coordinates, we can concatenate x and y into one string
x:123.456, y:789.321
gives
x_y = "123.456_789.321"
Let's create 3 variables avert = [], overt = {}, hexas = []
avert is an array of all vertices, avert[index] is an array(2) of the x_y vertices coordinates
overt is an object that, for each x_y coordinates gives an array of indexes in avert (the size should not become more than 2, as assumed above (and there is no >2 check))
hexas is an array of array(6), the list of hexagons found (each coordinate is formatted x_y)
In the first forEach, avert and overt are created.
The next forEach processes all avert vertices [x_y1, x_y2]
starting from the first vertex, tries to find the 6 points to form an hexagon
adds each vertex to one hexagon array hexa, starting from the next (after the first)
assume coordinates are not sorted, thus ensure we're not going back to previous vertice
skips the used vertices (in hexagons)
ensure the last vertex found has the same coordinates as the origin (first)
Initialization
let avert = [], overt = {}, hexas = [];
stamps.forEach(function(e, i){
let xy1 = e['vertices'][0]['x']+'_'+e['vertices'][0]['y'];
let xy2 = e['vertices'][1]['x']+'_'+e['vertices'][1]['y'];
// overt[XY] (array) should have two elements at most (no overlapping),
// one if orphan
if ( ! overt[xy1]) overt[xy1] = [];
overt[xy1].push( i );
if ( ! overt[xy2]) overt[xy2] = [];
overt[xy2].push( i );
avert.push([ xy1, xy2 ]);
});
Processing
avert.forEach(function (e){
let j,coord = e[0]; // first coords x_y
let origin = coord;
let hexa = [];
let lastindex = -1; // avoid going back!
// try to find 5 connected vertices + origin
for(j=0 ; j<6 ; j++) {
let o = overt[coord];
if ( o === undefined || o.length < 2 ) {
break; // not found(already processed), or orphan!
}
let index = o[0] == lastindex ? o[1] : o[0]; // no turn back!
lastindex = index;
coord = avert[index][0] === coord ? avert[index][1] : avert[index][0];
hexa.push(coord);
}
if (j >= 6) { // found all vertices
// check that the last coord is the starting point
if (hexa[5] === origin) { // got it
hexas.push( hexa );
hexa.forEach(function(h){ // delete used vertices
delete overt[h];
});
}
}
});
All hexagons should be in hexas
You can avoid the nested loop over all data by using a hash map. Key the individual vertices with a hash, for instance their JSON representation, and store the corresponding x, y coordinate together with a list of neigbor objects.
Once you have that, it is easy to walk through that graph and identify the hexagons.
Runnable snippet using the sample data you provided:
function findHexPolyPoints(stamps) {
// Create graph
let map = new Map;
for (let {vertices} of stamps) {
// Get unique identifier for each vertex (its JSON notation)
let keys = vertices.map(JSON.stringify);
// Create "nodes" for each vertex, keyed by their key
let nodes = keys.map(function (key, i) {
let {x, y} = vertices[i];
let node = map.get(key);
if (!node) map.set(key, node = { key, x, y, neighbors: [] });
return node;
});
// Link these two nodes in both directions
nodes[0].neighbors.push(nodes[1]);
nodes[1].neighbors.push(nodes[0]);
}
// Walk through the graph to detect and collect hexagons
let hexagons = [];
for (let [key, vertex] of map) {
let hexagon = [];
while (vertex && hexagon.push(vertex) < 6) {
vertex = vertex.neighbors.find(neighbor => !hexagon.includes(neighbor));
}
// Remove collected nodes so they don't get visited a second time
for (let {key} of hexagon) map.delete(key);
// Verify that they form indeed a hexagon:
if (vertex && vertex.neighbors.includes(hexagon[0])) {
// Simplify the hexagon to only coordinates (12 coordinates)
hexagons.push(hexagon.flatMap(({x, y}) => [x, y]));
}
}
return hexagons;
}
// Demo. Just replace `stamps` with your actual data.
const stamps = [{vertices: [{x: 114.5116411118, y: 342.9815785601},{x: 115.6663416502, y: 344.9815785601}]},{vertices: [{x: 115.6663416502, y: 340.9815785601},{x: 114.5116411118, y: 342.9815785601}]},{vertices: [{x: 122.6663416502, y: 364.9815785601},{x: 147.9757427269, y: 314.9815785601},]},{vertices: [{x: 117.9757427269, y: 340.9815785601},{x: 115.6663416502, y: 340.9815785601},]},{vertices: [{x: 119.1304432653, y: 342.9815785601},{x: 117.9757427269, y: 340.9815785601},]},{vertices: [{x: 117.9757427269, y: 344.9815785601},{x: 119.1304432653, y: 342.9815785601},]},{vertices: [{x: 115.6663416502, y: 344.9815785601},{x: 117.9757427269, y: 344.9815785601},]},];
let hexagons = findHexPolyPoints(stamps);
console.log(hexagons);
It is true that plain old for loops are somewhat faster than .map, .forEach, .reduce, .find and the likes, but here I kept using them, as the main speed up is really coming from using a hash map.

Nested arrays manipulation weird behaviour, js

I have a table of cells that need to be manipulated in a few specific spots accords to data I get
let arr = Array(3).fill(Array(3).fill(0));
[{x: 0, y: 0, value: 1}, {x: 1, y: 0, value: 2},{x: 2, y: 0, value: 3}].map(pos =>
arr[pos.x][pos.y] = pos.value
)
console.log(arr)
I expected the code to give [[1,0,0],[2,0,0],[3,0,0]] but instead it give [[3,0,0],[3,0,0],[3,0,0]], in other words it draws all as last y (value 3) and ignore the [pos.x] for some reason, don't sure why.
I'd liked to get some help with a possible workaround as explanation why this code not working as I expecting
thanks in advance!
the problem is when you do the outer fill , that fill function is executed only once, so basically you are having reference to same array in the nested arrays, so when you map you keep updating that same reference and hence all are same in the end
you might wanna do
let arr = [];
for(let i = 0; i < 3; i++) { arr.push(Array(3).fill(0));}
Try this instead:
var arr = Array.from({length:3},()=>Array(3).fill(0));
[{x: 0, y: 0, value: 1}, {x: 1, y: 0, value: 2},{x: 2, y: 0, value: 3}].map(pos =>
arr[pos.x][pos.y] = pos.value
)
console.log(arr);
The Array.from() method creates a new, shallow-copied Array instance from an array-like or iterable object.

Update ‘x’ and ‘y’ values of a trace using Plotly.update()

Is it possible to update the x and y properties of a trace using Plotly.update()?
Updating the marker.color property of the trace works well. But when I try updating the x or y properties the trace disappears from the graph. And there is no indication that something went wrong in the console. I would like to update the values by trace index and the update function looks like the right tool.
There may be a clue in the documentation for Plotly.react():
Important Note: In order to use this method to plot new items in arrays under data such as x or marker.color etc, these items must either have been added immutably (i.e. the identity of the parent array must have changed) or the value of layout.datarevision must have changed.
Though this may the complete unrelated because I am able to update marker.color using Plotly.update() without bumping the layout.datarevision.
Running example:
(codepen example here)
let myPlot = document.getElementById("graph");
let trace = {
type: 'scatter',
x: [0.5 * 255],
y: [0.5 * 255],
hoverinfo: "skip",
mode: 'markers',
marker: {color: "DarkSlateGrey", size: 20},
};
let layout = {
title: "The Worm Hole",
yaxis: { range: [0, 255] },
xaxis: { range: [0, 255] },
};
let config = {responsive: true};
Plotly.newPlot(myPlot, [trace], layout, config);
function updateGraphPoint(cmd) {
let x = Math.random() * 255;
let y = Math.random() * 255;
let z = Math.random() * 255;
let update = null;
if (cmd === "color") {
update = {'marker.color': `rgb(${x}, ${y}, ${z})`};
} else if (cmd === "position") {
update = {'x': [x], 'y': [y]};
}
Plotly.update(myPlot, update, {}, [0]);
}
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<button onclick="updateGraphPoint('color')">Change my color!</button>
<button onclick="updateGraphPoint('position')">Change my position!</button>
<div id="graph"></div>
Note: I also asked this question on the the plotly community forum but have not gotten any responses. Maybe someone here knows.
The data update object accepts an array containing an array of new x / y values for each trace you want to update. I.e. if you only want to update one trace, you still have to provide an array containing an array for that one trace:
update = {'x': [[x]], 'y': [[y]]};
let myPlot = document.getElementById("graph");
let trace = {
type: 'scatter',
x: [0.5 * 255],
y: [0.5 * 255],
hoverinfo: "skip",
mode: 'markers',
marker: {color: "DarkSlateGrey", size: 20},
};
let layout = {
title: "The Worm Hole",
yaxis: { range: [0, 255] },
xaxis: { range: [0, 255] },
};
let config = {responsive: true};
Plotly.newPlot(myPlot, [trace], layout, config);
function updateGraphPoint(cmd) {
let x = Math.random() * 255;
let y = Math.random() * 255;
let z = Math.random() * 255;
let update = null;
if (cmd === "color") {
update = {'marker.color': `rgb(${x}, ${y}, ${z})`};
} else if (cmd === "position") {
update = {'x': [[x]], 'y': [[y]]};
}
Plotly.update(myPlot, update, {}, [0]);
}
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<button onclick="updateGraphPoint('color')">Change my color!</button>
<button onclick="updateGraphPoint('position')">Change my position!</button>
<div id="graph"></div>

Stacked bar chart with (computed) average line in Plotly.js

I've got a (stacked) bar chart and I want an average line plotted on my chart.
Let's take this example:
var trace1 = {
x: ['giraffes', 'orangutans', 'monkeys'],
y: [20, 14, 23],
name: 'SF Zoo',
type: 'bar'
};
var trace2 = {
x: ['giraffes', 'orangutans', 'monkeys'],
y: [12, 18, 29],
name: 'LA Zoo',
type: 'bar'
};
var data = [trace1, trace2];
var layout = {barmode: 'stack'};
Plotly.newPlot('myDiv', data, layout, {showSendToCloud:true});
Result:
Expected output:
I've found a similar question, but in that case it was pretty easy to add a line with a 'fixed' value. In this case I've got a stacked bar chart nicolaskruchten/pivottable, so the user can easily drag and drop columns. That makes computing the average harder.
I can loop through all results and compute the average value, but since Plotly is very powerful and has something like aggregate functions, I feel like there should be a better way.
How can I add a (computed) average line to my (stacked) bar chart?
Plotly.js not provided any direct options for drawing average line.
But you can do this simple way.
//Find average value for Y
function getAverageY() {
allYValues = trace1.y.map(function (num, idx) {
return num + trace2.y[idx];
});
if (allYValues.length) {
sum = allYValues.reduce(function (a, b) {
return a + b;
});
avg = sum / allYValues.length;
}
return avg;
}
//Create average line in shape
var layout = {
barmode: 'stack',
shapes: [{
type: 'line',
xref: 'paper',
x0: 0,
y0: getAverageY(),
x1: 1,
y1: getAverageY(),
line: {
color: 'green',
width: 2,
dash: 'dot'
}
}]
};
Updated:
You need to update your graph after loading this drawing a average
line for any numbers of trace.
//Check graph is loaded
if (document.getElementById('myDiv')) {
//draw average line
drawAvgLine(document.getElementById('myDiv'))
}
function drawAvgLine(graph) {
var graphData = graph.data; //Loaded traces
//making new layout
var newLayout = {
barmode: 'stack',
shapes: [{
type: 'line',
xref: 'paper',
x0: 0,
y0: getAverageY(graphData),
x1: 1,
y1: getAverageY(graphData),
line: {
color: 'green',
width: 2,
dash: 'dot'
}
}]
};
//Update plot pass existing data
Plotly.update('myDiv', graphData, newLayout)
}
//Calculate avg value
function getAverageY(graphData) {
var total = [],
undefined;
for (var i = 0, n = graphData.length; i < n; i++) {
var arg = graphData[i].y
for (var j = 0, n1 = arg.length; j < n1; j++) {
total[j] = (total[j] == undefined ? 0 : total[j]) + arg[j];
}
}
return total.reduce(function (a, b) {
return a + b;
}) / total.length;
}

Creating a grid array of co ordinates

I'm a little confused with my array creation. I have a list of items with x:y co ordinates and also their dimensions (in grid sizes) ... for example:
x: 1
y: 7
width: 2 tiles
height: 2 tiles
So the idea im trying to do is create an array of x:y grids that are "occupied". But looping such data so the occupied tiles would there for be:
x: 1
y: 7
//
x: 2
y: 7
//
x: 1
y: 8
//
x: 2
y: 8
Because in the above example the item is 2 by 2 tiles ( a square ).
My object is structured like this (shown in console.log(sdata);)
7: Array[4]
0: "1"
1: "7"
2: "2"
3: "2"
So yeah im trying to make an array to store this kind of grid reference..Hope I explained what I trying to get at, but i can't work out how to structure a loop to create the array.
http://jsfiddle.net/rlemon/JeeV2/ Here is an example of how you could produce this type of 'grid' occupied data..
var chords = [ // instead of arrays we'll use objects.. they're nicer.
{
x: 1,
y: 7,
h: 2, // height
w: 2}, // width
{ // ohh look a second plot point.
x: 4,
y: 1,
h: 3,
w: 2},
];
var occupied = { // will be your cells points occupied
x: [],
y: []
};
chords.forEach(function(chord) { // now lets loop the chords and produce the occupied array
occupied.x.push( chord.x );
occupied.x.push( chord.x + (chord.w-1) ); // expand the width of the cell - initial point
occupied.y.push( chord.y );
occupied.y.push( chord.y + (chord.h-1) ); // expand the height of the cell - initial point
});
console.log( occupied );
// outputs
​Object
x: Array[4]
0: 1
1: 2
2: 4
3: 5
y: Array[4]
0: 7
1: 8
2: 1
3: 3
The resulting outputArray array is a collection of objects in the form { x: value, y: value}.
var inputArray = [ [1,7,2,2], ... ];
var outputArray = [];
for(var i = 0; i < inputArray.length; i++) {
var item = {
x: inputArray[i][0],
y: inputArray[i][1],
width: inputArray[i][2],
height: inputArray[i][3]
};
for(var xx = 0; xx < item.width; xx++) {
for(var yy = 0; yy < item.height; yy++) {
outputArray.push({
x: item.x + xx,
y: item.y + yy
});
}
}
}
​I added the x, y, width and height attributes to make it more understandable.
function range(a, b) {
/*
range(5) -> [0,1,2,3,4]
range(1,4) -> [1,2,3]
*/
if (b===undefined)
var start=0, stop=a;
else
var start=a, stop=b;
var R = [];
for(var i=start; i<stop; i++)
R.push(i);
return R;
}
Then it's a two-liner:
range(x, x+width).map(function(x) {
return range(y, y+height).map(function(y) {
return {x:x, y:y}; // anything you'd like
})
})
Result (obtained via JSON.stringify):
[
[ {"x":1,"y":7}, {"x":1,"y":8} ],
[ {"x":2,"y":7}, {"x":2,"y":8} ]
]

Categories