package ch.bfh.jass.game;
import ch.bfh.jass.exceptions.*;
import ch.bfh.jass.game.JassCardSet.CardColor;
import ch.bfh.jass.interfaces.IPlayer;
import ch.bfh.jass.interfaces.IRule;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author David Baumgartner <baumd9@bfh.ch>, Fabian Schneider <schnf6@bfh.ch>
* @version 1.0
*/
public class Game {
/**
* Unique ID of the game.
*/
private String gameID;
/**
* Rule with the game logic.
*/
private JassRule rule;
/**
* The table where the game takes place.
*/
private Table table;
/**
* List of all players in the game.
*/
private PlayerList<IPlayer> players;
/**
* List of the points of each team.
*/
private List<int[]> teamPoints = new ArrayList<int[]>();
/**
* List of the teams in the game.
*/
private List<List<IPlayer>> teams = new ArrayList<List<IPlayer>>();
/**
* List of winning players.
*/
private List<IPlayer> winnerList;
/**
* Player, who is the admin of the game.
*/
private IPlayer gameAdmin;
/**
* Shows if the game is started.
*/
private boolean started = false;
/**
* Shows if the game is finished.
*/
private boolean gameFinished = false;
/**
* Class Constructor
*
* @param gameFounder
* @throws PlayerAlreadyInGameException
* @throws PlaceTakenException
* @throws PlaceDoesNotExistException
*/
public Game(IPlayer gameFounder) throws PlayerAlreadyInGameException, PlaceTakenException, PlaceDoesNotExistException {
this.rule = new JassRule();
this.table = new Table(this.rule.getNumberOfTableCards());
this.players = new PlayerList<IPlayer>(this.rule.getNumberOfPlayers());
this.initGame(gameFounder);
}
/**
* Class Constructor
*
* @param gameFounder
* @param rule
* @throws PlayerAlreadyInGameException
* @throws PlaceTakenException
* @throws PlaceDoesNotExistException
*/
public Game(IPlayer gameFounder, JassRule rule) throws PlayerAlreadyInGameException, PlaceTakenException, PlaceDoesNotExistException {
this.rule = rule;
this.table = new Table(this.rule.getNumberOfTableCards());
this.players = new PlayerList<IPlayer>(this.rule.getNumberOfPlayers());
this.initGame(gameFounder);
}
/**
* Initialize the game and sets a game founder.
*
* @param gameFounder
* @throws PlayerAlreadyInGameException
* @throws PlaceTakenException
* @throws PlaceDoesNotExistException
*/
private void initGame(IPlayer gameFounder) throws PlayerAlreadyInGameException, PlaceTakenException, PlaceDoesNotExistException {
this.gameAdmin = gameFounder;
this.players.addPlayer(0, gameFounder);
this.gameAdmin.setPlace(0);
}
/**
* Starts the game.
*
* Fills the game with bots, if necessary, and initialize the teams.
* Initialize the Hands and Cards of all Players and sets the current
* player.
*
* @param player
* @throws YouMustNotException
*/
public void start(IPlayer player) throws YouMustNotException {
if (player.equals(this.gameAdmin)) {
if (this.players.getNumberOfTakenPlaces() < 4) {
this.fillBots();
}
this.initTeams();
this.initPlayerHands();
this.players.setCurrent(this.rule.getStartPlayer());
this.rule.setStarter(this.players.getCurrent());
this.started = true;
} else {
throw new YouMustNotException("You may not start the game!");
}
}
/**
* Initilaizes the teams of the game according to the rule.
*/
private void initTeams() {
for (int i = 0; i < this.rule.getNumberOfTeams(); i++) {
this.teams.add(new ArrayList<IPlayer>());
}
int team = 0;
for (int i = 0; i < this.players.size(); i++) {
this.teams.get(team).add(this.players.get(i));
if (team < this.rule.getNumberOfTeams() - 1) {
team++;
} else {
team = 0;
}
}
}
/**
* @return the teams of the game
*/
public List<List<IPlayer>> getTeams() {
return this.teams;
}
/**
* Fills the free places in the game with AIPlayers.
*/
private void fillBots() {
for (int i : this.players.getFreePlaces()) {
try {
AIJassPlayer aiPlayer = new AIJassPlayer();
this.players.addPlayer(i, aiPlayer);
aiPlayer.setUsername("Bot_" + i);
} catch (Exception ex) {
throw new RuntimeException("Something went really wrong!");
}
}
}
/**
* Initializes the hands of all players in the game.
*/
private void initPlayerHands() {
for (IPlayer player : this.players) {
player.initHand(this.rule);
}
}
/**
* Initializes a new round.
*
* @throws GameOverException
*/
private void initNewRound() throws GameOverException {
if (!this.hasWinner()) {
this.rule.setTrumpf(null);
this.rule.resetCardSet();
this.initPlayerHands();
} else {
this.winnerList = this.rule.getWinner(this.teams);
throw new GameOverException(this.winnerList.toString());
}
}
/**
* Plays a specified card.
*
* @param card
* @throws NotYourCardException
* @throws IsNotPlayableException
* @throws GameOverException
* @throws NoTrumpfSetException
*/
public void play(Card card) throws NotYourCardException, IsNotPlayableException, GameOverException, NoTrumpfSetException {
if (this.checkTrumpfIsSet() && !this.hasWinner()) {
card = this.players.getCurrent().getCard(card);
if (this.rule.isPlayable(card, table)) {
this.players.getCurrent().playCard(card);
this.table.addCard(this.players.indexOf(card.getHolder()), card);
this.nextPlayer();
} else {
throw new IsNotPlayableException("Card not playable!");
}
}
}
/**
* Plays a cord for an AIPlayer.
*
* @throws GameOverException
*/
public void playAI() throws GameOverException {
if (this.players.getCurrent().isAI()) {
try {
if (this.rule.getTrumpf() == null) {
this.setTrumpf(this.players.getCurrent().chooseTrumpf(), this.players.getCurrent());
}
this.play(this.players.getCurrent().chooseCard(this.rule, this.table));
} catch (NotYourCardException ex) {
throw new RuntimeException("Something went really wrong! Bot played wrong card.");
} catch (IsNotPlayableException ex) {
throw new RuntimeException("Something went really wrong! Bot has card which is not playable.");
} catch (NoTrumpfSetException ex) {
throw new RuntimeException("Something went really wrong! Bot not set a Trumpf.");
} catch (YouMustNotSetTrumpfException ex) {
throw new RuntimeException("Something went really wrong! Bot not set a Trumpf.");
}
}
}
/**
* Adds a player on a specified place in the game.
*
* @param player
* @param place
* @throws PlayerAlreadyInGameException
* @throws PlaceTakenException
* @throws PlaceDoesNotExistException
*/
public void addPlayer(IPlayer player, int place) throws PlayerAlreadyInGameException, PlaceTakenException, PlaceDoesNotExistException {
this.players.addPlayer(place, player);
player.setPlace(place);
}
/**
* @return the table of the game
*/
public Table getTable() {
return this.table;
}
/**
* Verifies if a specified player is in the game.
*
* @param player
* @return true if player is in the game, otherwise false
*/
public boolean hasPlayer(Player player) {
return this.players.contains(player);
}
/**
* @return the players in the game
*/
public List<IPlayer> getPlayers() {
return Collections.unmodifiableList(this.players);
}
/**
* @return the game admin
*/
public IPlayer getAdmin() {
return this.gameAdmin;
}
/**
* @return the rule object with the game logic
*/
public IRule getRule() {
return this.rule;
}
/**
* @return the current player
*/
public IPlayer getCurrentPlayer() {
return this.players.getCurrent();
}
/**
* Change the turn to the next player.
*
* @throws GameOverException
*/
private void nextPlayer() throws GameOverException {
if (this.table.getSize() < this.rule.getNumberOfTableCards()) {
this.players.next();
} else {
IPlayer turnWinner = this.getTurnWinner();
this.players.setCurrent(turnWinner);
this.clearTable();
if (!this.players.getCurrent().hasCards()) {
this.players.setCurrent(this.rule.getStarter());
this.rule.setStarter(this.players.next());
this.evaluatePoints(turnWinner);
this.initNewRound();
}
}
}
/**
* Verifies if the game has a winner.
*
* @return true if the game has a winner, otherwise false
* @throws GameOverException
*/
public boolean hasWinner() throws GameOverException {
if (this.gameFinished) {
if (this.started) {
this.winnerList = this.rule.getWinner(this.teams);
}
throw new GameOverException("GameOver");
} else if (this.rule.hasWinner(this.teams)) {
this.gameFinished = true;
}
return this.rule.hasWinner(this.teams);
}
/**
* Evaluates the points of each player and adds the points to the teams.
*/
private void evaluatePoints(IPlayer turnWinner) {
turnWinner.lastTrick(this.rule);
for (IPlayer player : this.players) {
player.evaluateWinnerStock(this.rule);
}
int[] points = this.rule.countPoints(this.teams);
for (int[] pastPoints : this.teamPoints) {
for (int i = 0; i < points.length; i++) {
points[i] = points[i] - pastPoints[i];
}
}
this.teamPoints.add(points);
}
/**
* @return the winner of a turn
*/
private IPlayer getTurnWinner() {
return this.rule.winnerCard(this.table).getHolder();
}
/**
* Clears the table an prepares it for a next round.
*/
private void clearTable() {
this.players.getCurrent().addToWinnerStock(this.table.getTableCards());
for (Card card : this.table.clearTable()) {
card.setHolder(this.players.getCurrent());
}
}
/**
* @return true if a trumpf is set, otherwise throws an exception
* @throws NoTrumpfSetException
*/
private boolean checkTrumpfIsSet() throws NoTrumpfSetException {
if (this.rule.getTrumpf() == null) {
throw new NoTrumpfSetException("No Trumpf is set!");
}
return true;
}
/**
* Sets the trumpf for the turn
*
* @param trumpf
* @param player
* @throws YouMustNotSetTrumpfException
*/
public void setTrumpf(CardColor trumpf, IPlayer player) throws YouMustNotSetTrumpfException {
if (this.rule.isTrumpfMoved()) {
if (this.getCurrentPlayer().equals(this.getTeamPlayer(player))) {
this.rule.setTrumpf(trumpf);
} else {
throw new YouMustNotSetTrumpfException("You must not set the trumpf!");
}
} else {
if (this.getCurrentPlayer().equals(player)) {
this.rule.setTrumpf(trumpf);
} else {
throw new YouMustNotSetTrumpfException("You must not set the trumpf!");
}
}
}
/**
* @return the trumpf color
*/
public CardColor getTrumpf() {
return this.rule.getTrumpf();
}
/**
* Moves the responsibility, to set the trumpf, to the other team member.
*
* @throws YouMustNotSetTrumpfException
*/
public void moveTrumpf() throws YouMustNotSetTrumpfException {
this.rule.moveTrumpf();
IPlayer teamPlayer = this.getTeamPlayer(this.players.getCurrent());
if (teamPlayer.isAI()) {
this.setTrumpf(teamPlayer.chooseTrumpf(), teamPlayer);
}
}
/**
* @return true if the trumpf is already moved, otherwise false
*/
public boolean isTrumpfMoved() {
return this.rule.isTrumpfMoved();
}
/**
* @return the unique game ID
*/
public String getGameID() {
return this.gameID;
}
/**
* Sets the unique game ID
*
* @param gameID
*/
public void setGameID(String gameID) {
this.gameID = gameID;
}
/**
* @return the number of taken places
*/
public int getPlayerCount() {
return this.players.getNumberOfTakenPlaces();
}
/**
* @return the number of allowed players in the game
*/
public int getMaxPlayerCount() {
return this.rule.getNumberOfPlayers();
}
/**
* @return the points of each team
*/
public List<int[]> getTeamPoints() {
return Collections.unmodifiableList(this.teamPoints);
}
/**
* @return true if the games is started, otherwise false
*/
public boolean isStarted() {
return this.started;
}
/**
* Marks the game as started
*
* @param started
*/
public void setStarted(boolean started) {
this.started = started;
}
/**
* @return the list of winners
*/
public List<IPlayer> getWinnerList() {
return winnerList;
}
/**
* Returns other team players.
* @param player
* @return player
*/
public IPlayer getTeamPlayer(IPlayer player) {
int position = this.players.indexOf(player);
return this.players.get((position + this.rule.getNumberOfTeams()) % this.rule.getNumberOfPlayers());
}
/**
* Force to finish the game.
*/
public void forceGameEnd() {
this.gameFinished = true;
this.table.clearTable();
for (IPlayer player : this.players) {
if (player != null) {
player.clearHand();
}
}
}
/**
* Verifies if the game is finished.
* @return true if the game is finished, otherwise false
*/
public boolean isFinished() {
return this.gameFinished;
}
}