Player.java
package soen6441riskgame.models;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Observable;
import com.google.gson.annotations.Expose;
import soen6441riskgame.enums.ChangedProperty;
import soen6441riskgame.enums.GamePhase;
import soen6441riskgame.models.strategies.Strategy;
import soen6441riskgame.singleton.GameBoard;
import soen6441riskgame.utils.ConsolePrinter;
import soen6441riskgame.utils.GameHelper;
/**
* Hold player data
*
* Each player is a node in a linked list
*/
public class Player extends Observable {
private static final int MAX_NUMBER_OF_CARD_TO_FORCE_EXCHANGE = 5;
private static final int LEAST_NUMBER_OF_ARMIES_INIT_IN_TURN = 3;
private static final int INIT_ARMY_DIVIDE_FRACTION = 3;
@Expose
private String name;
@Expose
private int unplacedArmies;
@Expose
private boolean isPlaying = false;
private Player nextPlayer;
@Expose
private String nextPlayerName;
private Player previousPlayer;
@Expose
private String previousPlayerName;
@Expose
private GamePhase currentPhase;
private ArrayList<Card> holdingCards = new ArrayList<Card>();
@Expose
private ArrayList<String> currentPhaseActions = new ArrayList<String>();
@Expose
private boolean isPlayerBeAwardCard = false;
private Strategy strategy;
/**
* constructor
*
* @param name player's name
*/
public Player(String name) {
this.name = name;
this.currentPhase = GamePhase.WAITING_TO_TURN;
}
/**
* copy constructor
*
* @param serializedPlayer serialized player
*/
@SuppressWarnings("unchecked")
public Player(Player serializedPlayer) {
this.name = serializedPlayer.name;
this.unplacedArmies = serializedPlayer.unplacedArmies;
this.isPlaying = serializedPlayer.isPlaying;
this.nextPlayerName = serializedPlayer.nextPlayerName;
this.previousPlayerName = serializedPlayer.previousPlayerName;
this.currentPhase = serializedPlayer.currentPhase;
this.currentPhaseActions = (ArrayList<String>) serializedPlayer.currentPhaseActions.clone();
this.isPlayerBeAwardCard = serializedPlayer.isPlayerBeAwardCard;
}
/**
* get player's strategy set by tournament mode
*
* @return player's strategy set by tournament mode
*/
public Strategy getStrategy() {
return strategy;
}
/**
* set player's strategy for tournament mode
*
* @param strategy player's strategy for tournament mode
*/
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
/**
* link next and previous players after construct;
*
* @param players list of current players
*/
public void linkNextAndPrevious(List<Player> players) {
for (Player player : players) {
if (nextPlayerName.equals(player.getName())) {
setNextPlayer(player);
continue;
}
if (previousPlayerName.equals(player.getName())) {
setPreviousPlayer(player);
continue;
}
}
}
/**
* re-construct player object
*/
public void reconstruct() {
holdingCards = new ArrayList<Card>();
this.addObserver(GameBoard.getInstance().getGameBoardPlayer().getPhaseView());
if (currentPhase == GamePhase.REINFORCEMENT) {
this.addObserver(GameBoard.getInstance().getExchangeCardView());
}
}
/**
* check if this player have conquered at least 1 country in the attack phase, therefore, be award a
* card from deck
*
* @return is player be award card
*/
private boolean isPlayerBeAwardCard() {
return isPlayerBeAwardCard;
}
/**
* mark this player have conquered at least 1 country in the attack phase, therefore, be award a
* card from deck
*
* @param isPlayerAwardCard set the mark
*/
public void setPlayerBeAwardCard(boolean isPlayerAwardCard) {
this.isPlayerBeAwardCard = isPlayerAwardCard;
}
/**
* get player's current phase
*
* @return game phase of the player
*/
public GamePhase getCurrentPhase() {
return currentPhase;
}
/**
* set player's current phase
*
* @param newPhase the phase to set
*/
public void setCurrentPhase(GamePhase newPhase) {
if (currentPhase != newPhase) {
boolean isChangePhaseAllowed = isChangePhaseAllowed(newPhase);
if (isChangePhaseAllowed) {
currentPhase = newPhase;
currentPhaseActions.clear();
setChanged();
notifyObservers(ChangedProperty.GAME_PHASE);
if (newPhase == GamePhase.REINFORCEMENT || newPhase == GamePhase.WAITING_TO_TURN) {
this.addObserver(GameBoard.getInstance().getExchangeCardView());
} else {
this.deleteObserver(GameBoard.getInstance().getExchangeCardView());
}
if (newPhase == GamePhase.FORTIFICATION) {
getACardFromDeck();
}
} else {
ConsolePrinter.printFormat("Player %s cannot change from phase %s to phase %s",
getName(),
currentPhase.toString(),
newPhase.toString());
}
}
}
/**
* check if changing phase is allowed
*
* @param newPhase the new phase
* @return if changing phase is allowed
*/
private boolean isChangePhaseAllowed(GamePhase newPhase) {
boolean isChangePhaseAllowed = true;
if ((newPhase.getGamePhaseAsInt() - currentPhase.getGamePhaseAsInt()) != 1) {
isChangePhaseAllowed = newPhase == GamePhase.WAITING_TO_TURN
&& currentPhase == GamePhase.FORTIFICATION;
}
if (newPhase == GamePhase.ATTACK) {
if (holdingCards.size() >= MAX_NUMBER_OF_CARD_TO_FORCE_EXCHANGE) {
ConsolePrinter.printFormat("You have more than %d cards. Must exchange before attacking.",
MAX_NUMBER_OF_CARD_TO_FORCE_EXCHANGE);
isChangePhaseAllowed = false;
}
// check if unplaced armies == 0 , then just skip the reinforcement phase
else if (this.getUnplacedArmies() == 0) {
isChangePhaseAllowed = true;
}
}
if (newPhase == GamePhase.END_OF_GAME && currentPhase == GamePhase.ATTACK) {
isChangePhaseAllowed = true;
}
if (newPhase == GamePhase.LOST) {
isChangePhaseAllowed = true;
}
return isChangePhaseAllowed;
}
/**
* add new card if player conquer at least 1 country during attack phase
*/
private void getACardFromDeck() {
if (isPlayerBeAwardCard()) {
Card newCard = GameBoard.getInstance().getRandomAvailableCard();
newCard.setHoldingPlayer(this);
holdingCards.add(newCard);
setPlayerBeAwardCard(false);
setChanged();
notifyObservers(ChangedProperty.CARD);
}
}
/**
* get player's list of cards
*
* @return player's list of cards
*/
public ArrayList<Card> getHoldingCards() {
return holdingCards;
}
/**
* get the player's card in specific position
*
* @param position start with 1
* @return null if position not exist
*/
public Card getHoldingCard(int position) {
if (position > holdingCards.size() || position <= 0) {
ConsolePrinter.printFormat("You only have %d card", holdingCards.size());
return null;
} else {
return holdingCards.get(position - 1);
}
}
/**
* exchange a list of card set
*
* @param cardSets list of card set to exchange
*/
public void exchangeCardSets(List<CardSet> cardSets) {
int tradeTime = 1;
int numberOfTradedArmies = 0;
setChanged();
notifyObservers(ChangedProperty.CARD);
for (CardSet cardSet : cardSets) {
if (cardSet != null) {
numberOfTradedArmies += cardSet.getTradeInArmies(tradeTime);
cardSet.setCardsExchanged();
tradeTime++;
}
}
int newUnplacedArmies = getUnplacedArmies() + numberOfTradedArmies;
setUnplacedArmies(newUnplacedArmies);
removeExchangedCards();
setChanged();
notifyObservers(ChangedProperty.CARD);
}
/**
* return all the exchanged cards that player is holding
*/
public void removeExchangedCards() {
for (Iterator<Card> cardList = holdingCards.listIterator(); cardList.hasNext();) {
Card card = cardList.next();
if (card.isExchanged()) {
card.setExchanged(false);
card.setHoldingPlayer(null);
cardList.remove();
}
}
}
/**
* get the list of action for current phase
*
* @return list of action for current phase
*/
public ArrayList<String> getCurrentPhaseActions() {
return currentPhaseActions;
}
/**
* add new action for current phase
*
* @param action the action string
*/
public void addCurrentPhaseAction(String action) {
currentPhaseActions.add(action);
setChanged();
notifyObservers(ChangedProperty.GAME_PHASE);
}
/**
* get previous player on the linked list
*
* @return previous player
*/
public Player getPreviousPlayer() {
return previousPlayer;
}
/**
* set previous player
*
* @param previousPlayer the player object
*/
public void setPreviousPlayer(Player previousPlayer) {
this.previousPlayer = previousPlayer;
this.previousPlayerName = previousPlayer.getName();
if (previousPlayer.getNextPlayer() != this) {
previousPlayer.setNextPlayer(this);
}
}
/**
* get next player on the linked list
*
* @return next player on the linked list
*/
public Player getNextPlayer() {
return nextPlayer;
}
/**
* set next player
*
* @param nextPlayer the player object
*/
public void setNextPlayer(Player nextPlayer) {
this.nextPlayer = nextPlayer;
this.nextPlayerName = nextPlayer.getName();
if (nextPlayer.getPreviousPlayer() != this) {
nextPlayer.setPreviousPlayer(this);
}
}
/**
* get player name
*
* @return player name
*/
public String getName() {
return name;
}
/**
* get total armies a player have
*
* @return total armies
*/
public int getTotalArmies() {
int totalArmies = 0;
ArrayList<Country> conqueredCountries = getConqueredCountries();
for (Country country : conqueredCountries) {
totalArmies += country.getArmyAmount();
}
totalArmies += getUnplacedArmies();
return totalArmies;
}
/**
* get a list of conquered continents of this player
*
* @return list of conquered continents
*/
public ArrayList<Continent> getConqueredContinents() {
ArrayList<Continent> conquered = new ArrayList<>();
for (Continent continent : GameBoard.getInstance().getGameBoardMap().getContinents()) {
if (continent != null) {
if (this.equals(continent.getConquerer())) {
conquered.add(continent);
}
}
}
return conquered;
}
/**
* get all the conquered country of this player
*
* @return empty list if no country
*/
public ArrayList<Country> getConqueredCountries() {
ArrayList<Country> conquered = new ArrayList<>();
for (Country country : GameBoard.getInstance().getGameBoardMap().getCountries()) {
if (country != null) {
if (this.equals(country.getConquerer())) {
conquered.add(country);
}
}
}
return conquered;
}
/**
* check if this player is still in the game
*
* @return is this player is still in the game
*/
public boolean isPlaying() {
return isPlaying;
}
/**
* set is this player is till in the game
*
* @param isPlaying is this player is till in the game
*/
public void setPlaying(boolean isPlaying) {
this.isPlaying = isPlaying;
}
/**
* get player unplaced armies
*
* @return player unplaced armies
*/
public int getUnplacedArmies() {
return unplacedArmies;
}
/**
* set player unplaced armies
*
* @param unplacedArmies the number of armies
*/
public void setUnplacedArmies(int unplacedArmies) {
this.unplacedArmies = unplacedArmies;
}
/**
* REINFORCEMENT PHASE get the number of armies player will get for reinforcement phase for all the
* country player have.
*
* @return the number of armies. Minimum number of armies are #{@value #INIT_ARMY_DIVIDE_FRACTION}
*/
private int getArmiesFromAllConqueredCountries() {
ArrayList<Country> conqueredCountries = getConqueredCountries();
return Math.round(conqueredCountries.size() / INIT_ARMY_DIVIDE_FRACTION);
}
/**
* REINFORCEMENT PHASE get the number of armies player will have for the conquered continent
*
* @return the number of armies. 0 if user don't own any continent.
*/
private int getArmiesFromConqueredContinents() {
int armiesFromConqueredContinents = 0;
for (Continent continent : GameBoard.getInstance().getGameBoardMap().getContinents()) {
if (this.equals(continent.getConquerer())) {
armiesFromConqueredContinents = armiesFromConqueredContinents + continent.getArmy();
}
}
return armiesFromConqueredContinents;
}
/**
* REINFORCEMENT PHASE calculate the number of armies a player will have for his reinforcement phase
*/
public void calculateReinforcementArmies() {
if (this.getCurrentPhase() != GamePhase.REINFORCEMENT) {
ConsolePrinter.printFormat("Cannot get new army for player %s on $%s phase",
this.getName(),
this.getCurrentPhase().toString());
return;
}
int armiesFromAllConqueredCountries = getArmiesFromAllConqueredCountries();
int armiesFromConqueredContinents = getArmiesFromConqueredContinents();
if (armiesFromAllConqueredCountries < LEAST_NUMBER_OF_ARMIES_INIT_IN_TURN) {
armiesFromAllConqueredCountries = LEAST_NUMBER_OF_ARMIES_INIT_IN_TURN;
}
int newUnplacedArmies = getUnplacedArmies() + armiesFromAllConqueredCountries + armiesFromConqueredContinents;
setUnplacedArmies(newUnplacedArmies);
}
/**
* do the reinforcement
*
* @param country country to reinforce
* @param numberOfArmies armies to reinforce
*/
public void reinforce(Country country, int numberOfArmies) {
if (!country.isCountryBelongToPlayer(this)) {
return;
}
if (this.getUnplacedArmies() != 0) {
country.receiveArmiesFromUnPlacedArmies(numberOfArmies);
this.addCurrentPhaseAction("Reinforce: " + country.getName() + " with " + numberOfArmies);
}
if (this.getUnplacedArmies() == 0) {
ConsolePrinter.printFormat("Player %s enter ATTACK phase", this.getName());
this.setCurrentPhase(GamePhase.ATTACK);
}
}
/**
* do the fortify
*
* @param fromCountry from country
* @param toCountry to country
* @param numberOfArmies armies to fortify
*/
public void fortify(Country fromCountry, Country toCountry, int numberOfArmies) {
fromCountry.moveArmies(toCountry, numberOfArmies);
this.addCurrentPhaseAction("Fortify: from "
+ fromCountry.getName()
+ " to "
+ toCountry.getName()
+ " with "
+ numberOfArmies
+ " armies");
}
/**
* do the attack
*
* @param attackingCountry attacker country
* @param defendingCountry defender country
* @param attackerNumDice number of dices for attacker
* @param defenderNumDice number of dices for defender
*/
public void attack(Country attackingCountry,
Country defendingCountry,
int attackerNumDice,
int defenderNumDice) {
// attack starts
int[] attackerDiceValues = new int[attackerNumDice];
int[] defenderDiceValues = new int[defenderNumDice];
printDiceValues(attackerNumDice, defenderNumDice, attackerDiceValues, defenderDiceValues);
Player currentPlayer = this;
// now we will check who loses an army
int attackerMaxDiceValue = GameHelper.getMax(attackerDiceValues, false);
int defenderMaxDiceValue = GameHelper.getMax(defenderDiceValues, false);
if (attackerMaxDiceValue > defenderMaxDiceValue) {
// defending army is lost
lostOneArmy(defendingCountry, currentPlayer);
} else {
// attacking army is lost
lostOneArmy(attackingCountry, currentPlayer);
}
if (defenderNumDice != 1 && attackerNumDice != 1) {
int attackerSecondMaxDiceValue = GameHelper.getMax(attackerDiceValues, true);
int defenderSecondMaxDiceValue = GameHelper.getMax(defenderDiceValues, true);
if (attackerSecondMaxDiceValue > defenderSecondMaxDiceValue) {
lostOneArmy(defendingCountry, currentPlayer);
} else {
lostOneArmy(attackingCountry, currentPlayer);
}
}
// attack ends
}
/**
* print dice value
*
* @param attackerNumDice attacker numdice
* @param defenderNumDice defender numdice
* @param attackerDiceValues attacker dice values
* @param defenderDiceValues defender dice values
*/
private void printDiceValues(int attackerNumDice,
int defenderNumDice,
int[] attackerDiceValues,
int[] defenderDiceValues) {
StringBuilder printDiceValues = new StringBuilder("Attacker: ");
for (int i = 0; i < attackerNumDice; i++) {
attackerDiceValues[i] = GameHelper.rollDice();
printDiceValues.append(attackerDiceValues[i]).append(" ");
}
printDiceValues.append("\nDefender: ");
for (int i = 0; i < defenderNumDice; i++) {
defenderDiceValues[i] = GameHelper.rollDice();
printDiceValues.append(defenderDiceValues[i]).append(" ");
}
ConsolePrinter.printFormat("%s", printDiceValues.toString());
}
/**
* lost 1 army
*
* @param lostArmyCountry country to lost
* @param lostArmyPlayer player to lost
*/
private void lostOneArmy(Country lostArmyCountry, Player lostArmyPlayer) {
String playerRole = lostArmyPlayer.getCurrentPhase() == GamePhase.ATTACK ? "attacker" : "defender";
lostArmyCountry.setArmyAmount(lostArmyCountry.getArmyAmount() - 1);
ConsolePrinter.printFormat("The %s %s has lost 1 army from %s. %d armies left.",
playerRole,
lostArmyCountry.getConquerer().getName(),
lostArmyCountry.getName(),
lostArmyCountry.getArmyAmount());
lostArmyPlayer.addCurrentPhaseAction("Attack: The "
+ playerRole
+ " "
+ lostArmyCountry.getConquerer().getName()
+ " has lost 1 army from "
+ lostArmyCountry.getName()
+ " | "
+ lostArmyCountry.getArmyAmount()
+ " armies left.");
}
/**
* do the attack move
*
* @param fromCountry from country
* @param toCountry to country
* @param numberOfArmies armies to move
*/
public void attackMove(Country fromCountry, Country toCountry, int numberOfArmies) {
fromCountry.moveArmies(toCountry, numberOfArmies);
}
/**
* build a valid card set from holding cards for tournament mode
* @return a valid card set
*/
public ArrayList<CardSet> buildValidCardSets() {
ArrayList<CardSet> cardSets = new ArrayList<>();
ArrayList<Card> cards = this.getHoldingCards();
if (cards.size() < 3) {
return cardSets;
}
HashMap<Card, Boolean> cardsInSet = new HashMap<>();
for (Card card : cards) {
cardsInSet.put(card, false);
}
ArrayList<Card> notPicked = (ArrayList<Card>) GameHelper.getAllKeysForValue(cardsInSet, false);
while (notPicked.size() >= 5) {
ArrayList<Integer> cardIndexes = new ArrayList<>();
for (Card card : notPicked) {
cardIndexes.add(cards.indexOf(card));
}
// randomly pick 3 card with index
ArrayList<Integer> picked = GameHelper.getRandomElements(cardIndexes, 3);
CardSet cardSet = new CardSet(cards.get(picked.get(0)),
cards.get(picked.get(1)),
cards.get(picked.get(2)));
if (cardSet.isSetValid()) {
cardSets.add(cardSet);
for (int cardIndex : picked) {
cardsInSet.put(cards.get(cardIndex), true);
}
}
notPicked = (ArrayList<Card>) GameHelper.getAllKeysForValue(cardsInSet, false);
}
return cardSets;
}
/**
* it sets the game phase to end of game when a player has won the game
*/
public void setEndOfGamePhase() {
ConsolePrinter.printFormat("Congratulations, The player %s has won the game.", this.getName());
this.setCurrentPhase(GamePhase.END_OF_GAME);
}
/**
* test if the player has conquered all countries and won the game.
*
* @return true if player win
*/
public boolean isGameEnded() {
// check whether this player has won the game
Player currentPlayer = this;
boolean gameEnded = true;
ArrayList<Country> countries = GameBoard.getInstance()
.getGameBoardMap()
.getCountries();
for (Country country : countries) {
if (country.getConquerer() != currentPlayer) {
gameEnded = false;
break;
}
}
return gameEnded;
}
/**
* it checks whether further attack is possible.(i.e. if the number of army in a country is greater
* than 1 and it has enemy countries as neighbor) If not, it returns false.
*
* @return boolean if further attack is possible or not
*
*/
public boolean furtherAttackPossible() {
// attack not possible if not more than 1 army + if no neighbours belonging to other countries.
ArrayList<Country> countries = this.getConqueredCountries();
for (Country country : countries) {
if (country.getArmyAmount() > 1) {
ArrayList<Country> neighbours = country.getNeighbors();
for (Country neighbouringCountry : neighbours) {
if (neighbouringCountry.getConquerer() != this) {
return true;
}
}
}
}
return false;
}
}