package squidpony.squidgrid.mapping; import squidpony.ArrayTools; import squidpony.squidai.DijkstraMap; import squidpony.squidgrid.Direction; import squidpony.squidmath.*; import java.util.*; /** * A static class that can be used to modify the char[][] dungeons that other generators produce. * Includes various utilities for random floor-finding, but also provides ways to take dungeons that use '#' * for walls and make a copy that uses unicode box drawing characters. * * @author Tommy Ettinger - https://github.com/tommyettinger * @see squidpony.squidgrid.mapping.DungeonGenerator DungeonGenerator uses this class a fair amount * Created by Tommy Ettinger on 4/1/2015. */ public class DungeonUtility { public DungeonUtility() { rng = new StatefulRNG(); } public DungeonUtility(StatefulRNG rng) { this.rng = rng; } public DungeonUtility(RNG rng) { this.rng = new StatefulRNG(new LightRNG(rng.nextLong())); } /** * The random number generator that will be used for all methods in this class with a random component. */ public StatefulRNG rng; /** * Finds a random Coord where the x and y match up to a [x][y] location on map that has '.' as a value. * Uses this class' rng field for pseudo-random number generation. * * @param map a char[][] that should contain a '.' floor tile * @return a Coord that corresponds to a '.' in map, or null if a '.' cannot be found or if map is too small */ public Coord randomFloor(char[][] map) { int width = map.length; int height = map[0].length; if (width < 3 || height < 3) return null; int x = rng.nextInt(width - 2) + 1, y = rng.nextInt(height - 2) + 1; for (int i = 0; i < 20; i++) { if (map[x][y] == '.') { return Coord.get(x, y); } else { x = rng.nextInt(width - 2) + 1; y = rng.nextInt(height - 2) + 1; } } x = 1; y = 1; if (map[x][y] == '.') return Coord.get(x, y); while (map[x][y] != '.') { x += 1; if (x >= width - 1) { x = 1; y += 1; } if (y >= height - 1) return null; } return Coord.get(x, y); } /** * Finds a random Coord where the x and y match up to a [x][y] location that is encoded as "on" in packed. * This is useful when you have used {@code DungeonUtility.packedFloors(char[][] map)} to encode all floors in map, * or {@code CoordPacker.pack(char[][] map, char... yes)} to encode all cells in a char[][] map that match a * particular type, like '.' for floors or '~' for deep water, and want to efficiently get one randomly-chosen tile * from it. Calling pack() is likely slightly less efficient than using randomFloor(), but it only needs to be done * once per map and cell type, and this method should be substantially more efficient when the type of cell is * uncommon on the map. * Uses this class' rng field for pseudo-random number generation. * * @param packed a packed array produced by CoordPacker encoding the cells to choose from as "on" * @return a Coord that corresponds to a '.' in map, or (-1, -1) if a '.' cannot be found or if map is too small */ public Coord randomCell(short[] packed) { return CoordPacker.singleRandom(packed, rng); } /** * A convenience wrapper for getting a packed-data representation of all floors ('.') in map, for randomCell(). * If you want other chars or more chars than just the period, you can use CoordPacker.pack() with a char[][] map * and one or more chars to find as the parameters. This is the same as calling {@code CoordPacker.pack(map, '.')}. * * @param map a char[][] that uses '.' to represent floors * @return all floors in map in packed data format (a special short array) that can be given to randomCell() */ public static short[] packedFloors(char[][] map) { return CoordPacker.pack(map, '.'); } /** * Finds a random Coord where the x and y match up to a [x][y] location on map that has the same value as the * parameter tile. Uses this class' rng field for pseudo-random number generation. * * @param map a char[][] that should contain the desired tile * @param tile the char to search for * @return a Coord that corresponds to a map element equal to tile, or null if tile cannot be found or if map is too small. */ public Coord randomMatchingTile(char[][] map, char tile) { int width = map.length; int height = map[0].length; if (width < 3 || height < 3) return null; int x = rng.nextInt(width - 2) + 1, y = rng.nextInt(height - 2) + 1; for (int i = 0; i < 30; i++) { if (map[x][y] == tile) { return Coord.get(x, y); } else { x = rng.nextInt(width - 2) + 1; y = rng.nextInt(height - 2) + 1; } } x = 1; y = 1; if (map[x][y] == tile) return Coord.get(x, y); while (map[x][y] != tile) { x += 1; if (x >= width - 1) { x = 1; y += 1; } if (y >= height - 1) return null; } return Coord.get(x, y); } /** * Gets a random Coord that is adjacent to start, validating whether the position can exist on the given map. * Adjacency defaults to four-way cardinal directions unless eightWay is true, in which case it uses Chebyshev. * This can step into walls, and should NOT be used for movement. It is meant for things like sound that can * exist in walls, or for assigning decor to floors or walls that are adjacent to floors. * * @param map a char[][] map that this will only use for its width and height; contents are ignored * @param start the starting position * @param eightWay true to choose a random orthogonal or diagonal direction; false to only choose from orthogonal * @return a Coord that is adjacent to start on the map, or null if start is off the map or the map is very small */ public Coord randomStep(char[][] map, Coord start, boolean eightWay) { int width = map.length; int height = map[0].length; if (width < 3 || height < 3 || start.x < 0 || start.y < 0 || start.x > width - 1 || start.y > height - 1) return null; Coord stepped = Coord.get(start.x, start.y); if (eightWay) { int mv = rng.nextInt(9); return Coord.get(Math.min(Math.max(0, stepped.x + (mv % 3) - 1), height - 1), Math.min(Math.max(0, stepped.y + (mv / 3) - 1), height - 1)); } else { int mv = rng.nextInt(5); switch (mv) { case 0: return Coord.get(Math.min(Math.max(0, stepped.x - 1), height - 1), stepped.y); case 1: return Coord.get(Math.min(Math.max(0, stepped.x + 1), height - 1), stepped.y); case 2: return Coord.get(stepped.x, Math.min(Math.max(0, stepped.y - 1), height - 1)); case 3: return Coord.get(stepped.x, Math.min(Math.max(0, stepped.y + 1), height - 1)); default: return stepped; } } } /** * Finds a random Coord where the x and y match up to a [x][y] location on map that has '.' as a value, * and a square of cells extending in the positive x and y directions with a side length of size must also have * '.' as their values. * Uses this class' rng field for pseudo-random number generation. * * @param map a char[][] that should contain at least one floor represented by '.' * @param size the side length of a square that must be completely filled with floors for this to return it * @return a Coord that corresponds to a '.' in map, or null if a '.' cannot be found or if map is too small. */ public Coord randomFloorLarge(char[][] map, int size) { int width = map.length; int height = map[0].length; if (width < size + 2 || height < size + 2) return null; int x = rng.nextInt(width - size), y = rng.nextInt(height - size); CELL: for (int i = 0; i < 20; i++, x = rng.nextInt(width - size), y = rng.nextInt(height - size)) { if (map[x][y] == '.') { for (int j = 0; j < size; j++) { for (int k = 0; k < size; k++) { if (map[x + j][y + k] != '.') continue CELL; } } return Coord.get(x, y); } } x = 1; y = 1; SLOW: while (true) { x += 1; if (x >= width - size) { x = 1; y += 1; } if (y >= height - size) return null; if (map[x][y] == '.') { for (int j = 0; j < size; j++) { for (int k = 0; k < size; k++) { if (map[x + j][y + k] != '.') continue SLOW; } } return Coord.get(x, y); } } } /** * Takes a char[][] dungeon map that uses '#' to represent walls, and returns a new char[][] that uses unicode box * drawing characters to draw straight, continuous lines for walls, filling regions between walls (that were * filled with more walls before) with space characters, ' '. If the lines "point the wrong way," such as having * multiple horizontally adjacent vertical lines where there should be horizontal lines, call transposeLines() on * the returned map, which will keep the dimensions of the map the same and only change the line chars. You will * also need to call transposeLines if you call hashesToLines on a map that already has "correct" line-drawing * characters, which means hashesToLines should only be called on maps that use '#' for walls. If you have a * jumbled map that contains two or more of the following: "correct" line-drawing characters, "incorrect" * line-drawing characters, and '#' characters for walls, you can reset by calling linesToHashes() and then * potentially calling hashesToLines() again. * * @param map a 2D char array indexed with x,y that uses '#' for walls * @return a copy of the map passed as an argument with box-drawing characters replacing '#' walls */ public static char[][] hashesToLines(char[][] map) { return hashesToLines(map, false); } private static final char[] wallLookup = new char[] { '#', '│', '─', '└', '│', '│', '┌', '├', '─', '┘', '─', '┴', '┐', '┤', '┬', '┼', '#', '│', '─', '└', '│', '│', '┌', '├', '─', '┘', '─', '┴', '┐', '┤', '┬', '┼', '#', '│', '─', '└', '│', '│', '┌', '├', '─', '┘', '─', '┴', '┐', '┤', '┬', '┼', '#', '│', '─', '└', '│', '│', '┌', '│', '─', '┘', '─', '┴', '┐', '┤', '┬', '┤', '#', '│', '─', '└', '│', '│', '┌', '├', '─', '┘', '─', '┴', '┐', '┤', '┬', '┼', '#', '│', '─', '└', '│', '│', '┌', '├', '─', '┘', '─', '┴', '┐', '┤', '┬', '┼', '#', '│', '─', '└', '│', '│', '┌', '├', '─', '┘', '─', '┴', '┐', '┤', '─', '┴', '#', '│', '─', '└', '│', '│', '┌', '│', '─', '┘', '─', '┴', '┐', '┤', '─', '┘', '#', '│', '─', '└', '│', '│', '┌', '├', '─', '┘', '─', '┴', '┐', '┤', '┬', '┼', '#', '│', '─', '└', '│', '│', '┌', '├', '─', '┘', '─', '─', '┐', '┤', '┬', '┬', '#', '│', '─', '└', '│', '│', '┌', '├', '─', '┘', '─', '┴', '┐', '┤', '┬', '┼', '#', '│', '─', '└', '│', '│', '┌', '│', '─', '┘', '─', '─', '┐', '┤', '┬', '┐', '#', '│', '─', '└', '│', '│', '┌', '├', '─', '┘', '─', '┴', '┐', '│', '┬', '├', '#', '│', '─', '└', '│', '│', '┌', '├', '─', '┘', '─', '─', '┐', '│', '┬', '┌', '#', '│', '─', '└', '│', '│', '┌', '├', '─', '┘', '─', '┴', '┐', '│', '─', '└', '#', '│', '─', '└', '│', '│', '┌', '│', '─', '┘', '─', '─', '┐', '│', '─', '\1' }; /** * Takes a char[][] dungeon map that uses '#' to represent walls, and returns a new char[][] that uses unicode box * drawing characters to draw straight, continuous lines for walls, filling regions between walls (that were * filled with more walls before) with space characters, ' '. If keepSingleHashes is true, then '#' will be used if * a wall has no orthogonal wall neighbors; if it is false, then a horizontal line will be used for stand-alone * wall cells. If the lines "point the wrong way," such as having multiple horizontally adjacent vertical lines * where there should be horizontal lines, call transposeLines() on the returned map, which will keep the dimensions * of the map the same and only change the line chars. You will also need to call transposeLines if you call * hashesToLines on a map that already has "correct" line-drawing characters, which means hashesToLines should only * be called on maps that use '#' for walls. If you have a jumbled map that contains two or more of the following: * "correct" line-drawing characters, "incorrect" line-drawing characters, and '#' characters for walls, you can * reset by calling linesToHashes() and then potentially calling hashesToLines() again. * * @param map a 2D char array indexed with x,y that uses '#' for walls * @param keepSingleHashes true if walls that are not orthogonally adjacent to other walls should stay as '#' * @return a copy of the map passed as an argument with box-drawing characters replacing '#' walls */ public static char[][] hashesToLines(char[][] map, boolean keepSingleHashes) { int width = map.length + 2; int height = map[0].length + 2; char[][] dungeon = new char[width][height]; for (int i = 1; i < width - 1; i++) { System.arraycopy(map[i - 1], 0, dungeon[i], 1, height - 2); } for (int i = 0; i < width; i++) { dungeon[i][0] = '\1'; dungeon[i][height - 1] = '\1'; } for (int i = 0; i < height; i++) { dungeon[0][i] = '\1'; dungeon[width - 1][i] = '\1'; } for (int x = 1; x < width - 1; x++) { for (int y = 1; y < height - 1; y++) { if (map[x - 1][y - 1] == '#') { int q = 0; q |= (y <= 1 || map[x - 1][y - 2] == '#' || map[x - 1][y - 2] == '+' || map[x - 1][y - 2] == '/') ? 1 : 0; q |= (x >= width - 2 || map[x][y - 1] == '#' || map[x][y - 1] == '+' || map[x][y - 1] == '/') ? 2 : 0; q |= (y >= height - 2 || map[x - 1][y] == '#' || map[x - 1][y] == '+' || map[x - 1][y] == '/') ? 4 : 0; q |= (x <= 1 || map[x - 2][y - 1] == '#' || map[x - 2][y - 1] == '+' || map[x - 2][y - 1] == '/') ? 8 : 0; q |= (y <= 1 || x >= width - 2 || map[x][y - 2] == '#' || map[x][y - 2] == '+' || map[x][y - 2] == '/') ? 16 : 0; q |= (y >= height - 2 || x >= width - 2 || map[x][y] == '#' || map[x][y] == '+' || map[x][y] == '/') ? 32 : 0; q |= (y >= height - 2 || x <= 1 || map[x - 2][y] == '#' || map[x - 2][y] == '+' || map[x - 2][y] == '/') ? 64 : 0; q |= (y <= 1 || x <= 1 || map[x - 2][y - 2] == '#' || map[x - 2][y - 2] == '+' || map[x - 2][y - 2] == '/') ? 128 : 0; if (!keepSingleHashes && wallLookup[q] == '#') { dungeon[x][y] = '─'; } else { dungeon[x][y] = wallLookup[q]; } } } } char[][] portion = new char[width - 2][height - 2]; for (int i = 1; i < width - 1; i++) { for (int j = 1; j < height - 1; j++) { switch (dungeon[i][j]) { case '\1': portion[i - 1][j - 1] = ' '; break; default: // ┼┌┘ portion[i - 1][j - 1] = dungeon[i][j]; } } } return portion; } /** * Reverses most of the effects of hashesToLines(). The only things that will not be reversed are the placement of * space characters in unreachable wall-cells-behind-wall-cells, which remain as spaces. This is useful if you * have a modified map that contains wall characters of conflicting varieties, as described in hashesToLines(). * * @param map a 2D char array indexed with x,y that uses box-drawing characters for walls * @return a copy of the map passed as an argument with '#' replacing box-drawing characters for walls */ public static char[][] linesToHashes(char[][] map) { int width = map.length; int height = map[0].length; char[][] portion = new char[width][height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { switch (map[i][j]) { case '\1': case '├': case '┤': case '┴': case '┬': case '┌': case '┐': case '└': case '┘': case '│': case '─': case '┼': portion[i][j] = '#'; break; default: portion[i][j] = map[i][j]; } } } return portion; } /** * If you call hashesToLines() on a map that uses [y][x] conventions instead of [x][y], it will have the lines not * connect as you expect. Use this function to change the directions of the box-drawing characters only, without * altering the dimensions in any way. This returns a new char[][], instead of modifying the parameter in place. * transposeLines is also needed if the lines in a map have become transposed when they were already correct; * calling this method on an incorrectly transposed map will change the directions on all of its lines. * * @param map a 2D char array indexed with y,x that uses box-drawing characters for walls * @return a copy of map that uses box-drawing characters for walls that will be correct when indexed with x,y */ public static char[][] transposeLines(char[][] map) { int width = map[0].length; int height = map.length; char[][] portion = new char[height][width]; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { switch (map[i][j]) { case '\1': portion[i][j] = ' '; break; case '├': portion[i][j] = '┬'; break; case '┤': portion[i][j] = '┴'; break; case '┴': portion[i][j] = '┤'; break; case '┬': portion[i][j] = '├'; break; case '┐': portion[i][j] = '└'; break; case '└': portion[i][j] = '┐'; break; case '│': portion[i][j] = '─'; break; case '─': portion[i][j] = '│'; break; // case '├ ┤ ┴ ┬ ┌ ┐ └ ┘ │ ─': default: // ┼┌┘ portion[i][j] = map[i][j]; } } } return portion; } /** * When a map is generated by DungeonGenerator with addDoors enabled, different chars are used for vertical and * horizontal doors ('+' for vertical and '/' for horizontal). This makes all doors '+', which is useful if you * want '/' to be used for a different purpose and/or to distinguish open and closed doors. * * @param map a char[][] that may have both '+' and '/' for doors * @return a char[][] that only uses '+' for all doors */ public static char[][] closeDoors(char[][] map) { int width = map.length; int height = map[0].length; char[][] portion = new char[width][height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { if (map[i][j] == '/') portion[i][j] = '+'; else portion[i][j] = map[i][j]; } } return portion; } /** * When a map is generated by DungeonGenerator with addDoors enabled, different chars are used for vertical and * horizontal doors ('+' for vertical and '/' for horizontal). This makes all doors '/', which is useful if you * want '+' to be used for a different purpose and/or to distinguish open and closed doors. * * @param map a char[][] that may have both '+' and '/' for doors * @return a char[][] that only uses '/' for all doors */ public static char[][] openDoors(char[][] map) { int width = map.length; int height = map[0].length; char[][] portion = new char[width][height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { if (map[i][j] == '+') portion[i][j] = '/'; else portion[i][j] = map[i][j]; } } return portion; } /** * Takes a char[][] dungeon map and returns a copy with all box drawing chars, special placeholder chars, or '#' * chars changed to '#' and everything else changed to '.' . * * @param map a char[][] with different characters that can be simplified to "wall" or "floor" * @return a copy of map with all box-drawing, placeholder, wall or space characters as '#' and everything else '.' */ public static char[][] simplifyDungeon(char[][] map) { int width = map.length; int height = map[0].length; char[][] portion = new char[width][height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { switch (map[i][j]) { case '\1': case '├': case '┤': case '┴': case '┬': case '┌': case '┐': case '└': case '┘': case '│': case '─': case '┼': case ' ': case '#': portion[i][j] = '#'; break; default: portion[i][j] = '.'; } } } return portion; } /** * Takes a dungeon map with either '#' as the only wall character or the unicode box drawing characters used by * hashesToLines(), and returns a new char[][] dungeon map with two characters per cell, mostly filling the spaces * next to non-walls with space characters, and only doing anything different if a box-drawing character would * continue into an adjacent cell, or if a '#' wall needs another '#' wall next to it. The recommended approach is * to keep both the original non-double-width map and the newly-returned double-width map, since the single-width * maps can be used more easily for pathfinding. If you need to undo this function, call unDoubleWidth(). * * @param map a char[][] that uses either '#' or box-drawing characters for walls, but one per cell * @return a widened copy of map that uses two characters for every cell, connecting box-drawing chars correctly */ public static char[][] doubleWidth(char[][] map) { int width = map.length; int height = map[0].length; char[][] paired = new char[width * 2][height]; for (int y = 0; y < height; y++) { for (int x = 0, px = 0; x < width; x++, px += 2) { paired[px][y] = map[x][y]; switch (paired[px][y]) { // case '┼ ├ ┤ ┴ ┬ ┌ ┐ └ ┘ │ ─' case '┼': case '├': case '┴': case '┬': case '┌': case '└': case '─': paired[px + 1][y] = '─'; break; case '#': paired[px + 1][y] = '#'; break; default: paired[px + 1][y] = ' '; break; /* case '.': case '┤': case '┐': case '┘': case '│': */ } } } return paired; } /** * Takes a dungeon map that uses two characters per cell, and condenses it to use only the left (lower index) * character in each cell. This should (probably) only be called on the result of doubleWidth(), and will throw an * exception if called on a map with an odd number of characters for width, such as "#...#" . * * @param map a char[][] that has been widened by doubleWidth() * @return a copy of map that uses only one char per cell */ public static char[][] unDoubleWidth(char[][] map) { int width = map.length; int height = map[0].length; if (width % 2 != 0) throw new IllegalArgumentException("Argument must be a char[width][height] with an even width."); char[][] unpaired = new char[width / 2][height]; for (int y = 0; y < height; y++) { for (int x = 0, px = 0; px < width; x++, px += 2) { unpaired[x][y] = map[px][y]; } } return unpaired; } /** * Produces an int[][] that can be used with any palette of your choice for methods in SquidPanel or for your own * rendering method. 1 is used as a default and for tiles with nothing in them; if the background is black, then * white would make sense as this default. Other indices used are 2 for walls (this doesn't care if the walls are * hashes or lines), 3 for floors (usually '.'), 4 for doors ('+' and '/' in the map), 5 for water, 6 for traps, and * 20 for grass. * * @param map a char[][] containing foreground characters that you want foreground palette indices for * @return a 2D array of ints that can be used as indices into a palette; palettes are available in related modules */ public static int[][] generatePaletteIndices(char[][] map) { int width = map.length; int height = map[0].length; int[][] portion = new int[width][height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { switch (map[i][j]) { case '\1': case '├': case '┤': case '┴': case '┬': case '┌': case '┐': case '└': case '┘': case '│': case '─': case '┼': case '#': portion[i][j] = 2; break; case '.': case ':': portion[i][j] = 3; break; case '+': case '/': portion[i][j] = 4; break; case ',': case '~': portion[i][j] = 5; break; case '"': portion[i][j] = 20; break; case '^': portion[i][j] = 6; break; default: portion[i][j] = 1; } } } return portion; } /** * Produces an int[][] that can be used with any palette of your choice for methods in SquidPanel or for your own * rendering method. 1 is used as a default and for tiles with nothing in them; if the background is black, then * white would make sense as this default. Other indices used are 2 for walls (this doesn't care if the walls are * hashes or lines), 3 for floors (usually '.'), 4 for doors ('+' and '/' in the map), 5 for water, 6 for traps, and * 20 for grass. * * @param map a char[][] containing foreground characters that you want foreground palette indices for * @return a 2D array of ints that can be used as indices into a palette; palettes are available in related modules */ public static int[][] generatePaletteIndices(char[][] map, char deepChar, int deepIndex, char shallowChar, int shallowIndex) { int width = map.length; int height = map[0].length; int[][] portion = new int[width][height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { switch (map[i][j]) { case '\1': case '├': case '┤': case '┴': case '┬': case '┌': case '┐': case '└': case '┘': case '│': case '─': case '┼': case '#': portion[i][j] = 2; break; case '.': case ':': portion[i][j] = 3; break; case '+': case '/': portion[i][j] = 4; break; case ',': case '~': portion[i][j] = 5; break; case '"': portion[i][j] = 20; break; case '^': portion[i][j] = 6; break; default: if (map[i][j] == deepChar) portion[i][j] = deepIndex; else if (map[i][j] == shallowChar) portion[i][j] = shallowIndex; else portion[i][j] = 1; } } } return portion; } /** * Produces an int[][] that can be used with any palette of your choice for methods in SquidPanel or for your own * rendering method, but meant for the background palette. This will produce 0 for most characters, but deep water * (represented by '~') will produce 24 (in the default palette, this is dark blue-green), shallow water * (represented by ',') will produce 23 (medium blue-green), and grass (represented by '"') will produce 21 (dark * green). If you use SquidLayers, you can cause the lightness of water and grass to vary as if currents or wind * are moving their surface using getLightnessModifiers() and a frame count argument. * * @param map a char[][] containing foreground characters that you want background palette indices for * @return a 2D array of ints that can be used as indices into a palette; palettes are available in related modules */ public static int[][] generateBGPaletteIndices(char[][] map) { int width = map.length; int height = map[0].length; int[][] portion = new int[width][height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { switch (map[i][j]) { case '\1': case '├': case '┤': case '┴': case '┬': case '┌': case '┐': case '└': case '┘': case '│': case '─': case '┼': case '#': portion[i][j] = 0; break; case '.': portion[i][j] = 0; break; case ':': portion[i][j] = 35; break; case '+': case '/': portion[i][j] = 0; break; case ',': portion[i][j] = 23; break; case '~': portion[i][j] = 24; break; case '"': portion[i][j] = 21; break; case '^': portion[i][j] = 0; break; default: portion[i][j] = 0; } } } return portion; } /** * Produces an int[][] that can be used with any palette of your choice for methods in SquidPanel or for your own * rendering method, but meant for the background palette. This will produce 0 for most characters, but deep water * (represented by '~') will produce 24 (in the default palette, this is dark blue-green), shallow water * (represented by ',') will produce 23 (medium blue-green), and grass (represented by '"') will produce 21 (dark * green). If you use SquidLayers, you can cause the lightness of water and grass to vary as if currents or wind * are moving their surface using getLightnessModifiers() and a frame count argument. * * @param map a char[][] containing foreground characters that you want background palette indices for * @return a 2D array of ints that can be used as indices into a palette; palettes are available in related modules */ public static int[][] generateBGPaletteIndices(char[][] map, char deepChar, int deepIndex, char shallowChar, int shallowIndex) { int width = map.length; int height = map[0].length; int[][] portion = new int[width][height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { switch (map[i][j]) { case '\1': case '├': case '┤': case '┴': case '┬': case '┌': case '┐': case '└': case '┘': case '│': case '─': case '┼': case '#': portion[i][j] = 0; break; case '.': portion[i][j] = 0; break; case ':': portion[i][j] = 35; break; case '+': case '/': portion[i][j] = 0; break; case ',': portion[i][j] = 23; break; case '~': portion[i][j] = 24; break; case '"': portion[i][j] = 21; break; case '^': portion[i][j] = 0; break; default: if (map[i][j] == deepChar) portion[i][j] = deepIndex; else if (map[i][j] == shallowChar) portion[i][j] = shallowIndex; else portion[i][j] = 0; } } } return portion; } /** * Produces an int[][] that can be used with SquidLayers to alter the background colors. * * @param map a char[][] that you want to be find background lightness modifiers for * @return a 2D array of lightness values from -255 to 255 but usually close to 0; can be passed to SquidLayers */ public static int[][] generateLightnessModifiers(char[][] map) { int width = map.length; int height = map[0].length; int[][] portion = new int[width][height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { switch (map[i][j]) { case '\1': case '├': case '┤': case '┴': case '┬': case '┌': case '┐': case '└': case '┘': case '│': case '─': case '┼': case '#': portion[i][j] = 30; break; case '.': portion[i][j] = 0; break; case ':': portion[i][j] = -15; break; case '+': case '/': portion[i][j] = -10; break; case ',': portion[i][j] = (int) (70 * (PerlinNoise.noise(i* 1.5, j* 1.5) / 2.5 - 0.45)); break; case '~': portion[i][j] = (int) (100 * (PerlinNoise.noise(i* 1.5, j* 1.5) / 2.5 - 0.65)); break; case '"': portion[i][j] = (int) (75 * (PerlinNoise.noise(i* 1.5, j* 1.5) / 4.0 - 1.5)); break; case '^': portion[i][j] = 40; break; default: portion[i][j] = 0; } } } return portion; } /** * Produces an int[][] that can be used with SquidLayers to alter the background colors, accepting a parameter for * animation frame if rippling water and waving grass using Perlin Noise are desired. * * @param map a char[][] that you want to be find background lightness modifiers for * @param frame a counter that typically should increase by between 10.0 and 20.0 each second; higher numbers make * water and grass move more * @return a 2D array of lightness values from -255 to 255 but usually close to 0; can be passed to SquidLayers */ public static int[][] generateLightnessModifiers(char[][] map, double frame) { int width = map.length; int height = map[0].length; int[][] portion = new int[width][height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { switch (map[i][j]) { case '\1': case '├': case '┤': case '┴': case '┬': case '┌': case '┐': case '└': case '┘': case '│': case '─': case '┼': case '#': portion[i][j] = 30; break; case '.': portion[i][j] = 0; break; case ':': portion[i][j] = -15; break; case '+': case '/': portion[i][j] = -10; break; case ',': portion[i][j] = (int) (70 * (PerlinNoise.noise(i* 1.5, j* 1.5, frame * 0.4) / 2.5 - 0.45)); break; case '~': portion[i][j] = (int) (100 * (PerlinNoise.noise(i* 1.5, j* 1.5, frame * 0.4) / 2.5 - 0.65)); break; case '"': portion[i][j] = (int) (75 * (PerlinNoise.noise(i* 1.5, j* 1.5, frame * 0.45) / 4.0 - 1.5)); break; case '^': portion[i][j] = 40; break; default: portion[i][j] = 0; } } } return portion; } /** * Produces an int[][] that can be used with SquidLayers to alter the background colors, accepting a parameter for * animation frame if rippling water and waving grass using Perlin Noise are desired. Also allows additional chars * to be treated like deep and shallow water regarding the ripple animation. * * @param map a char[][] that you want to be find background lightness modifiers for * @param frame a counter that typically should increase by between 10.0 and 20.0 each second; higher numbers make * water and grass move more * @param deepLiquid a char that will be treated like deep water when animating ripples * @param shallowLiquid a char that will be treated like shallow water when animating ripples * @return a 2D array of lightness values from -255 to 255 but usually close to 0; can be passed to SquidLayers */ public static int[][] generateLightnessModifiers(char[][] map, double frame, char deepLiquid, char shallowLiquid) { int width = map.length; int height = map[0].length; int[][] portion = new int[width][height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { switch (map[i][j]) { case '\1': case '├': case '┤': case '┴': case '┬': case '┌': case '┐': case '└': case '┘': case '│': case '─': case '┼': case '#': portion[i][j] = 30; break; case '.': portion[i][j] = 0; break; case ':': portion[i][j] = -15; break; case '+': case '/': portion[i][j] = -10; break; case ',': portion[i][j] = (int) (70 * (PerlinNoise.noise(i* 1.5, j* 1.5, frame * 0.4) / 2.5 - 0.45)); break; case '~': portion[i][j] = (int) (100 * (PerlinNoise.noise(i* 1.5, j* 1.5, frame * 0.4) / 2.5 - 0.65)); break; case '"': portion[i][j] = (int) (75 * (PerlinNoise.noise(i* 1.5, j* 1.5, frame * 0.45) / 4.0 - 1.5)); break; case '^': portion[i][j] = 40; break; default: if (map[i][j] == deepLiquid) portion[i][j] = (int) (180 * (PerlinNoise.noise(i * 1.2, j * 1.2, frame / 21.0) / 2.5 - 0.7)); else if (map[i][j] == shallowLiquid) portion[i][j] = (int) (110 * (PerlinNoise.noise(i* 1.5, j* 1.5, frame / 30.0) / 2.5 - 0.45)); else portion[i][j] = 0; } } } return portion; } /** * Given a char[][] for the map, produces a double[][] that can be used with FOV.calculateFOV(). It expects any * doors to be represented by '+' if closed or '/' if open (which can be caused by calling * DungeonUtility.closeDoors() ), any walls to be '#' or line drawing characters, and it doesn't care what other * chars are used (only doors, including open ones, and walls obscure light and thus have a resistance by default). * * @param map a dungeon, width by height, with any closed doors as '+' and open doors as '/' as per closeDoors() * @return a resistance map suitable for use with the FOV class */ public static double[][] generateResistances(char[][] map) { int width = map.length; int height = map[0].length; double[][] portion = new double[width][height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { switch (map[i][j]) { case '\1': case '├': case '┤': case '┴': case '┬': case '┌': case '┐': case '└': case '┘': case '│': case '─': case '┼': case '#': portion[i][j] = 1.0; break; case '/': case '"': portion[i][j] = 0.15; break; case '+': portion[i][j] = 0.95; break; case '.': case ',': case '~': case '^': default: portion[i][j] = 0.0; } } } return portion; } /** * Given a char[][] for the map, produces a double[][] that can be used with FOV.calculateFOV(), but does not treat * any cells as partly transparent, only fully-blocking or fully-permitting light. This is mainly useful if you * expect the FOV radius to be very high or (effectively) infinite, since anything less than complete blockage would * be passed through by infinite-radius FOV. This expects any doors to be represented by '+' if closed or '/' if * open (most door placement defaults to a mix of '+' and '/', so by calling * {@link DungeonUtility#closeDoors(char[][])} you can close all doors at the start), and any walls to be '#' or * line drawing characters. This will assign 1.0 resistance to walls and closed doors or 0.0 for any other cell. * * @param map a dungeon, width by height, with any closed doors as '+' and open doors as '/' as per closeDoors() * @return a resistance map suitable for use with the FOV class, but with no partially transparent cells */ public static double[][] generateSimpleResistances(char[][] map) { int width = map.length; int height = map[0].length; double[][] portion = new double[width][height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { switch (map[i][j]) { case '\1': case '├': case '┤': case '┴': case '┬': case '┌': case '┐': case '└': case '┘': case '│': case '─': case '┼': case '#': case '+': portion[i][j] = 1.0; break; default: portion[i][j] = 0.0; } } } return portion; } /** * Given a char[][] for the map, a Map of Character keys to Double values that will be used to determine costs, and * a double value for unhandled characters, produces a double[][] that can be used as a costMap by DijkstraMap. It * expects any doors to be represented by '+' if closed or '/' if open (which can be caused by calling * DungeonUtility.closeDoors() ) and any walls to be '#' or line drawing characters. In the parameter costs, there * does not need to be an entry for '#' or any box drawing characters, but if one is present for '#' it will apply * that cost to both '#' and all box drawing characters, and if one is not present it will default to a very high * number. For any other entry in costs, a char in the 2D char array that matches the key will correspond * (at the same x,y position in the returned 2D double array) to that key's value in costs. If a char is used in the * map but does not have a corresponding key in costs, it will be given the value of the parameter defaultValue. * <p/> * The values in costs are multipliers, so should not be negative, should only be 0.0 in cases where you want * infinite movement across all adjacent squares of that kind, should be higher than 1.0 for difficult terrain (2.0 * and 3.0 are reasonable), should be between 0.0 and 1.0 for easy terrain, and should be 1.0 for normal terrain. * If a cell should not be possible to enter for this character, 999.0 should be a reasonable value for a cost. * <p/> * An example use for this would be to make a creature unable to enter any non-water cell (like a fish), * unable to enter doorways (like some mythological versions of vampires), or to make a wheeled vehicle take more * time to move across rubble or rough terrain. * <p/> * A potentially common case that needs to be addressed is NPC movement onto staircases in games that have them; * some games may find it desirable for NPCs to block staircases and others may not, but in either case you should * give both '>' and '<', the standard characters for staircases, the same value in costs. * * @param map a dungeon, width by height, with any closed doors as '+' and open doors as '/' as per closeDoors() . * @param costs a Map of Character keys representing possible elements in map, and Double values for their cost. * @param defaultValue a double that will be used as the cost for any characters that don't have a key in costs. * @return a cost map suitable for use with DijkstraMap */ public static double[][] generateCostMap(char[][] map, Map<Character, Double> costs, double defaultValue) { int width = map.length; int height = map[0].length; double[][] portion = new double[width][height]; char current; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { current = map[i][j]; if (costs.containsKey(current)) { portion[i][j] = costs.get(current); } else { switch (current) { case '\1': case '├': case '┤': case '┴': case '┬': case '┌': case '┐': case '└': case '┘': case '│': case '─': case '┼': case '#': portion[i][j] = (costs.containsKey('#')) ? costs.get('#') : squidpony.squidai.DijkstraMap.WALL; break; default: portion[i][j] = defaultValue; } } } } return portion; } /** * Given a char[][] for the map, a Map of Character keys to Double values that will be used to determine costs, and * a double value for unhandled characters, produces a double[][] that can be used as a map by AStarSearch. It * expects any doors to be represented by '+' if closed or '/' if open (which can be caused by calling * DungeonUtility.closeDoors() ) and any walls to be '#' or line drawing characters. In the parameter costs, there * does not need to be an entry for '#' or any box drawing characters, but if one is present for '#' it will apply * that cost to both '#' and all box drawing characters, and if one is not present it will default to a negative * number, meaning it is impassable for AStarSearch. For any other entry in costs, a char in the 2D char array that * matches the key will correspond (at the same x,y position in the returned 2D double array) to that key's value in * costs. If a char is used in the map but does not have a corresponding key in costs, it will be given the value of * the parameter defaultValue, which is typically 0 unless a creature is limited to only moving in some terrain. * <p/> * The values in costs are different from those expected for DijkstraMap; negative numbers are impassable, 0 is the * cost for a normal walkable tile, and higher numbers are harder to enter. * <p/> * An example use for this would be to make a creature unable to enter any non-water cell (like a fish), * unable to enter doorways (like some mythological versions of vampires), or to make a wheeled vehicle take more * time to move across rubble or rough terrain. * <p/> * A potentially common case that needs to be addressed is NPC movement onto staircases in games that have them; * some games may find it desirable for NPCs to block staircases and others may not, but in either case you should * give both '>' and '<', the standard characters for staircases, the same value in costs. * * @param map a dungeon, width by height, with any closed doors as '+' and open doors as '/' as per closeDoors() . * @param costs a Map of Character keys representing possible elements in map, and Double values for their cost. * @param defaultValue a double that will be used as the cost for any characters that don't have a key in costs. * @return a cost map suitable for use with AStarSearch */ public static double[][] generateAStarCostMap(char[][] map, Map<Character, Double> costs, double defaultValue) { int width = map.length; int height = map[0].length; double[][] portion = new double[width][height]; char current; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { current = map[i][j]; if (costs.containsKey(current)) { portion[i][j] = costs.get(current); } else { switch (current) { case '\1': case '├': case '┤': case '┴': case '┬': case '┌': case '┐': case '└': case '┘': case '│': case '─': case '┼': case '#': portion[i][j] = (costs.containsKey('#')) ? costs.get('#') : squidpony.squidai.DijkstraMap.WALL; break; default: portion[i][j] = defaultValue; } } } } return portion; } public static double[][] translateAStarToDijkstra(double[][] astar) { if (astar == null) return null; if (astar.length <= 0 || astar[0].length <= 0) return new double[0][0]; double[][] dijkstra = new double[astar.length][astar[0].length]; for (int x = 0; x < astar.length; x++) { for (int y = 0; y < astar[x].length; y++) { if (astar[x][y] < 0) dijkstra[x][y] = DijkstraMap.WALL; else dijkstra[x][y] = DijkstraMap.FLOOR; } } return dijkstra; } public static double[][] translateDijkstraToAStar(double[][] dijkstra) { if (dijkstra == null) return null; if (dijkstra.length <= 0 || dijkstra[0].length <= 0) return new double[0][0]; double[][] astar = new double[dijkstra.length][dijkstra[0].length]; for (int x = 0; x < dijkstra.length; x++) { for (int y = 0; y < dijkstra[x].length; y++) { if (dijkstra[x][y] > DijkstraMap.FLOOR) astar[x][y] = -1; else astar[x][y] = 1; } } return astar; } /** * @param rng * @param map * @param acceptable * @param frustration The number of trials that this method can do. Usually 16 or * 32. * @return A random cell in {@code map} whose symbol is in * {@code acceptable}. Or {@code null} if not found. */ public static /* @Nullable */Coord getRandomCell(RNG rng, char[][] map, Set<Character> acceptable, int frustration) { if (frustration < 0) throw new IllegalStateException("Frustration should not be negative"); final int width = map.length; final int height = width == 0 ? 0 : map[0].length; if (width == 0 || height == 0) throw new IllegalStateException("Map must be non-empty to get a cell from it"); int i = 0; while (i < frustration) { final int x = rng.nextInt(width); final int y = rng.nextInt(height); if (acceptable.contains(map[x][y])) return Coord.get(x, y); i++; } return null; } /** * @param level dungeon/map level as 2D char array. x,y indexed * @param c Coord to check * @return {@code true} if {@code c} is valid in {@code level}, {@code false} otherwise. */ public static boolean inLevel(char[][] level, Coord c) { return inLevel(level, c.x, c.y); } /** * @param level dungeon/map level as 2D char array. x,y indexed * @param x x coordinate to check * @param y y coordinate to check * @return {@code true} if {@code c} is valid in {@code level}, {@code false} otherwise. */ public static boolean inLevel(char[][] level, int x, int y) { return 0 <= x && x < level.length && 0 <= y && y < level[x].length; } /** * @param level dungeon/map level as 2D double array. x,y indexed * @param c Coord to check * @return {@code true} if {@code c} is valid in {@code level}, {@code false} otherwise. */ public static boolean inLevel(double[][] level, Coord c) { return inLevel(level, c.x, c.y); } /** * @param level dungeon/map level as 2D double array. x,y indexed * @param x x coordinate to check * @param y y coordinate to check * @return {@code true} if {@code c} is valid in {@code level}, {@code false} otherwise. */ public static boolean inLevel(double[][] level, int x, int y) { return 0 <= x && x < level.length && 0 <= y && y < level[x].length; } /** * @param level a dungeon/map level as 2D array. x,y indexed * @param c Coord to check * @return {@code true} if {@code c} is valid in {@code level}, {@code false} otherwise. */ public static <T> boolean inLevel(T[][] level, Coord c) { return inLevel(level, c.x, c.y); } /** * @param level a dungeon/map level as 2D array. x,y indexed * @param x x coordinate to check * @param y y coordinate to check * @return {@code true} if {@code c} is valid in {@code level}, {@code false} otherwise. */ public static <T> boolean inLevel(T[][] level, int x, int y) { return 0 <= x && x < level.length && 0 <= y && y < level[x].length; } /** * An easy way to get the Coord items in a List of Coord that are at the edge of the region. Not the most * efficient way to do this; If you find you need to do more complicated manipulations of regions or are * calling this method often, consider using {@link squidpony.squidmath.GreasedRegion}, which should be * significantly faster and has better support for more intricate alterations on an area of Coords. * @param zone a List of Coord representing a region * @param buffer The list to fill if non null (i.e. if non-null, it is * returned). If null, a fresh list will be allocated and * returned. * @return Elements in {@code zone} that are neighbors to an element not in {@code zone}. */ public static List<Coord> border(final List<Coord> zone, /* @Nullable */ List<Coord> buffer) { final int zsz = zone.size(); final List<Coord> border = buffer == null ? new ArrayList<Coord>(zsz / 4) : buffer; for (int i = 0; i < zsz; i++) { final Coord c = zone.get(i); if (hasANeighborNotIn(c, zone)) border.add(c); } return border; } /** * Quickly counts the number of char elements in level that are equal to match. * * @param level the 2D char array to count cells in * @param match the char to search for * @return the number of cells that matched */ public static int countCells(char[][] level, char match) { if (level == null || level.length == 0) return 0; int counter = 0; for (int x = 0; x < level.length; x++) { for (int y = 0; y < level[x].length; y++) { if (level[x][y] == match) counter++; } } return counter; } /** * For when you want to print a 2D char array. Prints on multiple lines, with a trailing newline. * * @param level a 2D char array to print with a trailing newline */ public static void debugPrint(char[][] level) { if (level == null || level.length == 0 || level[0].length == 0) System.out.println("INVALID DUNGEON LEVEL"); else { for (int y = 0; y < level[0].length; y++) { for (int x = 0; x < level.length; x++) { System.out.print(level[x][y]); } System.out.println(); } } } /** * Changes the outer edge of a char[][] to the wall char, '#'. * * @param map A char[][] that stores map data; will be modified in place * @return the modified-in-place map with its edge replaced with '#' */ public static char[][] wallWrap(char[][] map) { int upperY = map[0].length - 1; int upperX = map.length - 1; for (int i = 0; i < map.length; i++) { map[i][0] = '#'; map[i][upperY] = '#'; } for (int i = 0; i < map[0].length; i++) { map[0][i] = '#'; map[upperX][i] = '#'; } return map; } /** * Ensures a path exists in a rough ring around the map by first creating the path (using * SerpentMapGenerator.pointPath with the given RNG), then finding chars in blocking that are on that path and * replacing them with replacement. Modifies map in-place (!) and returns an ArrayList of Coord points that will * always be on the path. * * @param map a 2D char array, x then y, etc. that will be modified directly; this is the "returned map" * @param rng used for random factors in the path choice * @param replacement the char that will fill be used where a path needs to be carved out; usually '.' * @param blocking an array or vararg of char that are considered blocking for the path and will be replaced if * they are in the way * @return the ArrayList of Coord points that are on the carved path, including existing non-blocking cells; will be empty if any parameters are invalid */ public static ArrayList<Coord> ensurePath(char[][] map, RNG rng, char replacement, char... blocking) { if (map == null || map.length <= 0 || blocking == null || blocking.length <= 0) return new ArrayList<Coord>(0); int width = map.length, height = map[0].length; ArrayList<Coord> points = SerpentMapGenerator.pointPath(width, height, rng); char[] blocks = new char[blocking.length]; System.arraycopy(blocking, 0, blocks, 0, blocking.length); Arrays.sort(blocks); for (Coord c : points) { if (c.x >= 0 && c.x < width && c.y >= 0 && c.y < height && Arrays.binarySearch(blocks, map[c.x][c.y]) >= 0) { map[c.x][c.y] = replacement; } } return points; } public static ArrayList<Coord> allMatching(char[][] map, char... matching) { if (map == null || map.length <= 0 || matching == null || matching.length <= 0) return new ArrayList<Coord>(0); int width = map.length, height = map[0].length; char[] matches = new char[matching.length]; System.arraycopy(matching, 0, matches, 0, matching.length); Arrays.sort(matches); ArrayList<Coord> points = new ArrayList<Coord>(map.length * 4); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if (Arrays.binarySearch(matches, map[x][y]) >= 0) points.add(Coord.get(x, y)); } } return points; } /** * Gets a List of Coord that are within radius distance of (x,y), and appends them to buf if it is non-null or makes * a fresh List to append to otherwise. Returns buf if non-null, else the fresh List of Coord. May produce Coord * values that are not within the boundaries of a map, such as (-5,-4), if the center is too close to the edge or * radius is too high. You can use {@link squidpony.squidgrid.Radius#inCircle(int, int, int, boolean, int, int, List)} * with surpassEdges as false if you want to limit Coords to within the map, or the more general * {@link squidpony.squidgrid.Radius#pointsInside(int, int, int, boolean, int, int, List)} on a Radius.SQUARE or * Radius.DIAMOND enum value if you want a square or diamond shape. * * @param x center x of the circle * @param y center y of the circle * @param radius inclusive radius to extend from the center; radius 0 gives just the center * @param buf Where to add the coordinates, or null for this method to * allocate a fresh list. * @return The coordinates of a circle centered {@code (x, y)}, whose * diameter is {@code (radius * 2) + 1}. * @see squidpony.squidgrid.Radius#inCircle(int, int, int, boolean, int, int, List) if you want to keep the Coords within the bounds of the map */ public static List<Coord> circle(int x, int y, int radius, /* @Nullable */ List<Coord> buf) { final List<Coord> result = buf == null ? new ArrayList<Coord>() : buf; radius = Math.max(0, radius); for (int dx = -radius; dx <= radius; ++dx) { final int high = (int) Math.floor(Math.sqrt(radius * radius - dx * dx)); for (int dy = -high; dy <= high; ++dy) { result.add(Coord.get(x + dx, y + dy)); } } return result; } private static boolean hasANeighborNotIn(Coord c, Collection<Coord> others) { for (Direction dir : Direction.OUTWARDS) { if (!others.contains(c.translate(dir))) return true; } return false; } /** * Fills {@code array2d} with {@code value}; delegates to ArrayTools, and using ArrayTools is preferred. * @param array2d a 2D array that will be modified in-place * @param value the value to fill all of array2D with * @deprecated Use {@link ArrayTools#fill(boolean[][], boolean)} instead */ @Deprecated public static void fill(boolean[][] array2d, boolean value) { ArrayTools.fill(array2d, value); } /** * Fills {@code array2d} with {@code value}; delegates to ArrayTools, and using ArrayTools is preferred. * @param array2d a 2D array that will be modified in-place * @param value the value to fill all of array2D with * @deprecated Use {@link ArrayTools#fill(char[][], char)} instead */ @Deprecated public static void fill(char[][] array2d, char value) { ArrayTools.fill(array2d, value); } /** * Fills {@code array2d} with {@code value}; delegates to ArrayTools, and using ArrayTools is preferred. * @param array2d a 2D array that will be modified in-place * @param value the value to fill all of array2D with * @deprecated Use {@link ArrayTools#fill(int[][], int)} instead */ @Deprecated public static void fill(int[][] array2d, int value) { ArrayTools.fill(array2d, value); } /** * Fills {@code array2d} with {@code value}; delegates to ArrayTools, and using ArrayTools is preferred. * @param array2d a 2D array that will be modified in-place * @param value the value to fill all of array2D with * @deprecated Use {@link ArrayTools#fill(double[][], double)} instead */ @Deprecated public static void fill(double[][] array2d, double value) { ArrayTools.fill(array2d, value); } }