package rabbitescape.engine;
import static rabbitescape.engine.util.Util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import rabbitescape.engine.WaterRegion;
import rabbitescape.engine.err.RabbitEscapeException;
import rabbitescape.engine.textworld.Comment;
import rabbitescape.engine.util.Dimension;
import rabbitescape.engine.util.LookupTable2D;
import rabbitescape.engine.util.Position;
public class World
{
public static class DontStepAfterFinish extends RabbitEscapeException
{
private static final long serialVersionUID = 1L;
public final String worldName;
public DontStepAfterFinish( String worldName )
{
this.worldName = worldName;
}
}
public static class NoBlockFound extends RabbitEscapeException
{
private static final long serialVersionUID = 1L;
public final int x;
public final int y;
public NoBlockFound( int x, int y )
{
this.x = x;
this.y = y;
}
}
public static class UnableToAddToken extends RabbitEscapeException
{
private static final long serialVersionUID = 1L;
public final Token.Type ability;
public UnableToAddToken( Token.Type ability )
{
this.ability = ability;
}
}
public static class NoSuchAbilityInThisWorld extends UnableToAddToken
{
private static final long serialVersionUID = 1L;
public NoSuchAbilityInThisWorld( Token.Type ability )
{
super( ability );
}
}
public static class NoneOfThisAbilityLeft extends UnableToAddToken
{
private static final long serialVersionUID = 1L;
public NoneOfThisAbilityLeft( Token.Type ability )
{
super( ability );
}
}
public static class CantAddTokenOutsideWorld extends UnableToAddToken
{
private static final long serialVersionUID = 1L;
public final int x;
public final int y;
public final Dimension worldSize;
public CantAddTokenOutsideWorld(
Token.Type ability, int x, int y, Dimension worldSize )
{
super( ability );
this.x = x;
this.y = y;
this.worldSize = worldSize;
}
}
public enum CompletionState
{
RUNNING,
PAUSED,
WON,
LOST
}
public final Dimension size;
public final LookupTable2D<Block> blockTable;
/** A grid of water. Only one water object should be stored in each location. */
public final LookupTable2D<WaterRegion> waterTable;
public final List<Rabbit> rabbits;
public final List<Thing> things;
public final Map<Token.Type, Integer> abilities;
public final String name;
public final String description;
public final String author_name;
public final String author_url;
public final String[] hints;
public final String[] solutions;
public final Comment[] comments;
public final int num_rabbits;
public final int num_to_save;
public final int[] rabbit_delay;
private int rabbit_index_count;
public int num_saved;
public int num_killed;
public int num_waiting;
public boolean paused;
public final WorldChanges changes;
public final String music;
public final VoidMarkerStyle.Style voidStyle;
public World(
Dimension size,
List<Block> blocks,
List<Rabbit> rabbits,
List<Thing> things,
Map<Position, Integer> waterAmounts,
Map<Token.Type, Integer> abilities,
String name,
String description,
String author_name,
String author_url,
String[] hints,
String[] solutions,
int num_rabbits,
int num_to_save,
int[] rabbit_delay,
String music,
int num_saved,
int num_killed,
int num_waiting,
int rabbit_index_count,
boolean paused,
Comment[] comments,
WorldStatsListener statsListener,
VoidMarkerStyle.Style voidStyle
)
{
this.size = size;
this.rabbits = rabbits;
this.things = things;
this.abilities = abilities;
this.name = name;
this.description = description;
this.author_name = author_name;
this.author_url = author_url;
this.hints = hints;
this.solutions = solutions;
this.num_rabbits = num_rabbits;
this.num_to_save = num_to_save;
this.rabbit_delay = rabbit_delay;
this.music = music;
this.num_saved = num_saved;
this.num_killed = num_killed;
this.num_waiting = num_waiting;
this.rabbit_index_count = rabbit_index_count;
this.paused = paused;
this.comments = comments;
this.voidStyle = voidStyle;
if ( -1 == size.width )
{
// make allowance for tests with no world
this.blockTable = null;
this.waterTable = new LookupTable2D<WaterRegion>( size );
}
else
{
this.blockTable = new LookupTable2D<Block>( blocks, size );
this.waterTable = WaterRegionFactory.generateWaterTable( blockTable,
waterAmounts );
}
this.changes = new WorldChanges( this, statsListener );
init();
}
private void init()
{
// Number the rabbits if necessary
for ( Rabbit r: rabbits )
{
rabbitIndex( r );
}
// Rearrange them, this may be necessary if they have been
// restored from state.
Collections.sort( rabbits );
for ( Thing thing : allThings() )
{
thing.calcNewState( this );
}
}
public void rabbitIndex( Rabbit r )
{
r.index = ( r.index == Rabbit.NOT_INDEXED )
? ++rabbit_index_count
: r.index;
}
public int getRabbitIndexCount()
{
return rabbit_index_count;
}
/**
* For levels with some rabbits in to start with.
* Then entering rabbits are indexed correctly.
*/
public void countRabbitsForIndex()
{
rabbit_index_count = rabbit_index_count == 0 ?
rabbits.size() : rabbit_index_count;
for ( Rabbit r:rabbits )
{
rabbit_index_count = rabbit_index_count > r.index ?
rabbit_index_count : r.index;
}
}
public void step()
{
if ( completionState() != CompletionState.RUNNING )
{
throw new DontStepAfterFinish( name );
}
for ( Thing thing : allThings() )
{
thing.step( this );
}
changes.rememberWhatWillHappen();
changes.apply();
for ( Thing thing : allThings() )
{
thing.calcNewState( this );
}
changes.blocksJustRemoved.clear();
changes.apply();
}
public ChangeDescription describeChanges()
{
ChangeDescription ret = new ChangeDescription();
for ( Thing thing : allThings() )
{
ret.add( thing.x, thing.y, thing.state );
}
return ret;
}
private Iterable<Thing> allThings()
{
return chain( waterTable.getItems(), rabbits, things );
}
public Block getBlockAt( int x, int y)
{
if ( x < 0 || y < 0 ||
x >= size.width || y >= size.height )
{
return null;
}
return blockTable.getItemAt( x, y );
}
public CompletionState completionState()
{
if ( paused )
{
return CompletionState.PAUSED;
}
else if ( rabbits.size() == 0 && this.num_waiting <= 0 )
{
if ( num_saved >= num_to_save )
{
return CompletionState.WON;
}
else
{
return CompletionState.LOST;
}
}
else
{
return CompletionState.RUNNING;
}
}
public Token getTokenAt( int x, int y )
{
// Note it is not worth using LookupTable2D for things.
// Handling their movement would complicate the code.
// There are not as many instances of Thing as Block.
// Iterating to check through is not too time
// consuming.
for ( Thing thing : things )
{
if ( thing.x == x && thing.y == y && thing instanceof Token )
{
if ( !changes.tokensToRemove.contains( thing ) )
{
return (Token)thing;
}
}
}
return null;
}
public List<Thing> getThingsAt( int x, int y )
{
ArrayList<Thing> ret = new ArrayList<Thing>();
for ( Thing thing : things )
{
if ( thing.x == x && thing.y == y )
{
if ( !changes.tokensToRemove.contains( thing ) &&
!changes.fireToRemove.contains( thing ) )
{
ret.add( thing );
}
}
}
return ret;
}
public boolean fireAt( int x, int y )
{
// See note for getTokenAt() about Thing storage.
for ( Thing thing : things )
{
if ( thing.x == x && thing.y == y && thing instanceof Fire )
{
if ( !changes.fireToRemove.contains( thing ) )
{
return true;
}
}
}
return false;
}
public Rabbit[] getRabbitsAt( int x, int y )
{
List<Rabbit> ret = new ArrayList<Rabbit>();
for ( Rabbit rabbit : rabbits )
{
if ( rabbit.x == x && rabbit.y == y )
{
ret.add( rabbit );
}
}
return ret.toArray( new Rabbit[ret.size()] );
}
public int numRabbitsOut()
{
return num_rabbits -
( this.num_waiting + num_killed + num_saved );
}
public void setPaused( boolean paused )
{
this.paused = paused;
}
public void recalculateWaterRegions( Position point )
{
waterTable.removeItemsAt( point.x, point.y );
WaterRegionFactory.createWaterRegionsAtPoint( blockTable, waterTable, point.x, point.y );
}
public Map<Position, Integer> getWaterContents()
{
Map<Position, Integer> waterAmounts = new HashMap<>();
for ( WaterRegion waterRegion : waterTable )
{
if ( waterAmounts.containsKey( waterRegion.getPosition() ) )
{
throw new IllegalStateException(
"There is currently no support for multiple WaterRegions "
+ "within a single cell." );
}
int contents = waterRegion.getContents();
if ( contents != 0 )
{
waterAmounts.put( waterRegion.getPosition(),
contents );
}
}
return waterAmounts;
}
}