package net.sf.nebulacards.comm;
import java.io.IOException;
import net.sf.nebulacards.main.AskTrumpRules;
import net.sf.nebulacards.main.BiddingRules;
import net.sf.nebulacards.main.ChatListenerRules;
import net.sf.nebulacards.main.Communicator;
import net.sf.nebulacards.main.DirectComRules;
import net.sf.nebulacards.main.GameProcedure;
import net.sf.nebulacards.main.GameResult;
import net.sf.nebulacards.main.HandSummaryRules;
import net.sf.nebulacards.main.PassingRules;
import net.sf.nebulacards.main.PileOfCards;
import net.sf.nebulacards.main.Player;
import net.sf.nebulacards.main.PlayingCard;
import net.sf.nebulacards.main.Rules;
import net.sf.nebulacards.main.Tableau;
import net.sf.nebulacards.main.UIListener;
import net.sf.nebulacards.util.proc.DealProc;
public class GameRunner implements Runnable {
public static final boolean verbose = true;
private ComManager cm;
private Communicator[] coms = new Communicator[4];
private Communicator broadcaster;
private Integer[] incomingBids = new Integer[4];
private PlayingCard[] incomingPlays = new PlayingCard[4];
private PileOfCards[] incomingPasses = new PileOfCards[4];
private String[] incomingResponses = new String[4];
private Thread self;
// game data
private Rules m_game = null;
// Hand-specific information
private PileOfCards beenPlayed = new PileOfCards();
private Tableau tableau = new Tableau(4);
private PileOfCards[] cardsWon = new PileOfCards[4];
public GameRunner(Rules r, ComManager _cm) {
m_game = r;
cm = _cm;
for (int i = 0; i < 4; i++) {
m_game.getPlayers()[i] = new Player("");
m_game.getPlayers()[i].setScoreAndBags(0, 0);
m_game.getHands()[i] = new PileOfCards();
cardsWon[i] = new PileOfCards();
incomingBids[i] = null;
incomingPasses[i] = null;
incomingPlays[i] = null;
incomingResponses[i] = null;
coms[i] = cm.getCommunicator(i);
coms[i].setCallbacks(new MyUIListener(i));
}
broadcaster = cm.getBroadcastCommunicator();
self = new Thread(this, "Game Runner");
self.start();
}
/*
* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
try {
synchronized (cm) {
while (cm.howManyActive() < 4)
cm.wait(20000);
}
conductGame();
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace(System.err);
return;
}
if (verbose) {
System.out.println("Game finished.");
}
}
public void join() throws InterruptedException {
self.join();
}
public boolean isAlive() {
return self.isAlive();
}
/**
* Utility method to check if all the elements in an array are null.
*/
public static boolean allNull(Object[] o) {
for (int i = 0; i < o.length; i++)
if (o[i] != null)
return false;
return true;
}
/**
* Utility method to safely read and reset an incoming data latch.
*/
public Object delatch(Object[] latches, int index) {
Object result;
synchronized (this) {
result = latches[index];
latches[index] = null;
}
return result;
}
public String getName(int where) {
return m_game.getPlayers()[where].getName();
}
public void setName(int where, String name) {
m_game.getPlayers()[where].setName(name);
}
class MyUIListener implements UIListener {
int im_slot;
public MyUIListener(int slot) {
im_slot = slot;
}
/**
* Clean up after a user quits. Even though other classes are
* responsible for putting the players into the ComManager, we can take
* them out.
*/
public void wantToQuit() {
System.out.println(getName(im_slot) + " is leaving the game.");
synchronized (cm) {
setName(im_slot, "");
cm.remove(im_slot);
cm.notifyAll();
}
broadcastTable();
}
public void submitChat(String msg) {
String chat = getName(im_slot) + "> " + msg;
if (!chat.endsWith("\n"))
chat += "\n";
// broadcast chat message
broadcaster.chat(chat);
if (m_game instanceof ChatListenerRules) {
((ChatListenerRules) m_game).receiveChat(msg, im_slot);
}
}
/*
* (non-Javadoc)
*
* @see net.sf.nebulacards.main.UIListener#submitResponse(java.lang.String)
*/
public void submitResponse(String s) {
synchronized (GameRunner.this) {
incomingResponses[im_slot] = s;
GameRunner.this.notifyAll();
}
}
/*
* (non-Javadoc)
*
* @see net.sf.nebulacards.main.UIListener#submitBid(int)
*/
public boolean submitBid(int bid) {
synchronized (GameRunner.this) {
incomingBids[im_slot] = new Integer(bid);
GameRunner.this.notifyAll();
}
return true;
}
/*
* (non-Javadoc)
*
* @see net.sf.nebulacards.main.UIListener#submitPlay(net.sf.nebulacards.main.PlayingCard)
*/
public boolean submitPlay(PlayingCard p) {
synchronized (GameRunner.this) {
incomingPlays[im_slot] = p;
GameRunner.this.notifyAll();
}
return true;
}
/*
* (non-Javadoc)
*
* @see net.sf.nebulacards.main.UIListener#submitPass(net.sf.nebulacards.main.PileOfCards)
*/
public boolean submitPass(PileOfCards p) {
synchronized (GameRunner.this) {
incomingPasses[im_slot] = new PileOfCards(p);
GameRunner.this.notifyAll();
}
return true;
}
}
private void conductPassing() throws IOException, InterruptedException {
int[] howmany = new int[4], where = new int[4];
PileOfCards[] pass = new PileOfCards[4];
boolean[] done = new boolean[4];
PassingRules pm_game = (PassingRules) m_game;
for (int i = 0; i < 4; i++) {
if ((howmany[i] = pm_game.numPass(i)) > 0) {
where[i] = pm_game.wherePass(i);
done[i] = false;
delatch(incomingPasses, i); // clean up input area
synchronized (coms[i]) {
coms[i].clearResend();
coms[i].yourTurnToPass(howmany[i], where[i]);
}
} else
done[i] = true;
}
while (!(done[0] && done[1] && done[2] && done[3])) {
synchronized (this) {
if (allNull(incomingPasses))
wait(1000);
}
for (int i = 0; i < 4; i++) {
pass[i] = (PileOfCards) delatch(incomingPasses, i);
if (pass[i] != null) {
if (pass[i].howManyCardsNotNull() == howmany[i]) {
done[i] = true;
for (int j = 0; j < pass[i].size(); j++) {
if (!m_game.getHands()[i].contains(pass[i].get(j)))
done[i] = false;
}
if (done[i])
done[i] = pm_game.checkPass(i, pass[i]);
}
if (!done[i]) { // illegal pass, have to ask again
synchronized (coms[i]) {
coms[i].clearResend();
coms[i].yourTurnToPass(howmany[i], where[i]);
}
}
} else {
if (howmany[i] > 0) {
delatch(incomingPasses, i);
synchronized (coms[i]) {
coms[i].resendOnly();
coms[i].yourTurnToPass(howmany[i], where[i]);
}
}
}
}
}
// All passes are in and contain the right number of cards.
// Now we move the cards.
for (int i = 0; i < 4; i++) {
if (howmany[i] > 0) {
m_game.getHands()[i].removeAll(pass[i]);
// cards can be thrown away by specifying where[i] = -1
if (where[i] >= 0 && where[i] < m_game.getCapacity())
m_game.getHands()[where[i]].addAll(pass[i]);
}
}
// distribute the new hands
for (int i = 0; i < 4; i++) {
m_game.getHands()[i].sort(m_game);
synchronized (coms[i]) {
coms[i].dealHand(new PileOfCards(m_game.getHands()[i]));
}
}
}
private void conductBidding() throws IOException, InterruptedException {
int stopCount = 0;
for (int w = 0; stopCount < 4; w = (w + 1) % 4) {
if (!((BiddingRules) m_game).shouldBid(w)) {
stopCount++;
continue;
}
stopCount = 0;
delatch(incomingBids, w);
synchronized (coms[w]) {
coms[w].clearResend();
coms[w].yourTurnToBid();
}
for (;;) { // wait for bid
synchronized (this) {
while (incomingBids[w] == null) {
wait(300);
synchronized (coms[w]) {
coms[w].resendOnly();
coms[w].yourTurnToBid();
}
}
}
Integer bid = (Integer) delatch(incomingBids, w);
if (bid != null) {
m_game.getPlayers()[w].setBid(bid.intValue());
((BiddingRules) m_game).reportBid(w, bid.intValue());
break;
}
}
broadcaster.setBid(w, m_game.getPlayers()[w].getBid());
}
((BiddingRules) m_game).doneBidding();
}
/**
* Ask a player to pick trump.
*/
private void conductTrumpDiscovery() throws IOException,
InterruptedException {
AskTrumpRules atm_game = (AskTrumpRules) m_game;
String response;
do {
int who = atm_game.whoPicksTrump();
delatch(incomingResponses, who);
synchronized (coms[who]) {
coms[who].clearResend();
coms[who].respond("Please choose the trump.");
}
synchronized (this) {
while (incomingResponses[who] == null) {
synchronized (coms[who]) {
coms[who].resendOnly();
coms[who].respond("Please choose the trump.");
}
wait(1000);
}
response = (String) delatch(incomingResponses, who);
}
} while (!atm_game.trumpResponse(response));
broadcaster.setTrump(m_game.getTrump(), m_game.getTrumpName());
}
private void conductGame() throws IOException, InterruptedException {
for (int handCount = 0;; handCount++) {
// start a new hand
// clear old piles
for (int i = 0; i < 4; i++) {
cardsWon[i] = new PileOfCards();
m_game.getHands()[i] = new PileOfCards();
}
beenPlayed = new PileOfCards();
tableau = new Tableau();
GameProcedure[] order = m_game.getProcedureOrdering();
for (int i = 0; order != null && i < order.length; i++) {
switch (order[i].getProcedure()) {
case GameProcedure.DEALCARDS:
(new DealProc()).runProcedure(coms, m_game);
break;
case GameProcedure.BIDDING:
if (m_game instanceof BiddingRules)
conductBidding();
break;
case GameProcedure.PASSING:
if (m_game instanceof PassingRules)
conductPassing();
break;
case GameProcedure.ASKTRUMP:
if (m_game instanceof AskTrumpRules)
conductTrumpDiscovery();
break;
case GameProcedure.DIRECTCOM:
if (m_game instanceof DirectComRules) {
Communicator[] tmp = (Communicator[]) coms.clone();
((DirectComRules) m_game).conductDirectCom(tmp);
}
break;
case GameProcedure.CUSTOM:
order[i]
.runProcedure((Communicator[]) coms.clone(), m_game);
break;
}
}
broadcaster.setTrump(m_game.getTrump(), m_game.getTrumpName());
int lastWinner = -1;
int lastPlayer = 0;
// play out the hand
while (m_game.getHands()[0].howManyCardsNotNull() > 0
&& !m_game.quitHand()) {
for (int cardsPlayed = 0; cardsPlayed < 4; cardsPlayed++) {
if (m_game.quitHand()) {
break;
}
int w = m_game.whoPlaysNext((cardsPlayed == 0), lastPlayer,
lastWinner);
if (w < 0 || w > 3) {
if (lastWinner < 0)
w = handCount % 4;
else if (cardsPlayed == 0)
w = lastWinner;
else
w = (lastPlayer + 1) % 4;
}
lastPlayer = w;
if (cardsPlayed == 0)
tableau.setLead(w);
delatch(incomingPlays, w);
synchronized (coms[w]) {
coms[w].clearResend();
coms[w].yourTurn();
}
PlayingCard c = null;
// loop until we get a valid play
while (true) {
// wait for play
synchronized (this) {
while (incomingPlays[w] == null) {
synchronized (coms[w]) {
coms[w].resendOnly();
coms[w].yourTurn();
}
wait(1000);
}
}
c = (PlayingCard) delatch(incomingPlays, w);
if (!m_game.validPlay(beenPlayed, tableau, m_game
.getHands()[w], c)
|| !m_game.getHands()[w].contains(c)) {
synchronized (coms[w]) {
coms[w].noResend();
coms[w].rejected();
// prompt again
coms[w].clearResend();
coms[w].yourTurn();
}
continue; // go wait for another play
}
// signal acceptance
synchronized (coms[w]) {
coms[w].noResend();
coms[w].accepted();
}
// remove card from hand
m_game.getHands()[w].remove(c);
// make sure a new player gets the correct hand.
synchronized (coms[w]) {
coms[w].resendOnly();
coms[w].dealHand(m_game.getHands()[w]);
}
beenPlayed.add(c);
tableau.set(w, c);
broadcaster.cardToTableau(w, c);
break;
}
} // now all players have played a card
int winner = m_game.whoWinsTrick(tableau);
m_game.trickDone(tableau, winner);
cardsWon[winner].addAll(tableau);
tableau.clear();
broadcaster.clearTableau(winner);
lastWinner = winner;
}
// now all tricks have been played
m_game.score(cardsWon, m_game.getPlayers());
broadcastTable();
broadcaster.endHand();
try {
String[] summary = ((HandSummaryRules) m_game).getSummary();
for (int i = 0; i < summary.length; i++) {
broadcaster.chat(summary[i]);
}
} catch (NullPointerException e) { /* no summary */
} catch (ClassCastException e) { /* m_game don't summarize */
}
GameResult gr = m_game.done(m_game.getPlayers());
if (gr.done) {
String res = "*** Game is complete. ***\nWinners are: ";
if (gr.winners.length > 0)
res += m_game.getPlayers()[gr.winners[0]].getName();
for (int i = 1; i < gr.winners.length; i++)
res += " & " + m_game.getPlayers()[gr.winners[i]].getName();
res += "\n";
broadcaster.chat(res);
broadcaster.endGame(new GameResult(gr));
break;
}
for (int i = 0; i < 4; i++) {
m_game.getPlayers()[i].clearBid();
}
}
// end of game
if (verbose) {
for (int count = 0; count < 4; count++) {
System.out.print(m_game.getPlayers()[count].getName() + ": ");
System.out.print(m_game.getPlayers()[count].getScore());
System.out.print("_");
System.out.println(m_game.getPlayers()[count].getBags());
}
}
}
/**
* Handle the connection of a new player in a previously used slot.
*
* @param where
* The position where the new player is sitting.
*/
public void reconnect(int where) {
synchronized (coms[where]) {
if (!m_game.getHands()[where].isEmpty())
coms[where].dealHand(new PileOfCards(m_game.getHands()[where]));
coms[where].setTrump(m_game.getTrump(), m_game.getTrumpName());
PileOfCards tmp = new PileOfCards(beenPlayed);
tmp.removeAll(tableau);
coms[where].playedSoFar(tmp);
// resend tableau
for (int i = 0; i < 4; i++) {
PlayingCard c = tableau.get(i);
if (!c.isNull())
coms[where].cardToTableau(i, c);
}
// resend bids
for (int i = 0; i < m_game.getPlayers().length; i++) {
if (m_game.getPlayers()[i].hasBid())
coms[where].setBid(i, m_game.getPlayers()[i].getBid());
}
if (m_game != null && m_game instanceof DirectComRules)
((DirectComRules) m_game).reconnect(where);
}
}
public void broadcastTable() {
Player[] bt = new Player[4];
for (int i = 0; i < 4; i++) {
if (m_game.getPlayers()[i] != null)
bt[i] = m_game.getPlayers()[i];
else
bt[i] = new Player("");
}
broadcaster.setPlayers(bt);
broadcaster.setGameName(m_game.getName());
}
// returns how many players are connected
public int howMany() {
return cm.howManyActive();
}
public String[] getNames() {
String[] names = new String[m_game.getPlayers().length];
for (int i = 0; i < names.length; i++)
names[i] = m_game.getPlayers()[i].getName();
return names;
}
}