package com.esir.sr.sweetsnake.session;
import java.rmi.RemoteException;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.esir.sr.sweetsnake.api.IClientCallback;
import com.esir.sr.sweetsnake.api.IGameSessionCallback;
import com.esir.sr.sweetsnake.callback.GameSessionCallback;
import com.esir.sr.sweetsnake.constants.GameConstants;
import com.esir.sr.sweetsnake.constants.PropertiesConstants;
import com.esir.sr.sweetsnake.dto.GameSessionDTO;
import com.esir.sr.sweetsnake.dto.PlayerDTO;
import com.esir.sr.sweetsnake.enumeration.MoveDirection;
import com.esir.sr.sweetsnake.enumeration.PlayerStatus;
import com.esir.sr.sweetsnake.exception.GameSessionNotFoundException;
import com.esir.sr.sweetsnake.exception.MaximumNumberOfPlayersException;
import com.esir.sr.sweetsnake.exception.PlayerNotFoundException;
import com.esir.sr.sweetsnake.exception.UnauthorizedActionException;
import com.esir.sr.sweetsnake.factory.DtoConverterFactory;
import com.esir.sr.sweetsnake.game.engine.GameEngine;
/**
*
* @author Herminaƫl Rougier
* @author Damien Jouanno
*
*/
@Component
@Scope("prototype")
public class GameSession extends AbstractSession
{
/**********************************************************************************************
* [BLOCK] STATIC FIELDS
**********************************************************************************************/
/** The logger */
private static final Logger log = LoggerFactory.getLogger(GameSession.class);
/**********************************************************************************************
* [BLOCK] FIELDS
**********************************************************************************************/
/** The session id */
private String id;
/** The players list */
private List<Player> players;
/** The current player nb */
private int currentPlayerNb;
/** The timelimit players mapping */
private Map<Player, Long> timeout;
/** The fictive players mapping */
private Map<String, Player> fictivePlayers;
/** The game engine */
private GameEngine engine;
/** Are all players ready to play */
private boolean allReady;
/** Is the game started */
private boolean isStarted;
/** The game session callback */
private GameSessionCallback callback;
/**********************************************************************************************
* [BLOCK] CONSTRUCTOR & INIT
**********************************************************************************************/
/**
*
*/
protected GameSession() {
super();
}
/**
*
* @throws RemoteException
*/
@PostConstruct
protected void init() {
log.info("Initializing a new game session");
id = RandomStringUtils.randomAlphanumeric(PropertiesConstants.GENERATED_ID_LENGTH);
players = new LinkedList<Player>();
currentPlayerNb = 1;
timeout = new LinkedHashMap<Player, Long>();
fictivePlayers = new LinkedHashMap<String, Player>();
callback = beanProvider.getPrototype(GameSessionCallback.class, this);
}
/**********************************************************************************************
* [BLOCK] PRIVATE METHODS
**********************************************************************************************/
/**
*
* @param startIndex
*/
private void updatePlayersNumber(final int startIndex) {
final ListIterator<Player> it = players.listIterator(startIndex);
while (it.hasNext()) {
final Player player = it.next();
player.setNumber(player.getNumber() - 1);
}
}
/**********************************************************************************************
* [BLOCK] PUBLIC CALLBACK METHODS
**********************************************************************************************/
/**
*
* @param playerClient
*/
public void ready(final IClientCallback playerClient) {
try {
final Player player = playersRegistry.get(playerClient.getName());
player.setStatus(PlayerStatus.READY);
allReady = true;
for (final Player _player : players) {
// TODO block if fictives ?
if (_player.getStatus() != PlayerStatus.READY) {
allReady = false;
}
}
final GameSessionDTO sessionDto = DtoConverterFactory.convertGameSession(this);
for (final Player _player : players) {
_player.getCallback().sessionJoined(_player.getNumber(), sessionDto);
}
log.debug("Player {} is ready to play", player.getName());
} catch (final PlayerNotFoundException | RemoteException e) {
log.error(e.getMessage(), e);
}
}
/**
*
* @param starterClient
* @throws UnauthorizedActionException
*/
public void startGame(final IClientCallback starterClient) throws UnauthorizedActionException {
try {
final Player starter = playersRegistry.get(starterClient.getName());
if (starter.getNumber() != 1) {
log.warn("Player {} tried to start session {} but was not authorized to", starter.getName(), id);
throw new UnauthorizedActionException("unauthorized start");
}
removeAllFictivePlayers();
engine = new GameEngine(this);
final GameSessionDTO sessionDto = DtoConverterFactory.convertGameSession(this);
for (final Player player : players) {
player.setStatus(PlayerStatus.PLAYING);
player.getCallback().sessionStarted(player.getNumber(), sessionDto);
}
isStarted = true;
engine.getGameBoard().clearRefreshes();
server.sendRefreshPlayersList();
server.sendRefreshSessionsList();
log.info("Game session {} has been started", id);
} catch (final PlayerNotFoundException | RemoteException e) {
log.error(e.getMessage(), e);
}
}
/**
*
*/
public void leaveGame(final IClientCallback leaverClient, final boolean fromDisconnect) {
try {
final Player leaver = playersRegistry.get(leaverClient.getName());
final PlayerDTO leaverDto = DtoConverterFactory.convertPlayer(leaver);
log.debug("Player {} is leaving session {}", leaver.getName(), id);
removePlayer(leaver);
if (!fromDisconnect) {
final GameSessionDTO sessionDto = DtoConverterFactory.convertGameSession(this);
leaver.getCallback().sessionLeft(sessionDto, leaverDto, true, true);
}
if (isStarted) {
engine.removeSnake(leaver);
}
final GameSessionDTO sessionDto = DtoConverterFactory.convertGameSession(this);
final boolean stopped = players.size() <= 1, finished = players.size() <= 0;
for (final Player player : players) {
if (!player.isFictive()) {
player.getCallback().sessionLeft(sessionDto, leaverDto, stopped, finished);
player.getCallback().refreshSession(sessionDto);
}
}
if (stopped) {
isStarted = false;
}
if (finished) {
sessionsRegistry.remove(id);
}
if (isStarted) {
engine.getGameBoard().clearRefreshes();
}
server.sendRefreshPlayersList();
server.sendRefreshSessionsList();
log.info("Game session has been left by {}", leaver);
} catch (final PlayerNotFoundException | GameSessionNotFoundException | RemoteException e) {
log.error(e.getMessage(), e);
}
}
/**
*
* @param player
* @param direction
*/
public void movePlayer(final IClientCallback client, final MoveDirection direction) {
if (isStarted) {
try {
final Player player = playersRegistry.get(client.getName());
if (System.currentTimeMillis() - timeout.get(player) > GameConstants.TIME_BETWEEN_2_MOVES) {
engine.moveSnake(direction, player);
final GameSessionDTO sessionDto = DtoConverterFactory.convertGameSession(this);
for (final Player _player : players) {
_player.getCallback().refreshSession(sessionDto);
}
engine.getGameBoard().clearRefreshes();
timeout.put(player, System.currentTimeMillis());
}
} catch (final PlayerNotFoundException | RemoteException e) {
log.error(e.getMessage(), e);
}
}
}
/**********************************************************************************************
* [BLOCK] PUBLIC METHODS
**********************************************************************************************/
/**
*
* @param player
*/
public void denied(final Player player) {
final Player fictivePlayer = fictivePlayers.get(player.getName());
players.remove(fictivePlayer);
fictivePlayers.remove(player.getName());
updatePlayersNumber(fictivePlayer.getNumber() - 1);
final GameSessionDTO sessionDto = DtoConverterFactory.convertGameSession(this);
for (final Player _player : getPlayers()) {
if (!_player.isFictive()) {
try {
_player.getCallback().sessionJoined(_player.getNumber(), sessionDto);
} catch (final RemoteException e) {
log.error(e.getMessage(), e);
}
}
}
}
/**
*
* @param player
* @throws MaximumNumberOfPlayersException
*/
public void addPlayer(final Player player) throws MaximumNumberOfPlayersException {
Player fictivePlayer = null;
if (fictivePlayers.containsKey(player.getName())) {
fictivePlayer = fictivePlayers.get(player.getName());
players.remove(fictivePlayer);
fictivePlayers.remove(player.getName());
}
if (players.size() >= GameConstants.MAX_NUMBER_OF_PLAYERS) {
log.warn("Player {} tried to join a full session", player.getName());
throw new MaximumNumberOfPlayersException("session is full");
}
player.setNumber(fictivePlayer == null ? currentPlayerNb : fictivePlayer.getNumber());
players.add(player.getNumber() - 1, player);
player.setGameSessionId(id);
player.setStatus(PlayerStatus.PRESENT);
allReady = false;
timeout.put(player, 0L);
currentPlayerNb++;
final GameSessionDTO sessionDto = DtoConverterFactory.convertGameSession(this);
if (!isStarted) {
for (final Player _player : players) {
if (!_player.isFictive()) {
try {
_player.getCallback().sessionJoined(_player.getNumber(), sessionDto);
} catch (final RemoteException e) {
log.error(e.getMessage(), e);
}
}
}
} else {
if (!player.isFictive()) {
try {
player.getCallback().sessionJoined(player.getNumber(), sessionDto);
} catch (final RemoteException e) {
log.error(e.getMessage(), e);
}
}
}
}
/**
*
* @param player
*/
public void addFictivePlayer(final Player player) {
final Player fictivePlayer = beanProvider.getPrototype(Player.class, player.getName());
fictivePlayers.put(fictivePlayer.getName(), fictivePlayer);
players.add(fictivePlayer);
fictivePlayer.setStatus(PlayerStatus.INVITED);
fictivePlayer.setNumber(players.size());
try {
final Player _player = players.get(0);
_player.getCallback().sessionJoined(_player.getNumber(), DtoConverterFactory.convertGameSession(this));
} catch (final RemoteException e) {
log.error(e.getMessage(), e);
}
}
/**
*
*/
public void removeAllFictivePlayers() {
for (final String playerName : fictivePlayers.keySet()) {
players.remove(fictivePlayers.get(playerName));
currentPlayerNb--;
}
fictivePlayers.clear();
}
/**
*
* @param player
*/
public void removePlayer(final Player player) {
timeout.remove(player);
players.remove(player);
currentPlayerNb--;
updatePlayersNumber(player.getNumber() - 1);
player.setStatus(PlayerStatus.AVAILABLE);
player.setGameSessionId(null);
player.setNumber(0);
player.setScore(0);
}
/**
*
* @param player
* @return
*/
public boolean contains(final Player player) {
return players.contains(player);
}
/**
*
*/
public void stopGame() {
try {
isStarted = false;
for (final Player player : players) {
player.setStatus(PlayerStatus.PRESENT);
}
final GameSessionDTO sessionDto = DtoConverterFactory.convertGameSession(this);
for (final Player player : players) {
player.getCallback().sessionFinished(sessionDto);
}
log.info("Game session {} has been finished", id);
} catch (final RemoteException e) {
log.error(e.getMessage(), e);
}
}
/**
*
*/
public void destroy() {
for (final Player player : players) {
removePlayer(player);
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "session[id=" + id + ", nbPlayers=" + players.size() + ", started=" + isStarted + "]";
}
/**********************************************************************************************
* [BLOCK] GETTERS
**********************************************************************************************/
/**
*
* @return
*/
public String getId() {
return id;
}
/**
*
* @return
*/
public List<Player> getPlayers() {
return players;
}
/**
*
* @return
*/
public GameEngine getGameEngine() {
return engine;
}
/**
*
* @return
*/
public boolean allReady() {
return allReady;
}
/**
*
* @return
*/
public boolean isStarted() {
return isStarted;
}
/**
*
* @return
*/
public IGameSessionCallback getCallback() {
return callback;
}
}