package com.mehtank.androminion.server;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.mehtank.androminion.R;
import com.vdom.api.ActionCard;
import com.vdom.api.Card;
import com.vdom.api.CurseCard;
import com.vdom.api.DurationCard;
import com.vdom.api.GameEvent;
import com.vdom.api.GameEvent.Type;
import com.vdom.api.GameEventListener;
import com.vdom.api.TreasureCard;
import com.vdom.api.VictoryCard;
import com.vdom.comms.Comms;
import com.vdom.comms.Event;
import com.vdom.comms.Event.EType;
import com.vdom.comms.Event.EventObject;
import com.vdom.comms.EventHandler;
import com.vdom.comms.GameStatus;
import com.vdom.comms.MyCard;
import com.vdom.comms.NewGame;
import com.vdom.comms.SelectCardOptions;
import com.vdom.core.CardList;
import com.vdom.core.Cards;
import com.vdom.core.ExitException;
import com.vdom.core.Game;
import com.vdom.core.MoveContext;
import com.vdom.core.Player;
import com.vdom.core.Util;
/**
* Class that you can use to play remotely.
* This seems to be the human player
*/
public class RemotePlayer extends IndirectPlayer implements GameEventListener, EventHandler {
@SuppressWarnings("unused")
private static final String TAG = "RemotePlayer";
static int nextPort = 2255;
static final int NUM_RETRIES = 3; // times to try anything before giving up.
static int maxPause = 300000; // Maximum time to wait for new player to connect = 5 minutes in ms;
private static VDomServer vdomServer = null; // points to the VDomServer object
// private static final String DISTINCT_CARDS = "Distinct Cards";
Comms comm = null;
// communication thread handled internally now
// Thread commThread;
private int myPort = 0;
protected String name;
private HashMap<String, Integer> cardNamesInPlay = new HashMap<String, Integer>();
private ArrayList<Card> cardsInPlay = new ArrayList<Card>();
private ArrayList<Player> allPlayers = new ArrayList<Player>();
private MyCard[] myCardsInPlay;
private ArrayList<Card> playedCards = new ArrayList<Card>();
private ArrayList<Boolean> playedCardsNew = new ArrayList<Boolean>();
private boolean hasJoined = false;
private Object hasJoinedMonitor;
long whenStarted = 0;
private Thread gameThread = null; // vdom-engine-thread
private int dieTries = 0; // How often we tried to kill the vdom-thread
public void waitForJoin() {
synchronized(hasJoinedMonitor) {
long startTime = System.currentTimeMillis();
while (!hasJoined ) {
debug("Waiting for " + maxPause + " ms...");
try {
hasJoinedMonitor.wait(maxPause);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
debug("Done waiting. hasJoined: " + (hasJoined?"True":"False"));
if ((System.currentTimeMillis() - startTime) > maxPause) {
debug("Timed out waiting for player to join.");
break;
}
}
}
}
public void playerJoined(){
synchronized(hasJoinedMonitor) {
hasJoined = true;
hasJoinedMonitor.notify();
}
}
public static void setVdomserver(VDomServer vdomserver) {
RemotePlayer.vdomServer = vdomserver;
maxPause = VDomServer.maxPause;
}
public static VDomServer getVdomserver() {
return vdomServer;
}
public int getPort() {
return myPort;
}
public boolean hasJoined() {
return hasJoined;
}
public static String getFullCardDescription(Card c) {
String ret = Strings.getCardDescription(c);
if (c.equals(Cards.curse)) {
ret = Strings.format(R.string.vp_single, "" + ((CurseCard) c).getVictoryPoints()) + "\n" + ret;
}
if (c instanceof VictoryCard) {
if (((VictoryCard) c).getVictoryPoints() > 1)
ret = Strings.format(R.string.vp_multiple, "" + ((VictoryCard) c).getVictoryPoints()) + "\n" + ret;
else if (((VictoryCard) c).getVictoryPoints() > 0)
ret = Strings.format(R.string.vp_single, "" + ((VictoryCard) c).getVictoryPoints()) + "\n" + ret;
else if (((VictoryCard) c).getVictoryPoints() < -1)
ret = Strings.format(R.string.vp_multiple, "" + ((VictoryCard) c).getVictoryPoints()) + "\n" + ret;
else if (((VictoryCard) c).getVictoryPoints() < 0)
ret = Strings.format(R.string.vp_single, "" + ((VictoryCard) c).getVictoryPoints()) + "\n" + ret;
}
if (c instanceof TreasureCard) {
ret = Strings.format(R.string.coin_worth, "" + ((TreasureCard) c).getValue()) + "\n" + ret;
}
if (c instanceof ActionCard) {
ActionCard ac = (ActionCard) c;
if (c instanceof DurationCard) {
DurationCard dc = (DurationCard) c;
if (dc.getAddGoldNextTurn() > 0) ret = Strings.format(R.string.coin_next_turn, "" + dc.getAddGoldNextTurn()) + "\n" + ret;
if (dc.getAddBuysNextTurn() > 1) ret = Strings.format(R.string.buys_next_turn_multiple, "" + dc.getAddBuysNextTurn()) + "\n" + ret;
else if (dc.getAddBuysNextTurn() > 0) ret = Strings.format(R.string.buy_next_turn_single, "" + dc.getAddBuysNextTurn()) + "\n" + ret;
if (dc.getAddActionsNextTurn() > 1) ret = Strings.format(R.string.actions_next_turn_multiple, "" + dc.getAddActionsNextTurn()) + "\n" + ret;
else if (dc.getAddActionsNextTurn() > 0) ret = Strings.format(R.string.action_next_turn_single, "" + dc.getAddActionsNextTurn()) + "\n" + ret;
if (dc.getAddCardsNextTurn() > 1) ret = Strings.format(R.string.cards_next_turn_multiple, "" + dc.getAddCardsNextTurn()) + "\n" + ret;
else if (dc.getAddCardsNextTurn() > 0) ret = Strings.format(R.string.card_next_turn_single, "" + dc.getAddCardsNextTurn()) + "\n" + ret;
}
if (ac.getAddGold() > 0) ret = Strings.format(R.string.card_coin, "" + ac.getAddGold()) + "\n" + ret;
if (ac.getAddBuys() > 1) ret = Strings.format(R.string.card_buys_multiple, "" + ac.getAddBuys()) + "\n" + ret;
else if (ac.getAddBuys() > 0) ret = Strings.format(R.string.card_buy_single, "" + ac.getAddBuys()) + "\n" + ret;
if (ac.getAddActions() > 1) ret = Strings.format(R.string.card_actions_multiple, "" + ac.getAddActions()) + "\n" + ret;
else if (ac.getAddActions() > 0) ret = Strings.format(R.string.card_action_single, "" + ac.getAddActions()) + "\n" + ret;
if (ac.getAddCards() > 1) ret = Strings.format(R.string.card_cards_multiple, "" + ac.getAddCards()) + "\n" + ret;
else if (ac.getAddCards() > 0) ret = Strings.format(R.string.card_card_single, "" + ac.getAddCards()) + "\n" + ret;
if (ac.getAddVictoryTokens() > 1) ret = Strings.format(R.string.card_victory_tokens_multiple, "" + ac.getAddVictoryTokens()) + "\n" + ret;
else if (ac.getAddVictoryTokens() > 0) ret = Strings.format(R.string.card_victory_token_single, "" + ac.getAddVictoryTokens()) + "\n" + ret;
}
return ret;
}
public static MyCard makeMyCard(Card c, int index, boolean isBane){
// MyCard card = new MyCard(index, c.getName());
MyCard card = new MyCard(index, Strings.getCardName(c), c.getSafeName(), c.getName());
card.desc = getFullCardDescription(c);
card.expansion = Strings.getCardExpansion(c);
card.originalExpansion = c.getExpansion();
card.cost = c.getCost(null);
card.costPotion = c.costPotion();
card.isBane = isBane;
card.isShelter = c.isShelter();
card.isLooter = c.isLooter();
card.isOverpay = c.isOverpay();
if (c.equals(Cards.virtualRuins))
card.isRuins = true;
else
card.isRuins = c.isRuins();
card.pile = MyCard.SUPPLYPILE;
if ((c.equals(Cards.bagOfGold)) ||
(c.equals(Cards.diadem)) ||
(c.equals(Cards.followers)) ||
(c.equals(Cards.princess)) ||
(c.equals(Cards.trustySteed))) {
card.pile = MyCard.PRIZEPILE;
card.isPrize = true;
}
if (c.equals(Cards.spoils) ||
c.equals(Cards.mercenary) ||
c.equals(Cards.madman))
{
card.pile = MyCard.NON_SUPPLY_PILE;
}
if (c.equals(Cards.necropolis) ||
c.equals(Cards.overgrownEstate) ||
c.equals(Cards.hovel))
{
card.pile = MyCard.SHELTER_PILES;
}
if ((c.equals(Cards.copper)) ||
(c.equals(Cards.silver)) ||
(c.equals(Cards.potion)) ||
(c.equals(Cards.gold)) ||
(c.equals(Cards.platinum))) card.pile = MyCard.MONEYPILE;
if ((c.equals(Cards.estate)) ||
(c.equals(Cards.duchy)) ||
(c.equals(Cards.province)) ||
(c.equals(Cards.colony)) ||
(c.equals(Cards.curse))) card.pile = MyCard.VPPILE;
if (Cards.ruinsCards.contains(c))
card.pile = MyCard.RUINS_PILES;
if (Cards.knightsCards.contains(c))
card.pile = MyCard.KNIGHTS_PILES;
if (c.equals(Cards.potion)) card.isPotion = true;
if (c.equals(Cards.curse)) {
card.isCurse = true;
card.vp = ((CurseCard) c).getVictoryPoints();
}
if (c instanceof VictoryCard) {
card.isVictory = true;
card.vp = ((VictoryCard) c).getVictoryPoints();
}
if (c instanceof TreasureCard) {
card.isTreasure = true;
card.gold = ((TreasureCard) c).getValue();
}
if (c instanceof ActionCard) {
ActionCard ac = (ActionCard) c;
card.isAction = true;
if (c instanceof DurationCard) {
card.isDuration = true;
} else
if (ac.isAttack() || c.equals(Cards.virtualKnight))
card.isAttack = true;
}
if (Cards.isReaction(c))
card.isReaction = true;
return card;
}
public Card intToCard(int i) {
return cardsInPlay.get(i);
}
public Card[] intArrToCardArr(int[] cards) {
Card[] cs = new Card[cards.length];
for (int i = 0; i < cards.length; i++) {
cs[i] = intToCard(cards[i]);
}
return cs;
}
public Card nameToCard(String o) {
return intToCard(cardNamesInPlay.get(o));
}
@Override
public int cardToInt(Card card) {
// TODO: NullPointerException for tournament prizes
if (cardNamesInPlay.containsKey(card.getName()))
return cardNamesInPlay.get(card.getName());
else
return -1;
}
public int[] cardArrToIntArr(Card[] cards) {
int[] is = new int[cards.length];
for (int i = 0; i < cards.length; i++) {
is[i] = cardToInt(cards[i]);
}
return is;
}
public int[] arrayListToIntArr(ArrayList<Card> cards)
{
int[] is = new int[cards.size()];
for (int i = 0; i < cards.size(); ++i)
{
is[i] = cardToInt((Card)cards.get(i));
}
return is;
}
public void setupCardsInPlay(MoveContext context) {
ArrayList<MyCard> myCardsInPlayList = new ArrayList<MyCard>();
int index = 0;
// ensure Copper is card #0
Card cop = Cards.copper;
MyCard mc = makeMyCard(cop, index, false);
myCardsInPlayList.add(mc);
cardNamesInPlay.put(cop.getName(), index);
cardsInPlay.add(index, cop);
index++;
for (Card c : context.getCardsInGame()) {
if (c.getSafeName().equals(Cards.copper.getSafeName()))
continue;
if (context.game.baneCard == null) {
mc = makeMyCard(c, index, false);
} else {
mc = makeMyCard(c, index, c.getSafeName().equals(context.game.baneCard.getSafeName()));
}
myCardsInPlayList.add(mc);
cardNamesInPlay.put(c.getName(), index);
cardsInPlay.add(index, c);
index++;
}
myCardsInPlay = myCardsInPlayList.toArray(new MyCard[0]);
}
// private Map<Card, Integer> getVictoryPointTotals(
// final Player player,
// final Map<Object, Integer> counts) {
//
// Map<Card, Integer> totals = new HashMap<Card, Integer>();
//
// for(Map.Entry<Object, Integer> entry : counts.entrySet()) {
// if(entry.getKey() instanceof VictoryCard) {
// VictoryCard victoryCard = (VictoryCard) entry.getKey();
// totals.put(victoryCard, victoryCard.getVictoryPoints() * entry.getValue());
// } else if(entry.getKey() instanceof CurseCard) {
// CurseCard curseCard = (CurseCard) entry.getKey();
// totals.put(curseCard, curseCard.getVictoryPoints() * entry.getValue());
// }
// }
//
// if(counts.containsKey(Cards.gardens))
// totals.put(Cards.gardens, counts.get(Cards.gardens) * (player.getAllCards().size() / 10));
// if(counts.containsKey(Cards.duke))
// totals.put(Cards.duke, counts.get(Cards.duke) * counts.get(Cards.duchy));
// if(counts.containsKey(Cards.fairgrounds))
// totals.put(Cards.fairgrounds, counts.get(Cards.fairgrounds) * ((counts.get(DISTINCT_CARDS) / 5) * 2));
// if(counts.containsKey(Cards.vineyard))
// totals.put(Cards.vineyard, counts.get(Cards.vineyard) * (player.getActionCardCount() / 3));
// if(counts.containsKey(Cards.silkRoad))
// totals.put(Cards.silkRoad, counts.get(Cards.silkRoad) * (player.getVictoryCardCount() / 4));
//
// return totals;
// }
private String getVPOutput(Player player) {
final Map<Object, Integer> counts = player.getVictoryCardCounts();
final Map<Card, Integer> totals = player.getVictoryPointTotals(counts);
final StringBuilder sb
= new StringBuilder()
.append(player.getPlayerName())
.append(": ")
.append(this.getVPs(totals))
.append(" ")
.append(Strings.getString(R.string.game_over_vps))
.append('\n');
sb.append(this.getCardText(counts, totals, Cards.estate));
sb.append(this.getCardText(counts, totals, Cards.duchy));
sb.append(this.getCardText(counts, totals, Cards.province));
if(counts.containsKey(Cards.colony)) {
sb.append(this.getCardText(counts, totals, Cards.colony));
}
// display victory cards from sets
for(Card card : totals.keySet()) {
if(!Cards.nonKingdomCards.contains(card)) {
sb.append(this.getCardText(counts, totals, card));
}
}
sb.append(this.getCardText(counts, totals, Cards.curse));
sb
.append("\tVictory Tokens: ")
.append(totals.get(Cards.victoryTokens))
.append('\n');
return sb.toString();
}
private String getCardText(final Map<Object, Integer> counts, final Map<Card, Integer> totals, final Card card) {
final StringBuilder sb = new StringBuilder()
.append('\t')
.append(card.getName())
.append(" x")
.append(counts.get(card))
.append(": ")
.append(totals.get(card))
.append(" ")
.append(Strings.getString(R.string.game_over_vps))
.append('\n');
return sb.toString();
}
public Event fullStatusPacket(MoveContext context, Player player, boolean isFinal) {
if (player == null)
player = context.getPlayer();
int[] supplySizes = new int[cardsInPlay.size()];
int[] embargos = new int[cardsInPlay.size()];
int[] costs = new int[cardsInPlay.size()];
for (int i = 0; i < cardsInPlay.size(); i++) {
if (!isFinal)
supplySizes[i] = context.getCardsLeftInPile(intToCard(i));
else
supplySizes[i] = player.getMyCardCount(cardsInPlay.get(i));
embargos[i] = context.getEmbargos(intToCard(i));
costs[i] = intToCard(i).getCost(context);
}
// show opponent hand if possessed
CardList shownHand = (player.isPossessed()) ? player.getHand() : getHand();
// ArrayList<Card> playedCards = context.getPlayedCards();
if (!allPlayers.contains(player))
allPlayers.add(player);
int numPlayers = allPlayers.size();
int curPlayerIndex = allPlayers.indexOf(player);
int numCards[] = new int[numPlayers];
int turnCounts[] = new int[numPlayers];
int deckSizes[] = new int[numPlayers];
int discardSizes[] = new int[numPlayers];
int handSizes[] = new int[numPlayers];
int pirates[] = new int[numPlayers];
int victoryTokens[] = new int[numPlayers];
int guildsCoinTokens[] = new int[numPlayers];
String realNames[] = new String[numPlayers];
for (int i=0; i<numPlayers; i++) {
Player p = allPlayers.get(i);
if (!isFinal)
handSizes[i] = p.getHand().size();
else
handSizes[i] = p.getVPs();
turnCounts[i] = p.getTurnCount();
deckSizes[i] = p.getDeckSize();
discardSizes[i] = p.getDiscardSize();
numCards[i] = p.getAllCards().size();
pirates[i] = p.getPirateShipTreasure();
victoryTokens[i] = p.getVictoryTokens();
guildsCoinTokens[i] = p.getGuildsCoinTokenCount();
realNames[i] = p.getPlayerName(false);
}
GameStatus gs = new GameStatus();
int[] playedArray = new int[playedCards.size()];
for (int i = 0; i < playedCards.size(); i++) {
Card c = playedCards.get(i);
boolean newcard = playedCardsNew.get(i).booleanValue();
playedArray[i] = (cardToInt(c) * (newcard ? 1 : -1));
}
gs.setTurnStatus(new int[] {context.getActionsLeft(),
context.getBuysLeft(),
context.getCoinForStatus(),
context.countThroneRoomsInEffect()
})
.setFinal(isFinal)
.setPossessed(player.isPossessed())
.setTurnCounts(turnCounts)
.setSupplySizes(supplySizes)
.setEmbargos(embargos)
.setCosts(costs)
.setHand(cardArrToIntArr(Game.sortCards ? shownHand.sort(new Util.CardHandComparator()) : shownHand.toArray()))
.setPlayedCards(playedArray)
.setCurPlayer(curPlayerIndex)
.setCurName(player.getPlayerName(!isFinal && game.maskPlayerNames))
.setRealNames(realNames)
.setHandSizes(handSizes)
.setDeckSizes(deckSizes)
.setNumCards(numCards)
.setPirates(pirates)
.setVictoryTokens(victoryTokens)
.setGuildsCoinTokens(guildsCoinTokens)
.setCardCostModifier(context.cardCostModifier)
.setPotions(context.getPotionsForStatus(player))
.setIsland(cardArrToIntArr(player.getIsland().toArray()))
.setVillage(cardArrToIntArr(player.getNativeVillage().toArray()))
.setTrash(arrayListToIntArr(player.game.GetTrashPile()));
if (game.getTopRuinsCard() != null)
gs.setRuinsTopCard(cardToInt(Cards.virtualRuins), Strings.getCardName(game.getTopRuinsCard()), getFullCardDescription(game.getTopRuinsCard()));
else
gs.setRuinsTopCard(cardToInt(Cards.virtualRuins), Cards.virtualRuins.getName(), Cards.virtualRuins.getDescription());
if (game.getTopKnightCard() != null)
gs.setKnightTopCard(cardToInt(Cards.virtualKnight), Strings.getCardName(game.getTopKnightCard()), getFullCardDescription(game.getTopKnightCard()), game.getTopKnightCard().getCost(context));
else
gs.setKnightTopCard(cardToInt(Cards.virtualKnight), Cards.virtualKnight.getName(), Cards.virtualKnight.getDescription(), Cards.virtualKnight.getCost(context));
Event p = new Event(EType.STATUS)
.setObject(new EventObject(gs));
return p;
}
@Override
public void newGame(MoveContext context) {
hasJoinedMonitor = new Object(); // every game needs a different monitor, otherwise we wake up threads that are supposed to be dead.
context.addGameListener(this);
setupCardsInPlay(context);
gameThread = Thread.currentThread();
allPlayers.clear();
myPort = connect();
if (vdomServer != null)
vdomServer.registerRemotePlayer(this);
if (myPort == 0)
quit("Could not create server.");
}
public Event sendWithAck(Event tosend, EType resp) throws IOException, NullPointerException {
Event p;
for (int i = 0; i < NUM_RETRIES; i++) {
comm.put_ts(tosend);
p = comm.get_ts();
if (p == null)
throw new IOException();
else if (p.t == resp)
return p;
}
throw new IOException();
}
@Override
public void sendErrorHandler(Exception e) {
e.printStackTrace();
comm.injectNullReceived(); // This causes sendWithAck to receive a null and therefore throw an error, which we want.
}
private void achievement(MoveContext context, String achievement) {
Event status = fullStatusPacket(curContext == null ? context : curContext, curPlayer, false).setString(achievement);
try {
sendWithAck(status.setType(EType.ACHIEVEMENT).setString(achievement), EType.Success);
} catch (IOException e) {
e.printStackTrace();
}
}
private Event query(MoveContext context, Event tosend, EType resp) {
Event reply;
for (int connections = 0; connections < NUM_RETRIES; connections++) {
try {
//sendWithAck(fullStatusPacket(context, null, false), EType.Success);
comm.put_ts(fullStatusPacket(context, null, false));
reply = sendWithAck(tosend, resp);
} catch (IOException e) {
reply = null;
}
if (reply != null)
return reply;
reconnect("Could not complete query.");
waitForJoin();
if (!hasJoined)
quit(Strings.getString(R.string.response_timed_out));
}
quit("Could not complete query.");
return null;
}
Player curPlayer = null;
MoveContext curContext = null;
boolean gameOver = false;
@Override
public void gameEvent(GameEvent event) {
super.gameEvent(event);
MoveContext context = event.getContext();
boolean sendEvent = true;
String strEvent = "";
boolean playerNameIncluded = false;
if (event.getPlayer() != null && event.getPlayer().getPlayerName() != null) {
strEvent += event.getPlayer().getPlayerName() + ": ";
playerNameIncluded = true;
}
if(event.getType() == GameEvent.Type.Status) {
String coin = "" + context.getCoinAvailableForBuy();
if(context.potions > 0)
coin += "p";
coin = "(" + coin + ")"; // <" + String.valueOf(event.player.discard.size()) + ">";
strEvent += Strings.format(R.string.action_buys_coin, context.getActionsLeft(), context.getBuysLeft(), coin);
}
else {
switch(event.getType()) {
case GameStarting:
strEvent += Strings.getString(R.string.GameStarting);
break;
case GameOver:
// Check for achievements
int provinces = 0;
for(Card c : getAllCards()) {
if(c.equals(Cards.province)) {
provinces++;
}
}
if(provinces == 8 && Game.players.length == 2) {
achievement(context, "2players8provinces");
}
if(provinces >= 10 && Game.players.length >= 3) {
achievement(context, "3or4players10provinces");
}
int vp = this.getVPs();
if(vp >= 100) {
achievement(context, "score100");
}
boolean beatBy50 = true;
boolean skunk = false;
boolean beatBy1 = false;
boolean mostVp = true;
for(Player opp : context.game.getPlayersInTurnOrder()) {
if(opp != this) {
int oppVP = opp.getVPs();
if(oppVP > vp) {
mostVp = false;
}
if(oppVP <= 0) {
skunk = true;
}
if(vp == oppVP + 1) {
beatBy1 = true;
}
if(vp < oppVP + 50) {
beatBy50 = false;
}
}
}
if(mostVp && beatBy50) {
achievement(context, "score50more");
}
if(mostVp && skunk) {
achievement(context, "skunk");
}
if(mostVp && beatBy1) {
achievement(context, "score1more");
}
if(mostVp && !achievementSingleCardFailed) {
achievement(context, "singlecard");
}
strEvent += Strings.getString(R.string.GameOver);
break;
case Embargo:
strEvent += Strings.getString(R.string.Embargo);
break;
case Status:
strEvent += Strings.getString(R.string.Status);
break;
case CantBuy:
String cards = "";
boolean first = true;
for(Card card : context.getCantBuy()) {
if(first) {
first = false;
}
else {
cards += ", ";
}
cards += Strings.getCardName(card);
}
strEvent += Strings.format(R.string.CantBuy, cards);
break;
case VictoryPoints:
strEvent += Strings.getString(R.string.VictoryPoints);
break;
case NewHand:
strEvent += Strings.getString(R.string.NewHand);
break;
case TurnBegin:
strEvent += Strings.getString(R.string.TurnBegin);
break;
case TurnEnd:
if(context != null && context.getPlayer() == this && context.vpsGainedThisTurn > 30) {
achievement(context, "gainmorethan30inaturn");
}
strEvent += Strings.getString(R.string.TurnEnd);
break;
case PlayingAction:
strEvent += Strings.getString(R.string.PlayingAction);
break;
case PlayedAction:
strEvent += Strings.getString(R.string.PlayedAction);
break;
case PlayingDurationAction:
strEvent += Strings.getString(R.string.PlayingDurationAction);
break;
case PlayingCoin:
strEvent += Strings.getString(R.string.PlayingCoin);
break;
case BuyingCard:
strEvent += Strings.getString(R.string.BuyingCard);
break;
case OverpayForCard:
if (context != null && context.overpayAmount >= 10)
{
achievement(context, "overpayby10ormore");
}
break;
case GuildsTokenObtained:
if (context != null && getGuildsCoinTokenCount() >= 50)
{
achievement(context, "stockpile50tokens");
}
break;
case NoBuy:
strEvent += Strings.getString(R.string.NoBuy);
break;
case DeckReplenished:
strEvent += Strings.getString(R.string.DeckReplenished);
break;
case PlayerAttacking:
strEvent += Strings.getString(R.string.PlayerAttacking);
break;
case PlayerDefended:
strEvent += Strings.getString(R.string.PlayerDefended);
break;
case CardOnTopOfDeck:
strEvent += Strings.getString(R.string.CardOnTopOfDeck);
break;
case CardObtained:
strEvent += Strings.getString(R.string.CardObtained);
break;
case CardTrashed:
if(context != null && context.getPlayer() == this && context.cardsTrashedThisTurn > 5) {
achievement(context, "trash5inaturn");
}
strEvent += Strings.getString(R.string.CardTrashed);
break;
case CardRevealed:
strEvent += Strings.getString(R.string.CardRevealed);
break;
case CardRevealedFromHand:
strEvent += Strings.getString(R.string.CardRevealedFromHand);
break;
case CardDiscarded:
strEvent += Strings.getString(R.string.CardDiscarded);
break;
case CardAddedToHand:
strEvent += Strings.getString(R.string.CardAddedToHand);
break;
case CardRemovedFromHand:
strEvent += Strings.getString(R.string.CardRemovedFromHand);
break;
default:
strEvent += event.getType().toString();
break;
}
}
if (event.getCard() != null && event.getType() != Type.CardAddedToHand && event.getType() != Type.PlayerAttacking)
strEvent += " " + Strings.getCardName(event.getCard()) + " ";
if (event.getType() == Type.TurnBegin && event.getPlayer().isPossessed())
strEvent += " possessed by " + event.getPlayer().controlPlayer.getPlayerName() + "!";
if (event.getAttackedPlayer() != null)
strEvent += " (" + event.getAttackedPlayer().getPlayerName() + ") ";
if (context != null && context.getMessage() != null) {
strEvent += "\n" + context.getMessage();
}
debug(" GAME EVENT - " + strEvent);
boolean newTurn = false;
boolean isFinal = false;
switch (event.getType()) {
case VictoryPoints:
sendEvent = false;
break;
case GameStarting:
if (event.getPlayer() == this) {
waitForJoin();
if (!hasJoined)
quit(Strings.getString(R.string.join_timed_out));
}
whenStarted = System.currentTimeMillis();
playedCards.clear();
gameOver = false;
// Only send the event if its the first game starting, which doesn't include the player
// name, so that the "Chance for plat/colony" shows up only once and so that only one
// GameStarting event gets shown in the status area.
if(playerNameIncluded) {
sendEvent = false;
}
break;
case TurnBegin:
curPlayer = event.getPlayer();
curContext = context;
newTurn = true;
playedCards.clear();
playedCardsNew.clear();
break;
case TurnEnd:
playedCards.clear();
playedCardsNew.clear();
break;
case CantBuy:
break;
case PlayingAction:
case PlayingDurationAction:
playedCards.add(event.getCard());
playedCardsNew.add(event.newCard);
break;
case PlayingCoin:
playedCards.add(event.getCard());
playedCardsNew.add(true);
break;
case CardObtained:
if (event.responsible.equals(Cards.hornOfPlenty) && event.card instanceof VictoryCard) {
int index = playedCards.indexOf(event.responsible);
playedCards.remove(index);
playedCardsNew.remove(index);
}
break;
case GameOver:
curPlayer = event.getPlayer();
curContext = context;
isFinal = true;
strEvent = getVPOutput(curPlayer);
if (!gameOver) {
String time = Strings.getString(R.string.game_over_status);
time += " ";
long duration = System.currentTimeMillis() - whenStarted;
if (duration > 1000 * 60 * 60)
time += (duration / (1000 * 60 * 60)) + "h ";
duration = duration % (1000 * 60 * 60);
if (duration > 1000 * 60)
time += (duration / (1000 * 60)) + "m ";
duration = duration % (1000 * 60);
time += (duration / (1000)) + "s.\n";
if(!event.getContext().cardsSpecifiedOnStartup()) {
time += Strings.getGameTypeName(event.getContext().getGameType());
}
time += "\n\n";
strEvent = time + strEvent;
gameOver = true;
newTurn = true;
}
break;
case BuyingCard:
break;
case CardAddedToHand:
break;
case CardDiscarded:
break;
case CardOnTopOfDeck:
break;
case CardRemovedFromHand:
break;
case CardRevealed:
break;
case CardTrashed:
break;
case DeckReplenished:
break;
case Embargo:
break;
case NewHand:
break;
case NoBuy:
break;
case PlayedAction:
break;
case PlayerAttacking:
break;
case PlayerDefended:
break;
case Status:
break;
default:
break;
}
Event status = fullStatusPacket(curContext == null ? context : curContext, curPlayer, isFinal)
.setString(strEvent)
.setBoolean(newTurn);
String playerInt = "" + allPlayers.indexOf(event.getPlayer());
if (event.getPlayer() != null) {
// try {
switch (event.getType()) {
case BuyingCard:
case CardObtained:
comm.put_ts(status.setType(EType.CARDOBTAINED).setString(playerInt).setInteger(cardToInt(event.getCard())));
break;
case CardTrashed:
comm.put_ts(status.setType(EType.CARDTRASHED).setString(playerInt).setInteger(cardToInt(event.getCard())));
break;
case CardRevealed:
comm.put_ts(status.setType(EType.CARDREVEALED).setString(playerInt).setInteger(cardToInt(event.getCard())));
break;
case PlayerDefended:
comm.put_ts(status);
comm.put_ts(status.setType(EType.CARDREVEALED).setString(playerInt).setInteger(cardToInt(event.getCard())));
break;
default:
if(sendEvent)
comm.put_ts(status);
}
comm.put_ts(new Event(EType.SLEEP).setInteger(100));
// } catch (IOException e) {
// debug("Sending general game event message failed, ignoring.");
// e.printStackTrace();
// }
}
}
@Override
public String getPlayerName() {
return name;
}
@Override
public String getPlayerName(boolean maskName) {
return getPlayerName();
}
@Override
protected Card[] pickCards(MoveContext context, String header, SelectCardOptions sco, int count, boolean exact) {
if (sco.allowedCards.size() == 0)
return null;
Event p = new Event(EType.GETCARD)
.setInteger(count)
.setBoolean(exact)
.setString(header)
.setObject(new EventObject(sco));
p = query(context, p, EType.CARD);
if (p == null)
return null;
else if (p.i == 0)
return null;
else if (p.i == 1 && p.o.is[0] == -1)
// Hack to notify that "All" was selected
return new Card[0];
else
return intArrToCardArr(p.o.is);
}
@Override
public String selectString(MoveContext context, String header, String[] s) {
// if(header != null && !header.equals("")) {
// header = header + ":";
// }
Event p = new Event(EType.GETSTRING)
.setString(header)
.setObject(new EventObject(s));
p = query(context, p, EType.STRING);
if (p == null)
return null;
else
return p.s;
}
@Override
protected int[] orderCards(MoveContext context, int[] cards) {
return orderCards(context, cards, Strings.getString(R.string.return_cards));
}
@Override
protected int[] orderCards(MoveContext context, int[] cards, String header) {
if(cards != null && cards.length == 1) {
return new int[]{ 0 };
}
Event p = new Event(EType.ORDERCARDS)
.setString(header)
.setObject(new EventObject(cards));
p = query(context, p, EType.CARDORDER);
if (p == null)
return null;
else
return p.o.is;
}
@Override
public boolean handle(Event e) {
if (e.t == EType.HELLO) {
name = (e.s == "" ? "Remote player" : e.s);
debug("Name set: " + name);
String[] players = new String[allPlayers.size()];
for (Player p : allPlayers)
players[allPlayers.indexOf(p)] = p.getPlayerName();
// try {
comm.put_ts(new Event(EType.NEWGAME).setObject(new EventObject(new NewGame(myCardsInPlay, players))));
playerJoined();
// } catch (Exception e1) {
// TODO:Because put_ts is asynchronous, this will not work the way it was intended. Is that bad?
// Probably not; if the connection is lost right after receiving a HELLO, we will notice soon enough.
// Maybe we should implement synchronous sending though.
// debug("Could not send NEWGAME -- ignoring, but not setting hasJoined");
// }
return true;
}
if (e.t == EType.SAY) {
vdomServer.say(name + ": " + e.s);
return true;
}
// if (e.t == EType.DISCONNECT) {
// debug("Comms issued disconnect");
// comm.doWait(); // clear notification
// reconnect("Comms issued disconnect.");
// }
return false;
}
private int connect() {
int port = 0;
hasJoined = false;
for (int connections = 0; connections < NUM_RETRIES; connections++) {
try {
comm = new Comms(this, nextPort++);
port = comm.getPort();
return port;
} catch (IOException e) {
// comm = null; // can cause NullPointerExceptions in different threads
e.printStackTrace();
debug ("Could not open a server for remote player... attempt " + (connections + 1));
}
}
return port;
}
private void disconnect() {
if (comm != null)
comm.stop();
// comm = null; // can cause NullPointerExceptions in different threads
hasJoined = false;
myPort = 0;
}
private void reconnect(String s) {
if (vdomServer != null) {
// TODO reconnect
debug("Reconnecting... " + s);
disconnect();
myPort = connect();
if (myPort == 0)
quit(s + "; Could not recreate server");
} else {
quit(s);
}
}
public void sendQuit(String s) {
String time = "\n\n";
time += Strings.getString(R.string.game_length_status);
time += " ";
long duration = System.currentTimeMillis() - whenStarted;
if (duration > 1000 * 60 * 60)
time += (duration / (1000 * 60 * 60)) + "h ";
duration = duration % (1000 * 60 * 60);
if (duration > 1000 * 60)
time += (duration / (1000 * 60)) + "m ";
duration = duration % (1000 * 60);
time += (duration / (1000)) + "s.";
// Try-Catch block made obsolete by sendErrorHandler
// try {
comm.put_ts(new Event(EType.QUIT).setString(s + time));
// } catch (Exception e) {
// // Whatever.
// }
disconnect();
}
private void quit(String s) {
debug("!!! Quitting: " + s + " !!!");
if (vdomServer != null)
vdomServer.endGame(name + " : " + s);
else
die();
}
private void die() {
if (gameThread == null) {
debug("die() called, but game thread already dead.");
kill_game();
return;
}
if (Thread.currentThread() == gameThread) {
gameThread = null;
throw new ExitException();
} else {
debug("die() called from outside vdom-thread");
if (dieTries > 4) {
debug("Could not kill vdom-thread");
return;
}
dieTries++;
kill_game();
}
}
public void kill_game() {
// Make main-thread throw an ExitException.
vdomServer = null;
playerJoined(); // Hack: Need thread to wake up
if (comm != null) {
comm.stop();
} else {
try
{
Thread.sleep(500); // HACK: wait for comm to be created
} catch (InterruptedException e) { }
if (comm != null) {
comm.stop();
} else {
debug("Could not kill vdom thread");
}
}
}
}