Skip to content

Commit

Permalink
Add RectangleRoomsFinder, a KISS algorithm to find rectangles in 2D d…
Browse files Browse the repository at this point in the history
…ungeons + add a new iterator in SquidIterators + add Rectangle (the type generated by RectangleRoomsFinder) + detail in ButtonsPanel and UIUtil
  • Loading branch information
smelc committed Apr 14, 2016
1 parent 7822a41 commit 50393b7
Show file tree
Hide file tree
Showing 8 changed files with 670 additions and 27 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ target/
*.iml
.idea/
.settings/
.classpath
.project

*.jar

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ protected boolean gridIsEmpty() {
*
* @author smelC
*/
public static class Square implements SquidIterator {
public static class CenteredSquare implements SquidIterator {

protected final int width;
protected final int height;
Expand Down Expand Up @@ -165,13 +165,13 @@ public static class Square implements SquidIterator {
* @throws IllegalStateException
* If {@code width <= 0 || height <= 0 || size < 0}.
*/
public Square(int width, int height, int x, int y, int size) {
public CenteredSquare(int width, int height, int x, int y, int size) {
this.width = width;
if (width <= 0)
throw new IllegalStateException("Cannot build a square iterator over an empty grid");
throw new IllegalStateException("Cannot build a centered square iterator over an empty grid");
this.height = height;
if (height <= 0)
throw new IllegalStateException("Cannot build a square iterator over an empty grid");
throw new IllegalStateException("Cannot build a centered square iterator over an empty grid");

this.xstart = x;
this.ystart = y;
Expand All @@ -193,7 +193,7 @@ public Square(int width, int height, int x, int y, int size) {
* @param start
* The starting coordinate.
*/
public Square(int width, int height, Coord start, int size) {
public CenteredSquare(int width, int height, Coord start, int size) {
this(width, height, start.x, start.y, size);
}

Expand Down Expand Up @@ -267,6 +267,92 @@ protected boolean isInGrid(int x, int y) {
}
}

/**
* An iterator that starts from a cell and iterates from the bottom left to
* the top right, in the rectangle defined by the given width and height. Widths
* and heights are like list-sizes w.r.t indexes. So a rectangle of width or height 0
* is empty, a rectangle of width and height 1 has one cell, a rectangle
* of width and height 2 has four cells, etc.
*
* <p>
* Put differently, the rectangle whose bottom left is (x, y) and has width
* and height 2, contains the cells (x, y), (x + 1, y),
* (x, y - 1), and (x + 1, y - 1); but it does NOT contain (x + 2, y), nor
* (x + 2, y - 1), nor (x + 2, y - 2).
* </p>
*
* @author smelC
*/
public static class RectangleFromBottomLeftToTopRight implements SquidIterator {

protected final int xstart;
protected final int ystart;

protected final int width;
protected final int height;

/** The last cell returned */
protected Coord previous = null;

public RectangleFromBottomLeftToTopRight(Coord start, int width, int height) {
this.xstart = start.x;
this.ystart = start.y;

if (width < 0)
throw new IllegalStateException(
"Width of " + getClass().getSimpleName() + " shouldn't be negative");
this.width = width;
if (height < 0)
throw new IllegalStateException(
"Height of " + getClass().getSimpleName() + " shouldn't be negative");
this.height = height;
}

@Override
public boolean hasNext() {
return next0() != null;
}

@Override
public Coord next() {
final Coord result = next0();
if (result == null)
throw new NoSuchElementException();
previous = result;
return result;
}

@Override
public void remove() {
throw new UnsupportedOperationException();
}

protected /*@Nullable*/ Coord next0() {
if (previous == null) {
/* Initialization */
return width == 0 || height == 0 ? null : Coord.get(xstart, ystart);
}
else {
/* We're in SquidLib coordinates: (0,0) is top left */
assert xstart <= previous.x && previous.x < xstart + width;
assert previous.y <= ystart && ystart - height < previous.y;

if (previous.x == xstart + width - 1) {
/* Need to go up and left (one column up, go left) */
if (previous.y == ystart - (height - 1) || previous.y == 0) {
/* We're done */
return null;
} else
/* One line above */
return Coord.get(xstart, previous.y - 1);
} else {
/* Can go right in the same line */
return Coord.get(previous.x + 1, previous.y);
}
}
}
}

/**
* An iterator that iterates around a starting position (counter clockwise).
* It can return at most 9 elements. Instances of this iterator only return
Expand Down
222 changes: 222 additions & 0 deletions squidlib-util/src/main/java/squidpony/squidgrid/mapping/Rectangle.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package squidpony.squidgrid.mapping;

import java.util.Comparator;
import java.util.Iterator;

import squidpony.squidgrid.Direction;
import squidpony.squidgrid.iterator.SquidIterators;
import squidpony.squidmath.Coord;

/**
* Rectangles in 2D grids. Checkout {@link Utils} for utility methods.
*
* @author smelC
*
* @see RectangleRoomsFinder How to find rectangles in a dungeon
*/
public interface Rectangle {

/**
* @return The bottom left coordinate of the room.
*/
public Coord getBottomLeft();

/**
* @return The room's width (from {@link #getBottomLeft()). It is greater
* than 0.
*/
public int getWidth();

/**
* @return The room's height (from {@link #getBottomLeft()). It is greater
* than 0.
*/
public int getHeight();

/**
* Utilities pertaining to {@link Room}
*
* @author smelC
*/
public static class Utils {

/** A comparator that uses {@link #size(Rectangle)} as the measure. */
public static final Comparator<Rectangle> SIZE_COMPARATOR = new Comparator<Rectangle>() {
@Override
public int compare(Rectangle o1, Rectangle o2) {
return Integer.compare(size(o1), size(o2));
}
};

/**
* @param r
* @param c
* @return Whether {@code r} contains {@code c}.
*/
public static boolean contains(Rectangle r, Coord c) {
final Coord bottomLeft = r.getBottomLeft();
final int width = r.getWidth();
final int height = r.getHeight();
if (c.x < bottomLeft.x)
/* Too much to the left */
return false;
if (bottomLeft.x + width < c.x)
/* Too much to the right */
return false;
if (c.y < bottomLeft.y)
/* Too low */
return false;
if (bottomLeft.y + height < c.y)
/* Too high */
return false;
return true;
}

/**
* @param rs
* @param c
* @return {@code true} if a member of {@code rs}
* {@link #contains(Room, Coord) contains} {@code c}.
*/
public static boolean contains(Iterable<? extends Rectangle> rs, Coord c) {
for (Rectangle r : rs) {
if (contains(r, c))
return true;
}
return false;
}

/**
* @param r
* @return The number of cells that {@code r} covers.
*/
public static int size(Rectangle r) {
return r.getWidth() * r.getHeight();
}

/**
* @param r
* @return The center of {@code r}.
*/
public static Coord center(Rectangle r) {
final Coord bl = r.getBottomLeft();
/*
* bl.y - ... : because we're in SquidLib coordinates (0,0) is top
* left
*/
return Coord.get(bl.x + Math.round(r.getWidth() / 2), bl.y - Math.round(r.getHeight() / 2));
}

/**
* @param r
* @return The cells that {@code r} contains, from bottom left to top
* right.
*/
public static Iterator<Coord> cells(Rectangle r) {
return new SquidIterators.RectangleFromBottomLeftToTopRight(r.getBottomLeft(), r.getWidth(),
r.getHeight());
}

/**
* @param d
* A direction.
* @return {@code r} extended to {@code d} by one row and/or column.
*/
public static Rectangle extend(Rectangle r, Direction d) {
final Coord bl = r.getBottomLeft();
final int width = r.getWidth();
final int height = r.getHeight();

switch (d) {
case DOWN_LEFT:
return new Rectangle.Impl(bl.translate(Direction.DOWN_LEFT), width + 1, height + 1);
case DOWN_RIGHT:
return new Rectangle.Impl(bl.translate(Direction.DOWN), width + 1, height + 1);
case NONE:
return r;
case UP_LEFT:
return new Rectangle.Impl(bl.translate(Direction.LEFT), width + 1, height + 1);
case UP_RIGHT:
return new Rectangle.Impl(bl, width + 1, height + 1);
case DOWN:
return new Rectangle.Impl(bl.translate(Direction.DOWN), width, height + 1);
case LEFT:
return new Rectangle.Impl(bl.translate(Direction.LEFT), width + 1, height);
case RIGHT:
return new Rectangle.Impl(bl, width + 1, height);
case UP:
return new Rectangle.Impl(bl, width, height + 1);
}
throw new IllegalStateException("Unmatched direction in Rectangle.Utils::extend: " + d);
}

}

/**
* @author smelC
*/
public static class Impl implements Rectangle {

protected final Coord bottomLeft;
protected final int width;
protected final int height;

public Impl(Coord bottomLeft, int width, int height) {
this.bottomLeft = bottomLeft;
this.width = width;
this.height = height;
}

@Override
public Coord getBottomLeft() {
return bottomLeft;
}

@Override
public int getWidth() {
return width;
}

@Override
public int getHeight() {
return height;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((bottomLeft == null) ? 0 : bottomLeft.hashCode());
result = prime * result + height;
result = prime * result + width;
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Impl other = (Impl) obj;
if (bottomLeft == null) {
if (other.bottomLeft != null)
return false;
} else if (!bottomLeft.equals(other.bottomLeft))
return false;
if (height != other.height)
return false;
if (width != other.width)
return false;
return true;
}

@Override
public String toString() {
return "Room at " + bottomLeft + ", width:" + width + ", height:" + height;
}
}

}
Loading

0 comments on commit 50393b7

Please sign in to comment.