All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.refcodes.checkerboard.impls.CheckerboardImpl Maven / Gradle / Ivy

Go to download

Artifact for providing some easy means to visualize (state of) board games or (state of) cellular automatons.

There is a newer version: 3.3.8
Show newest version
// /////////////////////////////////////////////////////////////////////////////
// REFCODES.ORG
// /////////////////////////////////////////////////////////////////////////////
// This code is copyright (c) by Siegfried Steiner, Munich, Germany and licensed
// under the following (see "http://en.wikipedia.org/wiki/Multi-licensing")
// licenses:
// -----------------------------------------------------------------------------
// GNU General Public License, v3.0 ("http://www.gnu.org/licenses/gpl-3.0.html")
// -----------------------------------------------------------------------------
// Apache License, v2.0 ("http://www.apache.org/licenses/LICENSE-2.0")
// -----------------------------------------------------------------------------
// Please contact the copyright holding author(s) of the software artifacts in
// question for licensing issues not being covered by the above listed licenses,
// also regarding commercial licensing models or regarding the compatibility
// with other open source licenses.
// /////////////////////////////////////////////////////////////////////////////

package org.refcodes.checkerboard.impls;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.refcodes.checkerboard.ChangePositionEvent;
import org.refcodes.checkerboard.Checkerboard;
import org.refcodes.checkerboard.CheckerboardEvent;
import org.refcodes.checkerboard.CheckerboardObserver;
import org.refcodes.checkerboard.DraggabilityChangedEvent;
import org.refcodes.checkerboard.GridDimensionChangedEvent;
import org.refcodes.checkerboard.GridModeChangedEvent;
import org.refcodes.checkerboard.Player;
import org.refcodes.checkerboard.PlayerAddedEvent;
import org.refcodes.checkerboard.PlayerEvent;
import org.refcodes.checkerboard.PlayerObserver;
import org.refcodes.checkerboard.PlayerRemovedEvent;
import org.refcodes.checkerboard.PositionChangedEvent;
import org.refcodes.checkerboard.StateChangedEvent;
import org.refcodes.checkerboard.ViewportDimensionChangedEvent;
import org.refcodes.checkerboard.ViewportOffsetChangedEvent;
import org.refcodes.checkerboard.VisibilityChangedEvent;
import org.refcodes.controlflow.ExecutionStrategy;
import org.refcodes.exception.VetoException;
import org.refcodes.graphical.Dimension;
import org.refcodes.graphical.GridDimension;
import org.refcodes.graphical.GridMode;
import org.refcodes.graphical.Position;
import org.refcodes.observer.Event;
import org.refcodes.observer.SubscribeEvent;
import org.refcodes.observer.UnsubscribeEvent;
import org.refcodes.observer.impls.AbstractObservable;
import org.refcodes.observer.impls.SubscribeEventImpl;
import org.refcodes.observer.impls.UnsubscribeEventImpl;

/**
 * @author steiner
 *
 */
public class CheckerboardImpl extends AbstractObservable, Event> implements Checkerboard {

	// @formatter:off
	// private static RuntimeLogger LOGGER = RuntimeLoggerFactorySingleton.createRuntimeLogger();
	// @formatter:on

	// /////////////////////////////////////////////////////////////////////////
	// STATICS:
	// /////////////////////////////////////////////////////////////////////////

	// /////////////////////////////////////////////////////////////////////////
	// CONSTANTS:
	// /////////////////////////////////////////////////////////////////////////

	// /////////////////////////////////////////////////////////////////////////
	// VARIABLES:
	// /////////////////////////////////////////////////////////////////////////

	private List> _players = new ArrayList<>();
	private Map>> _rowToColumn = new HashMap<>();
	private Map>> _columnToRow = new HashMap<>();
	private int _gridWidth = -1;
	private int _gridHeight = -1;
	private GridMode _gridMode = GridMode.NONE;
	private CheckerboardPlayerObserverImpl _observer = new CheckerboardPlayerObserverImpl();

	// /////////////////////////////////////////////////////////////////////////
	// CONSTRUCTORS:
	// /////////////////////////////////////////////////////////////////////////

	// /////////////////////////////////////////////////////////////////////////
	// BUILDER:
	// /////////////////////////////////////////////////////////////////////////

	@Override
	public Checkerboard withGridMode( GridMode aGridMode ) {
		setGridMode( aGridMode );
		return this;
	}

	@Override
	public Checkerboard withGridDimension( int aGridWidth, int aGridHeight ) {
		setGridDimension( aGridWidth, aGridHeight );
		return this;
	}

	@Override
	public Checkerboard withGridDimension( GridDimension aDimension ) {
		setGridDimension( aDimension );
		return this;
	}

	@Override
	public Checkerboard withGridDimension( Dimension aDimension ) {
		setGridDimension( aDimension );
		return this;
	}

	@Override
	public Checkerboard withGridWidth( int aWidth ) {
		setGridWidth( aWidth );
		return this;
	}

	@Override
	public Checkerboard withGridHeight( int aHeight ) {
		setGridHeight( aHeight );
		return this;
	}

	// /////////////////////////////////////////////////////////////////////////
	// INJECTION:
	// /////////////////////////////////////////////////////////////////////////

	// /////////////////////////////////////////////////////////////////////////
	// METHODS:
	// /////////////////////////////////////////////////////////////////////////

	@Override
	public boolean hasAtPosition( Position aPos ) throws IndexOutOfBoundsException {
		return hasAtPosition( aPos.getPositionX(), aPos.getPositionY() );
	}

	@Override
	public boolean hasAtPosition( int aPosX, int aPosY ) throws IndexOutOfBoundsException {
		Map> theColumn = _rowToColumn.get( aPosX );
		if ( theColumn != null ) { return theColumn.containsKey( aPosY ); }
		return false;
	}

	@Override
	public Player atPosition( Position aPos ) throws IndexOutOfBoundsException {
		return atPosition( aPos.getPositionX(), aPos.getPositionY() );
	}

	@Override
	public Player atPosition( int aPosX, int aPosY ) throws IndexOutOfBoundsException {
		Map> theColumn = _rowToColumn.get( aPosX );
		if ( theColumn != null ) { return theColumn.get( aPosY ); }
		return null;
	}

	@Override
	public boolean hasAtTopOf( Position aPos ) throws IndexOutOfBoundsException {
		int x = aPos.getPositionX();
		int y = toTopYPosition( aPos );
		return hasAtPosition( x, y );
	}

	@Override
	public Player atTopOf( Position aPos ) throws IndexOutOfBoundsException {
		int x = aPos.getPositionX();
		int y = toTopYPosition( aPos );
		return atPosition( x, y );
	}

	@Override
	public boolean hasAtTopRightOf( Position aPos ) throws IndexOutOfBoundsException {
		int y = toTopYPosition( aPos );
		int x = toRightXPosition( aPos );
		return hasAtPosition( x, y );
	}

	@Override
	public Player atTopRightOf( Position aPos ) throws IndexOutOfBoundsException {
		int y = toTopYPosition( aPos );
		int x = toRightXPosition( aPos );
		return atPosition( x, y );
	}

	@Override
	public boolean hasAtRightOf( Position aPos ) throws IndexOutOfBoundsException {
		int y = aPos.getPositionY();
		int x = toRightXPosition( aPos );
		return hasAtPosition( x, y );
	}

	@Override
	public Player atRightOf( Position aPos ) throws IndexOutOfBoundsException {
		int y = aPos.getPositionY();
		int x = toRightXPosition( aPos );
		return atPosition( x, y );
	}

	@Override
	public boolean hasAtBottomRightOf( Position aPos ) throws IndexOutOfBoundsException {
		int y = toLeftYPosition( aPos );
		int x = toRightXPosition( aPos );
		return hasAtPosition( x, y );
	}

	@Override
	public Player atBottomRightOf( Position aPos ) throws IndexOutOfBoundsException {
		int y = toLeftYPosition( aPos );
		int x = toRightXPosition( aPos );
		return atPosition( x, y );
	}

	@Override
	public boolean hasAtBottomOf( Position aPos ) throws IndexOutOfBoundsException {
		int x = aPos.getPositionX();
		int y = toLeftYPosition( aPos );
		return hasAtPosition( x, y );
	}

	@Override
	public Player atBottomOf( Position aPos ) throws IndexOutOfBoundsException {
		int x = aPos.getPositionX();
		int y = toLeftYPosition( aPos );
		return atPosition( x, y );
	}

	@Override
	public boolean hasAtBottomLeftOf( Position aPos ) throws IndexOutOfBoundsException {
		int y = toLeftYPosition( aPos );
		int x = toLeftXPosition( aPos );
		return hasAtPosition( x, y );
	}

	@Override
	public Player atBottomLeftOf( Position aPos ) throws IndexOutOfBoundsException {
		int y = toLeftYPosition( aPos );
		int x = toLeftXPosition( aPos );
		return atPosition( x, y );
	}

	@Override
	public boolean hasAtLeftOf( Position aPos ) throws IndexOutOfBoundsException {
		int y = aPos.getPositionY();
		int x = toLeftXPosition( aPos );
		return hasAtPosition( x, y );
	}

	@Override
	public Player atLeftOf( Position aPos ) throws IndexOutOfBoundsException {
		int y = aPos.getPositionY();
		int x = toLeftXPosition( aPos );
		return atPosition( x, y );
	}

	@Override
	public boolean hasAtTopLeftOf( Position aPos ) throws IndexOutOfBoundsException {
		int y = toTopYPosition( aPos );
		int x = toLeftXPosition( aPos );
		return hasAtPosition( x, y );
	}

	@Override
	public Player atTopLeftOf( Position aPos ) throws IndexOutOfBoundsException {
		int y = toTopYPosition( aPos );
		int x = toLeftXPosition( aPos );
		return atPosition( x, y );
	}

	@Override
	public Map> getRow( int aRow ) throws IndexOutOfBoundsException {
		return _rowToColumn.get( aRow );
	}

	@Override
	public Map> getColumn( int aColumn ) throws IndexOutOfBoundsException {
		return _columnToRow.get( aColumn );
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public GridMode getGridMode() {
		return _gridMode;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setGridMode( GridMode aGridMode ) {
		if ( aGridMode != _gridMode ) {
			GridModeChangedEvent theEvent = new GridModeChangedEventImpl<>( aGridMode, _gridMode, this );
			_gridMode = aGridMode;
			try {
				fireEvent( theEvent, ExecutionStrategy.SEQUENTIAL );
			}
			catch ( VetoException ignore ) {}
		}
	}

	@Override
	public List> getPlayers() {
		return _players;
	}

	@Override
	public Player putPlayer( Player aPlayer ) {
		_players.add( aPlayer );
		Player thePrevPlayer = atPosition( aPlayer );
		if ( thePrevPlayer != null ) {
			if ( !removePlayer( thePrevPlayer ) ) { throw new IllegalStateException( "Illegal state detected while replacing player \"" + thePrevPlayer + "> from position (" + thePrevPlayer.getPositionX() + " x " + aPlayer.getPositionY() + ") with player player \"" + aPlayer + "> from position (" + aPlayer.getPositionX() + " x " + aPlayer.getPositionY() + "). Propably the player's coordinates changed while removing." ); }
		}
		if ( addToLookup( aPlayer ) != null ) { throw new IllegalStateException( "Illegal state detected while removing player \"" + aPlayer + "> from position (" + aPlayer.getPositionX() + " x " + aPlayer.getPositionY() + "). Propably the player's coordinates changed while removing." ); }
		PlayerAddedEventImpl theEvent = new PlayerAddedEventImpl<>( aPlayer, this );
		aPlayer.subscribeObserver( _observer );

		try {
			fireEvent( theEvent, ExecutionStrategy.SEQUENTIAL );
		}
		catch ( VetoException ignore ) {}
		return thePrevPlayer;
	}

	@Override
	public boolean removePlayer( Player aPlayer ) {
		if ( _players.remove( aPlayer ) ) {
			if ( removeFromLookup( aPlayer ) != aPlayer ) { throw new IllegalStateException( "Illegal state detected while removing player \"" + aPlayer + "> from position (" + aPlayer.getPositionX() + " x " + aPlayer.getPositionY() + "). Propably the player's coordinates changed while removing." ); }
			PlayerRemovedEventImpl theEvent = new PlayerRemovedEventImpl<>( aPlayer, this );
			aPlayer.unsubscribeObserver( _observer );
			aPlayer.subscribeObserver( _observer );
			try {
				fireEvent( theEvent, ExecutionStrategy.SEQUENTIAL );
			}
			catch ( VetoException ignore ) {}
			return true;
		}
		return false;
	}

	@Override
	public void clearPlayers() {
		_players.clear();
	}

	@Override
	public int playerCount() {
		return _players.size();
	}

	@Override
	public boolean hasPlayers() {
		return !_players.isEmpty();
	}

	@Override
	public Iterator> players() {
		return _players.iterator();
	}

	@Override
	public boolean hasPlayer( Player aPlayer ) {
		return _players.contains( aPlayer );
	}

	@Override
	public int getGridWidth() {
		return _gridWidth;
	}

	@Override
	public void setGridDimension( int aWidth, int aHeight ) {
		if ( aWidth != _gridWidth || aHeight != _gridHeight ) {
			GridDimensionChangedEvent theEvent = new GridDimensionChangedEventImpl<>( aWidth, aHeight, _gridWidth, _gridHeight, this );
			_gridWidth = aWidth;
			_gridHeight = aHeight;
			try {
				fireEvent( theEvent, ExecutionStrategy.SEQUENTIAL );
			}
			catch ( VetoException ignore ) {}
		}
	}

	@Override
	public void setGridDimension( GridDimension aDimension ) {
		setGridDimension( aDimension.getGridWidth(), aDimension.getGridHeight() );

	}

	@Override
	public void setGridDimension( Dimension aDimension ) {
		setGridDimension( aDimension.getWidth(), aDimension.getHeight() );
	}

	@Override
	public void setGridWidth( int aWidth ) {
		setGridDimension( aWidth, _gridHeight );

	}

	@Override
	public void setGridHeight( int aHeight ) {
		setGridDimension( _gridWidth, aHeight );

	}

	@Override
	public int getGridHeight() {
		return _gridHeight;
	}

	// /////////////////////////////////////////////////////////////////////////
	// LIFECYCLE:
	// /////////////////////////////////////////////////////////////////////////

	@Override
	public boolean subscribeObserver( CheckerboardObserver aObserver ) {
		if ( super.subscribeObserver( aObserver ) ) {
			try {
				fireEvent( new SubscribeEventImpl>( this ), ExecutionStrategy.SEQUENTIAL );
			}
			catch ( VetoException ignore ) {}
			return true;
		}
		return false;
	}

	@Override
	public boolean unsubscribeObserver( CheckerboardObserver aObserver ) {
		try {
			fireEvent( new UnsubscribeEventImpl>( this ), ExecutionStrategy.SEQUENTIAL );
		}
		catch ( VetoException ignore ) {}
		return super.unsubscribeObserver( aObserver );
	}

	@Override
	public void destroy() {}

	// /////////////////////////////////////////////////////////////////////////
	// HOOKS:
	// /////////////////////////////////////////////////////////////////////////

	@SuppressWarnings("unchecked")
	@Override
	protected boolean fireEvent( Event aEvent, CheckerboardObserver aObserver, ExecutionStrategy aExecutionStrategy ) throws VetoException {

		if ( aEvent instanceof SubscribeEvent ) {
			aObserver.onSubscribe( (SubscribeEvent>) aEvent );
		}
		if ( aEvent instanceof UnsubscribeEvent ) {
			aObserver.onUnsubscribe( (UnsubscribeEvent>) aEvent );
		}

		if ( aEvent instanceof CheckerboardEvent ) {
			aObserver.onCheckerboardEvent( (CheckerboardEvent) aEvent );
			if ( aEvent instanceof PlayerAddedEvent ) {
				aObserver.onPlayerAddedEvent( (PlayerAddedEvent) aEvent );
			}
			if ( aEvent instanceof PlayerRemovedEvent ) {
				aObserver.onPlayerRemovedEvent( (PlayerRemovedEvent) aEvent );
			}
			if ( aEvent instanceof GridModeChangedEvent ) {
				aObserver.onGridModeChangedEvent( (GridModeChangedEvent) aEvent );
			}
			if ( aEvent instanceof ViewportDimensionChangedEvent ) {
				aObserver.onViewportDimensionChangedEvent( (ViewportDimensionChangedEvent) aEvent );
			}
			if ( aEvent instanceof ViewportOffsetChangedEvent ) {
				aObserver.onViewportOffsetChangedEvent( (ViewportOffsetChangedEvent) aEvent );
			}
		}

		else if ( aEvent instanceof PlayerEvent ) {
			aObserver.onPlayerEvent( (PlayerEvent) aEvent, this );
			if ( aEvent instanceof ChangePositionEvent ) {
				aObserver.onChangePositionEvent( (ChangePositionEvent) aEvent, this );
			}
			if ( aEvent instanceof PositionChangedEvent ) {
				aObserver.onPositionChangedEvent( (PositionChangedEvent) aEvent, this );
			}
			if ( aEvent instanceof StateChangedEvent ) {
				aObserver.onStateChangedEvent( (StateChangedEvent) aEvent, this );
			}
			if ( aEvent instanceof VisibilityChangedEvent ) {
				aObserver.onVisibilityChangedEvent( (VisibilityChangedEvent) aEvent, this );
			}
			if ( aEvent instanceof DraggabilityChangedEvent ) {
				aObserver.onDraggabilityChangedEvent( (DraggabilityChangedEvent) aEvent, this );
			}
		}
		return true;
	}

	// /////////////////////////////////////////////////////////////////////////
	// HELPER:
	// /////////////////////////////////////////////////////////////////////////

	private synchronized Player removeFromLookup( Position aPosition ) {
		Player removeFromRow = null;
		Map> theColumn = _rowToColumn.get( aPosition.getPositionX() );
		if ( theColumn != null ) {
			removeFromRow = theColumn.remove( aPosition.getPositionY() );
		}
		Player removeFromColumn = null;
		Map> theRow = _columnToRow.get( aPosition.getPositionY() );
		if ( theRow != null ) {
			removeFromColumn = theRow.remove( aPosition.getPositionX() );
		}
		if ( removeFromColumn != removeFromRow ) { throw new IllegalStateException( "Illegal state detected while removing player \"" + aPosition + "> from position (" + aPosition.getPositionX() + " x " + aPosition.getPositionY() + "). Propably the player's coordinates changed while removing." ); }
		return removeFromColumn;
	}

	private synchronized Player addToLookup( Player aPlayer ) {
		Player theAddFromRow = null;
		Map> theColumn = _rowToColumn.get( aPlayer.getPositionX() );
		if ( theColumn == null ) {
			theColumn = new HashMap<>();
			_rowToColumn.put( aPlayer.getPositionX(), theColumn );
		}
		theAddFromRow = theColumn.put( aPlayer.getPositionY(), aPlayer );

		Player theAddFromColumn = null;
		Map> theRow = _columnToRow.get( aPlayer.getPositionY() );
		if ( theRow == null ) {
			theRow = new HashMap<>();
			_columnToRow.put( aPlayer.getPositionY(), theRow );
		}
		theAddFromColumn = theRow.put( aPlayer.getPositionX(), aPlayer );

		if ( theAddFromColumn != theAddFromRow ) { throw new IllegalStateException( "Illegal state detected while removing player \"" + aPlayer + "> from position (" + aPlayer.getPositionX() + " x " + aPlayer.getPositionY() + "). Propably the player's coordinates changed while removing." ); }
		return theAddFromColumn;
	}

	private synchronized Player updatePlayer( Player thePlayer, Position thePrecedingPosition ) {
		Player theReplacedPlayer = atPosition( thePlayer );
		removePlayer( theReplacedPlayer );
		Player theTmpPlayer = addToLookup( thePlayer );
		if ( theTmpPlayer != null ) { throw new IllegalStateException( "Illegal state detected while moving player \"" + thePlayer + "> from position (" + thePrecedingPosition.toString() + "): The target position (although being cleared before) now contains another player \"" + theTmpPlayer + ">. Propably some thread race condtion and insufficient synchronization." ); }
		Player thePrevPlayerPos = removeFromLookup( thePrecedingPosition );
		if ( thePrevPlayerPos != thePlayer ) { throw new IllegalStateException( "Illegal state detected while moving player \"" + thePlayer + "> from position (" + thePrecedingPosition.toString() + "): The previous position contains the wrong player \"" + thePrevPlayerPos + "> instead of the expected player \"" + thePlayer + "\". Propably some thread race condtion and insufficient synchronization." ); }
		return theReplacedPlayer;
	}

	private int toTopYPosition( Position aPos ) {
		int y = aPos.getPositionY() - 1;
		if ( y < 0 ) {
			if ( getGridMode() == GridMode.PERIODIC ) {
				y = getGridHeight() - 1;
			}
			else {
				throw new IndexOutOfBoundsException( "As the grid is in mode <" + getGridMode() + "> an index of <" + aPos.toString() + "> is out of bounds!" );
			}
		}
		return y;
	}

	private int toRightXPosition( Position aPos ) {
		int x = aPos.getPositionX() + 1;
		if ( x > getGridWidth() - 1 ) {
			if ( getGridMode() == GridMode.PERIODIC ) {
				x = 0;
			}
			else {
				throw new IndexOutOfBoundsException( "As the grid is in mode <" + getGridMode() + "> an index of <" + aPos.toString() + "> is out of bounds!" );
			}
		}
		return x;
	}

	private int toLeftYPosition( Position aPos ) {
		int y = aPos.getPositionY() + 1;
		if ( y > getGridHeight() - 1 ) {
			if ( getGridMode() == GridMode.PERIODIC ) {
				y = 0;
			}
			else {
				throw new IndexOutOfBoundsException( "As the grid is in mode <" + getGridMode() + "> an index of <" + aPos.toString() + "> is out of bounds!" );
			}
		}
		return y;
	}

	private int toLeftXPosition( Position aPos ) {
		int x = aPos.getPositionX() - 1;
		if ( x < 0 ) {
			if ( getGridMode() == GridMode.PERIODIC ) {
				x = getGridWidth() - 1;
			}
			else {
				throw new IndexOutOfBoundsException( "As the grid is in mode <" + getGridMode() + "> an index of <" + aPos.toString() + "> is out of bounds!" );
			}
		}
		return x;
	}

	// /////////////////////////////////////////////////////////////////////////
	// INNER CLASSES:
	// /////////////////////////////////////////////////////////////////////////

	private class CheckerboardPlayerObserverImpl implements PlayerObserver {
		@Override
		public void onPlayerEvent( PlayerEvent aPlayerEvent ) {
			try {
				fireEvent( aPlayerEvent, ExecutionStrategy.SEQUENTIAL );
			}
			catch ( VetoException ignore ) {}
		}

		@Override
		public void onChangePositionEvent( ChangePositionEvent aPlayerEvent ) throws VetoException {
			try {
				fireEvent( aPlayerEvent, ExecutionStrategy.SEQUENTIAL );
			}
			catch ( VetoException ignore ) {}
		}

		@Override
		public void onPositionChangedEvent( PositionChangedEvent aPlayerEvent ) {
			Player thePlayer = aPlayerEvent.getSource();
			Position thePrecedingPosition = aPlayerEvent.getPrecedingPosition();
			updatePlayer( thePlayer, thePrecedingPosition );

			try {
				fireEvent( aPlayerEvent, ExecutionStrategy.SEQUENTIAL );
			}
			catch ( VetoException ignore ) {}
		}

		@Override
		public void onStateChangedEvent( StateChangedEvent aPlayerEvent ) {
			try {
				fireEvent( aPlayerEvent, ExecutionStrategy.SEQUENTIAL );
			}
			catch ( VetoException ignore ) {}
		}

		@Override
		public void onVisibilityChangedEvent( VisibilityChangedEvent aPlayerEvent ) {
			try {
				fireEvent( aPlayerEvent, ExecutionStrategy.SEQUENTIAL );
			}
			catch ( VetoException ignore ) {}
		}

		@Override
		public void onDraggabilityChangedEvent( DraggabilityChangedEvent aPlayerEvent ) {
			try {
				fireEvent( aPlayerEvent, ExecutionStrategy.SEQUENTIAL );
			}
			catch ( VetoException ignore ) {}
		}
	}
}