package net.alcuria.umbracraft.mapgen; import java.awt.Point; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.utils.Array; /** Generates a map into a 2d array. Also contains some methods to render the map * using simple shapes. * @author Andrew Keturi */ public class MapGenerator implements InputProcessor { Point entrance, exit; boolean[][] filled; int[][] map; int mapH = 3; int mapW = 5; ShapeRenderer renderer = new ShapeRenderer(); int sz = 8; public MapGenerator() { generate(); } /** Clears the filled map */ private void clearFilled() { for (int i = 0; i < filled.length; i++) { for (int j = 0; j < filled[0].length; j++) { filled[i][j] = false; } } } public void draw(SpriteBatch batch) { renderer.setAutoShapeType(true); renderer.begin(); renderer.setColor(Color.WHITE); for (int i = 0; i < map.length; i++) { for (int j = map[0].length - 1; j >= 0; j--) { renderer.set(filled[i][j] ? ShapeType.Filled : ShapeType.Line); renderer.setColor(filled[i][j] ? Color.WHITE : Color.GRAY); renderer.rect(i * sz + 50, j * sz + 50, sz, sz); } } renderer.set(ShapeType.Filled); renderer.setColor(Color.GREEN); renderer.rect(entrance.x * sz + 50, entrance.y * sz + 50, sz, sz); renderer.setColor(Color.YELLOW); renderer.rect(exit.x * sz + 50, exit.y * sz + 50, sz, sz); renderer.end(); } /** Given coordinates, fills a random adjacent point, if it is valid */ private void fillNearby(int i, int j) { i += MathUtils.random(-1, 1); j += MathUtils.random(-1, 1); if (i >= 0 && i < mapW && j >= 0 && j < mapH) { filled[i][j] = true; } } /** Given a point, sets it to filled */ private void fillPoint(Point p) { if (p.x >= 0 && p.y >= 0 && p.x < filled.length && p.y < filled[0].length) { filled[p.x][p.y] = true; } } /** Iterates thru the filled tiles and if an unfilled tile is found with 4 * adjacent filled tiles, we fill it in. */ private void fillSuffocated() { for (int i = 1; i < filled.length - 1; i++) { for (int j = 1; j < filled[0].length - 1; j++) { if (!filled[i][j] && filled[i + 1][j] && filled[i - 1][j] && filled[i][j + 1] && filled[i][j - 1]) { filled[i][j] = true; } } } } /** Fills a weighted path from a starting point to an ending point */ private void fillWalk(Point start, Point end) { fillPoint(start); if (start.equals(end)) { return; } else { int dX = 0, dY = 0; do { float xDist = Math.abs(end.x - start.x); float yDist = Math.abs(end.y - start.y); float xWeight = (xDist / (xDist + yDist)); if (MathUtils.random() < xWeight) { // try to step x if (end.x > start.x) { dX = MathUtils.random(0, 1); } else if (end.x < start.x) { dX = MathUtils.random(-1, 0); } } else { // try to step y if (end.y > start.y) { dY = MathUtils.random(0, 1); } else if (end.y < start.y) { dY = MathUtils.random(-1, 0); } } } while (dX != 0 && dY != 0); Point startInterval = new Point(start); startInterval.x += dX; startInterval.y += dY; fillWalk(startInterval, end); } } /** Given a starting point, some point values, and an offset to that values * array, this finds and returns the nearest point (as a new point) */ private Point findNearest(Point start, Array<Point> values, int indexOffset) { double nearestDist = Double.MAX_VALUE; Point nearest = new Point(start); for (int i = indexOffset; i < values.size; i++) { final Point p = values.get(i); if (!p.equals(start) && p.distance(start) < nearestDist) { nearestDist = p.distance(start); nearest = p; } } return nearest; } public void generate() { long startTime = System.nanoTime(); int widenTimes = MathUtils.random(2, 10); int margin = widenTimes + 1; int entranceAttempts = 50; int intervalPtSize = MathUtils.random(2, 100); mapW = MathUtils.random(40, 85); mapH = MathUtils.random(40, 85); int minEntranceExitDistance = (mapW + mapH) / 4; map = new int[mapW][mapH]; filled = new boolean[mapW][mapH]; entrance = new Point(); exit = new Point(); // -------- clearFilled(); // set the entrance and exit points, assuring they aren't too close // together if possible do { setEdgePoint(entrance); setEdgePoint(exit); entranceAttempts--; } while (entranceAttempts > 0 && entrance.distance(exit) < minEntranceExitDistance); fillPoint(entrance); fillPoint(exit); // create the interval points Array<Point> intervals = new Array<Point>(); for (int i = 0; i < intervalPtSize; i++) { intervals.add(new Point(MathUtils.random(margin, mapW - margin), MathUtils.random(margin, mapH - margin))); } // fill the paths between interval points fillWalk(entrance, findNearest(entrance, intervals, 0)); for (int i = 0; i < intervals.size - 1; i++) { fillWalk(intervals.get(i), findNearest(intervals.get(i), intervals, i + 1)); } fillWalk(exit, findNearest(exit, intervals, 0)); long endTime = System.nanoTime(); // widen the path for (int amt = 0; amt < widenTimes; amt++) { boolean[][] filledRef = new boolean[mapW][mapH]; for (int i = 0; i < filledRef.length; i++) { for (int j = 0; j < filledRef[0].length; j++) { filledRef[i][j] = filled[i][j]; } } for (int i = 0; i < filledRef.length; i++) { for (int j = 0; j < filledRef[0].length; j++) { if (hasAdjacentEmptySpace(filledRef, i, j)) { fillNearby(i, j); } } } } // clean up any stray unfilled tiles fillSuffocated(); System.out.println(String.format("Generated! (Took %d ns)", (endTime - startTime))); } public int getHeight() { return mapH; } public int getWidth() { return mapW; } private boolean hasAdjacentEmptySpace(boolean[][] filledRef, int i, int j) { if (!filledRef[i][j]) { return false; } // check if each dir is empty (l, r, d, u) if (i > 0 && !filledRef[i - 1][j]) { return true; } else if (i < mapW - 1 && !filledRef[i + 1][j]) { return true; } else if (j > 0 && !filledRef[i][j - 1]) { return true; } else if (j < mapH - 1 && !filledRef[i][j + 1]) { return true; } return false; } public boolean isFilled(int x, int y) { if (filled == null || x < 0 || x >= mapW || y < 0 || y >= mapH) { return true; } return filled[x][y]; } @Override public boolean keyDown(int keycode) { return false; } @Override public boolean keyTyped(char character) { if (character == 'g') { generate(); return true; } return false; } @Override public boolean keyUp(int keycode) { return false; } @Override public boolean mouseMoved(int screenX, int screenY) { return false; } @Override public boolean scrolled(int amount) { return false; } private void setEdgePoint(Point p) { // top, right, bottom, left switch (MathUtils.random(3)) { case 0: p.setLocation(MathUtils.random(mapW - 1), mapH - 1); return; case 1: p.setLocation(mapW - 1, MathUtils.random(mapH - 1)); return; case 2: p.setLocation(MathUtils.random(mapW - 1), 0); return; case 3: p.setLocation(0, MathUtils.random(mapH - 1)); return; } } @Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { generate(); return true; } @Override public boolean touchDragged(int screenX, int screenY, int pointer) { return false; } @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { return false; } }