/**
Creep Smash, a multiplayer towerdefence game
created as a project at the Hochschule fuer
Technik Stuttgart (University of Applied Science)
http://www.hft-stuttgart.de
Copyright (C) 2008 by
* Andreas Wittig
* Bernd Hietler
* Christoph Fritz
* Fabian Kessel
* Levin Fritz
* Nikolaj Langner
* Philipp Schulte-Hubbert
* Robert Rapczynski
* Ron Trautsch
* Sven Supper
http://creepsmash.sf.net/
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
package de.creepsmash.server.game;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import org.apache.log4j.Logger;
import de.creepsmash.common.IConstants;
import de.creepsmash.common.messages.client.GameMessage;
import de.creepsmash.common.messages.server.PlayerJoinedMessage;
import de.creepsmash.common.messages.server.ServerMessage;
import de.creepsmash.server.AuthenticationService;
import de.creepsmash.server.Client;
import de.creepsmash.server.PersistenceManager;
import de.creepsmash.server.QueueConsumerThread;
import de.creepsmash.server.QueueMessage;
import de.creepsmash.server.model.GameJournal;
import de.creepsmash.server.model.Player;
/**
* Coordinates a game of Tower Defence. This class's job is mainly coordinating
* the various queues and threads, handling message and other events is
* delegated to instances of {@link GameState GameState}.
*/
public class Game
implements QueueConsumerThread.Consumer<GameMessage>,
TickThread.TickReceiver {
private int gameId;
private String gameName;
private int mapId;
private int maxPlayers;
private int PlayersScore;
private String Passwort;
private Integer MaxEloPoints;
private Integer MinEloPoints;
private GameState gameState;
private PlayerList clients;
private List<String> PlayerNames = new ArrayList<String>();
private BlockingQueue<QueueMessage<GameMessage>> queue;
private TickThread tickThread;
private List<GameObserver> observers;
private static Logger logger = Logger.getLogger(Game.class);
/**
* An object that observes a Game.
*/
public interface GameObserver {
/**
* Notify the observer of a change in the game's state.
* @param game the game that changed.
*/
void gameStateChanged(Game game);
/**
* Notify the observer of a change in the game's players.
* @param game the game that changed.
*/
void gamePlayersChanged(Game game);
/**
* Notify the observer of the game's end.
* @param game the game that changed.
*/
void gameEnded(Game game);
/**
* Notify the observer of the game's termination.
* @param game the game that changed.
*/
void gameTerminated(Game game);
}
/**
* No-arg constructor for subclasses.
* @param gameId the game's id.
* @param gameName a name for the game.
* @param mapId the id of the map.
* @param maxPlayers the max number of players in this game.
*/
protected Game(int gameId, String gameName, int mapId, int maxPlayers,String Passwort,int MaxEloPoints,int MinEloPoints) {
this.gameId = gameId;
this.gameName = gameName;
this.mapId = mapId;
this.PlayersScore = 0;
this.maxPlayers = maxPlayers;
this.Passwort = Passwort;
this.MaxEloPoints = MaxEloPoints;
this.MinEloPoints = MinEloPoints;
this.observers = new LinkedList<GameObserver>();
this.clients = new PlayerList();
}
/**
* Create a new game with the given gameId. This sets up the queue and the
* thread that takes messages from the queue.
* @param gameId the game's id.
* @param gameName a name for the game.
* @param mapId the id of the map.
* @param maxPlayers the max number of players in this game.
* @param creator the client who created the game.
*/
public Game(int gameId, String gameName, int mapId, int maxPlayers,
Client creator,String Passwort,int MaxEloPoints,int MinEloPoints) {
this.gameId = gameId;
this.gameName = gameName;
this.mapId = mapId;
this.maxPlayers = maxPlayers;
this.Passwort = Passwort;
this.MaxEloPoints = MaxEloPoints;
this.MinEloPoints = MinEloPoints;
this.gameState = new WaitingGameState(this, creator);
this.clients = new PlayerList();
this.queue = new LinkedBlockingQueue<QueueMessage<GameMessage>>();
(new QueueConsumerThread<GameMessage>(this, this.queue)).start();
this.observers = new LinkedList<GameObserver>();
}
/**
* Returns the current list of players.
* @return the current list of players.
*/
public synchronized PlayerList getClients() {
return this.clients ;
}
/**
* Sets the current list of players.
* @param clients the new list of players. Must not be null.
*/
public synchronized void setClients(PlayerList clients) {
if (clients == null) {
//logger.info("score Playersender: " + String.valueOf(clients));
throw new IllegalArgumentException("'clients' was null");
}
this.clients = clients;
}
/**
* Set the game's mapId.
* @return the game's mapId.
*/
public synchronized void setMapId(int mapId) {
this.mapId = mapId;
}
/**
* Send a message to all players.
* @param message the message to be sent
*/
public void sendAll(ServerMessage message) {
for (PlayerInGame p : this.clients) {
p.getClient().send(message);
}
}
/**
* Returns the current number of players.
* @return the current number of players.
*/
public int getCurrentPlayers() {
return this.clients.size();
}
/**
* Return the game's gameId.
* @return the game's gameId.
*/
public int getGameId() {
return this.gameId;
}
public int getPlayerScore() {
return this.PlayersScore;
}
/**
* Return the game's gameName.
* @return the game's gameName.
*/
public String getGameName() {
return this.gameName;
}
/**
* Return the game's mapId.
* @return the game's mapId.
*/
public int getMapId() {
return this.mapId;
}
/**
* Return the maximum number of players for this game.
* @return the maximum number of players for this game.
*/
public int getMaxPlayers() {
return this.maxPlayers;
}
/**
* Return the Game Passwort
* @return Passwort.
*/
public String getPasswort() {
return this.Passwort;
}
/**
* Return the Game Passwort
* @return Passwort.
*/
public int getMaxEloPoints() {
return this.MaxEloPoints;
}
/**
* Return the Game Passwort
* @return Passwort.
*/
public int getMinEloPoints() {
return this.MinEloPoints;
}
public List<String> getPlayerNames(){
return this.PlayerNames;
}
/**
* Returns the queue that can be used to send messages to this game.
* @return the game's queue
*/
public BlockingQueue<QueueMessage<GameMessage>> getQueue() {
return this.queue;
}
/**
* Returns the current state.
* @return "waiting", "running", "ended" or "terminated".
*/
public String getState() {
return this.gameState.toString();
}
/**
* Handle a message (from a client, presumably).
* @param message the message
*/
public synchronized void consume(GameMessage message) {
try {
PlayerInGame sender = this.clients.get(message.getClientId());
if (sender == null) {
logger.error("got message from client " + message.getClientId()
+ ", but that client is not in the game.");
}
changeState(this.gameState.consume(message, sender));
} catch (Exception e) {
logger.error(
"Caught Exception while handling message '" + message + "'", e);
}
}
/**
* Change the game's GameState.
* @param newState the new state.
*/
private void changeState(GameState newState) {
if (this.gameState.getClass() != newState.getClass()) {
logger.info("game '" + this + "' enters state " + newState);
}
if (this.gameState instanceof WaitingGameState
&& newState instanceof RunningGameState) {
this.tickThread = new TickThread(this, IConstants.TICK_MS);
this.tickThread.start();
}
if (newState instanceof EndedGameState) {
if (this.tickThread != null) {
this.tickThread.terminate();
}
gameEnded();
}
if (newState instanceof TerminatedGameState) {
shutdown();
gameTerminated();
}
GameState oldState = this.gameState;
this.gameState = newState;
if (oldState.getClass() != newState.getClass()) {
gameStateChanged();
}
}
/**
* Called periodically by TickThread when the game is running.
*/
public synchronized void tick() {
this.gameState.tick();
}
/**
* Returns true if another player can join this game.
* @return true if another player can join this game.
*/
public synchronized boolean canClientJoin() {
return
this.gameState instanceof WaitingGameState
&& this.clients.size() < this.maxPlayers;
}
/**
* Register a new client with the game.
* @param newClient the new client. Must not be null.
*/
public synchronized void newClient(Client newClient) {
if (newClient == null) {
throw new IllegalArgumentException("'newClient' was null!");
}
this.PlayersScore += AuthenticationService.getPlayer(
newClient.getUserName()).getElopoints() - 500;
this.PlayerNames.add(newClient.getUserName());
if (!(this.gameState instanceof WaitingGameState)) {
throw new RuntimeException("game has started--no more players can join");
}
if (this.clients.size() >= this.maxPlayers) {
throw new RuntimeException(
"max # of players reached--no more players can join");
}
newClient.send(
new PlayerJoinedMessage(
newClient.getUserName(), newClient.getClientID(), AuthenticationService.getPlayer(
newClient.getUserName()).getElopoints() - 500));
sendAll(
new PlayerJoinedMessage(
newClient.getUserName(), newClient.getClientID(), AuthenticationService.getPlayer(
newClient.getUserName()).getElopoints() - 500));
for (PlayerInGame p : this.clients) {
Client c = p.getClient();
newClient.send(
new PlayerJoinedMessage(c.getUserName(), c.getClientID(), AuthenticationService.getPlayer(
c.getUserName()).getElopoints() - 500));
}
this.clients.add(newClient);
gamePlayersChanged();
}
public synchronized void removeClient(Client removeClient) {
this.PlayersScore -= AuthenticationService.getPlayer(
removeClient.getUserName()).getElopoints() - 500;
this.clients.remove(removeClient.getClientID());
this.PlayerNames.remove(removeClient.getUserName());
}
/**
* Shutdown the game, including all threads it might have spawned.
*/
public synchronized void shutdown() {
if (this.tickThread != null) {
this.tickThread.terminate();
}
try {
this.queue.put(QueueMessage.<GameMessage>sentinel());
} catch (InterruptedException e) {
logger.warn("caught InterruptedException", e);
}
}
/**
* Adds an observer. It will be notified of any change in the game's state or
* list of players.
* @param observer the observer. Must not be null.
*/
public void addObserver(GameObserver observer) {
if (observer == null) {
throw new IllegalArgumentException("'observer' was null");
}
this.observers.add(observer);
}
/**
* Notifies all observers.
*/
public void gameStateChanged() {
for (GameObserver observer : this.observers) {
observer.gameStateChanged(this);
}
}
/**
* Notifies all observers.
*/
public void gamePlayersChanged() {
for (GameObserver observer : this.observers) {
observer.gamePlayersChanged(this);
}
}
/**
* Notifies all observers.
*/
public void gameEnded() {
for (GameObserver observer : this.observers) {
observer.gameEnded(this);
}
}
/**
* Notifies all observers.
*/
public void gameTerminated() {
for (GameObserver observer : this.observers) {
observer.gameTerminated(this);
}
}
/**
* Returns the gameName.
* @return the gameName.
*/
public String toString() {
return this.gameName;
}
/**
* Set the GameState.
* @param gameState the new GameState.
*/
protected void setGameState(GameState gameState) {
this.gameState = gameState;
}
/**
* Return the current GameState.
* @return the current GameState.
*/
protected GameState getGameState() {
return this.gameState;
}
/**
* The Function saves actual ended game into DB
*
* @param playerNamePositionMap Players with position
* @param startDate Start date of game
*/
public void SaveToJournal(Map<String, Integer> playerNamePositionMap, long startDate) {
try {
EntityManager entityManager = PersistenceManager.getInstance().getEntityManager();
EntityTransaction entityTransaction = entityManager.getTransaction();
entityTransaction.begin();
GameJournal gameJournalEntry = new GameJournal();
String mapname = IConstants.Map.getMapById(mapId).getFilename();
mapname = mapname.replaceAll("de/creepsmash/client/resources/maps/map_", "");
gameJournalEntry.setMap(mapname.replaceAll(".map", ""));
gameJournalEntry.setName(gameName);
gameJournalEntry.setNumPlayers(playerNamePositionMap.size());
gameJournalEntry.setStart_date(startDate);
gameJournalEntry.setEnd_date(System.currentTimeMillis() / 1000);
int i = 0;
for (String playerName : playerNamePositionMap.keySet()) {
i++;
Player player = AuthenticationService.getPlayer(playerName);
if (player != null) {
int num = playerNamePositionMap.get(playerName);
// Ich habe keine idee wie man da ohne switch macht^^
switch(i) {
case 1:
gameJournalEntry.setPlayer1(player.getName());
gameJournalEntry.setPlayer1_score(player.getOldElopoints());
gameJournalEntry.setScore1(player.getElopoints());
gameJournalEntry.setPlayer1_position(num);
gameJournalEntry.setIp1(player.getIp());
gameJournalEntry.setMac1(player.getMac());
break;
case 2:
gameJournalEntry.setPlayer2(player.getName());
gameJournalEntry.setPlayer2_score(player.getOldElopoints());
gameJournalEntry.setScore2(player.getElopoints());
gameJournalEntry.setPlayer2_position(num);
gameJournalEntry.setIp2(player.getIp());
gameJournalEntry.setMac2(player.getMac());
break;
case 3:
gameJournalEntry.setPlayer3(player.getName());
gameJournalEntry.setPlayer3_score(player.getOldElopoints());
gameJournalEntry.setScore3(player.getElopoints());
gameJournalEntry.setPlayer3_position(num);
gameJournalEntry.setIp3(player.getIp());
gameJournalEntry.setMac3(player.getMac());
break;
case 4:
gameJournalEntry.setPlayer4(player.getName());
gameJournalEntry.setPlayer4_score(player.getOldElopoints());
gameJournalEntry.setScore4(player.getElopoints());
gameJournalEntry.setPlayer4_position(num);
gameJournalEntry.setIp4(player.getIp());
gameJournalEntry.setMac4(player.getMac());
break;
default:
logger.error("False number of players: " + i);
break;
}
}
}
entityManager.persist(gameJournalEntry);
entityManager.flush();
entityTransaction.commit();
logger.debug("GameJournal saved.");
} catch (Throwable t) {
logger.error("error while saving GameJournal", t);
}
}
/**
* The function check for multiaccounting.
*
* @param ip IP Address of Client
*/
public boolean check4Multi(String ip, String mac) {
if (IConstants.MUTIACCOUNT_IP_CHECK || IConstants.MUTIACCOUNT_MAC_CHECK) {
boolean a = false;
for (PlayerInGame p : this.clients) {
Client c = p.getClient();
// If the client from same Computer
if (mac.equalsIgnoreCase(c.getMACAddress())
&& IConstants.MUTIACCOUNT_MAC_CHECK) {
logger.warn("Multiaccounting detected. MAC: " + mac);
return false;
}
// If the client from same IP
if (ip.equalsIgnoreCase(c.getIPAddress())
&& IConstants.MUTIACCOUNT_IP_CHECK) {
if (a) {
logger.warn("Multiaccounting detected. IP: " + ip);
return false;
}
a = true;
}
}
}
return true;
}
}