package server;
import java.io.PrintStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import card.Card;
import player.BasePlayer;
public class ServerGame extends BaseGame {
public static final byte PROTO_VIBE_DUMB = 1;
private List<Card> cardsInDeck;
private long seed;
private Random rand;
private boolean gameStarted;
int movesLeft;
private boolean won;
private boolean lost;
private ServerSocket tcpServerSocket;
private List<Client> clients; // accesses must be synchronized.
private int nNotReady; // accesses must synchronize on `clients`.
private static Socket getClientSocket(ServerGame sg) throws IOException {
synchronized(sg.clients) {
Socket sock = sg.tcpServerSocket.accept();
sg.incNNotReady();
return sock;
}
}
// private static class ServerGameClient extends Client {
// ServerGameClient(ServerGame sg, Connector conn, byte protocol, int id) {
// super(sg, conn, protocol, id);
// }
// @Override
// public Message recieveMsg() {
// return null;
// }
// }
static class VibeDumbServer extends VibeDumb {
int id;
VibeDumbServer(Connector c, int id) { super(c); this.id = id; }
@Override
public Message receiveMsg() throws IOException {
Message m = super.receiveMsg();
m.setOrigin(id);
return m;
}
}
private class TcpClient extends Client {
TcpClient(ServerSocket s, byte protocol, int id) throws IOException {
super(ServerGame.this, new TcpConnector(getClientSocket(ServerGame.this)), protocol, true, id);
}
}
private void startGame() {
try {
tcpServerSocket.close();
} catch(IOException ex) {
// TODO
Utils.log(Utils.DEBUG, "Could not close tcpServerSocket due to IO Error.");
System.exit(0);
}
gameStarted = true;
}
private int incNNotReady() {
synchronized(clients) {
return ++nNotReady;
}
}
private int decNNotReady() {
synchronized(clients) {
return --nNotReady;
}
}
private boolean allAreReady() {
synchronized(clients) {
return nNotReady == 0;
}
}
private boolean haveEnoughPlayers() {
synchronized(clients) {
return clients.size() >= 2;
}
}
private static class Options {
// TODO: whole class is a stub!
enum Act { HELP, ERROR, VERSION, LICENSE, PLAY };
Options(String[] argsMain) { }
Act getAct() { return Act.PLAY; }
}
private Options opts;
private static final int EXIT_SUCCESS = 0;
private static final int EXIT_INVOCATION_ERROR = 1;
private static final int EXIT_INTERNAL_ERROR = 2;
private static void printHelpExit(PrintStream ps, int exitCode) {
// TODO
ps.println("[Imagine some useful information here.]");
System.exit(exitCode);
}
private static void printVersionExit() {
// TODO
System.out.println("Not even alpha yet...");
System.exit(EXIT_SUCCESS);
}
private static void printLicenseExit() {
// TODO
System.out.println("Who knows?");
System.exit(EXIT_SUCCESS);
}
public static void main(String[] args) {
Options opts = new Options(args);
switch(opts.getAct()) {
case HELP:
printHelpExit(System.out, EXIT_SUCCESS);
case ERROR:
printHelpExit(System.err, EXIT_INVOCATION_ERROR);
case VERSION:
printVersionExit();
case LICENSE:
printLicenseExit();
default:
System.exit(EXIT_INTERNAL_ERROR); // TODO Handle? This is an internal error...
// Anyway, the following code assumes we exit here.
case PLAY:
break;
}
ServerGame sg = new ServerGame(opts);
sg.addClients();
try {
Thread.currentThread().wait();
} catch (InterruptedException ex) {
Utils.log(Utils.DEBUG, "Interrupted while waiting.");
System.exit(EXIT_SUCCESS);
}
// System.exit(EXIT_SUCCESS);
}
public ServerGame(Options opts) {
// TODO: Don't ignore `opts`.
clients = new ArrayList<>();
}
private Client nextClient() {
// TODO `opts` is currently ignored...
Client cl = null;
synchronized(clients) {
int id = clients.size();
try {
cl = new TcpClient(tcpServerSocket, PROTO_VIBE_DUMB, id);
} catch(IOException ex) {
// FIXME
}
clients.add(cl);
}
cl.start();
return cl;
}
protected void addClients() {
// TODO `opts` is currently ignored...
try {
tcpServerSocket = new ServerSocket(1337);
} catch(IOException ex) {
// FIXME
}
do {
nextClient();
} while(!haveEnoughPlayers() || !allAreReady());
startGame();
}
// protected void establishConnection() throws JSONException {
// port = 1337;
// sockets = new ArrayList<>();
// try {
// server = new ServerSocket(port);
// while(nPlayers < 2){ //TODO
// Socket socket = server.accept();
// sockets.add(socket);
// JSONObject connectionJson = new JSONObject();
// connectionJson.put("id", nPlayers);
// sendToClient(CONNECTION_VALID, connectionJson.toString(), socket);
// nPlayers++;
// Utils.log(Utils.VERBOSE, nPlayers + " players connected!");
// // TODO do this somewhere else
// Utils.log(Utils.VERBOSE, "Creating new Socket Listener for #" + (sockets.size()-1));
// Utils.log(Utils.VERBOSE, "Socket id: " + socket);
// new Thread(new SocketListenerThread(socket)).start();
// }
// } catch (IOException e) {
// // TODO Auto-generated catch block
// // TODO exit
// // e.printStackTrace();
// System.err.println("Exception is currently suppressed");
// }
// Utils.log(Utils.VERBOSE, "Initalizing game...");
// init();
// Utils.log(Utils.VERBOSE, "Sending game to clients");
// Utils.log(Utils.VERBOSE, "game: " + toJson().toString());
// String msg = toJson().toString();
// sendToAll(MOVE_UPDATE, msg);
// addToHistory(msg);
// }
@Override
protected void init() {
super.init();
seed = 0;
rand = new Random(seed);
movesLeft = -1;
createPlayers();
currentPlayer = 0;
createPlayingDeck();
createTrash();
// shuffleCards(); // TODO use this instead of addCards()
addCards();
dealCards();
}
protected void shuffleCards() {
cardsInDeck = new ArrayList<>();
//TODO add cards
//TODO shuffle
}
protected Card getRealCard(Card c, int[] index){
// TODO: Get rid of this ridiculous function.
Card[] cards = getPlayer(currentPlayer).getCards();
for(int i=0; i<cards.length; ++i){
Card card = cards[i];
if(card.getId() == c.getId()){
if(card.getColorInt() == c.getColorInt() && card.getValue() == c.getValue()){
if(index != null) index[0] = i;
return card;
}
}
}
return null;
}
protected boolean placeCard(Card c) {
int[] index = new int[1];
Card card = getRealCard(c, index);
if(card == null) return false;
int color = card.getColorInt();
int value = card.getValue();
int deckValue = playingDeckCounter[color];
if(deckValue == value){ //place card possible
playingDeckCounter[color]++;
playingDeck[color][value] = card;
if (value + 1 == 5) addHint();
} else { //card too high or too low
addFlash();
moveCardToTrash(card, true);
}
setWonOrLost();
dealCard(index[0], currentPlayer);
return true;
}
protected boolean moveCardToTrash(Card c){
return moveCardToTrash(c, false);
}
protected boolean moveCardToTrash(Card c, boolean receivedFlash) {
int[] index = new int[1];
Card card = getRealCard(c, index);
if(card == null) return false;
// TODO Fix clone removal
if(!receivedFlash) addHint();
trash.add(card);
dealCard(index[0], currentPlayer);
return true;
}
protected boolean giveHint(int type, int value, int playerId) {
if(!hintAvailable()) return false;
if(currentPlayer == playerId) return false;
Card[] cards = getPlayer(playerId).getCards();
for(Card c : cards) {
boolean is = false;
if(type == Move.HINT_COLOR) is = c.getColorInt() == value;
else if(type == Move.HINT_VALUE) is = c.getValue() == value;
else throw new InternalHanabiError("Attempt to give hint which is neither color nor value hint.");
c.addCardInfo(type, is, value, currentPlayer);
}
removeHint();
return true;
}
protected void createPlayers() {
players = new BasePlayer[nPlayers];
for(int i=0; i<nPlayers; ++i){
players[i] = new BasePlayer(i, CARDS_PER_PLAYER, "Player " + i);
}
}
protected void dealCards() {
for(int dealtCards=0; dealtCards<CARDS_PER_PLAYER; ++dealtCards) {
for (int i = 0; i < nPlayers; ++i) {
int index = random(nCardsInDeck--);
Card card = cardsInDeck.remove(index);
players[i].setCard(dealtCards, card);
// TODO use this code as soon as the shuffle is implemented
// Card card = getCardFromDeck();
// players[i].setCard(dealtCards, card);
}
}
}
//FIXME no endless do-while...
private void addCards() {
cardsInDeck = new ArrayList<>();
int[] dist = new int[]{ 3, 2, 2, 2, 1 };
int[][] tmp_cards = new int[][]{ dist.clone(), dist.clone(), dist.clone(), dist.clone(), dist.clone() };
for (int i = 0; i < N_CARDS; ++i) {
int[] p = new int[2];
do {
p = randomPosition();
} while (tmp_cards[p[0]][p[1]] == 0);
tmp_cards[p[0]][p[1]]--;
cardsInDeck.add(new Card(p[0], p[1], i));
}
}
private int[] randomPosition(){
int[] p = new int[2];
p[0] = random(5);
p[1] = random(5);
return p;
}
private int random(int max) {
return rand.nextInt(max);
}
private void dealCard(int cardPos, int playerId) {
Card card;
if (nCardsInDeck > 0) {
int index = random(nCardsInDeck--);
card = cardsInDeck.remove(index);
// TODO Use this code as soon as shuffle is implemented
// card = getCardFromDeck();
} else {
if (movesLeft == -1) movesLeft = nPlayers;
card = Card.DUMMY_CARD;
}
players[playerId].setCard(cardPos, card);
}
void setWonOrLost() {
boolean won = true;
boolean lost = false;
for(int i=0; i<COLORS; ++i) {
if(playingDeckCounter[i] != 5) {
won = false;
break;
}
}
if(flashs == MAX_FLASHS) {
lost = true;
won = false;
}
if(movesLeft == 0) lost = true;
if(won) sendToAll(Message.MSG_WON);
else if(lost) sendToAll(Message.MSG_LOST);
this.won = won;
this.lost = lost;
}
protected void sendToClient(byte msgType, Client c) {
sendToClient(new Message(msgType), c);
}
protected void sendToClient(Message m, Client c) {
try {
c.comm.sendMsg(m);
} catch(IOException ex) {
// FIXME
}
}
protected void sendToAll(byte msgType) {
sendToAll(new Message(msgType));
}
protected void sendToAll(Message m) {
for(Client c : clients) sendToClient(m, c);
}
protected void removeHint() { hints--; }
protected void addHint() {
if(hints != MAX_HINTS) {
hints++;
if(hints > MAX_HINTS) hints = MAX_HINTS;
}
}
protected void addFlash() { flashs++; }
/**
* Stores the current state of the game in the history
*
* @param json
* the current state as json object
*/
// protected void addToHistory(String json) {
// history.add(json);
// }
protected void addToHistory(Message m) {
history.add(m.getMove());
}
@Override
protected void createHistory() {
history = new ArrayList<>();
}
@Override
protected void createTrash() {
trash = new ArrayList<>();
}
protected void processMsg(Message m) {
byte msgType = m.getMsgType();
if(m.isNonMove()) {
switch(msgType) {
case Message.MSG_READY: {
Client client = clients.get(m.getOrigin());
synchronized (clients) {
if(client.isReady) break;
client.isReady = true;
decNNotReady();
}
}
break;
case Message.MSG_UNREADY: {
Client client = clients.get(m.getOrigin());
synchronized (clients) {
if(!client.isReady) break;
client.isReady = false;
incNNotReady();
}
}
break;
case Message.MSG_LEAVE:
clients.get(m.getOrigin()).stopListening();
break;
default:
throw new InternalHanabiError("Invalid meta move.");
}
return;
}
if(!gameStarted || currentPlayer != m.getOrigin()) {
answerInvalidMove(m);
return;
}
boolean isValid = true;
boolean isMove = false;
// TODO MOVE_DRY
switch(msgType){
case Move.PLACE_CARD:
isValid = placeCard(new Card(m));
isMove = true;
break;
case Move.TRASH_CARD:
isValid = moveCardToTrash(new Card(m));
isMove = true;
break;
case Move.HINT:
Move move = m.getMove();
int hintType = move.getHintType();
int value = move.getValue();
int player = move.getHintRecipient();
isValid = giveHint(hintType, value, player);
isMove = true;
break;
default:
throw new InternalHanabiError("Invalid (non-meta) move.");
}
if(!isValid) {
answerInvalidMove(m);
return;
}
if(isMove) {
if(movesLeft >= 0) movesLeft--;
currentPlayer++;
currentPlayer %= nPlayers;
sendToAll(m);
addToHistory(m);
if(won) sendToAll(Message.MSG_WON);
else if(lost) sendToAll(Message.MSG_LOST);
else itsYourTurn(clients.get(currentPlayer));
}
}
//FIXME
protected boolean handleIOException(IOException ex) { throw new RuntimeException(ex); }
protected void answerInvalidMove(Message m) {
Message answer = Message.makeInvalidMoveAnswer(m);
sendToClient( answer, clients.get(m.getOrigin()) );
}
protected void itsYourTurn(Client c) {
Message t = Message.makeItsYourTurnMessage();
sendToClient(t, c);
}
}