package org.gbcpainter.game.model.monsters;
import net.jcip.annotations.GuardedBy;
import org.gbcpainter.game.model.Monster;
import org.gbcpainter.game.view.animations.AnimatedSingleSlotMonster;
import org.gbcpainter.geom.PERPENDICULAR_DIRECTION;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.util.Collections;
/**
* Implementation of the monster named "Bomber"
* <p/>
* The Bomber moves randomly in the game grid at high speed, drops a bomb, travels a little more and then stops, activating the bomb countdown. After the bomb exploded it waits a
* little then restarts this cycle
*
* @author Lorenzo Pellegrini
*/
public class Bomber extends AnimatedSingleSlotMonster implements Monster {
/* Start steps waiting times */
private static final int RUNNING_BOMBER_SPEED = 4;
private static final int BOMB_TIMER = 5;
private static final int AFTER_BOMB_EXPLOSION = 12;
private static final int RUN_STEPS_BEFORE_PLANT = 7;
private static final int RUN_STEPS_AFTER_PLANT = 1;
private static final int BOMB_EXPLOSION_TIME = 3;
/* End steps waiting times */
//Bomber has only one texture
private static final Iterable<String> BOMBER_TEXTURES_ROTATION = Collections.singletonList( "Bomber" );
private static final double HALF_POINT = 0.5;
/* Start bomber state variables */
@GuardedBy ("this")
private BOMBER_STATE state = BOMBER_STATE.WAIT_AFTER_BOMB_EXPLOSION;
@GuardedBy ("this")
private int remainingStateSteps;
@Nullable
@GuardedBy ("this")
private Point bombPosition = null;
@Nullable
@GuardedBy ("this")
private Bomb placedBomb = null;
/* End bomber state variables */
/**
* Creates a bomber at the given initial position, initially waiting as if its bomb just exploded
*
* @param initialPosition The initial position of the bomber
*
* @throws Exception If an error occurs while loading textures
*/
public Bomber( @NotNull final Point initialPosition ) throws Exception {
super( initialPosition, BOMBER_TEXTURES_ROTATION );
this.remainingStateSteps = getRequiredSteps( this.state );
/*final Bomb loadTexturesTrick =*/ new BombImpl( 1, 2, 3, new Point( 1, 1 ) );
}
@NotNull
@Override
public PERPENDICULAR_DIRECTION getNewDirection( @Nullable final PERPENDICULAR_DIRECTION previousDirection, final boolean shouldReset ) {
final PERPENDICULAR_DIRECTION exclude;
if ( shouldReset ) {
exclude = previousDirection;
} else if ( previousDirection != null ) {
exclude = previousDirection.getOpposite();
} else {
exclude = null;
}
synchronized (this) {
final PERPENDICULAR_DIRECTION direction = utilGetFilteredDirection( exclude );
setDirection( direction );
return direction;
}
}
@Override
public synchronized void draw( @NotNull Graphics2D g2d ) throws Exception {
super.draw( g2d );
drawBomb( g2d );
}
@Override
public synchronized void setPosition( @NotNull Point position ) {
super.setPosition( position );
checkBombHit();
}
@Override
public synchronized int getSpeed() {
return this.state.isRunning() ? RUNNING_BOMBER_SPEED : 0;
}
@Override
public boolean modifiesColor() {
return false;
}
@Override
public boolean isColoring() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public synchronized void resetState() {
this.applyAnimation( null );
this.state = BOMBER_STATE.WAIT_AFTER_BOMB_EXPLOSION;
this.bombPosition = null;
this.placedBomb = null;
}
@Override
public synchronized String toString() {
return "Bomber{" +
"state=" + this.state +
", remainingStateSteps=" + this.remainingStateSteps +
", bombPosition=" + this.bombPosition +
", placedBomb=" + this.placedBomb +
"} " + super.toString();
}
@Override
public synchronized void endStep() throws Exception {
super.endStep();
this.checkBombHit();
this.remainingStateSteps--;
if ( this.remainingStateSteps <= 0 ) {
switch ( this.state ) {
case WAIT_BOMB:
this.state = BOMBER_STATE.WAIT_EXPLOSION;
break;
case WAIT_EXPLOSION:
this.setDirection( this.utilGetFilteredDirection( null ) );
this.state = BOMBER_STATE.WAIT_AFTER_BOMB_EXPLOSION;
this.bombPosition = null;
this.placedBomb = null;
break;
case WAIT_AFTER_BOMB_EXPLOSION:
this.setDirection( this.utilGetFilteredDirection( null ) );
this.state = BOMBER_STATE.RUN_BEFORE_PLACE;
break;
case RUN_BEFORE_PLACE:
this.setDirection( this.utilGetFilteredDirection( null ) );
this.state = BOMBER_STATE.RUN_AFTER_PLACE;
this.bombPosition = this.getPosition();
final int runAfterPlaceSteps = this.getRequiredSteps( BOMBER_STATE.RUN_AFTER_PLACE );
final int bombTimerSteps = this.getRequiredSteps( BOMBER_STATE.WAIT_BOMB );
final int bombExplosionSteps = this.getRequiredSteps( BOMBER_STATE.WAIT_EXPLOSION );
this.placedBomb = new BombImpl( this.getOwner().getNextStepTime( runAfterPlaceSteps ),
this.getOwner().getNextStepTime( runAfterPlaceSteps + bombTimerSteps ),
this.getOwner().getNextStepTime( runAfterPlaceSteps + bombTimerSteps + bombExplosionSteps ),
this.bombPosition );
break;
case RUN_AFTER_PLACE:
this.state = BOMBER_STATE.WAIT_BOMB;
break;
default:
throw new AssertionError( "Invalid enum constant: " + this.state );
}
this.remainingStateSteps = this.getRequiredSteps( this.state );
}
}
@GuardedBy ("this")
private void drawBomb( @NotNull Graphics2D g2d ) throws Exception {
if ( this.placedBomb != null ) {
this.placedBomb.draw( g2d );
}
}
/**
* Utility methods for mapping a state to the steps required to complete.
*
* @param state A bomber state
*
* @return The required steps for this state
*/
private int getRequiredSteps( @NotNull BOMBER_STATE state ) {
switch ( state ) {
case WAIT_BOMB:
return BOMB_TIMER;
case WAIT_EXPLOSION:
return BOMB_EXPLOSION_TIME;
case WAIT_AFTER_BOMB_EXPLOSION:
return AFTER_BOMB_EXPLOSION;
case RUN_BEFORE_PLACE:
return RUN_STEPS_BEFORE_PLANT;
case RUN_AFTER_PLACE:
return RUN_STEPS_AFTER_PLANT;
default:
throw new AssertionError( "Invalid enum constant: " + state );
}
}
private boolean checkBombHit() {
boolean hit = false;
final Point actualBombPosition;
final BOMBER_STATE actualState;
final Bomb actualBomb;
synchronized (this) {
actualState = this.state;
actualBombPosition = this.bombPosition;
actualBomb = this.placedBomb;
}
if ( actualState == BOMBER_STATE.RUN_AFTER_PLACE || actualState == BOMBER_STATE.WAIT_BOMB || actualState == BOMBER_STATE.WAIT_EXPLOSION ) {
//Check collision with the bomb
assert actualBomb != null;
assert actualBombPosition != null;
final Point playerPosition = this.getOwner().getPlayerPosition();
if ( actualState == BOMBER_STATE.RUN_AFTER_PLACE || actualState == BOMBER_STATE.WAIT_BOMB ) {
if ( playerPosition.equals( actualBombPosition ) ) {
hit = true;
}
} else {
//Bisogna tenere conto del raggio dell'esplosione
Shape explosionArea = actualBomb.getExplosion( actualBombPosition );
if ( explosionArea != null ) {
final Rectangle2D playerBox = new Rectangle2D.Double( playerPosition.getX() - HALF_POINT, playerPosition.getY() - HALF_POINT, 1.0, 1.0 );
if ( explosionArea.intersects( playerBox ) || explosionArea.contains( playerBox ) ) {
hit = true;
}
}
}
if ( hit ) {
synchronized (this) {
this.getOwner().hitPlayer();
this.placedBomb.lockAnimation( this.getOwner().getStepTime() );
}
}
}
return hit;
}
private enum BOMBER_STATE {
WAIT_BOMB, WAIT_EXPLOSION, WAIT_AFTER_BOMB_EXPLOSION, RUN_BEFORE_PLACE, RUN_AFTER_PLACE;
public boolean isRunning() {
return ( this == RUN_AFTER_PLACE ) || ( this == RUN_BEFORE_PLACE );
}
}
}