Newer
Older
hanabi-networking / src / server / ServerGame.java
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);
	}
}