package jrummikub.model; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import jrummikub.util.Pair; /** * A StoneTray is a collection of positioned objects (for example {@link Stone}s * or {@link StoneSet}s. * * @param * Type of positioned objects (must implement Sizeable) */ public class StoneTray implements IStoneTray { private static final long serialVersionUID = -6329309928640027222L; protected HashMap> objects = new HashMap>(); /** Possible move directions in case of overlapping Stones/Sets */ protected static enum Direction { LEFT, RIGHT, TOP, BOTTOM; } @Override public void drop(E object, Position position) { if (object != null) { drop(object, position, null); } } /** * Subroutine to "drop" to consider and determine the direction the objects * dropped one collides with position-wise evade in * * @param object * the object to add to Hand * @param position * {@link Position} to put the object * @param direction * the direction the other stones evade in */ private void drop(E object, Position position, Direction direction) { Pair update = fixInvalidDrop(object, position, direction); if (update != null) { position = update.getFirst(); direction = update.getSecond(); } dropUnchecked(object, position, direction); } /** * Subroutine to "drop" to execute the actual drop * * @param object * the object to add to Hand * @param position * {@link Position} to put the object * @param direction * the direction the other stones evade in */ @SuppressWarnings("unchecked") private void dropUnchecked(E object, Position position, Direction direction) { objects.put(object, new Pair(object, position)); for (Pair i : ((Map>) objects.clone()) .values()) { Direction newDirection = direction; E currentObject = i.getFirst(); if (currentObject == object) continue; Position currentPosition = getPosition(currentObject); if (!objectsOverlap(object, position, currentObject, currentPosition)) { continue; } // Object would be placed inside the current object if (newDirection == null) { newDirection = getMoveDirection(object, position, i); } Position newPosition = null; // Move object to avoid overlap newPosition = getNewPosition(object, position, newDirection, currentObject, currentPosition); objects.remove(currentObject); drop(currentObject, newPosition, newDirection); } } private Position getNewPosition(E object, Position position, Direction newDirection, E currentObject, Position currentPosition) { Position newPosition = new Position(0, 0); switch (newDirection) { case TOP: newPosition = new Position(currentPosition.getX(), position.getY() - currentObject.getHeight()); break; case BOTTOM: newPosition = new Position(currentPosition.getX(), position.getY() + object.getHeight()); break; case LEFT: newPosition = new Position(position.getX() - currentObject.getWidth(), currentPosition.getY()); break; case RIGHT: newPosition = new Position(position.getX() + object.getWidth(), currentPosition.getY()); break; } return newPosition; } /** * Checks whether the object may be placed on the given position, computes * new position if not * * @param object * to be dropped * @param dir * @param pos * the object is dropped at * @return null if the drop is valid, new position otherwise */ protected Pair fixInvalidDrop(E object, Position pos, Direction dir) { return null; } /** * Static method for determining a less or equal relation considering a * small fuzziness * * @param d * the value to be less or equal * @param e * than the other one * @return if d is less or equal e */ private static boolean lessOrEqual(double d, double e) { if (-0.000001 < e && e < 0.000001) { return (d < e + 0.000001); } double q = d / e; if (0.999999 < q && q < 1.000001) { return true; } return d < e; } /** * Tests whether two objects overlap * * @param object1 * first object * @param position1 * first object's position * @param object2 * second object * @param position2 * second object's position * * @return whether they overlap **/ private boolean objectsOverlap(E object1, Position position1, E object2, Position position2) { // Tests if position is left of, above ... the current object if (lessOrEqual(position1.getX() + object1.getWidth(), position2.getX())) { return false; } if (lessOrEqual(position1.getY() + object1.getHeight(), position2.getY())) { return false; } if (lessOrEqual(position2.getX() + object2.getWidth(), position1.getX())) { return false; } if (lessOrEqual(position2.getY() + object2.getHeight(), position1.getY())) { return false; } return true; } /** * Returns the direction to move the object in * * @param object * the object * @param position * the object's position * @param blocking * the object thats blocking * @return the direction */ private Direction getMoveDirection(E object, Position position, Pair blocking) { boolean isVertical = getMoveOrientation(object, position, blocking); double objectMidpointX = position.getX() + object.getWidth() / 2; double objectMidpointY = position.getY() + object.getHeight() / 2; double blockingMidpointX = blocking.getSecond().getX() + blocking.getFirst().getWidth() / 2; double blockingMidpointY = blocking.getSecond().getY() + blocking.getFirst().getHeight() / 2; if (isVertical) { if (objectMidpointY < blockingMidpointY) { return Direction.BOTTOM; } else { return Direction.TOP; } } else { if (objectMidpointX < blockingMidpointX) { return Direction.RIGHT; } else { return Direction.LEFT; } } } /** * Will the object be moved horizontally or vertically * * @param object * the object * @param position * the objects position * @param blocking * the object thats blocking * * @return boolean vertical movement */ private boolean getMoveOrientation(E object, Position position, Pair blocking) { double objectRight = position.getX() + object.getWidth(); double blockingRight = blocking.getSecond().getX() + blocking.getFirst().getWidth(); double overlapRight = Math.min(objectRight, blockingRight); double overlapLeft = Math.max(position.getX(), blocking.getSecond() .getX()); double overlapX = overlapRight - overlapLeft; double objectBottom = position.getY() + object.getHeight(); double blockingBottom = blocking.getSecond().getY() + blocking.getFirst().getHeight(); double overlapBottom = Math.min(objectBottom, blockingBottom); double overlapTop = Math.max(position.getY(), blocking.getSecond() .getY()); double overlapY = overlapBottom - overlapTop; return overlapX > overlapY; } @Override public Position getPosition(E object) { Pair entry = objects.get(object); if (entry == null) { return null; } return entry.getSecond(); } @Override public boolean contains(E object) { return objects.get(object) != null; } @Override public Iterator> iterator() { return objects.values().iterator(); } @Override public boolean pickUp(E object) { return null != objects.remove(object); } @SuppressWarnings("unchecked") @Override public IStoneTray clone() { try { StoneTray copy = (StoneTray) super.clone(); copy.objects = (HashMap>) objects.clone(); return copy; } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } @Override public int getSize() { return objects.size(); } }