package com.rzg.zombieland.server.meta;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import com.rzg.zombieland.comunes.comunicacion.pojo.POJOCreacionPartida;
import com.rzg.zombieland.comunes.comunicacion.pojo.POJOPartida;
import com.rzg.zombieland.comunes.comunicacion.pojo.POJOResultadoRonda;
import com.rzg.zombieland.comunes.misc.EstadoPartida;
import com.rzg.zombieland.comunes.misc.Log;
import com.rzg.zombieland.comunes.misc.ZombielandException;
import com.rzg.zombieland.server.comunicacion.ServicioJuego;
import com.rzg.zombieland.server.comunicacion.peticion.PeticionProyeccion;
import com.rzg.zombieland.server.juego.BucleJuego;
import com.rzg.zombieland.server.juego.Tablero;
import com.rzg.zombieland.server.sesion.Jugador;
import com.rzg.zombieland.server.sesion.ServicioSesion;
import com.rzg.zombieland.server.sesion.Sesion;
import com.rzg.zombieland.server.sesion.Sesion.SesionListener;
/**
* Define una partida. La partida empieza cuando es creada por un jugador y
* termina cuando el �ltimo jugador es convertido en zombie.
*
* @author nicolas
*
*/
public class Partida implements SesionListener {
/**
* Interfaz para escuchar cambios en la vida de la partida.
*
* @author nicolas
*
*/
public interface PartidaListener {
/**
* Notifica que la partida ha quedado vac�a.
*
* @param partida
* - la partida que se ha quedado vac�a.
*/
void notificarPartidaVacia(Partida partida);
}
/**
* Un jugador que, adem�s, tiene una bandera de arranque de partida.
* @author nicolas
*
*/
public class JugadorEstado extends Jugador {
// True si "levant� la bandera" para jugar, false de lo contrario.
private boolean listo;
public JugadorEstado(Jugador jugador) {
super(jugador);
listo = true;
}
}
public static final String MENSAJE_PARTIDA_EN_PROGRESO = "No se puede unir a una partida en progreso";
// ID �nico que identifica la partida.
private UUID id;
// Jugador que cre� la partida.
private Jugador administrador;
// El nombre de la partida.
private String nombre;
// Listado de jugadores unidos a la partida. Incluye al |administrador|.
private List<JugadorEstado> jugadores;
// Listado de espectadores viendo la partida.
private List<Jugador> espectadores;
// Indica el estado actual de la partida.
private EstadoPartida estado;
// Resultado acumulado de las rondas.
private ResultadoRonda resultado;
// La cantidad m�xima de jugadores permitida. La partida arrancar� cuando se
// alcance.
private int cantidadMaximaJugadores;
// La cantidad de rondas.
private int cantidadRondas;
// El n�mero de ronda actual. Empieza por cero.
private int rondaActual;
// El objeto que escucha los cambios en la vida de la partida.
private PartidaListener listener;
// El tablero de partida.
private Tablero tablero;
/**
* Crea una partida nueva a partir de un administrador.
*
* @param administrador
* - el jugador que crea la partida.
* @param nombre
* - el nombre de la partida, arbitrario.
* @param datosPartida
* - el POJO que viene del cliente con los datos de la partida.
* @throws NullPointerException
* si el administrador o los datos de partida son null.
*/
public Partida(Jugador administrador, POJOCreacionPartida datosPartida) {
this(administrador, datosPartida.getNombre(), getLista(administrador),
new ArrayList<Jugador>(), datosPartida.getCantidadRondas(), datosPartida
.getCantidadMaximaJugadores());
Sesion sesion = ServicioSesion.getInstancia().getSesion(administrador);
if (sesion != null)
sesion.addListener(this);
}
/**
* @param administrador
* - el administrador que cre� la partida.
* @return una lista de jugadores a partir del administrador.
*/
private static List<Jugador> getLista(Jugador administrador) {
List<Jugador> listado = new ArrayList<Jugador>(1);
listado.add(administrador);
return listado;
}
/**
* Constructor por par�metros.
*
* @param administrador
* @param jugadores
* @param espectadores
* @param cantidadRondas
* @param cantidadMaximaJugadores
*/
private Partida(Jugador administrador, String nombre, List<Jugador> jugadores,
List<Jugador> espectadores, int cantidadRondas, int cantidadMaximaJugadores) {
if (administrador == null)
throw new NullPointerException();
if (nombre == null || nombre.isEmpty())
throw new NullPointerException();
this.id = UUID.randomUUID();
this.administrador = administrador;
this.nombre = nombre;
this.jugadores = new ArrayList<JugadorEstado>(jugadores.size());
for (Jugador jugador : jugadores)
this.jugadores.add(new JugadorEstado(jugador));
this.espectadores = espectadores;
this.cantidadMaximaJugadores = cantidadMaximaJugadores;
this.cantidadRondas = cantidadRondas;
this.estado = EstadoPartida.EN_ESPERA;
}
/**
* Devuelve el resultado de una partida para un jugador.
*
* @param jugador
* - el jugador para el que se quieren obtener los resultados.
* @return el resultado de la partida.
*/
public ResultadoJugador getResultadoPartida(Jugador jugador) {
// TODO implementar.
return null;
}
/**
* @param jugador
* - el jugador que solicita el POJO. Puede ser null, en cuyo
* caso no se enviar� la proyecci�n del tablero.
* @return un pojo con los datos de la partida.
*/
public POJOPartida getPOJO(Jugador jugador) {
return new POJOPartida(id.toString(), administrador.getNombre(),
proyectarNombres(jugadores), proyectarNombres(espectadores), cantidadRondas,
cantidadMaximaJugadores, nombre, estado, tablero == null
|| jugador == null ? null :
tablero.getProyeccionJugador(
jugador, getTiempoJuego()));
}
private int getTiempoJuego() {
if (estado == EstadoPartida.ACTIVA)
return BucleJuego.TIEMPO_TURNO;
if (continuaSiguienteRonda())
return BucleJuego.TIEMPO_ENTRE_PARTIDAS;
return 0;
}
/**
* @param jugadores
* @return un listado con los nombres de los jugadores.
*/
private List<String> proyectarNombres(List<? extends Jugador> jugadores) {
List<String> nombresJugadores = new ArrayList<String>(jugadores.size());
synchronized (jugadores) {
for (Jugador jugador : jugadores)
nombresJugadores.add(jugador.getNombre());
}
return nombresJugadores;
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof Partida))
return false;
Partida otro = (Partida) obj;
return otro.id.equals(id);
}
/**
* @return el ID de la partida.
*/
public UUID getId() {
return id;
}
/**
* @return el listado de jugadores unidos a la partida.
*/
public List<? extends Jugador> getJugadores() {
return jugadores;
}
/**
* Agrega un jugador a la partida.
*
* @param jugadorNuevo
* @throws ZombielandException
*/
public void addJugador(Jugador jugadorNuevo) throws ZombielandException {
if (estado != EstadoPartida.EN_ESPERA)
throw new ZombielandException(MENSAJE_PARTIDA_EN_PROGRESO);
synchronized (jugadores) {
if (jugadores.size() == cantidadMaximaJugadores)
throw new ZombielandException("La partida est� llena");
jugadores.add(new JugadorEstado(jugadorNuevo));
}
Sesion sesion = ServicioSesion.getInstancia().getSesion(jugadorNuevo);
if (sesion != null)
sesion.addListener(this);
if (jugadores.size() == cantidadMaximaJugadores) {
boolean arrancarPartida = true;
for (JugadorEstado jugador : jugadores) {
if (!jugador.listo) {
arrancarPartida = false;
break;
}
}
if (arrancarPartida)
arrancarPartida();
}
for (Jugador jugador : jugadores) {
if (jugador.equals(jugadorNuevo))
continue;
jugador.notificarCambioPartida();
}
notificarEspectadores();
ServicioPartidas.getInstancia().notificarClientes();
}
private void arrancarPartida() {
rondaActual = 0;
BucleJuego bucle = new BucleJuego(this);
resultado = new ResultadoRonda(jugadores);
notificarPuntajes();
siguiente();
ServicioJuego.getInstancia().agregarBucle(bucle);
bucle.start();
}
/**
* Notifica a los espectadores del cambio de partida.
*/
private void notificarEspectadores() {
synchronized (espectadores) {
for (Jugador espectador : espectadores)
espectador.notificarCambioPartida();
}
}
/**
* @return el administrador de la partida.
*/
public Jugador getAdministrador() {
return administrador;
}
/**
* @return la cantidad de rondas en la partida.
*/
public int getCantidadRondas() {
return cantidadRondas;
}
/**
* @return la cantidad m�xima de jugadores admitida por la partida.
*/
public int getMaximoJugadores() {
return cantidadMaximaJugadores;
}
/**
* @return el nombre asignado por el jugador de la partida.
*/
public String getNombre() {
return nombre;
}
/**
* Establece el objeto que recibir� notificaciones sobre la partida.
*
* @param listener
*/
public void setListener(PartidaListener listener) {
this.listener = listener;
}
/**
* Quita un jugador de la partida.
*
* @param jugadorEliminado
* @throws ZombielandException
* si el jugador no estaba unido a la partida.
*/
public void removerJugador(Jugador jugadorEliminado) throws ZombielandException {
synchronized (espectadores) {
if (espectadores.contains(jugadorEliminado)) {
espectadores.remove(jugadorEliminado);
return;
}
}
synchronized (jugadores) {
if (!jugadores.remove(jugadorEliminado))
throw new ZombielandException("El jugador no estaba unido a la partida");
if (jugadores.isEmpty()) {
estado = EstadoPartida.FINALIZADA;
if (listener != null)
listener.notificarPartidaVacia(this);
return;
}
// Si la partida estaba en FINALIZADA, se le pregunt� a los jugadores si quieren
// continuar. Uno de ellos dijo que no, entonces la pasamos a EN_ESPERA.
if (estado == EstadoPartida.FINALIZADA)
estado = EstadoPartida.EN_ESPERA;
if (tablero != null)
tablero.removerJugador(jugadorEliminado);
for (Jugador jugador : jugadores)
jugador.notificarCambioPartida();
notificarEspectadores();
}
ServicioPartidas.getInstancia().notificarClientes();
}
/**
* Mueve todos los jugadores.
*/
public void moverTodos() throws ZombielandException {
if (estado != EstadoPartida.ACTIVA)
throw new ZombielandException("La partida debe estar activa para mover a todos");
tablero.moverTodos();
if (tablero.partidaFinalizada()) {
estado = EstadoPartida.ENTRE_RONDAS;
if (jugadores.size() > 1) {
resultado.addPuntaje(tablero.getJugadorConvertidoNumero(jugadores.size() - 1), 3);
resultado.addPuntaje(tablero.getJugadorConvertidoNumero(jugadores.size() - 2), 2);
notificarPuntajes();
}
}
}
/**
* @return true si se pueden unir jugadores a esta partida, false de lo
* contrario.
*/
public boolean puedenUnirseJugadores() {
return jugadores.size() < cantidadMaximaJugadores && estado == EstadoPartida.EN_ESPERA;
}
/**
* @return el estado actual de la partida.
*/
public EstadoPartida getEstado() {
return estado;
}
public void enviarProyeccion() {
enviarProyeccion(jugadores);
enviarProyeccion(espectadores);
}
/**
* Env�a las proyecciones a un listado de jugadores.
* @param jugadores
*/
private void enviarProyeccion(List<? extends Jugador> jugadores) {
synchronized (jugadores) {
for (Jugador jugador : jugadores)
enviarProyeccionTablero(jugador);
}
}
/**
* Env�a la proyecci�n del tablero a un jugador.
*
* @param jugador
*/
private void enviarProyeccionTablero(Jugador jugador) {
Sesion sesion = ServicioSesion.getInstancia().getSesion(jugador);
if (sesion == null)
return;
try {
sesion.enviarPeticion(
new PeticionProyeccion(tablero.getProyeccionJugador(jugador,
getTiempoJuego())));
} catch (ZombielandException e) {
Log.error("No le lleg� la proyecci�n del tablero a un espectador.");
}
}
/**
* @return true si la partida est� siendo jugada, false de lo contrario.
*/
public boolean activa() {
return estado == EstadoPartida.ACTIVA;
}
/**
* Arranca la siguiente ronda.
*
* @return true si la partida debe continuar, false si la partida termin�.
*/
public boolean siguiente() {
Log.info("Arrancando siguiente ronda...");
synchronized (jugadores) {
if (!continuaSiguienteRonda()) {
Log.info("...termin� la partida - notificando jugadores");
// La partida termin�.
estado = EstadoPartida.FINALIZADA;
tablero = null;
for (JugadorEstado jugador : jugadores) {
jugador.listo = false;
jugador.notificarCambioPartida();
}
notificarEspectadores();
ServicioPartidas.getInstancia().notificarClientes();
return false;
}
estado = EstadoPartida.ACTIVA;
int cantidadCasilleros = new Random().nextInt(10) + 10;
int jugadorZombie = rondaActual % jugadores.size();
tablero = new Tablero(cantidadCasilleros, jugadores, jugadores.get(jugadorZombie));
rondaActual++;
Log.info("...arrancada");
}
return true;
}
private boolean continuaSiguienteRonda() {
return jugadores.size() != 0 && rondaActual < cantidadRondas;
}
@Override
public void notificarSesionCerrada(Sesion sesion) {
try {
removerJugador(sesion.getJugador());
} catch (ZombielandException e) {
Log.error("El jugador no pudo quitarse de la lista al desconectarse:");
Log.error(e.getMessage());
}
}
/**
* Agrega un espectador a la partida.
*
* @param espectador
*/
public void addEspectador(Jugador espectador) {
Sesion sesion = ServicioSesion.getInstancia().getSesion(espectador);
if (sesion != null)
sesion.addListener(this);
synchronized (espectadores) {
if (estado != EstadoPartida.EN_ESPERA)
espectador.notificarPuntajePartida(resultado.getPojo());
espectadores.add(espectador);
}
}
/**
* Env�a un mensaje de chat a todos los jugadores y espectadores de la partida.
* @param mensaje
*/
public void enviarMensajeChat(String mensaje) {
synchronized (jugadores) {
for (Jugador jugador : jugadores) {
jugador.enviarMensajeChat(mensaje);
}
}
synchronized (espectadores) {
for (Jugador espectador : espectadores) {
espectador.enviarMensajeChat(mensaje);
}
}
}
/**
* Env�a una notificaci�n con los puntajes a los jugadores y espectadores.
*/
private void notificarPuntajes() {
POJOResultadoRonda resultado = this.resultado.getPojo();
synchronized (jugadores) {
for (Jugador jugador : jugadores) {
jugador.notificarPuntajePartida(resultado);
}
}
synchronized (espectadores) {
for (Jugador espectador : espectadores) {
espectador.notificarPuntajePartida(resultado);
}
}
}
/**
* Cambia el estado de la bandera "listo" del jugador dado. De acuerdo a las condiciones, puede
* arrancar la partida.
* @param jugador
* @param listo
*/
public void cambiarListoJugador(Jugador jugador, boolean listo) {
Log.info("El jugador " + jugador.getNombre() + " indica "
+ "que " + (listo ? "" : "no ") + "est� listo");
synchronized (jugadores) {
boolean empezarProxima = listo;
for (JugadorEstado jugadorEstado : jugadores) {
if (jugadorEstado.equals(jugador)) {
if (estado == EstadoPartida.FINALIZADA)
estado = EstadoPartida.EN_ESPERA;
jugadorEstado.listo = listo;
}
if (!jugadorEstado.listo)
empezarProxima = false;
}
if (jugadores.size() == cantidadMaximaJugadores && empezarProxima) {
arrancarPartida();
notificarEspectadores();
for (Jugador jugadorNotificado : jugadores)
jugadorNotificado.notificarCambioPartida();
}
}
ServicioPartidas.getInstancia().notificarClientes();
}
}