org.refcodes.checkerboard.impls.CheckerboardImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of refcodes-checkerboard Show documentation
Show all versions of refcodes-checkerboard Show documentation
Artifact for providing some easy means to visualize (state of) board
games or (state of) cellular automatons.
// /////////////////////////////////////////////////////////////////////////////
// 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 ) {}
}
}
}