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 org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONException;
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;
private 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 class TcpClient extends Client {
TcpClient(ServerSocket s, byte protocol, int id) throws IOException {
super(ServerGame.this, new TcpConnector(getClientSocket(ServerGame.this)), protocol, 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 RuntimeException(); // TODO "vorerst"
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(Move.META_WON);
else if(lost) sendToAll(Move.META_LOST);
this.won = won;
this.lost = lost;
}
protected void sendToClient(byte msgType, Client c) {
sendToClient(new Move(msgType), c);
}
protected void sendToClient(Move m, Client c) {
try {
c.comm.sendMove(m);
} catch(IOException ex) {
// FIXME
}
}
protected void sendToAll(byte msgType) {
sendToAll(new Move(msgType));
}
protected void sendToAll(Move 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(Move m) {
history.add(m);
}
protected JSONObject toJson() throws JSONException {
JSONObject root = new JSONObject();
root.put("nCardsInDeck", nCardsInDeck);
root.put("hints", hints);
root.put("nFlashs", flashs);
root.put("movesLeft", movesLeft);
JSONArray trashArray = new JSONArray();
for(Card c : trash){
trashArray.put(c.toJson());
}
root.put("trash", trashArray);
JSONObject deckRow = new JSONObject();
for(int i=0; i<playingDeck.length; ++i){
JSONObject deckCol = new JSONObject();
for(int j=0; j<playingDeck[i].length; ++j){
deckCol.put(String.valueOf(j), playingDeck[i][j].toJson());
//else deckCol.put(String.valueOf(j), new JSONObject());
}
deckRow.put(String.valueOf(i), deckCol);
}
root.put("playingDeck", deckRow);
JSONObject deckCounterObj = new JSONObject();
for(int i=0; i<playingDeckCounter.length; ++i){
deckCounterObj.put(String.valueOf(i), playingDeckCounter[i]);
}
root.put("playingDeckCounter", deckCounterObj);
JSONArray playerArray = new JSONArray();
for(BasePlayer p : players){
playerArray.put(p.toJson());
}
root.put("players", playerArray);
root.put("currentPlayer", currentPlayer);
root.put("nPlayers", nPlayers);
return root;
}
@Override
protected void createHistory() {
history = new ArrayList<>();
}
@Override
protected void createTrash() {
trash = new ArrayList<>();
}
protected void processMove(Move m) {
byte moveType = m.getMoveType();
if(m.isMeta()) {
switch(moveType) {
case Move.META_READY: {
Client client = clients.get(m.getOrigin());
synchronized (clients) {
if(client.isReady) break;
client.isReady = true;
decNNotReady();
}
}
break;
case Move.META_UNREADY: {
Client client = clients.get(m.getOrigin());
synchronized (clients) {
if(!client.isReady) break;
client.isReady = false;
incNNotReady();
}
}
break;
case Move.META_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(moveType){
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:
int hintType = m.getHintType();
int value = m.getValue();
int player = m.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(Move.META_WON);
else if(lost) sendToAll(Move.META_LOST);
else itsYourTurn(clients.get(currentPlayer));
}
}
//FIXME
protected boolean handleIOException(IOException ex) { throw new RuntimeException(ex); }
protected void answerInvalidMove(Move m) {
Move answer = Move.makeInvalidMoveAnswer(m);
sendToClient( answer, clients.get(m.getOrigin()) );
}
protected void itsYourTurn(Client c) {
Move t = Move.makeItsYourTurnMessage();
sendToClient(t, c);
}
}