this.playerArrows.addAll( this.playerDirectionNew );
final Set<PERPENDICULAR_DIRECTION> actualDirection = this.playerArrows;
final Player player = this.painter.getFirst();
assert player != null;
final boolean anyKeyPressed = ! actualDirection.isEmpty();
final boolean oppositePressed = ( actualDirection
.contains( PERPENDICULAR_DIRECTION.DOWN ) && actualDirection
.contains( PERPENDICULAR_DIRECTION.UP ) )
|| ( actualDirection
.contains( PERPENDICULAR_DIRECTION.RIGHT ) && actualDirection
.contains( PERPENDICULAR_DIRECTION.LEFT ) );
final boolean isMoving = anyKeyPressed && ! oppositePressed;
@Nullable
PathAnimation playerPath = null;
int remainingPlayerSpeed = isMoving ? player.getSpeed() : 0;
player.endStep();
/* End player variables */
/* Start monsters variables initialization */
final Map<Monster, Integer> roundRobin = new HashMap<>();
final Set<Monster> levelMonsters = this.getMonsters();
final Set<Monster> stillMonsters = new HashSet<>();
for (Monster mob : levelMonsters) {
if ( mob instanceof AnimatedElement ) {
this.monstersAndAnimations.put( ( (AnimatedElement) mob ), null );
}
final int mobSpeed = mob.getSpeed();
if ( mobSpeed > 0 ) {
roundRobin.put( mob, mobSpeed );
} else {
stillMonsters.add( mob );
}
mob.endStep();
}
/* End monsters variables and initialization */
/* Start changes reset from previous move */
this.resetModifiedElements();
for (Pipe pipe : this.getMap().edgeSet()) {
pipe.clearAnimations();
}
for (Junction junction : this.getMap().vertexSet()) {
junction.clearAnimations();
}
/* End changes reset from previous move */
/*
This is the main loop
Every entity is moved ny a number of places equal to its speed
Monsters aren't moved in a burst!
A round robin algorithm is used instead.
Monsters and player speed was previously saved. This will be used as a counter.
Every monster and the player moves by one place each loop and the counter associated is lowered by one.
The loop ends when every entity has a 0 counter
*/
while ( ( ! roundRobin.isEmpty() ) || remainingPlayerSpeed > 0 ) {
if ( remainingPlayerSpeed > 0 ) {
final Point playerPosition = player.getPosition();
/* Muovo il giocatore */
remainingPlayerSpeed--;
int elem = 0;
PERPENDICULAR_DIRECTION[] choices = new PERPENDICULAR_DIRECTION[2];
Set<PERPENDICULAR_DIRECTION> availableDirections = this.getAvailablePlayerDirections();
if ( actualDirection.contains( PERPENDICULAR_DIRECTION.DOWN ) && availableDirections
.contains( PERPENDICULAR_DIRECTION.DOWN ) ) {
choices[elem] = PERPENDICULAR_DIRECTION.DOWN;
elem++;
}
if ( actualDirection.contains( PERPENDICULAR_DIRECTION.UP ) && availableDirections
.contains( PERPENDICULAR_DIRECTION.UP ) ) {
choices[elem] = PERPENDICULAR_DIRECTION.UP;
elem++;
}
if ( actualDirection.contains( PERPENDICULAR_DIRECTION.LEFT ) && availableDirections
.contains( PERPENDICULAR_DIRECTION.LEFT ) ) {
choices[elem] = PERPENDICULAR_DIRECTION.LEFT;
elem++;
}
if ( actualDirection
.contains( PERPENDICULAR_DIRECTION.RIGHT ) && availableDirections
.contains( PERPENDICULAR_DIRECTION.RIGHT ) ) {
choices[elem] = PERPENDICULAR_DIRECTION.RIGHT;
elem++;
}
boolean changedDirection = false;
assert elem != 0;
if ( elem == 1 ) {
if ( this.playerDirection != choices[0] ) {
/* Direction change */
this.playerDirection = choices[0];
changedDirection = true;
}
} else {
/* The arrow that differs from the actual direction as an higher priority */
if ( choices[0] == this.playerDirection ) {
changedDirection = true;
this.playerDirection = choices[1];
} else if ( choices[1] == this.playerDirection ) {
changedDirection = true;
this.playerDirection = choices[0];
} else {
/*
User is pressing two valid arrow in the same time and was not
following none of these before
*/
this.playerDirection = null;
}
}
if ( this.playerDirection != null ) {
//Player moves
if ( changedDirection ) {
player.setDirection( this.playerDirection );
}
MapGridElement playerFooting = this.painter.getSecond();
assert playerFooting != null;
//Apply the coloring animations to the grid element
if ( playerPath == null ) {
//If the path wasn't created yet, create it
playerPath = new PathAnimationImpl();
playerPath.startNewPart( this.playerDirection );
playerPath.addPoint( playerPosition );
if ( player.modifiesColor() ) {
utilPushAnimation( entitiesColoringTrace,
playerFooting,
playerPath,
player.isColoring(),
playerPath.getNewPart() );
}
} else if ( changedDirection ) {
playerPath.startNewPart( this.playerDirection );
playerPath.addPoint( playerPosition );
if ( player.modifiesColor() ) {
utilPushAnimation( entitiesColoringTrace,
playerFooting,
playerPath,
player.isColoring(),
playerPath.getNewPart() );
}
}
switch ( this.playerDirection ) {
case LEFT:
playerPosition.x--;
break;
case RIGHT:
playerPosition.x++;
break;
case UP:
playerPosition.y--;
break;
case DOWN:
playerPosition.y++;
break;
}
player.setPosition( playerPosition );
playerPath.addPoint( playerPosition );
if ( ! playerFooting.contains( playerPosition ) ) {
//The player changed grid element, the animations must be applied to this element too
playerFooting = findElementRelativeTo( this.levelGraph, playerFooting,
this.playerDirection );
this.painter.setSecond( playerFooting );
if ( player.modifiesColor() ) {
utilPushAnimation( entitiesColoringTrace,
playerFooting,
playerPath,
player.isColoring(),
playerPath.getNewPart() );
}
}
if ( player.modifiesColor() ) {
//Colors this part of the grid
if ( playerFooting.isColoredAt( playerPosition ) != player.isColoring() ) {
if ( player.isColoring() ) {
this.addScore( COLORING_SCORE );
} else {
//Decoloration by the player is allowed, only for completeness
this.addScore( - COLORING_SCORE );
}
}
this.utilColorGridElementAndUpdateChanges( playerFooting,
playerPosition,
player.isColoring() );
}
for (Monster stillMonster : stillMonsters) {
//Moving monster will be checked for collision in the code below
if ( stillMonster.getPosition().equals( playerPosition ) ) {
deadMan = true;
}
}
} else {
remainingPlayerSpeed = 0;
}
}
final Point actualPlayerPosition = player.getPosition();
//Monsters are moved
Iterator<Map.Entry<Monster, Integer>> monstersIterator = roundRobin.entrySet()
.iterator();
while ( monstersIterator.hasNext() ) {
final Map.Entry<Monster, Integer> monsterWithSpeed = monstersIterator.next();
final int remainingMonsterSpeed = monsterWithSpeed.getValue();
final Monster mob = monsterWithSpeed.getKey();
if ( remainingMonsterSpeed > 0 ) {
monsterWithSpeed.setValue( remainingMonsterSpeed - 1 );
final Point monsterPosition = mob.getPosition();
if ( actualPlayerPosition.equals( monsterPosition ) ) {
deadMan = true;
}
final PathAnimation mobAnimation;
@Nullable
PERPENDICULAR_DIRECTION monsterDirection;
if ( this.firstStep ) {
monsterDirection = null;
} else {
monsterDirection = mob.getActualDirection();
}
MapGridElement monsterFooting = this.monstersPosition.get( mob );
assert monsterFooting != null;
if ( monsterFooting instanceof Junction || monsterDirection == null ) {
//Monster is in a junction, must change direction
monsterDirection = mob.getNewDirection( monsterDirection, false );
}
if ( mob instanceof AnimatedElement ) {
//This is an animated monster, an animations must be applied
PathAnimation animation = this.monstersAndAnimations.get( mob );
if ( animation == null ) {
//If the animations wasn't created yet, create it
animation = new PathAnimationImpl();
animation.startNewPart( monsterDirection );
animation.addPoint( monsterPosition );
this.monstersAndAnimations.put( ( (AnimatedElement) mob ), animation );
if ( mob.modifiesColor() ) {
utilPushAnimation( entitiesColoringTrace,
monsterFooting,
animation,
mob.isColoring(),
animation.getNewPart() );
}
} else if ( monsterDirection != animation.getNewPartDirection() ) {
animation.startNewPart( monsterDirection );
animation.addPoint( monsterPosition );
if ( mob.modifiesColor() ) {
utilPushAnimation( entitiesColoringTrace,
monsterFooting,
animation,
mob.isColoring(),
animation.getNewPart() );
}
}
mobAnimation = animation;
} else {
mobAnimation = null;
}
switch ( monsterDirection ) {
case LEFT:
monsterPosition.x--;
break;
case RIGHT:
monsterPosition.x++;
break;
case UP:
monsterPosition.y--;
break;
case DOWN:
monsterPosition.y++;
break;
}
mob.setPosition( monsterPosition );
if ( mobAnimation != null ) {
mobAnimation.addPoint( monsterPosition );
}
if ( ! monsterFooting.contains( monsterPosition ) ) {
//The monster changed grid element, the animations must be applied to this element too
monsterFooting = findElementRelativeTo( this.levelGraph,
monsterFooting,
monsterDirection );
this.monstersPosition.put( mob, monsterFooting );
if ( mob.modifiesColor() && mobAnimation != null ) {
utilPushAnimation( entitiesColoringTrace,
monsterFooting,
mobAnimation,
mob.isColoring(),
mobAnimation.getNewPart() );
}
}
if ( mob.modifiesColor() ) {
//(De)Colors this part of the grid
final boolean isMobColoring = mob.isColoring();
final boolean wasColoredHere = monsterFooting.isColoredAt( monsterPosition );
if ( wasColoredHere != isMobColoring ) {
if ( wasColoredHere ) {
this.addScore( - COLORING_SCORE );
} else {
this.addScore( COLORING_SCORE );
}
}
this.utilColorGridElementAndUpdateChanges( monsterFooting, monsterPosition,
isMobColoring );
}
if ( actualPlayerPosition.equals( monsterPosition ) ) {
deadMan = true;
}
} else {
//Monster remaining speed is 0, remove it from the moving monsters
stillMonsters.add( mob );
monstersIterator.remove();
}
}
}
//Computes the end time of all the animations
final long scheduledAnimationEndTime = millisCallTime + GameSettings.getInstance()
.getValue( GameSettings.INTEGER_SETTINGS_TYPE.MOVE_MILLIS_TIMEOUT );
if ( playerPath != null && player instanceof AnimatedElement ) {
//Applies the previously computed path animations to the Player entity as an entity animations
playerPath.startAnimation( millisCallTime, scheduledAnimationEndTime );
( (AnimatedElement) player )
.applyAnimation( new PathBasedMovementAnimation( playerPath ) );
}
for (Map.Entry<AnimatedElement, PathAnimation> animatedEntry : this.monstersAndAnimations
.entrySet()) {
//Applies the previously computed path animations to the Monster entity as an entity animations
final PathAnimation entryAnimation = animatedEntry.getValue();
if ( entryAnimation != null ) {
entryAnimation.startAnimation( millisCallTime, scheduledAnimationEndTime );
animatedEntry.getKey()
.applyAnimation( new PathBasedMovementAnimation( entryAnimation ) );
}
}
for (Map.Entry<MapGridElement, Map<PathAnimation, Pair<Boolean, Collection<Segment>>>> mapGridElementEntry : entitiesColoringTrace
.entrySet()) {
//The key of the outer map keeps the grid element the animations refer to
final MapGridElement mapElement = mapGridElementEntry.getKey();
if ( mapElement instanceof Pipe ) {
final Collection<PipeColoringAnimation> resultElementAnimations = new LinkedList<>();
for (Map.Entry<PathAnimation, Pair<Boolean, Collection<Segment>>> pathAnimationEntry : mapGridElementEntry
.getValue().entrySet()) {
/*
The inner map keeps the mapping path animations: <coloring/decoloring flag; list of segments>.
The list of segments usually keeps only one segment because entities speeds are in the range 0-3 so
the same entity can't enter and, exit and then enter another time the same grid element is a single step
*/
final PathAnimation animation = pathAnimationEntry.getKey();
final boolean isColoring = pathAnimationEntry.getValue().getFirst();
for (Segment segment : pathAnimationEntry.getValue().getSecond()) {
resultElementAnimations.add( new PathBasedPipeColoringAnimation( animation,
segment,
( (Pipe) mapElement )
.getSegment(),
isColoring ) );
}
}
( (Pipe) mapElement ).applyAnimations( resultElementAnimations );
} else /*if ( mapElement instanceof Junction )*/ {
final Collection<JunctionColoringAnimation> resultElementAnimations = new LinkedList<>();
for (Map.Entry<PathAnimation, Pair<Boolean, Collection<Segment>>> pathAnimationEntry : mapGridElementEntry
.getValue().entrySet()) {
final PathAnimation animation = pathAnimationEntry.getKey();
final boolean isColoring = pathAnimationEntry.getValue().getFirst();
//Segment list is not needed when creating the JunctionColoringAnimation
resultElementAnimations.add( new PathBasedJunctionColoringAnimation( animation,
( (Junction) mapElement )
.getPosition(),
isColoring ) );
}
( (Junction) mapElement ).applyAnimations( resultElementAnimations );
}
}
if ( ! this.playerHitted ) {
if ( deadMan ) {
this.hitPlayer();
} else if ( this.checkWinCondition(this.modifiedMapElements) ) {
this.state = LEVEL_STATE.LEVEL_WIN;
}
}
if ( this.coloredFaces > previousColoredFacesNumber ) {
final int coloredFacesThisStep = this.coloredFaces - previousColoredFacesNumber;
this.addScore( FACE_COLORING_SCORE * ( 2 * coloredFacesThisStep - 1 ) );
}
for (Monster mob : levelMonsters) {
mob.startStep();
}
player.startStep();
this.firstStep = false;
}