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 { 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); } } 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); } @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 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; } objects.remove(currentObject); drop(currentObject, newPosition, newDirection); } } /** * 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; } private boolean lessOrEqual(float x, float y) { if (-0.000001f < y && y < 0.000001f) { return (x < y + 0.000001f); } float q = x / y; if (0.999999f < q && q < 1.000001f) { return true; } return x < y; } /** Tests whether two objects 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; } private Direction getMoveDirection(E object, Position position, Pair blocking) { boolean isVertical = getMoveOrientation(object, position, blocking); float objectMidpointX = position.getX() + object.getWidth() / 2; float objectMidpointY = position.getY() + object.getHeight() / 2; float blockingMidpointX = blocking.getSecond().getX() + blocking.getFirst().getWidth() / 2; float 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; } } } private boolean getMoveOrientation(E object, Position position, Pair blocking) { float objectRight = position.getX() + object.getWidth(); float blockingRight = blocking.getSecond().getX() + blocking.getFirst().getWidth(); float overlapRight = Math.min(objectRight, blockingRight); float overlapLeft = Math.max(position.getX(), blocking.getSecond().getX()); float overlapX = overlapRight - overlapLeft; float objectBottom = position.getY() + object.getHeight(); float blockingBottom = blocking.getSecond().getY() + blocking.getFirst().getHeight(); float overlapBottom = Math.min(objectBottom, blockingBottom); float overlapTop = Math.max(position.getY(), blocking.getSecond().getY()); float overlapY = overlapBottom - overlapTop; // vertical or horizontal Shift // TODO magic factor return overlapX > overlapY; } /* * (non-Javadoc) * * @see jrummikub.model.IStoneTray#getPosition(E) */ @Override public Position getPosition(E object) { return objects.get(object).getSecond(); } @Override public Iterator> iterator() { return objects.values().iterator(); } /* * (non-Javadoc) * * @see jrummikub.model.IStoneTray#pickUp(E) */ @Override public boolean pickUp(E object) { return null != objects.remove(object); } /* * (non-Javadoc) * * @see jrummikub.model.IStoneTray#clone() */ @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(); } }