package com.mediamonks.googleflip.pages.game.management;
import android.util.Log;
import com.mediamonks.googleflip.GoogleFlipGameApplication;
import com.mediamonks.googleflip.data.constants.LevelColor;
import com.mediamonks.googleflip.data.constants.LevelDifficulty;
import com.mediamonks.googleflip.data.constants.PlayerState;
import com.mediamonks.googleflip.data.vo.ClientVO;
import com.mediamonks.googleflip.data.vo.LevelResultVO;
import com.mediamonks.googleflip.data.vo.LevelVO;
import com.mediamonks.googleflip.data.vo.PlayerScoreVO;
import com.mediamonks.googleflip.pages.game.management.gamemessages.GameMessage;
import com.mediamonks.googleflip.pages.game.management.gamemessages.GameMessageConverter;
import com.mediamonks.googleflip.pages.game.management.gamemessages.c2s.C2SClientNameMessage;
import com.mediamonks.googleflip.pages.game.management.gamemessages.c2s.C2SRoundFinishedMessage;
import com.mediamonks.googleflip.pages.game.management.gamemessages.s2c.S2CClientAckMessage;
import com.mediamonks.googleflip.pages.game.management.gamemessages.s2c.S2CClientsScoreChangedMessage;
import com.mediamonks.googleflip.pages.game.management.gamemessages.s2c.S2CConnectedClientsChangedMessage;
import com.mediamonks.googleflip.pages.game.management.gamemessages.s2c.S2CGameFinishedMessage;
import com.mediamonks.googleflip.pages.game.management.gamemessages.s2c.S2CRoundFinishedMessage;
import com.mediamonks.googleflip.pages.game.management.gamemessages.s2c.S2CRoundStartedMessage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import temple.multiplayer.net.common.connection.Connection;
/**
* Implementation of the GameServer interface
*/
public class GameServerImpl implements GameServer {
private static final String TAG = GameServerImpl.class.getSimpleName();
private static final int MAX_PLAYER_COUNT = 4;
private static int sCurrentId = 0;
private final List<Player> _players = new ArrayList<>();
private boolean _debug;
private List<Long> _rounds;
private int _roundIndex;
private List<LevelColor> _backgroundColors = new ArrayList<>();
private HashMap<Integer, List<LevelVO>> _levelDifficultyMap;
public GameServerImpl() {
}
public void initBackgroundColors() {
LevelColor[] colors = {LevelColor.BLUE, LevelColor.CYAN, LevelColor.PINK, LevelColor.PURPLE};
List<LevelColor> colorList = Arrays.asList(colors);
Collections.shuffle(colorList);
_backgroundColors = new LinkedList<>();
_backgroundColors.addAll(colorList);
}
@Override
public boolean hasRoomForMorePlayers() {
return _players.size() < MAX_PLAYER_COUNT;
}
@Override
public void addPlayer(final Player player) {
if (_debug) Log.d(TAG, "addPlayer: ");
// initialize player with unique ID & background color
player.setPlayerId(getUniqueId());
player.setPlayerLevelColor(getLevelColor());
player.getConnection().setMessageHandler(
new Connection.MessageHandler() {
@Override
public void onMessageReceived(String message) {
GameServerImpl.this.onMessageReceived(player, message);
}
}
);
player.getConnection().setConnectionHandler(
new Connection.ConnectionHandler() {
@Override
public void onConnectionLost() {
removePlayer(player);
}
}
);
_players.add(player);
// send this now, even though the client may not be ready to receive it
sendClientAck(player);
}
private void sendClientAck(Player player) {
ClientVO clientVO = player.getClientVO();
player.getConnection().writeMessage(GameMessageConverter.writeMessage(new S2CClientAckMessage(clientVO.id, clientVO.levelColor, clientVO.name)));
}
private LevelColor getLevelColor() {
if (_backgroundColors.size() == 0) {
initBackgroundColors();
}
return _backgroundColors.remove(0);
}
private void onConnectedClientsChanged() {
List<ClientVO> clients = new ArrayList<>();
for (Player player : _players) {
clients.add(player.getClientVO());
}
broadcastMessage(new S2CConnectedClientsChangedMessage(clients));
}
@Override
public void removePlayer(Player player) {
if (_debug) Log.d(TAG, "removePlayer: player = " + player);
_backgroundColors.add(player.getClientVO().levelColor);
disconnectPlayer(player);
_players.remove(player);
onConnectedClientsChanged();
}
private void disconnectPlayer(Player player) {
player.getConnection().setMessageHandler(null);
player.getConnection().setConnectionHandler(null);
player.getConnection().disconnect();
}
@Override
public void removePlayer(String deviceAddress) {
if (_debug) Log.d(TAG, "removePlayer: deviceAddress = " + deviceAddress);
for (Player player : _players) {
if (player.getConnection().getDeviceAddress().equals(deviceAddress)) {
removePlayer(player);
return;
}
}
}
private void onMessageReceived(Player player, String message) {
GameMessage gameMessage = GameMessageConverter.readMessage(message);
if (gameMessage == null) {
Log.e(TAG, "onMessageReceived: couldn't convert message " + message);
return;
}
switch (gameMessage.getType()) {
case C2S_CLIENT_NAME:
onClientNameMessageReceived(player, (C2SClientNameMessage) gameMessage);
break;
case C2S_ROUND_FINISHED:
onRoundFinishedMessageReceived(player, (C2SRoundFinishedMessage) gameMessage);
break;
default:
if (_debug) Log.d(TAG, "onMessageReceived: unhandled message type " + gameMessage.getType());
break;
}
}
private void onRoundFinishedMessageReceived(Player player, C2SRoundFinishedMessage message) {
player.addLevelResult(message.levelResultVO);
player.setPlayerState(PlayerState.FINISHED);
updateScores();
checkRoundFinished();
}
/**
* Check if all players are done with the current round
*/
private void checkRoundFinished() {
for (Player player : _players) {
if (player.getClientVO().playerState.equals(PlayerState.PLAYING)) {
return;
}
}
_roundIndex++;
if (_debug) Log.d(TAG, "checkRoundFinished: " + _roundIndex);
// check if round is finished, or whole game is finished
if (_roundIndex < _rounds.size()) {
broadcastMessage(new S2CRoundFinishedMessage());
} else {
broadcastMessage(new S2CGameFinishedMessage(getWinnerId()));
}
}
/**
* Determine the ID of the winner
*/
private int getWinnerId() {
int id = 0;
float minTime = Float.MAX_VALUE;
for (Player player : _players) {
float totalTime = 0;
for (LevelResultVO resultVO : player.getLevelResults()) {
totalTime += resultVO.seconds;
}
if (totalTime < minTime) {
minTime = totalTime;
id = player.getClientVO().id;
}
}
return id;
}
private void clearScores() {
for (Player player : _players) {
player.clearLevelResults();
}
}
private void updateScores() {
List<PlayerScoreVO> playerScores = new ArrayList<>();
for (Player player : _players) {
ClientVO clientVO = player.getClientVO();
PlayerScoreVO scoreVO = new PlayerScoreVO(clientVO, _roundIndex);
float totalTime = 0;
for (LevelResultVO resultVO : player.getLevelResults()) {
scoreVO.roundScores.add(resultVO.seconds);
totalTime += resultVO.seconds;
}
scoreVO.totalTime = totalTime;
scoreVO.isPlaying = (player.getClientVO().playerState.equals(PlayerState.PLAYING));
playerScores.add(scoreVO);
}
// update order
Collections.sort(playerScores);
for (int i = 0; i < playerScores.size(); i++) {
playerScores.get(i).order = i + 1;
}
broadcastMessage(new S2CClientsScoreChangedMessage(playerScores));
}
private void onClientNameMessageReceived(Player player, C2SClientNameMessage message) {
String playerName = player.getClientVO().name;
if (playerName != null && playerName.equals(message.name) && player.getClientVO().id == message.id) {
// onClientNameMessageReceived: player already known
} else {
player.setPlayerName(message.name);
sendClientAck(player);
onConnectedClientsChanged();
}
}
private static int getUniqueId() {
return sCurrentId++;
}
public void setDebug(boolean debug) {
_debug = debug;
}
@Override
public void setLevels(List<LevelVO> levels) {
// create map with shuffled lists of levels per difficulty
_levelDifficultyMap = new HashMap<>();
for (int difficulty : LevelDifficulty.DIFFICULTIES) {
_levelDifficultyMap.put(difficulty, new ArrayList<LevelVO>());
}
for (LevelVO levelVO : levels) {
_levelDifficultyMap.get(levelVO.difficulty).add(levelVO);
}
for (int difficulty : LevelDifficulty.DIFFICULTIES) {
Collections.shuffle(_levelDifficultyMap.get(difficulty));
}
}
@Override
public void stop() {
while (_players.size() > 0) {
Player player = _players.remove(0);
disconnectPlayer(player);
}
}
@Override
public void startGame() {
clearScores();
initRounds();
_roundIndex = 0;
startRound();
}
private void initRounds() {
_rounds = new ArrayList<>();
for (int difficulty : LevelDifficulty.DIFFICULTIES) {
List<LevelVO> levels = _levelDifficultyMap.get(difficulty);
int index = (int)(Math.floor(levels.size() * Math.random()));
_rounds.add(levels.get(index).id);
}
Log.d(TAG, "initRounds: " + _rounds);
}
public void startRound() {
Long currentLevel = _rounds.get(_roundIndex);
for (Player player : _players) {
player.setPlayerState(PlayerState.PLAYING);
}
GoogleFlipGameApplication.getUserModel().selectLevelById(currentLevel);
broadcastMessage(new S2CRoundStartedMessage(currentLevel, _roundIndex));
}
private void broadcastMessage(GameMessage gameMessage) {
String message = GameMessageConverter.writeMessage(gameMessage);
for (Player player : _players) {
player.getConnection().writeMessage(message);
}
}
}