diff --git a/src/neighborhood.js b/src/neighborhood.js index 2b1323a..0ee1878 100644 --- a/src/neighborhood.js +++ b/src/neighborhood.js @@ -1,7 +1,7 @@ -import Subtype from './subtype'; -import NeighborhoodCell from './neighborhoodCell'; -import NeighborhoodDrawer from './neighborhoodDrawer'; -import { Direction } from './tiles'; +import Subtype from "./subtype"; +import NeighborhoodCell from "./neighborhoodCell"; +import NeighborhoodDrawer from "./neighborhoodDrawer"; +import { Direction } from "./tiles"; module.exports = class Neighborhood extends Subtype { constructor(maze, config = {}) { @@ -10,6 +10,7 @@ module.exports = class Neighborhood extends Subtype { this.sheetRows = this.skin_.sheetRows; this.squareSize = this.skin_.squareSize; + this.assetList = []; } /** @@ -18,7 +19,7 @@ module.exports = class Neighborhood extends Subtype { isNeighborhood() { return true; } - + /** * @override */ @@ -46,19 +47,23 @@ module.exports = class Neighborhood extends Subtype { const asset = this.drawer.getBackgroundTileInfo(row, col); if (asset) { + // add asset id to the assetList + this.assetList.push("tileElement" + `${tileId}-asset`); // add assset on top of blank tile if it exists // asset is in format {name: 'sample name', sheet: x, row: y, column: z} const assetHref = this.skin_.assetUrl(asset.sheet); - const [sheetWidth, sheetHeight] = this.getDimensionsForSheet(asset.sheet); + const [sheetWidth, sheetHeight] = this.getDimensionsForSheet( + asset.sheet + ); this.drawer.drawTileHelper( - svg, - [asset.column, asset.row], - row, - col, - `${tileId}-asset`, + svg, + [asset.column, asset.row], + row, + col, + `${tileId}-asset`, assetHref, - sheetWidth, - sheetHeight, + sheetWidth, + sheetHeight, this.squareSize ); } @@ -67,19 +72,25 @@ module.exports = class Neighborhood extends Subtype { }); } - /** - * @override - **/ + /** + * @override + **/ createDrawer(svg) { - this.drawer = new NeighborhoodDrawer(this.maze_.map, this.skin_, svg, this.squareSize, this); + this.drawer = new NeighborhoodDrawer( + this.maze_.map, + this.skin_, + svg, + this.squareSize, + this + ); } /** * Paint the current location of the pegman with id pegmanId. * @param {String} pegmanId - * @param {String} color Color to paint current location. + * @param {String} color Color to paint current location. * Must be hex code or html color. - **/ + **/ addPaint(pegmanId, color) { const col = this.maze_.getPegmanX(pegmanId); const row = this.maze_.getPegmanY(pegmanId); @@ -87,18 +98,20 @@ module.exports = class Neighborhood extends Subtype { const cell = this.getCell(row, col); cell.setColor(color); this.drawer.updateItemImage(row, col, true); + this.drawer.drawAssets(); } /** * Remove paint from the location of the pegman with id pegmanId. * @param {String} pegmanId - **/ - removePaint(pegmanId) { + **/ + removePaint(pegmanId) { const col = this.maze_.getPegmanX(pegmanId); const row = this.maze_.getPegmanY(pegmanId); this.drawer.resetTile(row, col); this.drawer.updateItemImage(row, col, true); + this.drawer.drawAssets(); } /** @@ -146,4 +159,9 @@ module.exports = class Neighborhood extends Subtype { getDimensionsForSheet(sheet) { return [10 * this.squareSize, this.sheetRows[sheet] * this.squareSize]; } -} + + // Retrieve the asset list + getAssetList() { + return this.assetList; + } +}; diff --git a/src/neighborhoodDrawer.js b/src/neighborhoodDrawer.js index 1831771..5aea97d 100644 --- a/src/neighborhoodDrawer.js +++ b/src/neighborhoodDrawer.js @@ -1,6 +1,6 @@ const { SVG_NS } = require("./drawer"); -const Drawer = require('./drawer') -const tiles = require('./tiles'); +const Drawer = require("./drawer"); +const tiles = require("./tiles"); const ROTATE180 = "rotate(180)"; const ROTATENEG90 = "rotate(-90)"; @@ -10,12 +10,12 @@ const CUT = "cut"; const PIE = "pie"; /** - * This is a helper for creating SVG Elements. + * This is a helper for creating SVG Elements. * Groups are created by grid tile, under which paths are nested. These groups * begin with "g" in the id. By checking for this when determining its position * within the hierarchy, we can nest these groups just before the pegman, * ensuring the pegman will appear on top of the paint. - * + * * @param tag representing the element type, 'g' for group, 'path' for paths * @param props representing the details of the element * @param parent the parent it should be nested under @@ -29,13 +29,12 @@ function svgElement(tag, props, parent, id) { node.setAttribute("id", id); } Object.keys(props).map(function (key) { - node.setAttribute(key, props[key]) + node.setAttribute(key, props[key]); }); if (parent && id.startsWith("g")) { - let pegmanElement = parent.getElementsByClassName('pegman-location')[0]; + let pegmanElement = parent.getElementsByClassName("pegman-location")[0]; parent.insertBefore(node, pegmanElement); - } - else if (parent) { + } else if (parent) { parent.appendChild(node); } return node; @@ -46,34 +45,33 @@ function svgElement(tag, props, parent, id) { // / | // +---+ function quarterCircle(size) { - let halfSize = size/2; - let quarterSize = size/4; + let halfSize = size / 2; + let quarterSize = size / 4; return `m${halfSize} ${halfSize}h-${halfSize}c0-${quarterSize} ${quarterSize}-${halfSize} ${halfSize}-${halfSize}z`; } -// Path of the the slice of a square remaining once a quarter circle is +// Path of the the slice of a square remaining once a quarter circle is // removed from it // +----+ // | / -// + +// + function cutout(size) { let halfSize = size / 2; let quarterSize = size / 4; - return `m0 0v${halfSize}c0-${quarterSize} ${quarterSize}-${halfSize} ${halfSize}-${halfSize}z` + return `m0 0v${halfSize}c0-${quarterSize} ${quarterSize}-${halfSize} ${halfSize}-${halfSize}z`; } /** - * This drawer hosts all paint glomming logic. - * A note on layering paint: If paint is applied on top of existing paint + * This drawer hosts all paint glomming logic. + * A note on layering paint: If paint is applied on top of existing paint * (that has not been removed/scraped), portions of the cell might still * display the first layer of paint. Example: [blue][blue] in layer 1 will * create a "pill" visual. If the second cell is then painted [yellow], the * yellow circle will appear on top, with the blue cutouts still visible below. */ module.exports = class NeighborhoodDrawer extends Drawer { - constructor(map, skin, svg, squareSize, neighborhood) { - super(map, '', svg); + super(map, "", svg); this.squareSize = squareSize; this.neighborhood = neighborhood; this.skin_ = skin; @@ -81,24 +79,24 @@ module.exports = class NeighborhoodDrawer extends Drawer { /** * Set the color of this tile back to null, and remove any svg elements - * (colors) that currently exist on this tile and its neighbors. - * - * @param row - * @param col + * (colors) that currently exist on this tile and its neighbors. + * + * @param row + * @param col */ resetTile(row, col) { let neighbors = [ "g" + row + "." + col, "g" + (row - 1) + "." + (col - 1), "g" + row + "." + (col - 1), - "g" + (row - 1) + "." + col + "g" + (row - 1) + "." + col, ]; const cell = this.neighborhood.getCell(row, col); cell.setColor(null); for (const neighbor of neighbors) { var node = document.getElementById(neighbor); if (node) { - node.querySelectorAll('*').forEach(n => n.remove()); + node.querySelectorAll("*").forEach((n) => n.remove()); } } } @@ -117,12 +115,16 @@ module.exports = class NeighborhoodDrawer extends Drawer { getBackgroundTileInfo(row, col) { const cell = this.neighborhood.getCell(row, col); // If the tile has an asset id and it is > 0 (0 is a blank tile and will always be added), - // return the sprite asset. - // Ignore the asset id if this is a start tile or the cell has an original value. + // return the sprite asset. + // Ignore the asset id if this is a start tile or the cell has an original value. // Start tiles will handle placing the pegman separately, // and tiles with a value are paint cans, which are handled as images instead of background tiles. - if (cell.getAssetId() != null && cell.getAssetId() > 0 && - cell.getTile() !== tiles.SquareType.START && !cell.getOriginalValue()) { + if ( + cell.getAssetId() != null && + cell.getAssetId() > 0 && + cell.getTile() !== tiles.SquareType.START && + !cell.getOriginalValue() + ) { return this.getSpriteData(cell); } } @@ -151,43 +153,102 @@ module.exports = class NeighborhoodDrawer extends Drawer { } // Helper method for determining color and path based on neighbors - pathCalculator(subjectCell, adjacent1, adjacent2, diagonal, transform, grid, id) { + pathCalculator( + subjectCell, + adjacent1, + adjacent2, + diagonal, + transform, + grid, + id + ) { let pie = quarterCircle(this.squareSize); let cutOut = cutout(this.squareSize); let tag = "path"; - // Add a quarter circle to the top left corner of the block if there is + // Add a quarter circle to the top left corner of the block if there is // a color value there if (subjectCell) { - svgElement(tag, {d: pie, stroke: subjectCell, transform: transform, fill: subjectCell}, grid, `${id}-${PIE}`); + svgElement( + tag, + { + d: pie, + stroke: subjectCell, + transform: transform, + fill: subjectCell, + }, + grid, + `${id}-${PIE}` + ); } // Add the cutout if the top left corner has a color and an adjacent cell // shares that color, filling in the top left quadrant of the block entirely - if (subjectCell && (subjectCell === adjacent1 || subjectCell === adjacent2)) { - svgElement(tag, {d: cutOut, stroke: subjectCell, transform: transform, fill: subjectCell}, grid, `${id}-${CUT}`); - } - // Otherwise, if the two adjacent corners have the same color, add the + if ( + subjectCell && + (subjectCell === adjacent1 || subjectCell === adjacent2) + ) { + svgElement( + tag, + { + d: cutOut, + stroke: subjectCell, + transform: transform, + fill: subjectCell, + }, + grid, + `${id}-${CUT}` + ); + } + // Otherwise, if the two adjacent corners have the same color, add the // cutout shape with that color - else if (adjacent1 && adjacent1 === adjacent2 && - ((!diagonal || !subjectCell) || subjectCell !== diagonal)) { - svgElement(tag, {d: cutOut, stroke: adjacent1, transform: transform, fill: adjacent1}, grid, `${id}-${CUT}`); + else if ( + adjacent1 && + adjacent1 === adjacent2 && + (!diagonal || !subjectCell || subjectCell !== diagonal) + ) { + svgElement( + tag, + { d: cutOut, stroke: adjacent1, transform: transform, fill: adjacent1 }, + grid, + `${id}-${CUT}` + ); } - // Fill in center corner only if an adjacent cell has the same color, or if + // Fill in center corner only if an adjacent cell has the same color, or if // the diagonal cell is same color and either adjacent is empty // Note: this handles the "clover case", where we want each // cell to "pop" out with its own color if diagonals are matching - else if (subjectCell && (adjacent1 === subjectCell || adjacent2 === subjectCell || - (diagonal === subjectCell && ((!adjacent1 || !adjacent2) || adjacent1 !== adjacent2)))) { - svgElement(tag, {d: cutOut, stroke: subjectCell, transform: transform, fill: subjectCell}, grid, `${id}-${CUT}`); + else if ( + subjectCell && + (adjacent1 === subjectCell || + adjacent2 === subjectCell || + (diagonal === subjectCell && + (!adjacent1 || !adjacent2 || adjacent1 !== adjacent2))) + ) { + svgElement( + tag, + { + d: cutOut, + stroke: subjectCell, + transform: transform, + fill: subjectCell, + }, + grid, + `${id}-${CUT}` + ); } } makeGrid(row, col, svg) { let id = "g" + row + "." + col; - return svgElement("g", { - transform: `translate(${col * this.squareSize + this.squareSize}, - ${row * this.squareSize + this.squareSize})` - }, svg, id); + return svgElement( + "g", + { + transform: `translate(${col * this.squareSize + this.squareSize}, + ${row * this.squareSize + this.squareSize})`, + }, + svg, + id + ); } /** @@ -201,23 +262,36 @@ module.exports = class NeighborhoodDrawer extends Drawer { const tileSheetHeight = this.squareSize; super.drawTileHelper( - svg, - tileSheetLocation, - row, - col, - tileId, - tileSheetHref, - tileSheetWidth, - tileSheetHeight, + svg, + tileSheetLocation, + row, + col, + tileId, + tileSheetHref, + tileSheetWidth, + tileSheetHeight, this.squareSize ); } + // Iterates through all neighborhood assets and inserts them after the pegman + drawAssets() { + let assetList = this.neighborhood.getAssetList(); + var i; + for (i = 0; i < assetList.length; i++) { + let asset = assetList[i]; + let node = document.getElementById(asset); + let pegmanElement = + this.svg_.getElementsByClassName("pegman-location")[0]; + this.svg_.insertBefore(node, pegmanElement); + } + } + /** * @override * This method is used to display the paint and paint buckets. * It has to reprocess the entire grid to get the paint glomming correct, but - * it only updates the bucket at the specified itemRow and itemCol if necessary. + * it only updates the bucket at the specified itemRow and itemCol if necessary. * @param {number} itemRow: row of update * @param {number} itemCol: column of update * @param {boolean} running: if the maze is currently running (not used here, but part of signature of super) @@ -225,22 +299,30 @@ module.exports = class NeighborhoodDrawer extends Drawer { updateItemImage(itemRow, itemCol, running) { let cell = this.map_.getCell(itemRow, itemCol); - // if the cell value has ever been greater than 0, this has been or - // is a paint can square. Ensure it is shown/hidden appropriately + // if the cell value has ever been greater than 0, this has been or + // is a paint can square. Ensure it is shown/hidden appropriately // and with the correct value. if (cell.getOriginalValue() > 0) { - const newValue = cell.getCurrentValue() > 0 ? cell.getCurrentValue() : ''; + const newValue = cell.getCurrentValue() > 0 ? cell.getCurrentValue() : ""; // drawImage_ calls getAsset. If currentValue() is 0, getAsset will return // undefined and the paint can will be hidden. Otherwise we will get the paint can image. - super.drawImage_('', itemRow, itemCol, this.squareSize); - super.updateOrCreateText_('counter', itemRow, itemCol, newValue, this.squareSize, 1, 1, 'karel-counter-text paint'); + super.drawImage_("", itemRow, itemCol, this.squareSize); + super.updateOrCreateText_( + "counter", + itemRow, + itemCol, + newValue, + this.squareSize, + 1, + 1, + "karel-counter-text paint" + ); } // Because this processes a grid of cells at a time, we start at -1 to allow for // a 'padding' row and column with no color. for (let row = -1; row < this.map_.ROWS; row++) { for (let col = -1; col < this.map_.COLS; col++) { - /** * In a grid of four cells: top left, top right, bottom left, bottom right * So if we are painting cell 0, adjacent cells are 1 & 2, diagonal is 3 @@ -252,9 +334,9 @@ module.exports = class NeighborhoodDrawer extends Drawer { */ let cells = [ this.cellColor(row, col), - this.cellColor(row, col+1), - this.cellColor(row+1, col), - this.cellColor(row+1,col+1) + this.cellColor(row, col + 1), + this.cellColor(row + 1, col), + this.cellColor(row + 1, col + 1), ]; if (cells[0] || cells[1] || cells[2] || cells[3]) { @@ -266,10 +348,42 @@ module.exports = class NeighborhoodDrawer extends Drawer { let id3 = row + "." + col + "." + ROTATE0; // Calculate all the svg paths based on neighboring cell colors - this.pathCalculator(cells[0], cells[1], cells[2], cells[3], ROTATE180, grid, id0); - this.pathCalculator(cells[1], cells[0], cells[3], cells[2], ROTATENEG90, grid, id1); - this.pathCalculator(cells[2], cells[0], cells[3], cells[1], ROTATE90, grid, id2); - this.pathCalculator(cells[3], cells[1], cells[2], cells[0], ROTATE0, grid, id3); + this.pathCalculator( + cells[0], + cells[1], + cells[2], + cells[3], + ROTATE180, + grid, + id0 + ); + this.pathCalculator( + cells[1], + cells[0], + cells[3], + cells[2], + ROTATENEG90, + grid, + id1 + ); + this.pathCalculator( + cells[2], + cells[0], + cells[3], + cells[1], + ROTATE90, + grid, + id2 + ); + this.pathCalculator( + cells[3], + cells[1], + cells[2], + cells[0], + ROTATE0, + grid, + id3 + ); } } }