package org.gbcpainter.view.draw;
import org.gbcpainter.env.GameSettings;
import org.gbcpainter.env.GraphicsEnv;
import org.gbcpainter.game.levels.Level;
import org.gbcpainter.game.levels.LivesLevel;
import org.gbcpainter.game.levels.ScoreLevel;
import org.gbcpainter.game.model.Monster;
import org.gbcpainter.game.model.Player;
import org.gbcpainter.game.view.animations.AnimatedElement;
import org.gbcpainter.game.view.animations.EntityMovementAnimation;
import org.gbcpainter.game.view.animations.TerminatedAnimationException;
import org.gbcpainter.game.model.grid.Face;
import org.gbcpainter.game.model.grid.Junction;
import org.gbcpainter.game.model.grid.OpaqueRandomFace;
import org.gbcpainter.game.model.grid.Pipe;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jgrapht.Graph;
import org.jgrapht.graph.UnmodifiableUndirectedGraph;
import java.awt.*;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.*;
import java.util.List;
/**
* Implementation of the {@link ComposableRedrawJob}.
* <p/>
* It draws the game, lives and score
*
* @author Lorenzo Pellegrini
*/
public class RedrawJobImpl extends ComposableRedrawJob<RedrawJobImpl.COMPONENTS> {
private static final double SCORE_WIDTH_DIMENSION = 0.6;
private static final double SCORE_HEIGHT_DIMENSION = 0.1;
private static final double LIVES_WIDTH_DIMENSION = 0.4;
private static final double LIVES_HEIGHT_DIMENSION = 0.1;
private static final int MAX_LATERAL_SPACE = 2;
private static final boolean THROW_EXCEPTION_ON_FACES_ERROR = true;
private static final List<COMPONENTS> COMPONENTS_LIST = Arrays.asList( COMPONENTS.CORE_VIEW, COMPONENTS.LIVES_VIEW, COMPONENTS.SCORE_VIEW );
@NotNull
private final Map<Face, Integer> faces;
@NotNull
private final Rectangle levelBounds;
/**
* Creates a new redraw job given the canvas where to draw to, the level and the initial fps
*
* @param whereTo The canvas where to draw to
* @param level The level to be drawn
* @param refreshPerSecond The FPS
*
* @throws java.lang.IllegalArgumentException If FPS is lower than 0
*/
public RedrawJobImpl( @NotNull final Canvas whereTo, @NotNull final Level level, final int refreshPerSecond ) throws IllegalArgumentException {
super( whereTo, level, refreshPerSecond, COMPONENTS_LIST );
if ( refreshPerSecond < 0 ) {
throw new IllegalArgumentException( "FPS can't be less than 0" );
}
Map<Set<Pipe>, Integer> facesMap = level.getFacesMap();
UnmodifiableUndirectedGraph<Junction, Pipe> levelMap = level.getMap();
this.faces = new HashMap<>( facesMap.size() );
for (Map.Entry<Set<Pipe>, Integer> faceAndId : facesMap.entrySet()) {
/* Per ogni faccia ... */
Pipe firstPipe = faceAndId.getKey().iterator().next();
Junction lastJunction = levelMap.getEdgeSource( firstPipe );
Pipe lastPipe = null;
GeneralPath faceShape = new GeneralPath();
faceShape.moveTo( lastJunction.getPosition().x, lastJunction.getPosition().y );
while ( ! firstPipe.equals( lastPipe ) ) {
/* Aggiunge la linea*/
Pipe nextPipe = null;
final Set<Pipe> connectedSegments = new HashSet<>( levelMap.edgesOf( lastJunction ) );
if ( lastPipe == null ) {
/* È il primo loop */
lastPipe = firstPipe;
}
/* Scarta il segmento già percorso */
connectedSegments.remove( lastPipe );
for (Pipe perimeterSegment : faceAndId.getKey()) {
/* Tra tutti i segmenti del perimetro seleziona solo quello che confina con il vertice corrente */
if ( connectedSegments.contains( perimeterSegment ) ) {
nextPipe = perimeterSegment;
break;
}
}
if ( nextPipe == null ) {
if( THROW_EXCEPTION_ON_FACES_ERROR ) {
throw new IllegalArgumentException("Invalid faces set");
} else {
lastPipe = firstPipe;
faceShape.closePath();
}
} else {
lastPipe = nextPipe;
final Junction edgeSource = levelMap.getEdgeSource( nextPipe );
lastJunction = edgeSource.equals( lastJunction ) ? levelMap
.getEdgeTarget( nextPipe ) : edgeSource;
final Point junctionPosition = lastJunction.getPosition();
faceShape.lineTo( junctionPosition.x, junctionPosition.y );
}
}
faces.put( new OpaqueRandomFace( faceShape ), faceAndId.getValue() );
}
this.levelBounds = makeBoundRectangle( level.getMap() );
}
@NotNull
private static Rectangle makeBoundRectangle( @NotNull Graph<Junction, Pipe> levelGraph ) {
int minY = Integer.MAX_VALUE;
int maxY = 0;
int minX = Integer.MAX_VALUE;
int maxX = 0;
for (Junction vertex : levelGraph.vertexSet()) {
final Point vertexPosition = vertex.getPosition();
minX = Math.min( vertexPosition.x, minX );
maxX = Math.max( vertexPosition.x, maxX );
minY = Math.min( vertexPosition.y, minY );
maxY = Math.max( vertexPosition.y, maxY );
}
return new Rectangle( minX, minY, maxX - minX, maxY - minY );
}
@Override
protected final void drawComponent( @NotNull final COMPONENTS component, @NotNull final Graphics2D g2d ) throws Exception {
if ( GameSettings.getInstance().getInstanceValue( GameSettings.INTEGER_SETTINGS_TYPE.ANTIALIAS ) != 0 ) {
g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
}
switch ( component ) {
case CORE_VIEW:
drawCore( g2d );
break;
case SCORE_VIEW:
if ( getLevel() instanceof ScoreLevel ) {
drawScore( g2d );
}
break;
case LIVES_VIEW:
if ( getLevel() instanceof LivesLevel ) {
drawLives( g2d );
}
break;
}
}
@Nullable
@Override
protected final Rectangle getComponentRect( @NotNull final COMPONENTS component ) {
final Dimension windowSize = GraphicsEnv.getInstance().getWindowSize();
switch ( component ) {
case CORE_VIEW:
return new Rectangle( windowSize );
case SCORE_VIEW:
if ( getLevel() instanceof ScoreLevel ) {
final int yPosition = (int) Math.ceil( windowSize.height * ( 1.0 - SCORE_HEIGHT_DIMENSION ) );
return new Rectangle( 0,
yPosition,
(int) Math.floor( windowSize.width * SCORE_WIDTH_DIMENSION ),
windowSize.height - yPosition );
}
case LIVES_VIEW:
if ( getLevel() instanceof LivesLevel ) {
final int yPosition = (int) Math.ceil( windowSize.height * ( 1.0 - LIVES_HEIGHT_DIMENSION ) );
return new Rectangle( (int) Math.floor( windowSize.width * SCORE_WIDTH_DIMENSION ) + 1,
yPosition,
(int) Math.ceil( windowSize.width * LIVES_WIDTH_DIMENSION ),
windowSize.height - yPosition );
}
}
return null;
}
private void drawCore( @NotNull Graphics2D g2d ) throws Exception {
final Level level = getLevel();
final Player player = level.getPlayer();
final Point2D screenPlayerPosition;
if ( player instanceof AnimatedElement ) {
final EntityMovementAnimation animation = ( (AnimatedElement) player ).getAnimation();
Point2D animationPosition;
if ( animation != null ) {
try {
animationPosition = animation.getActualPosition();
} catch ( TerminatedAnimationException e ) {
animationPosition = player.getPosition();
}
} else {
animationPosition = player.getPosition();
}
screenPlayerPosition = GraphicsEnv.getInstance().centeredGamePointToScreen( animationPosition );
} else {
screenPlayerPosition = GraphicsEnv.getInstance().centeredGamePointToScreen( player.getPosition() );
}
int actualGamePointSize = GraphicsEnv.getInstance().getScaledPointDimension();
double screenMaxLateralSize = actualGamePointSize * MAX_LATERAL_SPACE;
final Rectangle2D gameScreenBounds = new Rectangle2D.Double( ( this.levelBounds.x * actualGamePointSize ) - screenMaxLateralSize,
( this.levelBounds.y * actualGamePointSize ) - screenMaxLateralSize,
( this.levelBounds.width * actualGamePointSize ) + ( screenMaxLateralSize * 2 ),
( this.levelBounds.height * actualGamePointSize ) + ( screenMaxLateralSize * 2 ) );
final Rectangle clipBounds = g2d.getClipBounds();
final Rectangle2D gameScreen = new Rectangle2D.Double( screenPlayerPosition.getX() - ( clipBounds.width / 2 ),
screenPlayerPosition.getY() - ( clipBounds.height / 2 ),
clipBounds.width, clipBounds.height );
if ( ! gameScreenBounds.contains( gameScreen ) ) {
if ( gameScreen.getWidth() < gameScreenBounds.getWidth() ) {
if ( gameScreen.getMaxX() > gameScreenBounds.getMaxX() ) {
gameScreen.setFrame( gameScreen.getX() - ( gameScreen.getMaxX() - gameScreenBounds.getMaxX() ),
gameScreen.getY(),
gameScreen.getWidth(),
gameScreen.getHeight() );
}
if ( gameScreen.getMinX() < gameScreenBounds.getMinX() ) {
gameScreen.setFrame( gameScreenBounds.getMinX(), gameScreen.getMinY(), gameScreen.getWidth(), gameScreen.getHeight() );
}
} else {
gameScreen.setFrame( gameScreenBounds.getCenterX() - ( gameScreen.getWidth() / 2 ), gameScreen.getMinY(),
gameScreen.getWidth(), gameScreen.getHeight() );
}
if ( gameScreen.getHeight() < gameScreenBounds.getHeight() ) {
if ( gameScreen.getMaxY() > gameScreenBounds.getMaxY() ) {
gameScreen.setFrame( gameScreen.getX(),
gameScreen.getY() - ( gameScreen.getMaxY() - gameScreenBounds.getMaxY() ),
gameScreen.getWidth(),
gameScreen.getHeight() );
}
if ( gameScreen.getMinY() < gameScreenBounds.getMinY() ) {
gameScreen.setFrame( gameScreen.getMinX(), gameScreenBounds.getMinY(), gameScreen.getWidth(), gameScreen.getHeight() );
}
} else {
gameScreen.setFrame( gameScreenBounds.getMinX(), gameScreenBounds.getCenterY() - ( gameScreen.getHeight() / 2 ),
gameScreen.getWidth(), gameScreen.getHeight() );
}
}
g2d.translate( - gameScreen.getX(), - gameScreen.getY() );
for (Map.Entry<Face, Integer> faceEntry : this.faces.entrySet()) {
final Face face = faceEntry.getKey();
face.setColored( level.isFaceColored( faceEntry.getValue() ) );
face.draw( g2d );
}
for (Pipe pipe : level.getMap().edgeSet()) {
pipe.draw( g2d );
}
for (Junction junction : level.getMap().vertexSet()) {
junction.draw( g2d );
}
for (Monster mob : level.getMonsters()) {
mob.draw( g2d );
}
player.draw( g2d ) ;
}
private void drawScore( @NotNull Graphics2D g2d ) throws Exception {
final Rectangle clipBounds = getComponentRect( COMPONENTS.SCORE_VIEW );
assert clipBounds != null;
final Level level = getLevel();
assert level instanceof ScoreLevel;
g2d.setFont( GraphicsEnv.getInstance().getScoreFont() );
g2d.setColor( Color.GREEN );
g2d.drawString( "" + ( (ScoreLevel) level ).getScore(), clipBounds.x, clipBounds.y + clipBounds.height - 1 );
}
private void drawLives( @NotNull Graphics2D g2d ) throws Exception {
final Rectangle clipBounds = getComponentRect( COMPONENTS.LIVES_VIEW );
assert clipBounds != null;
final Level level = getLevel();
assert level instanceof LivesLevel;
g2d.setFont( GraphicsEnv.getInstance().getScoreFont() );
g2d.setColor( Color.RED );
g2d.drawString( "" + ( (LivesLevel) level ).getActualLives(), clipBounds.x, clipBounds.y + clipBounds.height - 1 );
}
public static enum COMPONENTS {
CORE_VIEW, SCORE_VIEW, LIVES_VIEW
}
@Override
@NonNls
public String toString() {
return "CoreRedrawJob{" +
"faces=" + faces +
", levelBounds=" + levelBounds +
"} " + super.toString();
}
}