From 0aa0b3c1d7744296f8e86483ad69a5939f08e0a9 Mon Sep 17 00:00:00 2001 From: Trinh Ho Date: Fri, 11 Jan 2013 15:40:44 -0800 Subject: [PATCH 1/2] Allow empty spaces to be specifed in template. Allow user to specify empty spaces with '~' instead of single-cell tiles ('.') in template to fill needed empty space. See http://jsfiddle.net/iNeedFat/vA4Nc/1/ to see where this would be necessary. --- src/Template.js | 435 ++++++++++++++++++++++++------------------------ 1 file changed, 218 insertions(+), 217 deletions(-) diff --git a/src/Template.js b/src/Template.js index 078d23d..40ecf94 100644 --- a/src/Template.js +++ b/src/Template.js @@ -1,217 +1,218 @@ - -/* - A grid template specifies the layout of variably sized tiles. A single - cell tile should use the period character. Larger tiles may be created - using any character that is unused by a adjacent tile. Whitespace is - ignored when parsing the rows. - - Examples: - - var simpleTemplate = [ - ' A A . B ', - ' A A . B ', - ' . C C . ', - ] - - var complexTemplate = [ - ' J J . . E E ', - ' . A A . E E ', - ' B A A F F . ', - ' B . D D . H ', - ' C C D D G H ', - ' C C . . G . ', - ]; -*/ - -(function($) { - - // remove whitespace and create 2d array - var parseCells = function(rows) { - var cells = [], - numRows = rows.length, - x, y, row, rowLength, cell; - - // parse each row - for(y = 0; y < numRows; y++) { - - row = rows[y]; - cells[y] = []; - - // parse the cells in a single row - for (x = 0, rowLength = row.length; x < rowLength; x++) { - cell = row[x]; - if (cell !== ' ') { - cells[y].push(cell); - } - } - } - - // TODO: check to make sure the array isn't jagged - - return cells; - }; - - function Rectangle(x, y, width, height) { - this.x = x; - this.y = y; - this.width = width; - this.height = height; - } - - Rectangle.prototype.copy = function() { - return new Rectangle(this.x, this.y, this.width, this.height); - }; - - Tiles.Rectangle = Rectangle; - - // convert a 2d array of cell ids to a list of tile rects - var parseRects = function(cells) { - var rects = [], - numRows = cells.length, - numCols = numRows === 0 ? 0 : cells[0].length, - cell, height, width, x, y, rectX, rectY; - - // make a copy of the cells that we can modify - cells = cells.slice(); - for (y = 0; y < numRows; y++) { - cells[y] = cells[y].slice(); - } - - // iterate through every cell and find rectangles - for (y = 0; y < numRows; y++) { - for(x = 0; x < numCols; x++) { - cell = cells[y][x]; - - // skip cells that are null - if (cell == null) { - continue; - } - - width = 1; - height = 1; - - if (cell !== Tiles.Template.SINGLE_CELL) { - - // find the width by going right until cell id no longer matches - while(width + x < numCols && - cell === cells[y][x + width]) { - width++; - } - - // now find height by going down - while (height + y < numRows && - cell === cells[y + height][x]) { - height++; - } - } - - // null out all cells for the rect - for(rectY = 0; rectY < height; rectY++) { - for(rectX = 0; rectX < width; rectX++) { - cells[y + rectY][x + rectX] = null; - } - } - - // add the rect - rects.push(new Rectangle(x, y, width, height)); - } - } - - return rects; - }; - - Tiles.Template = function(rects, numCols, numRows) { - this.rects = rects; - this.numTiles = this.rects.length; - this.numRows = numRows; - this.numCols = numCols; - }; - - Tiles.Template.prototype.copy = function() { - - var copyRects = [], - len, i; - for (i = 0, len = this.rects.length; i < len; i++) { - copyRects.push(this.rects[i].copy()); - } - - return new Tiles.Template(copyRects, this.numCols, this.numRows); - }; - - // appends another template (assumes both are full rectangular grids) - Tiles.Template.prototype.append = function(other) { - - if (this.numCols !== other.numCols) { - throw 'Appended templates must have the same number of columns'; - } - - // new rects begin after the last current row - var startY = this.numRows, - i, len, rect; - - // copy rects from the other template - for (i = 0, len = other.rects.length; i < len; i++) { - rect = other.rects[i]; - this.rects.push( - new Rectangle(rect.x, startY + rect.y, rect.width, rect.height)); - } - - this.numRows += other.numRows; - }; - - Tiles.Template.fromJSON = function(rows) { - // convert rows to cells and then to rects - var cells = parseCells(rows), - rects = parseRects(cells); - return new Tiles.Template( - rects, - cells.length > 0 ? cells[0].length : 0, - cells.length); - }; - - Tiles.Template.prototype.toJSON = function() { - // for now we'll assume 26 chars is enough (we don't solve graph coloring) - var LABELS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - NUM_LABELS = LABELS.length, - labelIndex = 0, - rows = [], - i, len, rect, x, y, label; - - // fill in single tiles for each cell - for (y = 0; y < this.numRows; y++) { - rows[y] = []; - for (x = 0; x < this.numCols; x++) { - rows[y][x] = Tiles.Template.SINGLE_CELL; - } - } - - // now fill in bigger tiles - for (i = 0, len = this.rects.length; i < len; i++) { - rect = this.rects[i]; - if (rect.width > 1 || rect.height > 1) { - - // mark the tile position with a label - label = LABELS[labelIndex]; - for(y = 0; y < rect.height; y++) { - for(x = 0; x < rect.width; x++) { - rows[rect.y + y][rect.x + x] = label; - } - } - - // advance the label index - labelIndex = (labelIndex + 1) % NUM_LABELS; - } - } - - // turn the rows into strings - for (y = 0; y < this.numRows; y++) { - rows[y] = rows[y].join(''); - } - - return rows; - }; - - // period used to designate a single 1x1 cell tile - Tiles.Template.SINGLE_CELL = '.'; - -})(jQuery); + +/* + A grid template specifies the layout of variably sized tiles. A single + cell tile should use the period character. Larger tiles may be created + using any character that is unused by a adjacent tile. Whitespace is + ignored when parsing the rows. + + Examples: + + var simpleTemplate = [ + ' A A . B ', + ' A A . B ', + ' . C C . ', + ] + + var complexTemplate = [ + ' J J . . E E ', + ' . A A . E E ', + ' B A A F F . ', + ' B . D D . H ', + ' C C D D G H ', + ' C C . . G . ', + ]; +*/ + +(function($) { + + // remove whitespace and create 2d array + var parseCells = function(rows) { + var cells = [], + numRows = rows.length, + x, y, row, rowLength, cell; + + // parse each row + for(y = 0; y < numRows; y++) { + + row = rows[y]; + cells[y] = []; + + // parse the cells in a single row + for (x = 0, rowLength = row.length; x < rowLength; x++) { + cell = row[x]; + if (cell !== ' ') { + cells[y].push(cell); + } + } + } + + // TODO: check to make sure the array isn't jagged + + return cells; + }; + + function Rectangle(x, y, width, height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + Rectangle.prototype.copy = function() { + return new Rectangle(this.x, this.y, this.width, this.height); + }; + + Tiles.Rectangle = Rectangle; + + // convert a 2d array of cell ids to a list of tile rects + var parseRects = function(cells) { + var rects = [], + numRows = cells.length, + numCols = numRows === 0 ? 0 : cells[0].length, + cell, height, width, x, y, rectX, rectY; + + // make a copy of the cells that we can modify + cells = cells.slice(); + for (y = 0; y < numRows; y++) { + cells[y] = cells[y].slice(); + } + + // iterate through every cell and find rectangles + for (y = 0; y < numRows; y++) { + for(x = 0; x < numCols; x++) { + cell = cells[y][x]; + + // skip cells that are null + if (cell == null) { + continue; + } + + width = 1; + height = 1; + + if (cell !== Tiles.Template.SINGLE_CELL) { + + // find the width by going right until cell id no longer matches + while(width + x < numCols && + cell === cells[y][x + width]) { + width++; + } + + // now find height by going down + while (height + y < numRows && + cell === cells[y + height][x]) { + height++; + } + } + + // null out all cells for the rect + for(rectY = 0; rectY < height; rectY++) { + for(rectX = 0; rectX < width; rectX++) { + cells[y + rectY][x + rectX] = null; + } + } + + // add the rect if not empty space padding + if (cell !== Tiles.Template.CELL_PADDING) { + rects.push(new Rectangle(x, y, width, height)); + } + } + } + + return rects; + }; + + Tiles.Template = function(rects, numCols, numRows) { + this.rects = rects; + this.numTiles = this.rects.length; + this.numRows = numRows; + this.numCols = numCols; + }; + + Tiles.Template.prototype.copy = function() { + + var copyRects = [], + len, i; + for (i = 0, len = this.rects.length; i < len; i++) { + copyRects.push(this.rects[i].copy()); + } + + return new Tiles.Template(copyRects, this.numCols, this.numRows); + }; + + // appends another template (assumes both are full rectangular grids) + Tiles.Template.prototype.append = function(other) { + + if (this.numCols !== other.numCols) { + throw 'Appended templates must have the same number of columns'; + } + + // new rects begin after the last current row + var startY = this.numRows, + i, len, rect; + + // copy rects from the other template + for (i = 0, len = other.rects.length; i < len; i++) { + rect = other.rects[i]; + this.rects.push( + new Rectangle(rect.x, startY + rect.y, rect.width, rect.height)); + } + + this.numRows += other.numRows; + }; + + Tiles.Template.fromJSON = function(rows) { + // convert rows to cells and then to rects + var cells = parseCells(rows), + rects = parseRects(cells); + return new Tiles.Template( + rects, + cells.length > 0 ? cells[0].length : 0, + cells.length); + }; + + Tiles.Template.prototype.toJSON = function() { + // for now we'll assume 26 chars is enough (we don't solve graph coloring) + var LABELS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + NUM_LABELS = LABELS.length, + labelIndex = 0, + rows = [], + i, len, rect, x, y, label; + + // fill entire grid with cell paddings + for (y = 0; y < this.numRows; y++) { + rows[y] = []; + for (x = 0; x < this.numCols; x++) { + rows[y][x] = Tiles.Template.CELL_PADDING; + } + } + + // now fill in bigger tiles + for (i = 0, len = this.rects.length; i < len; i++) { + rect = this.rects[i]; + + // mark the tile position with a label + label = (rect.width === 1 && rect.height === 1) ? Tiles.Template.SINGLE_CELL : LABELS[labelIndex]; + for(y = 0; y < rect.height; y++) { + for(x = 0; x < rect.width; x++) { + rows[rect.y + y][rect.x + x] = label; + } + } + + // advance the label index + labelIndex = (labelIndex + 1) % NUM_LABELS; + } + + // turn the rows into strings + for (y = 0; y < this.numRows; y++) { + rows[y] = rows[y].join(''); + } + + return rows; + }; + + // period used to designate a single 1x1 cell tile + Tiles.Template.SINGLE_CELL = '.'; + Tiles.Template.CELL_PADDING = '~'; + +})(jQuery); From 7d61ad8618529fdba4eea7d18bf3a479f96a7e2d Mon Sep 17 00:00:00 2001 From: Trinh Ho Date: Mon, 4 Mar 2013 17:11:50 -0800 Subject: [PATCH 2/2] Adding Smart Rearranging Template Provider --- grunt.js | 1 + src/Grid.js | 2 +- src/SmartArrangeTemplates.js | 79 ++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/SmartArrangeTemplates.js diff --git a/grunt.js b/grunt.js index cac25ab..641ed4c 100644 --- a/grunt.js +++ b/grunt.js @@ -13,6 +13,7 @@ module.exports = function(grunt) { 'src/Tile.js', 'src/Template.js', 'src/UniformTemplates.js', + 'src/SmartArrangeTemplates.js', 'src/Grid.js' ] }, diff --git a/src/Grid.js b/src/Grid.js index e063683..ab12b55 100644 --- a/src/Grid.js +++ b/src/Grid.js @@ -201,7 +201,7 @@ // ensure that we have at least one column numCols = Math.max(1, numCols); - var template = this.templateFactory.get(numCols, targetTiles); + var template = this.templateFactory.get(numCols, targetTiles, this.tiles); if (!template) { // fallback in case the default factory can't generate a good template diff --git a/src/SmartArrangeTemplates.js b/src/SmartArrangeTemplates.js new file mode 100644 index 0000000..a31e711 --- /dev/null +++ b/src/SmartArrangeTemplates.js @@ -0,0 +1,79 @@ +// template provider which returns simple templates with 1x1 tiles +Tiles.SmartArrangeTemplates = { + get: function (numCols, targetTiles, tiles) { + var LABELS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + i, j, iLen, jLen, regExp, d, dx, match, strReplace, tileSegmentStr, strBuilder, templateArr, matchIndex, + DEFAULT_TILE_SIZE = { x: 1, y: 1 }; + + //helper function to build new line of template. + var buildTemplateLineSegment = function (n) { + strBuilder = []; + for (i = 0; i < n; i++) { + strBuilder.push('.'); + } + strBuilder.push('*'); + return strBuilder.join(''); + }; + + //Fall back to uniform templates factory if tiles is null or empty. + if (!tiles || tiles.length === 0) { + return Tiles.UniformTemplates.get(numCols, targetTiles); + } + + //Initialize template string. + var templateString = buildTemplateLineSegment(numCols); + + for (var index = 0, tile; tile = tiles[index]; index++) { + + d = tile.id.size || DEFAULT_TILE_SIZE; + dx = numCols < d.x ? numCols : d.x; + + //Regular Expression use to see if current tile can fit within the current template. + regExp = new RegExp('(\\.{' + dx + '}.{' + (numCols - dx + 1) + '}){' + d.y + '}', 'g'); + + match = null; + //Append to template until we can fit the current tile. + while (!(match = templateString.match(regExp))) { + templateString += buildTemplateLineSegment(numCols); + } + + //Once we find the section of the template that can fit the tile, insert the tile into template. + strReplace = match[0].split(''); + tileSegmentStr = ''; + strBuilder = []; + + //Building the tile horizontal segment. + for (i = 0; i < dx; i++) { + strBuilder.push(LABELS[index % 26]); + } + tileSegmentStr = strBuilder.join(''); + + //Replace the empty space in template with tile segments. + for (i = 0, iLen = strReplace.length; i < iLen; i += numCols + 1) { + for (j = 0, jLen = tileSegmentStr.length; j < jLen; j++) { + strReplace[i + j] = tileSegmentStr[j]; + } + } + + //Rebuilding the template + templateArr = []; + matchIndex = templateString.indexOf(match[0]); + + //Assure that no following small tiles can fit within this space to avoid tiles to be out of order. + templateArr.push(templateString.substring(0, matchIndex).replace(/\./g, '~')); + + templateArr.push(strReplace.join('')); + templateArr.push(templateString.substring(matchIndex + match[0].length)); + + templateString = templateArr.join(''); + } + templateString = templateString.replace(/\*/ig, '').replace(/(\.)/ig, '~'); + //Convert template into json object. + var templateJson = []; + for (i = 0, iLen = templateString.length; i < iLen; i += numCols) { + templateJson.push(templateString.slice(i, i + numCols)); + } + + return Tiles.Template.fromJSON(templateJson); + } +}; \ No newline at end of file