Javascript grid calculation - javascript
I am creating a game where I represent boxes with a two dimensional array of their id.
var grid = [[X,X,X,X,X,X,X,X,X,X],
[X,X,4,4,4,4,X,X,X,X],
[X,3,3,3,3,X,X,X,X,X],
[X,X,X,2,2,2,2,X,X,X],
[X,1,1,1,1,5,5,5,5,5]];
The boxes stack on top of each other, and X represents a blank spot.
If one of the boxes are deleted I want any of the boxes above (That can fit) to shift down. So they are always neatly stacked.
So if I was to delete the box with ID: 1 I would get a new grid like this:
var grid = [[X,X,X,X,X,X,X,X,X,X],
[X,X,4,4,4,4,X,X,X,X],
[X,3,3,3,3,X,X,X,X,X],
[X,X,X,2,2,2,2,X,X,X],
[X,X,X,X,X,5,5,5,5,5]];
Then I would want Box: 3 to slide down into its spot like so:
var grid = [[X,X,X,X,X,X,X,X,X,X],
[X,X,4,4,4,4,X,X,X,X],
[X,X,X,X,X,X,X,X,X,X],
[X,X,X,2,2,2,2,X,X,X],
[X,3,3,3,3,5,5,5,5,5]];
Finally Box: 4 should move down into where 3 was:
var grid = [[X,X,X,X,X,X,X,X,X,X],
[X,X,X,X,X,X,X,X,X,X],
[X,X,4,4,4,4,X,X,X,X],
[X,X,X,2,2,2,2,X,X,X],
[X,3,3,3,3,5,5,5,5,5]];
Is there an easy way of doing this? I was thinking of a callback that checks the grid when a box is destroyed but what I came up with was mostly IF statements. Is there something elegant out there?
The box class itself also has the start position and its length:
box = {id: 3,
start: 1,
length: 4};
This is actually not an easy task. I created a little fiddle that does what you wanted to achieve (i think).
I extended the box prototype with some functions. My solution relies on the variables grid and blocks, but you could abstract that even more if you like to.
The functions testFunctionality and printGridToElement are just there for testing purposes.
My new Box prototype:
function Box(i, s, l) {
this.id = i;
this.start = s;
this.length = l;
this.row;
blocks.push(this);
}
Box.prototype.insertIntoGrid = function (row) {
this.row = row;
if (!grid[row]) grid[row] = [];
for (var i = 0; i < this.length; i++) {
grid[row][this.start + i] = this.id;
}
};
Box.prototype.destroy = function () {
blocks.splice(blocks.indexOf(this), 1);
this.removeFromGrid();
this.checkRemainingBlocksForMoveDown();
};
Box.prototype.checkRemainingBlocksForMoveDown = function () {
for (var i = 0; i < blocks.length; i++) {
var btmd = blocks[i].checkForMoveDown();
if (btmd) {
btmd[0].move(btmd[1]);
btmd[0].checkRemainingBlocksForMoveDown();
}
}
}
Box.prototype.move = function (row) {
this.removeFromGrid();
this.insertIntoGrid(row);
};
Box.prototype.removeFromGrid = function () {
for (var i = 0; i < this.length; i++) {
grid[this.row][this.start + i] = 0;
}
};
Box.prototype.checkForMoveDown = function () {
for (var i = 0; i < this.row; i++) {
var move = true;
for (var j = 0; j < this.length; j++) {
if (grid[i][this.start + j] != 0) {
move = false;
break;
}
}
if (move) {
return [this, i];
}
}
};
and the usage of it:
var b1 = new Box(1, 1, 4);
b1.insertIntoGrid(0);
var b2 = new Box(2, 3, 4);
b2.insertIntoGrid(1);
var b3 = new Box(3, 1, 4);
b3.insertIntoGrid(2);
var b4 = new Box(4, 2, 4);
b4.insertIntoGrid(3);
var b5 = new Box(5, 5, 5);
b5.insertIntoGrid(0);
b1.destroy();
b2.destroy();
b3.destroy();
NOTE: I designed the grid with 0 being the lowest row
I'm late, but here goes.
You should probably swap rows, and columns. That is make it like:
var rows = [];
column = [x,x,x,x,x,x,x,x];
rows.push(column);
Instead of:
var columns = [];
var row = [x,x,x,x,x,x,x,x];
columns.push(row);
This way a drop is just an array operation on columns. You can then do things like splice out a block, splice in a block, unshift, shift, and so on.
Do the array operations before animations, but not before you get the column, and row information from the grid.
You can even name the methods that do it by array methods. shift drops the bottom block, splice(start, stop, [optional]new block). Like that.
#Markai did swap the columns, and rows in their answer, but I thought I'd add some clarity.
This is what I came up with (Not working but the gist)
fallCheck = function(deletedPosition, deletedLength) {
var fallable = grid.reduce(
function(array, row) {
var unique = row.filter(function(item, i, ar) { return ar.indexOf(item) === i;});
var id = unique.find( function(boxId) {
var box = boxes.iterate("id", boxId, Phaser.Group.RETURN_CHILD); //Finds the instance within a Phaser Group
return (box.start >= deletedPosition) && (box.start + box.length) <= (deletedPosition + deletedLength);
});
if (id != -1) array.push(id);
}, []);
if (fallable.length > 0) { fall(fallable[0]); } //fall simply moves the box to the lowest position on the grid
};
Related
Google Script working as 2 separate scripts but not inside the same function
Basically I have a script that is in 4 blocks: 1. Copies within a range each row provided it meets a criteria 2. Removes all empty rows 3. Sets all numbers as percentage 4. Applies conditional cell formatting to one of the columns The 4th part is the one that is causing me issues. The script runs without any error message AND block 4 works perfectly fine if it's in another script alone with the same variables defined but as soon as it is inside the same function as the others it simply doesn't run without any error message of any kind. Tried changing the name of the variables to single use ones to ensure it wasn't because one of the "var" was modified above it, removing the "else if" to keep only an "if" in the loop, moving it around to other parts of the script but if the block 1 is in the script then block 4 won't apply (will apply if it is only with 2 & 3. 2 & 3 which follow the same structure work well with 1. Does any one have any clue what's wrong with my script ? :) Each block is commented with what it does function copy() { //Set variables & criterion to choose which rows to copy var s = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/1bEiLWsbFszcsz0tlQudMBgTk5uviyv_wDx7fFa8txFM/edit'); var ssSource = s.getSheetByName('Variations'); var ssDest = s.getSheetByName('Email'); var lastRowSource = ssSource.getLastRow(); var lastRowDest = ssDest.getLastRow(); var lastColSource = ssSource.getLastColumn() var criteria = 0; var titles = ssSource.getRange(1,1,1, lastColSource).getValues() //Copies the range ssDest.getRange(1,1,1, lastColSource).setValues(titles) for (var i = 2; i < lastRowSource; i++ ) { var test = ssSource.getRange(i ,1); Logger.log(test.getValue()+ ' <? ' + criteria); if (ssSource.getRange(i ,6).getValue() > criteria) { ssSource.getRange(i ,1,1,ssSource.getLastColumn()).copyTo(ssDest.getRange(i ,1,1,ssSource.getLastColumn()), {contentsOnly:true}); // copy/paste content only } } //Removes empty rows var data = ssDest.getDataRange().getValues(); var targetData = new Array(); for(n=0;n<data.length;++n){ if(data[n].join().replace(/,/g,'')!=''){ targetData.push(data[n])}; Logger.log(data[n].join().replace(/,/g,'')) } ssDest.getDataRange().clear(); ssDest.getRange(1,1,targetData.length,targetData[0].length).setValues(targetData); //Formats numbers as percentages var rangePercent = ssDest.getRange(1,1,ssDest.getLastRow(),ssDest.getLastColumn()); var rowsPercent = rangePercent.getNumRows(); var colsPercent = rangePercent.getNumColumns(); for(var rowPercent = 1; rowPercent <= rowsPercent; rowPercent++) { for(var colPercent = 1; colPercent <= colsPercent; colPercent++) { var cellPercent = rangePercent.getCell(rowPercent, colPercent); var valuePercent = cellPercent.getValue(); if(typeof(valuePercent) == 'number') { cellPercent.setNumberFormat("##.#%"); } } } //Adds conditional background colours for (var z = 2; z < lastRowDest+1;z++) { var avgCpc = 4; var rangeColour = ssDest.getRange(z,avgCpc); var dataColour = rangeColour.getValue() if (dataColour < 0) { ssDest.getRange(z,avgCpc).setBackground('#d9ead3') } else if (dataColour > 0) { ssDest.getRange(z,avgCpc).setBackground('#f4cccc') } } //Centers Values }
The problem you're having is your code has performance issues because you're calling too many times methods such as getRange() and getValue() inside various loops, therefore Apps Script can't keep up with all those calls. Please check Best Practices. Having said that, I modified your code in order to make it more efficient. Besides your copy function, I added two more functions to make the code more readable. function copy As before this function sets the variables, but now it calls two other functions, which are setPositiveCostValues and formatCells function copy() { //Set variables & criterion to choose which rows to copy var ss = SpreadsheetApp.openByUrl('your-url'); var ssSource = ss.getSheetByName('Variations'); var ssDest = ss.getSheetByName('Email'); // set the title var titles = ssSource.getRange(1,1,1, ssSource.getLastColumn()).getValues(); ssDest.getRange(1,1,1, ssSource.getLastColumn()).setValues(titles); // get the positive values you want from the cost col var positiveValues = setPositiveCostValues(ssSource, ssDest, ssSource.getLastRow()); // fomrat the cells you want as percentage and set the color formatCells(ssDest, positiveValues); } function setPositiveCostValues This will take the values where the cost is positive and it will get rip off of the cells with empty values and "n/a" values. function setPositiveCostValues(ssSource,ssDest, lastRowSource){ var postiveCost = ssSource.getRange(2, 1, lastRowSource, 6).getValues(); // this loop will clean the empty elements and the ones that only have n/a for (var i = postiveCost.length - 1; i >= 0; i--) { if (postiveCost[i][0]) { postiveCost.splice(i + 1, postiveCost.length - (i + 1)); postiveCost = postiveCost.filter(function(el){ return el != 'n/a'}) break; } } return postiveCost; } function formatCells This will format the cells in the cost col as a percentage and will set the right color in your avgCpc col. function formatCells(ssDest, postiveCost){ var avgCpc = 4, cost = 6, row = 2, criteria = 0; // iterate over the array and depending on the criteria format the cells postiveCost.forEach(function(el){ if(el[cost - 1] > criteria){ var ssDestRange = ssDest.getRange(row, 1, 1, cost); ssDestRange.setValues([el]); ssDestRange.getCell(1, cost).setNumberFormat("##.#%"); // set the color depending on the avgCpc value condition if(el[avgCpc - 1] < criteria) ssDest.getRange(row, avgCpc).setBackground('#d9ead3'); else ssDest.getRange(row, avgCpc).setBackground('#f4cccc'); row++; } }); } Code Your whole code now it will look like this: function copy() { //Set variables & criterion to choose which rows to copy var ss = SpreadsheetApp.openByUrl('your-url'); var ssSource = ss.getSheetByName('Variations'); var ssDest = ss.getSheetByName('Email'); // set the title var titles = ssSource.getRange(1,1,1, ssSource.getLastColumn()).getValues(); ssDest.getRange(1,1,1, ssSource.getLastColumn()).setValues(titles); // get the positive values you want from the cost col var positiveValues = setPositiveCostValues(ssSource, ssDest, ssSource.getLastRow()); // fomrat the cells you want as percentage and set the color formatCells(ssDest, positiveValues); } function setPositiveCostValues(ssSource,ssDest, lastRowSource){ var postiveCost = ssSource.getRange(2, 1, lastRowSource, 6).getValues(); // this loop will clean the empty elements and the ones that only have n/a for (var i = postiveCost.length - 1; i >= 0; i--) { if (postiveCost[i][0]) { postiveCost.splice(i + 1, postiveCost.length - (i + 1)); postiveCost = postiveCost.filter(function(el){ return el != 'n/a'}) break; } } return postiveCost; } function formatCells(ssDest, postiveCost){ var avgCpc = 4, cost = 6, row = 2, criteria = 0; // iterate over the array and depending on the criteria format the cells postiveCost.forEach(function(el){ if(el[cost - 1] > criteria){ var ssDestRange = ssDest.getRange(row, 1, 1, cost); ssDestRange.setValues([el]); ssDestRange.getCell(1, cost).setNumberFormat("##.#%"); // set the color depending on the avgCpc value condition if(el[avgCpc - 1] < criteria) ssDest.getRange(row, avgCpc).setBackground('#d9ead3'); else ssDest.getRange(row, avgCpc).setBackground('#f4cccc'); row++; } }); }
Why is my JavaScript array showing a two element count when there is supposed to be only one?
I have an JS Array that is supposed to show only one element. It does, however its index is 1 rather than 0 and the count is 2. Also the array does not show a 0 index. My code: var _UnitOfMeasureRelatedUnitData = []; var rows = $('#jqxUOMRelatedUnitsDropdownGrid').jqxGrid('getrows'); var RecordCount = 0; if (rows.length !== 1 && rows[0]["UOMRelatedUnit_Name"] !== ""){ for(var i = 0; i < rows.length; i++){ var row = rows[i]; var _row = {}; if(row.UOMRelatedUnit_AddItem !== F) { RecordCount += 1; _row["Name"] = $("#txtUnitOfMeasureSetName").val(); _row["Active"] = T; _row["UnitOfMeasureTypeID"] = $("input[type='radio'][id='rblUnitOfMeasureType']:checked").val(); _row["BaseUnitID"] = $("input[type='radio'][id='rblUnitOfMeasureBaseUnit']:checked").val(); _row["RelatedUnitDisplayOrder"] = RecordCount; _row["RelatedUnitName"] = row.UOMRelatedUnit_Name; _row["RelatedUnitAbbreviation"] = row.UOMRelatedUnit_Abbreviation; _row["RelatedUnitConversionRatio"] = row.UOMRelatedUnit_ConversionOfBaseUnits; _row["UnitOfMeasureSetID"] = UnitOfMeasureSetID; _UnitOfMeasureRelatedUnitData[i] = _row; } } .... } In my JQx Grid, I have at least four choices. For this issue, Ive only selected the 2 choice in the Grid and its AddItem value is True, everything else is False. What do I need to change in my logic as I can not see it at this point? EDIT 1 I overlooked the placement of RecordCount += 1;, I will try moving it to the end of the assignments and see what happens. EDIT 2 The placement made no difference.
Maintain another variable for indexing your data like this var index=0; // place this outside of for loop _UnitOfMeasureRelatedUnitData[index++] = _row; you don't need RecordCount += 1; . you can get the rowscount by using _UnitOfMeasureRelatedUnitData.length
Cannot read properties of an object
I have spent many hours on this irritating problem in my code. I define a class to hold several properties. I then populate those properties into a 2d array. From there, I then attempt to modify random elements in the 2d array. I keep getting the message "Cannot read property of 'attribute' of undefined" where attribute is any one of the three attributes in my object. Here is my code: var table = new Array(); var adjacencyList = new Array(); var cell = function (prop) { var atrbs = { x: prop.x, y: prop.y, isVisited: prop.isVisited } return atrbs; }; var createTable = function(size){ for(var row = 0; row < size; row++){ table[row] = new Array(); for(var column = 0; column < size; column++){ table[row][column] = new cell({x: row, y: column, isVisited: false}); } } }; function print(){ for(var row = 0; row < table.length; row++){ for(var column = 0; column < table[row].length; column++){ console.log(table[row][column]); } } }; var randomizeMaze = function(){ var randomX = Math.floor((Math.random() * table.length)); var randomY = Math.floor((Math.random() * table.length)); var currentCell = table[randomX][randomY]; currentCell.isVisited = true; adjacencyList.push(currentCell); while( adjacencyList.length > 0 ) { currentCell.isVisited = true; var adjacentNodes = getAdjacentNodes(currentCell); //randomly select a node to add to path var nextInPath = adjacentNodes[Math.floor((Math.random() * adjacentNodes.length))]; //add to path to not visit it again adjacencyList.push.apply(adjacencyList, adjacentNodes); var removeNode = adjacencyList.indexOf(currentCell); adjacencyList.splice(removeNode, 1); //reset currentCell to random cell from resized list currentCell = adjacencyList[Math.floor((Math.random() * adjacencyList.lenth))]; } }; function getAdjacentNodes(pCell){ var adjacentNodes = new Array(); //check left if(pCell.x - 1 >= 0){ var node = table[pCell.x-1][pCell.y]; adjacentNodes.push(node); adjacencyList.push(node); } //check right if(pCell.x + 1 < table.length){ var node = table[pCell.x+1][pCell.y]; adjacentNodes.push(node); adjacencyList.push(node); } //check top if(pCell.y - 1 >= 0){ var node = table[pCell.x][pCell.y - 1]; adjacentNodes.push(node); adjacencyList.push(node); } //check bottom if(pCell.y + 1 < table.length){ var node = table[pCell.x][pCell.y + 1]; adjacentNodes.push(node); adjacencyList.push(node); } return adjacentNodes; }; createTable(3); //print(); randomizeMaze(); Whenever I try and access/change any property inside of a cell, namely in the functions 'randomeMaze' and 'getAdjacentNodes', it will throw the error I mentioned. I can print the objects to console, I see the objects as being populated when I debug. Maybe I'm missing something and am going crazy. Any help would be greatly appreciated.
Combing Slickgrid example 4 and example 9 (adding row reordering to dataview)
I'm trying to figure out how to combine Slickgrid's example 4 and example 9. Basically adding row reordering to a dataview grid. So far I have row reordering working as long as there is only one page in the grid. With multiple pages, row reordering works only on the first page and on any other pages, rows can be dragged up or down, but will not reorder. example 4: https://github.com/mleibman/SlickGrid/blob/master/examples/example4-model.html example 9: https://github.com/mleibman/SlickGrid/blob/master/examples/example9-row-reordering.html Any ideas? Thanks so much! Here is the row reordering code I have on my dataview grid: //Re-order rows on drag var moveRowsPlugin = new Slick.RowMoveManager({}); moveRowsPlugin.onBeforeMoveRows.subscribe(function (e, inboxData) { for (var i = 0; i < inboxData.rows.length; i++) { // no point in moving before or after itself if (inboxData.rows[i] == inboxData.insertBefore || inboxData.rows[i] == inboxData.insertBefore - 1) { e.stopPropagation(); return false; } } return true; }); moveRowsPlugin.onMoveRows.subscribe(function (e, args) { var extractedRows = [], left, right; var rows = args.rows; var insertBefore = args.insertBefore; left = inboxData.slice(0, insertBefore); right = inboxData.slice(insertBefore, inboxData.length); rows.sort(function(a,b) { return a-b; }); for (var i = 0; i < rows.length; i++) { extractedRows.push(inboxData[rows[i]]); } rows.reverse(); for (var i = 0; i < rows.length; i++) { var row = rows[i]; if (row < insertBefore) { left.splice(row, 1); } else { right.splice(row - insertBefore, 1); } } inboxData = left.concat(extractedRows.concat(right)); var selectedRows = []; for (var i = 0; i < rows.length; i++) selectedRows.push(left.length + i); inboxGrid.resetActiveCell(); inboxDataView.setItems(inboxData); inboxGrid.setSelectedRows(selectedRows); inboxGrid.render(); }); inboxGrid.registerPlugin(moveRowsPlugin); //End re-order rows
I'm not sure, but maybe these methods will help you: inboxGrid.invalidateAllRows(); //tells the grid that all the rows has been changed and it needs to rerender them. inboxGrid.invalidateRows(rows); // tells the grid that the specified rows has been changed and it needs to rerender them. You also need to use beginUpdate and endUpdate when updating the dataView: inboxDataView.beginUpdate(); inboxDataView.setItems(inboxData); inboxDataView.endUpdate(); Hope these help.
JavaScript delete merged table cell
I have been working on a scheduling website for the past few weeks. I am showing the schedules as PHP generated html-tables. I use merged cells for showing events. I have come to a problem when trying to delete events using JS. Since those are merged cells, using rowspan, I have to go through the table and re-adding empty cells whenever there is a need when I delete one. My solution is working fine for when my table contains one merged cell among nothing but empty cells, but with a more complex table, it fails. I can't really grasp what's wrong with it, except that it doesn't correctly find the cellIndex anymore. Does anyone have a clue? Here is what I'm talking about: http://aturpin.mangerinc.com/table.html (Click on an event to remove it, or attempt to anyhow)
This sample may help you find your solution. It seems to demonstrate your problem as well as have some sample code to generate a matrix to help you solve it. EDIT: I liked the puzzle and decided to play with it for a bit, here is a "functioning" example of implementing that sample (although sometimes the table doesn't seem to redraw correctly. This should probably help you get further along the way. function getTableState(t) { var matrix = []; var lookup = {}; var trs = t.getElementsByTagName('TR'); var c; for (var i=0; trs[i]; i++) { lookup[i] = []; for (var j=0; c = trs[i].cells[j]; j++) { var rowIndex = c.parentNode.rowIndex; var rowSpan = c.rowSpan || 1; var colSpan = c.colSpan || 1; var firstAvailCol; // initalized the matrix in this row if needed. if(typeof(matrix[rowIndex])=="undefined") { matrix[rowIndex] = []; } // Find first available column in the first row for (var k=0; k<matrix[rowIndex].length+1; k++) { if (typeof(matrix[rowIndex][k])=="undefined") { firstAvailCol = k; break; } } lookup[rowIndex][c.cellIndex] = firstAvailCol; for (var k=rowIndex; k<rowIndex+rowSpan; k++) { if(typeof(matrix[k])=="undefined") { matrix[k] = []; } var matrixrow = matrix[k]; for (var l=firstAvailCol; l<firstAvailCol+colSpan; l++) { matrixrow[l] = {cell: c, rowIndex: rowIndex}; } } } } // lets build a little object that has some useful funcitons for this table state. return { cellMatrix: matrix, lookupTable: lookup, // returns the "Real" column number from a passed in cell getRealColFromElement: function (cell) { var row = cell.parentNode.rowIndex; var col = cell.cellIndex; return this.lookupTable[row][col]; }, // returns the "point" to insert at for a square in the perceived row/column getPointForRowAndColumn: function (row,col) { var matrixRow = this.cellMatrix[row]; var ret = 0; // lets look at the matrix again - this time any row that shouldn't be in this row doesn't count. for (var i=0; i<col; i++) { if (matrixRow[i].rowIndex == row) ret++; } return ret; } }; } function scheduleClick(e) { if (e.target.className != 'event') return; //Get useful info before deletion var numRows = e.target.rowSpan; var cellIndex = e.target.cellIndex; var rowIndex = e.target.parentNode.rowIndex; var table = e.target.parentNode.parentNode; var tableState = getTableState(table); var colIndex = tableState.getRealColFromElement(e.target); //Deletion e.target.parentNode.deleteCell(cellIndex); //Insert empty cells in each row for(var i = 0; i < numRows; i++) { var row = table.rows[rowIndex + i]; row.insertCell(tableState.getPointForRowAndColumn(rowIndex+i, colIndex)); } }