Package com.szuppe.jakub.model

Source Code of com.szuppe.jakub.model.Level

/**
*
*/
package com.szuppe.jakub.model;

import java.awt.Dimension;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import com.szuppe.jakub.common.Acceleration2D;
import com.szuppe.jakub.common.Coordinates2D;
import com.szuppe.jakub.common.Direction;
import com.szuppe.jakub.common.ImaginaryRootsException;
import com.szuppe.jakub.common.QuadraticEquation;
import com.szuppe.jakub.common.SpeedVector2D;
import com.szuppe.jakub.common.Vertices2D;
import com.szuppe.jakub.config.model.LevelConfig;
import com.szuppe.jakub.mockups.*;
import com.thoughtworks.xstream.XStream;

/**
* Klasa reprezentująca przestrzeń gry - plansze.
* <br/>
* Plansza posiada piłkę, paletkę, klocki, a także listę map.
* <br/>
* Symuluje fizykę zderzeń.
*
* @author Jakub Szuppe <j.szuppe at gmail.com>
*
*/
/**
* @author Jakub Szuppe <j.szuppe at gmail.com>
*
*/
class Level
{
  /** Informacje o graczu. (Życie, punkty) */
  private final Player                         player;
  /** Wymiary planszy */
  private final Dimension                        levelDimension;
  /** Piłka. */
  private final Ball                          ball;
  /** Paletka. */
  private final Paddle                        paddle;
  /** Lista map/poziomów gry. */
  private final LinkedList<List<Brick>>                listOfLevels;
  /** Obiekt zawierający aktualnie niezbite klocki na planszy. */
  private final Bricks                        bricks;
  /** Mapa, która danej kolizji przyporządkowuje odpowiednia strategie.*/
  private final Map<Class<? extends Collision>, CollisionStrategy>  collisionStrategyMap;
  /** Ostatnie czas, kiedy zmienialiśmy stan planszy. */
  private long                            lastUpdateTime;
  /** Czas przez, który plansza jest poprawna. */
  private long                            validTime;
  /** Lista przewidzianych kolizji. */
  private final List<Collision>                    predictedCollisions;
  /** Konfiguracja planszy. */
  @SuppressWarnings("unused")
  private final LevelConfig                      levelConfig;

  /**
   * Tworzy nową planszę zgodnie na podstawie podanej
   * konfiguracji.
   *
   * @param levelConfig
   */
  @SuppressWarnings("unchecked")
  public Level(LevelConfig levelConfig)
  {
    this.levelConfig = levelConfig;
    this.levelDimension = levelConfig.getLevelDimension();
    this.paddle = new Paddle(levelConfig.getPaddleConfig());
    this.ball = new Ball(levelConfig.getBallConfig());
    this.player = new Player(levelConfig.getPlayerConfig());

    XStream xStream = new XStream();

    URL urlToListOfLevels = getClass().getResource("/com/szuppe/jakub/resources/listOfLevels.xml");
    this.listOfLevels = (LinkedList<List<Brick>>) xStream.fromXML(urlToListOfLevels);
    this.bricks = new Bricks(listOfLevels.removeFirst());
   
    // Nie było przeliczania stanu.
    this.lastUpdateTime = 0;
    // Stan jest ciągle dobry.
    this.validTime = -1;
   
    this.collisionStrategyMap = new HashMap<>();
    predictedCollisions = new LinkedList<>();
    initCollisionStrategyMap();
  }

  /**
   * Wczytuje kolejny poziom gry i ustawia piłkę oraz paletkę na
   * ich pozycjach startowych.
   *
   * @throws NoMoreMapsException - rzucany jeżeli nie ma kolejnego poziomu.
   */
  public void loadNextBricksMap() throws NoMoreMapsException
  {
    try
    {
      bricks.addAllBricks(listOfLevels.removeFirst());
      ball.resetToStartSituation();
      paddle.resetToStartSituation()
      predictedCollisions.clear();
    } catch (NoSuchElementException e)
    {
      throw new NoMoreMapsException();
    }   
  }
 
  /**
   * Przelicza nowy stan planszy po upłynięciu validTime.
   * Zaleca się wykonywanie wyłącznie {@link #recalculateValidTime()}.
   *
   * @throws PlayerDiedException - jeżeli platforma straciła wszystkie życia.
   * @throws AllBricksDestroyedException - jeżeli wszystkie klocki zbite.
   */
  public void update() throws PlayerDiedException, AllBricksDestroyedException
  {
    // Wykonanie kolizji.
    performPredictedCollisions();
    // validTime nieaktualny.
    validTime = -1;
    // Przewidujemy nowe kolizje, ustala się nowy validTime
    predictedCollisions.addAll(predictEarliestCollisions());
    // Jeżeli kolizja zachodzi za 0 ms to ją wykonajmy.
    while (validTime == 0)
    {
      // Wykonanie kolizji.
      performPredictedCollisions();
      // validTime nieaktualny.
      validTime = -1;
      // Przewidujemy nowe kolizje, ustala się nowy validTime
      predictedCollisions.addAll(predictEarliestCollisions());
    }
    lastUpdateTime = System.currentTimeMillis();   
    // Paletka umarła ostatecznie, straciła wszystkie życia.
    if(!player.isAlive())
    {
      throw new PlayerDiedException();
    }   
    // Wszystkie klocki na aktualnej mapie zniszczone.
    if(bricks.allBricksDestroyed())
    {
      throw new AllBricksDestroyedException();
    }
  }

  /**
   * Odpowiednio przelicza czas poprawności stanu planszy.
   * Wykonuje odpowiednie operacje na obiektach.
   * <br/>
   * Jeżeli podczas wywołania minął właśnie validTime wykonywane są
   * przewidziane kolizję i obliczany kolejny stan.
   * <br/>
   * Bezpieczniejsze niż {@link #update()}.
   *
   * @throws PlayerDiedException - jeżeli platforma straciła wszystkie życia.
   * @throws AllBricksDestroyedException - jeżeli wszystkie klocki zbite.
   */
  public void recalculateValidTime() throws PlayerDiedException, AllBricksDestroyedException
  {
    long timeInterval = System.currentTimeMillis() - lastUpdateTime;
    lastUpdateTime = System.currentTimeMillis();   
    if (timeInterval < validTime)
    {   
      validTime = -1;
      moveAllObjects(timeInterval);
      predictedCollisions.clear();
      predictedCollisions.addAll(predictEarliestCollisions());
    }
    else
    {     
      update();
    }       
  }

  /**
   * Wypuszczenie piłki.
   */
  public void releaseBall()
  {
    ball.release();
    validWithTimeLimit();
  }

  /**
   * Nadaje platformie energii w podanym kierunku.
   * Energia występuje w formie prędkości.
   *
   * @param direction
   */
  public void acceleratePaddle(Direction direction)
  {
    validWithTimeLimit();
    paddle.accelerate(direction);
    if (!ballIsBouncing())
    {
      ball.setSpeed(paddle.getSpeed());
      ball.setAcceleration(paddle.getAcceleration());
    }    
  }

  /**
   * @return Makietę stanu całej planszy (gry).
   */
  public GameMockup getGameMockup()
  {
    return new GameMockup(getBallMockup(), getPaddleMockup(), getLevelMockup(), getBricksMockup(), getPlayerMockup());
  }

  /**
   * @return Makietę z informacjami o graczu. (np. życie)
   */
  public PlayerMockup getPlayerMockup()
  {
    return new PlayerMockup(player.getLives());
  }
 
  /**
   * @return Makietę piłki.
   */
  public BallMockup getBallMockup()
  {
    final int ballRadius = ball.getRadius();
    final Coordinates2D topLeftBallCoordinates = ball.getCoordinates();
    topLeftBallCoordinates.add(new Coordinates2D(-ballRadius, ballRadius));
    return new BallMockup(topLeftBallCoordinates, ballRadius, ball.getSpeed(), ball.getAcceleration());
  }

  /**
   * @return Makietę platformy.
   */
  public PaddleMockup getPaddleMockup()
  {
    final Coordinates2D topLeftPaddleCoordinates = new Coordinates2D(paddle.getMinX(), paddle.getMaxY());
    return new PaddleMockup(topLeftPaddleCoordinates, paddle.getSpeed(), paddle.getAcceleration());
  }

  /**
   * @return Makietę planszy.
   */
  private LevelMockup getLevelMockup()
  {
    return new LevelMockup(new Dimension(levelDimension));
  }

  /**
   * @return Makietę klocków.
   */
  public BricksMockup getBricksMockup()
  {
    List<Brick> bricksList = bricks.getBricksList();
    BricksMockup bricksMockup = new BricksMockup();
    for (Brick brick : bricksList)
    {
      bricksMockup.add(new BrickMockup(brick.getTopLeftCornerCoordinates(), brick.getBrickType()));
    }
    return bricksMockup;
  }

  /**
   * @return Prawdę jeżeli piłka jest wypuczona; fałsz wpp.
   */
  public boolean ballIsBouncing()
  {
    return ball.isReleased();   
  }

  /**
   * @return Czas przez, który poprawny będzie stan planszy.
   */
  public long getValidTime()
  {
    return validTime;
  }

  /**
   * Przemieszcza wszystkie obiekty po czasie.
   *
   * @param timeInterval - czas.
   */
  private void moveAllObjects(long timeInterval)
  {
    ball.move(timeInterval);
    paddle.move(timeInterval);
  }
 
  /**
   * Stan planszy był ważny bezterminowo,
   * to już nie jest.
   */
  private void validWithTimeLimit()
  {
    if(validTime == -1)     
    {
      validTime = Long.MAX_VALUE;
    }
  }

  /**
   * Wykonuje wcześniej przewidziane kolizje.
   */
  private void performPredictedCollisions()
  {
    moveAllObjects(validTime);
    for (Collision collision : predictedCollisions)
    {
      collisionStrategyMap.get(collision.getClass()).handle(collision);
    }
    predictedCollisions.clear();
  }

  /**
   * Przewiduje najwcześniejsze kolizje, które zajdą
   * na planszy. Ustawia odpowiednio czas poprawności
   * stanu planszy.
   *
   * @return Listę przewidzianych najwcześniejszy kolizji.
   */
  private List<Collision> predictEarliestCollisions()
  {
    List<Collision> collisionsList = checkCollisions();
    Collections.sort(collisionsList);
    List<Collision> nearestCollisionsList = new LinkedList<Collision>();
    for (Collision collision : collisionsList)
    {
      if (collision.equals(collisionsList.get(0)))
      {
        this.validTime = collision.getTimeToCollision();
        nearestCollisionsList.add(collision);
      }
    }
    return nearestCollisionsList;
  }

  /**
   * Sprawdza wszystkie kolizje, które mogą zajść
   * i je zwraca w postaci nieuporządkowanej listy.
   *
   * @return Listę wszystkich kolizji, które mogą zajść.
   */
  private List<Collision> checkCollisions()
  {
    List<Collision> collisionsList = new LinkedList<>();
    if (ballIsBouncing())
    {
      collisionsList.addAll(checkCollisionsWithBall());
      collisionsList.addAll(bricks.checkCollisionsWithBall(ball));
      collisionsList.addAll(paddle.checkCollisionsWithBall(ball));
    }
    collisionsList.addAll(checkCollisionsWithPaddle());
    return collisionsList;
  }

  /**
   * Sprawdza kolizje, które mogą wystąpić między planszą
   * a platformą. Sprawdza również, kiedy zatrzyma się
   * platforma.
   *
   * Platforma może przekroczyć ścianę planszy o 50 jednostek.
   *
   * @return Listę kolizji między platformą a planszą.
   */
  private List<Collision> checkCollisionsWithPaddle()
  {
    List<Collision> collisionsList = new LinkedList<>();
    final SpeedVector2D paddleSpeed = paddle.getSpeed();
    final float paddleXSpeed = paddleSpeed.getXSpeed();
    final Acceleration2D paddleAcc = paddle.getAcceleration();
    final float paddleXAcc = paddleAcc.getxAcc();

    if (paddleXSpeed == 0 || paddleXAcc == 0)
    {
      return Collections.emptyList();
    }
    final double paddleLevelSidesCross = 50;
    final double timeWhenPaddleWillStop = Math.abs(paddleXSpeed / paddleXAcc);
    collisionsList.add(new PaddleWillStop((long) timeWhenPaddleWillStop));
    if (paddleXSpeed > 0)
    {
      final double distanceToLevelMaxX = Math.abs(paddleLevelSidesCross + levelDimension.width - paddle.getMaxX());
      // s = t * ( v + 1/2 * a * t) = vt + 1/2 * a * t^2
      final double distanceToStopPoint = timeWhenPaddleWillStop
          * Math.abs(paddleXSpeed + paddleXAcc * timeWhenPaddleWillStop * 0.5);
      if (distanceToLevelMaxX <= distanceToStopPoint)
      {
        // Will stop after crossing level right side;
        try
        {
          double possibleCollisionTimes[] = QuadraticEquation.solve(paddleXAcc / 2, paddleXSpeed,
              -distanceToLevelMaxX);
          if (possibleCollisionTimes.length == 2)
          {
            for (double collisionTime : possibleCollisionTimes)
            {
              if(collisionTime >= 0)
              {
                collisionsList.add(new PaddleWithLevelRightSideCollision((long)collisionTime));
              }
            }
          }
          else
          {
            if(possibleCollisionTimes[0] >= 0)
            {
              collisionsList.add(new PaddleWithLevelRightSideCollision((long)possibleCollisionTimes[0]));
            }
          }         
        } catch (ImaginaryRootsException e)
        {

        }
      }
    } 
    else if (paddleXSpeed < 0)
    {
      final double distanceToLevelMinX = Math.abs(paddle.getMinX() + paddleLevelSidesCross);
      // s = t * ( v + 1/2 * a * t) = vt + 1/2 * a * t^2
      final double distanceToStopPoint = timeWhenPaddleWillStop
          * Math.abs(paddleXSpeed + paddleXAcc * timeWhenPaddleWillStop * 0.5);
      if (distanceToLevelMinX <= distanceToStopPoint)
      {
        // Will stop after crossing level right side;
        try
        {
          double possibleCollisionTimes[] = QuadraticEquation.solve(- paddleXAcc / 2, -paddleXSpeed,
              -distanceToLevelMinX);
          if (possibleCollisionTimes.length == 2)
          {
            for (double collisionTime : possibleCollisionTimes)
            {
              if(collisionTime >= 0)
              {
                collisionsList.add(new PaddleWithLevelLeftSideCollision((long)collisionTime));
              }
            }
          }
          else
          {
            if(possibleCollisionTimes[0] >= 0)
            {
              collisionsList.add(new PaddleWithLevelLeftSideCollision((long)possibleCollisionTimes[0]));
            }
          }         
        } catch (ImaginaryRootsException e)
        {
         
        }
      }
    }   
    return collisionsList;
  }

  /**
   * Sprawdza możliwe kolizje między planszą a piłką.
   * Zwraca je jako nieuporządkowana list.
   *
   * @return Listę kolizji między planszą a piłką.
   */
  private List<Collision> checkCollisionsWithBall()
  {
    List<Collision> collisionsList = new LinkedList<>();

    final float ballXSpeed = ball.getXSpeed();
    final float ballYSpeed = ball.getYSpeed();

    if (ballXSpeed > 0)
    {
      final float distanceToLevelMaxX = levelDimension.width - (ball.getX() + ball.getRadius());
      final long millisecondsToCollision = Math.abs((long) (distanceToLevelMaxX / ballXSpeed));
      collisionsList.add(new BallWithLevelRightSideCollision(millisecondsToCollision));
    }
    else
    {
      final float distanceToLevelMinX = ball.getX() - ball.getRadius();
      final long millisecondsToCollision = Math.abs((long) (distanceToLevelMinX / ballXSpeed));
      collisionsList.add(new BallWithLevelLeftSideCollision(millisecondsToCollision));
    }

    if (ballYSpeed > 0)
    {
      final float distanceToLevelMaxY = levelDimension.height - (ball.getY() + ball.getRadius());
      final long millisecondsToCollision = Math.abs((long) (distanceToLevelMaxY / ballYSpeed));
      collisionsList.add(new BallWithLevelTopCollision(millisecondsToCollision));
    }
    else
    {
      final float distanceToLevelMinY = ball.getY() - ball.getRadius();
      final long millisecondsToCollision = Math.abs((long) (distanceToLevelMinY / ballYSpeed));
      collisionsList.add(new BallWithLevelBottomCollision(millisecondsToCollision));
    }
    return collisionsList;
  }

  /**
   * Wypełnia mapę strategii kolizji.
   */
  private void initCollisionStrategyMap()
  {
    collisionStrategyMap.clear();
    collisionStrategyMap.put(BallWithLevelRightSideCollision.class, new BallWithLevelSideCollisionStrategy());
    collisionStrategyMap.put(BallWithLevelLeftSideCollision.class, new BallWithLevelSideCollisionStrategy());
    collisionStrategyMap.put(BallWithLevelBottomCollision.class, new BallWithLevelBottomCollisionStrategy());
    collisionStrategyMap.put(BallWithLevelTopCollision.class, new BallWithLevelTopCollisionStrategy());
    collisionStrategyMap.put(BallWithPaddleCollision.class, new BallWithPaddleCollisionStrategy());
    collisionStrategyMap.put(BallWithBrickCollision.class, new BallWithBrickCollisionStrategy());
    collisionStrategyMap.put(PaddleWithLevelRightSideCollision.class, new PaddleWithLevelSideCollisionStrategy());
    collisionStrategyMap.put(PaddleWithLevelLeftSideCollision.class, new PaddleWithLevelSideCollisionStrategy());
    collisionStrategyMap.put(PaddleWillStop.class, new PaddleWillStopStrategy());
  }
 
  /**
   * Abstrakcyjna klasa reprezentująca
   * strategię wykonywaną podczas odpowiedniej
   * kolizji.
   *
   * @author Jakub Szuppe <j.szuppe at gmail.com>
   */
  abstract class CollisionStrategy
  {
    /**
     * Metoda, która odpowiednio reaguje na kolizje
     * i zmienia stan planszy.
     *
     * @param collision - kolizja.
     */
    public abstract void handle(final Collision collision);
  }
 
  /**
   * Strategia opisująca zachowanie się podczas
   * kolizji piłki z platformą.
   *
   * @author Jakub Szuppe <j.szuppe at gmail.com>
   */
  class BallWithPaddleCollisionStrategy extends CollisionStrategy
  {
    /**
     * Odbija piłkę od platformy i odpowiednio przekształca jej
     * wektor prędkości.
     */
    @Override
    public void handle(Collision collision)
    {
      final Vertices2D paddleVertices = paddle.getVerticles();
      double alpha = (ball.getCoordinates().getX() - paddleVertices.getMinX()) / (paddleVertices.getMaxX()-paddleVertices.getMinX());
      if(alpha < 0)
      {
        alpha = 0;
      }
      else if(alpha > 1)
      {
        alpha = 1;
      }
      alpha = countAngleInRange(alpha, Math.PI*0.66d, Math.PI * 1.166d);   
      ball.rotateSpeedVector(alpha);
      ball.reverseYSpeed()
    }
   
    /**
     * Metoda do obliczania kąta rotacji wektora
     * prędkości piłki odbijającej się od platformy.
     *
     * @param alpha
     * @param rangeWidth
     * @param min
     * @return
     */
    private double countAngleInRange(double alpha, double rangeWidth, double min)
    {
      return (alpha * rangeWidth) + min;
    }   
  }
 
  /**
   * Strategia opisująca zachowanie się podczas
   * kolizji piłki z dolną ścianą planszy.
   *
   * @author Jakub Szuppe <j.szuppe at gmail.com>
   */
  class BallWithLevelBottomCollisionStrategy extends CollisionStrategy
  {
    /**
     * Platforma traci życie.
     * Platforma i piłka są ustawione na pozycje startowe.
     */
    @Override
    public void handle(Collision collision)
    {
      player.lostLife();
      paddle.resetToStartSituation();
      ball.resetToStartSituation();
    }
  }
 
  /**
   * Strategia opisująca zachowanie się podczas
   * kolizji piłki z lewą lub prawą ścianą planszy.
   *
   * @author Jakub Szuppe <j.szuppe at gmail.com>
   */
  class BallWithLevelSideCollisionStrategy extends CollisionStrategy
 
    /**
     * Odbija piłę od ściany.
     */
    @Override
    public void handle(Collision collision)
    {
      ball.reverseXSpeed();
   
  }
 
  /**
   * Strategia opisująca zachowanie się podczas
   * kolizji piłki z górą planszy.
   *
   * @author Jakub Szuppe <j.szuppe at gmail.com>
   */
  class BallWithLevelTopCollisionStrategy extends CollisionStrategy
  {
    /**
     * Odbija piłkę od góry.
     */
    @Override
    public void handle(Collision collision)
    {
      ball.reverseYSpeed();   
    }
  }
 
  /**
   * Strategia opisująca zachowanie się podczas
   * kolizji piłki z klockiem.
   *
   * @author Jakub Szuppe <j.szuppe at gmail.com>
   */
  class BallWithBrickCollisionStrategy extends CollisionStrategy
 
    /**
     * Niszczy klocek. Odbija piłkę od klocka
     * zgodnie z punktem zderzenia.
     */
    @Override
    public void handle(Collision collision)
    {
      final BallWithBrickCollision localCollison = (BallWithBrickCollision) collision;
      if(localCollison.getSide() == Side.LEFT || localCollison.getSide() == Side.RIGHT)
      {     
        ball.reverseXSpeed();
      }
      else if(localCollison.getSide() == Side.CORNER)
      {
        ball.reverseXSpeed();
        ball.reverseYSpeed();
      }
      else
      {
        ball.reverseYSpeed();
      }
      bricks.removeBrick(localCollison.getBrick());
    }   
  }
 
  /**
   * Strategia opisująca zachowanie się podczas
   * kolizji platformy ze ścianą planszy.
   *
   * @author Jakub Szuppe <j.szuppe at gmail.com>
   */
  class PaddleWithLevelSideCollisionStrategy extends CollisionStrategy
  {
    /**
     * Odbicie platformy od ściany.
     */
    @Override
    public void handle(Collision collision)
    {
      paddle.reverseXSpeed();
      paddle.reverseXAcc();
      if (!ball.isReleased())
      {
        ball.reverseXSpeed();
        ball.reverseXAcc();
      }
    }
  }
 
  /**
   *
   * @author Jakub Szuppe <j.szuppe at gmail.com>
   */
  class PaddleWillStopStrategy extends CollisionStrategy
 
     
    /**
     * Zatrzymanie platformy.
     */
    @Override
    public void handle(Collision collision)
    {
      paddle.stop();
   
  }

}
TOP

Related Classes of com.szuppe.jakub.model.Level

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.