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 */ private static enum Direction { LEFT, RIGHT, TOP, BOTTOM; } /* * (non-Javadoc) * * @see jrummikub.model.IStoneTray#pickUp(jrummikub.model.Position) */ @Override public E pickUp(Position position) { for (Map.Entry i : objects.entrySet()) { Position currentPosition = i.getValue(); E currentObject = i.getKey(); // Tests if position is left of, above ... the current object if (position.getX() < currentPosition.getX()) { continue; } if (position.getY() < currentPosition.getY()) { continue; } if (position.getX() > currentPosition.getX() + currentObject.getWidth()) { continue; } if (position.getY() > currentPosition.getY() + currentObject.getHeight()) { continue; } // Position is inside the current object objects.remove(i.getKey()); return currentObject; } return null; } /* * (non-Javadoc) * * @see jrummikub.model.IStoneTray#drop(E, jrummikub.model.Position) */ @Override public void drop(E object, Position position) { if (object != null) { drop(object, position, null); } } @SuppressWarnings("unchecked") private void drop(E object, Position position, Direction direction) { for (Map.Entry i : ((Map) objects.clone()) .entrySet()) { Position currentPosition = i.getValue(); E currentObject = i.getKey(); if (!objectsOverlap(object, position, currentObject, currentPosition)) { continue; } // Object would be placed inside the current object if (direction == null) { direction = getMoveDirection(object, position, i); } Position newPosition = null; // Move object to avoid overlap switch (direction) { 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(i.getKey()); drop(currentObject, newPosition, direction); } objects.put(object, position); } /** 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 (position1.getX() + object1.getWidth() <= position2.getX()) { return false; } if (position1.getY() + object1.getHeight() <= position2.getY()) { return false; } if (position1.getX() >= position2.getX() + object2.getWidth()) { return false; } if (position1.getY() >= position2.getY() + object2.getHeight()) { return false; } return true; } private Direction getMoveDirection(E object, Position position, Map.Entry blocking) { boolean isVertical = getMoveOrientationn(object, position, blocking); float objectMidpointX = position.getX() + object.getWidth() / 2; float objectMidpointY = position.getY() + object.getHeight() / 2; float blockingMidpointX = blocking.getValue().getX() + blocking.getKey().getWidth() / 2; float blockingMidpointY = blocking.getValue().getY() + blocking.getKey().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 getMoveOrientationn(E object, Position position, Map.Entry blocking) { float objectRight = position.getX() + object.getWidth(); float blockingRight = blocking.getValue().getX() + blocking.getKey().getWidth(); float overlapRight = Math.min(objectRight, blockingRight); float overlapLeft = Math.max(position.getX(), blocking.getValue().getX()); float overlapX = overlapRight - overlapLeft; float objectBottom = position.getY() + object.getHeight(); float blockingBottom = blocking.getValue().getY() + blocking.getKey().getHeight(); float overlapBottom = Math.min(objectBottom, blockingBottom); float overlapTop = Math.max(position.getY(), blocking.getValue().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); } @Override public Iterator> iterator() { final Iterator> entryIterator = objects.entrySet() .iterator(); return new Iterator>() { Iterator> iterator = entryIterator; @Override public boolean hasNext() { return iterator.hasNext(); } @Override public Pair next() { Map.Entry entry = iterator.next(); return new Pair(entry.getKey(), entry.getValue()); } @Override public void remove() { iterator.remove(); } }; } /* * (non-Javadoc) * * @see jrummikub.model.IStoneTray#pickUp(E) */ @Override public void pickUp(E object) { objects.remove(object); } /* * (non-Javadoc) * * @see jrummikub.model.IStoneTray#clone() */ @SuppressWarnings("unchecked") @Override public IStoneTray clone() { StoneTray copy = new StoneTray(); copy.objects = (HashMap) objects.clone(); return copy; } }