package invaders101;
import invaders101.spritesystem.ImageCache;
import invaders101.spritesystem.SpriteFactory;
import invaders101.entities.MovingGameEntity;
import invaders101.entities.ShotEntity;
import invaders101.entities.AlienEntity;
import invaders101.entities.ShipEntity;
import invaders101.entities.AlienBonusShip;
import invaders101.entities.BaseEntity;
import invaders101.interfaces.Drawable;
import invaders101.interfaces.Updateable;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* Clase que controla el desarrollo del juego
* @author Alejandro Cerutti, Mauricio Blint, Patricio Marrone
*/
public class Game extends JPanel {
private static final int WIN_LEVEL = 10;
private enum MainMessage {
GAME_OVER("GAME OVER!"),
LEVEL_UP("LEVEL UP!"),
SPACE_INVADERS("SPACE INVADERS"),
YOU_WIN("YOU WIN!!");
private String message;
MainMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
public static final int SCREEN_HEIGHT = 600;
public static final int SCREEN_WIDTH = 800;
private Random randomGenerator = new Random();
private static SpriteFactory spriteFactory;
/** Estrategia para la gestion del buffer, permite usar page flipping acelerado */
private BufferStrategy strategy;
/** True si el juego está actualmente "corriendo" */
private boolean gameRunning = true;
private boolean logicRequiredThisLoop = false;
private String title;
/** Lista de todas las entidades existentes en el juego */
private ArrayList<BaseEntity> entities = new ArrayList();
/** Lista de las entidades a remover del juego en el actual loop */
private ArrayList removeList = new ArrayList();
private ArrayList<BaseEntity> addList = new ArrayList<BaseEntity>();
/** Entidad que representa al jugador */
private ShipEntity ship;
/** La velocidad a la que la nave deberia moverse (pixels/sec) */
private boolean shouldRestart = false;
/** Número de aliens en pantalla */
private int alienCount;
private long millisToNextBonus = 0;
/** Mensaje que se muestra mientras se espera por una tecla apretada */
private String message = "";
private KeyInputHandler keyInputHandler;
private ScoreHandler scoreHandler;
private int level = 1;
private int lives = 3;
private MainMessage mainMessage = MainMessage.SPACE_INVADERS;
/**
* Constructor del juego
* @param title
*/
public Game(String title) {
this.title = title;
}
private void processInsertionsDeletions() {
// elimina cualquier entidad que alla sido marcada para limpiar
if (!addList.isEmpty()) {
entities.addAll(addList);
addList.clear();
}
if (!removeList.isEmpty()) {
entities.removeAll(removeList);
removeList.clear();
}
}
private void resetBonusTimer() {
this.millisToNextBonus = 1000 * (int) (10 + randomGenerator.nextFloat() * 5);
}
private void checkForBonusAlien(long delta) {
//Si ya existe un alien en pantalla, no realiza ninguna acción
if (AlienBonusShip.getBonusAlienCount() > 0) {
return;
}
millisToNextBonus -= delta;
if (millisToNextBonus <= 0) {
BaseEntity bounusAlien = new AlienBonusShip(
this, 50, 50,
0.1f + randomGenerator.nextFloat() * 0.05f);
this.resetBonusTimer();
this.addEntity(bounusAlien);
}
}
/**
* Limpia datos viejos e inicializa las entidades
*/
protected void startGame() {
if (lives == -1) {
this.lives = 3;
this.scoreHandler.resetScore();
this.level = 1;
}
//Limpia las entidades del nivel o pantalla anterior
for (BaseEntity entity : entities) {
entity.onCleanUp();
}
addList.clear();
removeList.clear();
entities.clear();
//Reinicia las entidades del juegi
initEntities();
keyInputHandler.resetKeys();
keyInputHandler.setWaitingForRestart(false);
shouldRestart = false;
}
public void addScore(int score) {
scoreHandler.addScore(score);
}
/**
* Inicializa el estado inicial de los aliens y la nave del jugador.
* Cada entidad se añadirá a la lista general de las entidades en el juego.
*/
protected void initEntities() {
// crea la nave del jugador y la coloca en el centro de la pantalla
ship = new ShipEntity(this, SCREEN_WIDTH / 2, SCREEN_HEIGHT - 50);
ship.init();
entities.add(ship);
//crea un bloque de aliens
alienCount = 0;
//Separaciones en X e Y entre las naves enemigas
int separationX = 50, separationY = 45;
for (int row = 0; row < 3; row++) { //3
for (int x = 0; x < 11; x++) { //11
MovingGameEntity alien = new AlienEntity(this, 100 + (x * separationX), (100) + row * separationY);
entities.add(alien);
alienCount++;
}
}
}
/**
* Notificacion de una entidad del juego que la logica
* debe ser actualizada
*/
public void updateLogic() {
logicRequiredThisLoop = true;
}
/**
* Elimina una entidad del juego. La misma no se movera
* ni sera pintada
*
* @param entity la entidad que debe ser eliminada
*/
public void removeEntity(BaseEntity entity) {
entity.onCleanUp();
removeList.add(entity);
}
/**
* Añade una entidad al juego.
* @param entity La entidad a ser añadida
*/
public void addEntity(BaseEntity entity) {
addList.add(entity);
}
/**
* Notifica que el jugador a perdido
*/
public void notifyDeath(String message) {
lives = -1;
this.message = message;
keyInputHandler.setWaitingForKeyPress(true);
this.mainMessage = MainMessage.GAME_OVER;
}
/**
* Notifica que el jugador ganó, por lo tanto
* todos los aliens han sido eliminados.
*/
public void notifyWin() {
this.mainMessage = MainMessage.YOU_WIN;
message = "¡Felicidades! ¡Tu ganas!";
this.shouldRestart = true;
//Informa que debe reiniciar el juego
this.lives = -1;
keyInputHandler.setWaitingForKeyPress(true);
}
/**
* Notifica al juego que se ha pasado al siguiente nivel
*/
public void levelUp() {
//Informa que el juego debe esperar a que ser presione una tecla
keyInputHandler.setWaitingForKeyPress(true);
//El juego debe reiniciarse el próximo ciclo
shouldRestart = true;
//Aumenta el contador de niveles
if (level >= WIN_LEVEL) {
notifyWin();
} else {
mainMessage = MainMessage.LEVEL_UP;
this.message = "¡Prepárate para la próxima oleada!";
level++;
}
}
public int getLevel() {
return level;
}
public int getLifes() {
return lives;
}
public void loseLife() {
lives--;
}
/**
* Notifica que el alien a sido eliminado
*/
public void notifyAlienKilled() {
// reduce la cuenta de aliens.
alienCount--;
//si es 0 el jugador ganó y pasa de nivel
if (alienCount == 0) {
levelUp();
}
//los aliens que no han sido eliminados aceleraran un 2%
AlienEntity.setGroupDx(AlienEntity.getGroupDx() * 1.02);
}
/**
* Este loop corre durante todo el juego y es
* responsable de las siguientes actividades:
* - Determinar a qué velocidad avanza el juego para
* actualizar la posición de las naves
* - Computar las colisiones
* - Mover las entidades del juego
* - Dibujar en pantalla los contenidos del juego.
* - Actualizar las entidades
*/
public void run() {
init();
long lastLoopTime = System.currentTimeMillis();
// Mantiene el loop principal hasta que termine el juego
while (gameRunning) {
shouldRestart = shouldRestart || this.keyInputHandler.isWaitingForRestart();
if (shouldRestart) {
this.startGame();
}
// Calcula la cantidad de segundos desde el último ciclo
long delta = System.currentTimeMillis() - lastLoopTime;
lastLoopTime = System.currentTimeMillis();
processInsertionsDeletions();
this.draw();
if (!keyInputHandler.isWaitingForKeyPress()) {
this.computeCollisions();
this.moveAliensForward();
this.update(delta);
this.checkForBonusAlien(delta);
}
try {
Thread.sleep(lastLoopTime + 10 - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
/**
* Método de fuerza bruta para colisiones, compara cada entidad en contra
* de todas las entidades. Si alguna de ellas colision, notifica
* a ambas entidades que una colision a ocurrido.
*/
private void computeCollisions() {
for (int p = 0; p < entities.size(); p++) {
if (entities.get(p) instanceof MovingGameEntity) {
MovingGameEntity entity1 = (MovingGameEntity) entities.get(p);
for (int s = p + 1; s < entities.size(); s++) {
if (entities.get(s) instanceof MovingGameEntity) {
MovingGameEntity entity2 = (MovingGameEntity) entities.get(s);
if (entity1.isCollidingWith(entity2)) {
entity1.notifyCollisionWith(entity2);
entity2.notifyCollisionWith(entity1);
}
}
}
}
}
}
/**
* Informa al juego que los aliens deben cambiar
* de dirección en conjunto.
*/
private void moveAliensForward() {
if (logicRequiredThisLoop) {
AlienEntity.revertGroupDirection();
for (BaseEntity entity : entities) {
if (entity instanceof AlienEntity) {
AlienEntity alien = (AlienEntity) entity;
alien.moveForward();
}
}
logicRequiredThisLoop = false;
}
}
/**
* Dibuja las vidas del jugador en pantalla
* @param ref La url de la sprite
* @param x La posición x donde se tiene que dibujar la primera vida
* @param y La posición y donde se tiene que dibujar la primera vida
* @param g El contexto gráfico
* @param vidas El número de vidas a dibujar
*/
private void drawLives(String ref, int x, int y, Graphics g, int vidas) {
Image life = ImageCache.getInstance().getImage(ref);
int separation = 35;
for (int i = 0; i < vidas; i++) {
g.drawImage(life, x + separation * i, y, null);
}
if (vidas == 0) {
g.setColor(Color.red);
g.setFont(new Font("Arial", Font.BOLD, 20));
g.drawString("WARNING!!!", SCREEN_HEIGHT, 534);
}
}
/**
* Dibuja los elementos gráficos del juego en pantalla
*/
private void draw() {
//Recupera el contexto gráfico del juego
//Crea un contexto grafico para el drawing buffer
Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
g.setColor(Color.black);
g.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
// ciclo que dibuja todas las entidades que tiene el juego
for (int i = 0; i < entities.size(); i++) {
if (entities.get(i) instanceof Drawable) {
Drawable entity = (Drawable) entities.get(i);
entity.draw(g);
}
}
drawLives("sprites/life.png", SCREEN_HEIGHT, 550, g, lives);
// Si está esperando que se presione una tecla
// muestra un mensaje
if (keyInputHandler.isWaitingForKeyPress()) {
g.setColor(Color.white);
g.setFont(new Font("Tahoma", Font.BOLD, 25));
g.drawString(mainMessage.getMessage(), (SCREEN_WIDTH - g.getFontMetrics().stringWidth(mainMessage.getMessage())) / 2, 300);
g.setFont(new Font("Tahoma", Font.BOLD, 16));
g.drawString(message, (SCREEN_WIDTH - g.getFontMetrics().stringWidth(message)) / 2, 330);
String pressKeyMessage = "Presione enter para continuar";
g.drawString(pressKeyMessage, (SCREEN_WIDTH - g.getFontMetrics().stringWidth(pressKeyMessage)) / 2, 350);
}
g.setColor(Color.white);
g.setFont(new Font("Arial", Font.BOLD, 20));
g.drawString("Score: " + scoreHandler.getScore(), 100, 534);
g.setColor(Color.white);
g.setFont(new Font("Arial", Font.BOLD, 20));
g.drawString("Level: " + level, 100, 500);
//Terminada la etapa de dibujo, limpia los gràficos
//y muestra los gráficos del back buffer
g.dispose();
strategy.show();
}
private void update(long delta) {
for (int i = 0; i < entities.size(); i++) {
Updateable entity = entities.get(i);
entity.update(delta);
}
ship.hadleShipInput(keyInputHandler.isLeftPressed(), keyInputHandler.isRightPressed(),
keyInputHandler.isFirePressed());
}
/**
* Realiza la inicialización básica del juego.
* Éste método sólo debería correr una vez por ejecución
*/
protected void init() {
initGameWindow();
initEntities();
//Llama a la inicialización de los sonidos
SoundEffect.init();
}
/**
* Prepara la ventana donde correrá el juego
*/
protected void initGameWindow() {
Dimension size = new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT);
JFrame container = new JFrame(this.title);
container.setSize(size);
container.setPreferredSize(size);
container.setResizable(false);
// Añade un listener para que el programa se cierre cuando el
// usuario cierre la ventana
container.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
// Hace que la ventana sea visible
container.setVisible(true);
// crea el buffering strategy el cual va a permitir
// manejar los gráficos acelerados
// el parametro 2 son los buffers a utilizar
container.createBufferStrategy(2);
strategy = container.getBufferStrategy();
// Asigna propiedades de dimensión al área del juego
this.setPreferredSize(size);
this.setMaximumSize(size);
this.setMinimumSize(size);
this.setSize(size);
//Utiliza el área de juego como la hoja
//de contenidos de la ventana
container.setContentPane(this);
// Desactiva el llamado a repaint automático. Los llamados
// a paint serán hechos manualmente en modo acelerado
this.setIgnoreRepaint(true);
// agrega un sistema para las entradas del teclado
// KeyInputHandler maneja la entrada del teclado
this.addKeyListener(keyInputHandler = new KeyInputHandler(this));
// solicita el foco de atencion para que se capturen los key events
this.requestFocus();
//Reinicia el temporizador del juego e inicializa el puntaje
resetBonusTimer();
scoreHandler = new ScoreHandler();
}
}