diff --git a/src/card/Card.java b/src/card/Card.java index 66d50c9..e5125a0 100644 --- a/src/card/Card.java +++ b/src/card/Card.java @@ -1,10 +1,14 @@ package card; +import server.InternalHanabiError; +import server.Move; + import java.util.ArrayList; import java.util.List; import org.json.JSONArray; import org.json.JSONObject; +import org.json.JSONException; /* * COLORS: @@ -31,35 +35,39 @@ DUMMY_CARD.cardInfos = null; } - public Card(int color, int value, int id){ + public Card(int color, int value, int id) { this(); this.color = color; this.value = value; this.id = id; } - public Card(){ + public Card() { cardInfos = new ArrayList<>(); } - - public Card(JSONObject jo){ + + public Card(Move m) { this(); - fromJson(jo); + try { + fromJson(m.getJson()); + } catch(JSONException ex) { + throw new InternalHanabiError(ex); + } } - public void addCardInfo(int type, boolean is, int what, int from){ + public void addCardInfo(int type, boolean is, int what, int from) { cardInfos.add(new CardInfo(type, is, what, from)); } - public List getCardInfos(){ + public List getCardInfos() { return cardInfos; } - public String colorToText(){ + public String colorToText() { return intColorToText(color); } - public static String intColorToText(int id){ + public static String intColorToText(int id) { String color = ""; switch(id){ case 0: @@ -81,27 +89,27 @@ return color; } - public boolean isSelected(){ + public boolean isSelected() { return isSelected; } - public void setSelected(boolean set){ + public void setSelected(boolean set) { isSelected = set; } - public int getColorInt(){ + public int getColorInt() { return color; } - public int getValue(){ + public int getValue() { return value; } - public int getId(){ + public int getId() { return id; } - public JSONObject toJson(){ + public JSONObject toJson() throws JSONException { // TODO: handle dummy card JSONObject root = new JSONObject(); root.put("value", value); @@ -111,7 +119,7 @@ return root; } - private JSONArray cardInfosToJson(){ + private JSONArray cardInfosToJson() throws JSONException { JSONArray infoArray = new JSONArray(); if(cardInfos == null) return infoArray; for(CardInfo ci : cardInfos){ @@ -120,7 +128,7 @@ return infoArray; } - private void fromJson(JSONObject jo){ + private void fromJson(JSONObject jo) throws JSONException { // TODO: handle dummy card cardInfos = new ArrayList<>(); int color = jo.getInt("color"); @@ -129,7 +137,7 @@ JSONArray cardInfoArray = jo.getJSONArray("cardInfos"); for(int i=0; i history; + //List history; + List history; public BaseGame(){ - keepRunning = true; + //keepRunning = true; + Utils.init(System.err, Utils.VERBOSE, true); //init(); } - protected void init(){ + protected void init() { createPlayingDeck(); createHistory(); createTrash(); - CARDS_PER_PLAYER = (nPlayers <= 3) ? 5 : 4; + CARDS_PER_PLAYER = nPlayers<=3 ? 5 : 4; hints = MAX_HINTS; flashs = 0; nCardsInDeck = N_CARDS; @@ -86,40 +86,41 @@ protected abstract void createHistory(); protected abstract void createTrash(); - protected void createPlayingDeck(){ + + protected void createPlayingDeck() { playingDeck = new Card[COLORS][5]; playingDeckCounter = new int[5]; - for (int i=0; i 0; } protected void createPlayers() { players = new BasePlayer[nPlayers]; - for(int i=0; i getHistory(int move){ + public List getHistory(int move) { final int index = move>=0 ? move : history.size()+move; return history.subList(0, index); } - protected abstract void receive(DataInputStream inStream); +// protected abstract void receive(DataInputStream inStream) throws Exception; + protected abstract void processMove(Move m); + protected abstract boolean handleIOException(IOException ex); - public static void Log(String msg){ - SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); - System.out.println("[" + sdf.format(new Date()) + "]: " + msg); - } +// public static void Log(String msg){ +// SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); +// System.out.println("[" + sdf.format(new Date()) + "]: " + msg); +// } - class SocketListenerThread implements Runnable { - Socket socket; - DataInputStream inStream; - - public SocketListenerThread(Socket socket){ - this.socket = socket; - try { - inStream = new DataInputStream(socket.getInputStream()); - } catch (IOException e) { - throw new RuntimeException(e); // TODO wer faengt das? - } - } - - @Override - public void run() { - while(keepRunning){ - receive(inStream); - } - } - } -} \ No newline at end of file +// class CommunicationListener extends Thread { +// //Socket socket; +// //DataInputStream inStream; +// private Communicator comm; +// private volatile boolean keepRunning; +// private final int TIMEOUT; +// +// CommunicationListener(Communicator c, int timeout) { +// comm = c; +// keepRunning = true; +// TIMEOUT = timeout; +// } +// +// void stop() { keepRunning = false; } +// +// //SocketListenerThread(Socket socket){ +// // this.socket = socket; +// // try { +// // inStream = new DataInputStream(socket.getInputStream()); +// // } catch (IOException e) { +// // throw new RuntimeException(e); // TODO wer faengt das? +// // } +// //} +// +// @Override +// public void run() { +// //receive(inStream); +// boolean retry = true; +// do { +// try { +// comm.establish(); +// break; +// } catch(IOException ex) { +// retry = retryFailedIO(ex); +// } +// } while(retry); +// +// try { +// comm.establishConnection(); +// } catch(IOException ex) { +// handleIOException(ex); +// if(!keepRunning) return; +// } +// try { +// comm.setTimeout(TIMEOUT); +// } catch(IOException ex) { +// handleIOException(ex); +// if(!keepRunning) return; +// } +// try { +// comm.waitUntilReady(); +// } catch(IOException ex) { +// handleIOException(ex); +// if(!keepRunning) return; +// } +// synchronized(comm) { +// try { +// comm.wait(); +// } catch(InterruptedException ex) { } +// } +// while(keepRunning) { +// Move m = null; +// try { +// m = comm.receiveMove(); +// } catch(IOException ex) { +// handleIOException(ex); +// if(!keepRunning) return; +// } +// try { +// processMove(m); +// } catch(IOException ex) { +// handleIOException(ex); +// } +// } +// } +// } +} diff --git a/src/server/Client.java b/src/server/Client.java new file mode 100644 index 0000000..3042c16 --- /dev/null +++ b/src/server/Client.java @@ -0,0 +1,68 @@ +package server; + +import java.io.IOException; + +public class Client extends Thread { + BaseGame game; + Communicator comm; + int id; + boolean isReady; + volatile boolean keepRunning; + Client(BaseGame game, Communicator c, int id) { + this.game = game; + comm = c; + this.id = id; + isReady = false; + keepRunning = false; + } + + Client(BaseGame game, Connector conn, byte proto, int id) { + this(game, ctorHelper(conn, proto), id); + } + + private static Communicator ctorHelper(Connector conn, byte proto) { + Communicator comm = null; + switch(proto) { + case ServerGame.PROTO_VIBE_DUMB: + comm = new VibeDumb(conn); + break; + default: + throw new InternalHanabiError(); // TODO + } + return comm; + } + + void stopListening() { keepRunning = false; } + + @Override + public void run() { + keepRunning = true; + boolean retry = true; + do { + try { + comm.establish(); + break; + } catch(IOException ex) { + retry = game.handleIOException(ex); + } + } while(retry); + listen(); + } + + void listen() { + while(keepRunning) { + Move m = null; + boolean retry = true; + while(retry){ + try { + m = comm.receiveMove(); + break; + } catch(IOException ex) { + retry = game.handleIOException(ex); + if(!keepRunning) return; + } + } + game.processMove(m); + } + } +} diff --git a/src/server/ClientGame.java b/src/server/ClientGame.java index be62df1..96b5d2c 100644 --- a/src/server/ClientGame.java +++ b/src/server/ClientGame.java @@ -1,13 +1,10 @@ package server; import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.net.Socket; -import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Scanner; import org.json.JSONArray; import org.json.JSONException; @@ -26,49 +23,21 @@ int difficulty; int fontSize; - Socket socket; - String hostName; +// Socket socket; +// int port; +// String hostName; + Client client; volatile boolean isConnected; volatile boolean initGameReceived; volatile int ownId; // TODO lock? - public ClientGame(){ - preinit(); + public ClientGame(Communicator comm) throws IOException{ + preinit(comm); } - - protected void preinit(){ - port = 1337; - hostName = "localhost"; - isConnected = false; - try { - socket = new Socket(hostName, port); - } catch (UnknownHostException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - new Thread(new SocketListenerThread(socket)).start(); - while(!isConnected){ - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - Log("Waiting for connection to host"); - } - Log("Connection to " + hostName + " on port " + port + " as player " + ownId + " established!"); - while(!initGameReceived){ - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - Log("Client " + ownId + " waiting for game..."); - } + + protected void preinit(Communicator comm) throws IOException{ + client = new Client(this, comm, -1); + client.comm.establish(); } @Override @@ -88,9 +57,35 @@ trash = new ArrayList<>(); } + protected void sendReady() throws IOException { + try { + JSONObject jo = new JSONObject(); + jo.put("origin", ownId); + Move m = new Move(jo); + m.setMoveType(Move.META_READY); + send(m); + } catch(JSONException ex) { + throw new InternalHanabiError(ex); + } + } + + protected void sendUnReady() throws IOException { + try { + JSONObject jo = new JSONObject(); + jo.put("origin", ownId); + Move m = new Move(jo); + m.setMoveType(Move.META_UNREADY); + send(m); + } catch(JSONException ex) { + throw new InternalHanabiError(ex); + } + } + protected void loadOptions(){ try { - JSONTokener tokener = new JSONTokener(new FileInputStream(options)); + Scanner optionsScanner = new Scanner(options, "UTF-8"); + JSONTokener tokener = new JSONTokener(optionsScanner.useDelimiter("\\A").next()); + optionsScanner.close(); optionsRoot = new JSONObject(tokener); difficulty = optionsRoot.getInt("difficulty"); fontSize = optionsRoot.getInt("fontSize"); @@ -103,72 +98,101 @@ } } - protected void movePlaceCard(Card c){ + protected void movePlaceCard(Card c) throws IOException{ movePlaceCard(c, false); } - protected void movePlaceCard(Card c, boolean isDry) { - byte msgType = isDry ? MOVE_PLACE_CARD | MOVE_DRY : MOVE_PLACE_CARD; - send(msgType, c.toJson().toString()); + protected void movePlaceCard(Card c, boolean isDry) throws IOException { + byte msgType = isDry ? Move.PLACE_CARD | Move.DRY : Move.PLACE_CARD; + send(msgType, c); +// send(msgType, c.toJson().toString()); } - protected void moveTrashCard(Card c){ + protected void moveTrashCard(Card c) throws IOException{ moveTrashCard(c, false); } - protected void moveTrashCard(Card c, boolean isDry) { - byte msgType = isDry ? MOVE_TRASH_CARD | MOVE_DRY : MOVE_TRASH_CARD; - send(msgType, c.toJson().toString()); + protected void moveTrashCard(Card c, boolean isDry) throws IOException { + byte msgType = isDry ? Move.TRASH_CARD | Move.DRY : Move.TRASH_CARD; + send(msgType, c); +// send(msgType, c.toJson().toString()); } - protected void moveHint(boolean isValue, int value, int playerId){ + protected void moveHint(boolean isValue, int value, int playerId) throws IOException { moveHint(isValue, value, playerId, false); } - protected void moveHint(boolean isValue, int value, int playerId, boolean isDry){ - byte msgType = isDry ? MOVE_HINT | MOVE_DRY : MOVE_HINT; + protected void moveHint(boolean isValue, int value, int playerId, boolean isDry) throws IOException { + byte msgType = isDry ? Move.HINT|Move.DRY : Move.HINT; JSONObject msg = new JSONObject(); - if(isValue){ - msg.put("type", HINT_VALUE); + try { + if(isValue){ + msg.put("type", Move.HINT_VALUE); + } else { + msg.put("type", Move.HINT_COLOR); + } + msg.put("value", value); + msg.put("playerId", playerId); + } catch(JSONException ex) { + throw new InternalHanabiError(ex); } - else { - msg.put("type", HINT_COLOR); - } - msg.put("value", value); - msg.put("playerId", playerId); - send(msgType, msg.toString()); + send(msgType, msg); +// send(msgType, msg.toString()); } - protected void moveHint(Card c){ + protected void moveHint(Card c) throws IOException { moveHint(c, false); } - protected void moveHint(Card c, boolean isDry) { - byte msgType = isDry ? MOVE_HINT | MOVE_DRY : MOVE_HINT; - send(msgType, c.toJson().toString()); + protected void moveHint(Card c, boolean isDry) throws IOException { + byte msgType = isDry ? Move.HINT|Move.DRY : Move.HINT; + send(msgType, c); +// send(msgType, c.toJson().toString()); } - private void send(byte msgType, String msg) { - Log("Client - Sending to server"); - Log("sending " + msg); - DataOutputStream os = null; + private void send(byte moveType, Card c) throws IOException { try { - os = new DataOutputStream(socket.getOutputStream()); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - os.write(MAGIC); - os.write(ServerGame.PROTOCOL.VIBE_DUMB.ordinal()); - os.write(msgType); - os.write(new byte[]{ 0, 0 }); - os.writeUTF(msg); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + send(moveType, c.toJson()); + } catch(JSONException ex) { + throw new InternalHanabiError(ex); } } + private void send(byte moveType, JSONObject jo) throws IOException { + try { + jo.put("origin", ownId); + Move m = new Move(jo); + m.setMoveType(moveType); + send(m); + } catch(JSONException ex) { + throw new InternalHanabiError(ex); + } + } + + private void send(Move m) throws IOException { + client.comm.sendMove(m); + } + +// private void send(byte msgType, String msg) { +// Utils.log(Utils.VERBOSE, "Client - Sending to server"); +// Utils.log(Utils.VERBOSE, "sending " + msg); +// DataOutputStream os = null; +// try { +// os = new DataOutputStream(socket.getOutputStream()); +// } catch (IOException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// try { +// os.write(Move.MAGIC); +// os.write(ServerGame.PROTO_VIBE_DUMB); +// os.write(msgType); +// os.write(new byte[]{ 0, 0 }); +// os.writeUTF(msg); +// } catch (IOException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// } //public abstract void printMessage(String msg, MSG_TYPES type); @@ -176,13 +200,13 @@ protected abstract void createUi(); protected abstract void updateUi(); - public int getDifficulty(){ + public int getDifficulty() { return difficulty; } public abstract void loadFont(); - public int getFontSize(){ + public int getFontSize() { return fontSize; } @@ -191,27 +215,27 @@ return null; } - @Override +// @Override protected void receive(DataInputStream inStream) { - Log("Client - Received data!"); - byte[] magic = new byte[4]; - byte proto = 0; - byte msgType = 0; - byte[] useless = new byte[2]; - try { - inStream.read(magic); - proto = inStream.readByte(); - msgType = inStream.readByte(); - inStream.read(useless); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - Log("Received magic: " + magic[0] + magic[1] + magic[2] + magic[3]); - Log("Received proto: " + proto); - Log("Received msgType: " + msgType); - Log("Received useless: " + useless[0] + useless[1]); +// Utils.log(Utils.VERBOSE, "Client - Received data!"); +// byte[] magic = new byte[4]; +// byte proto = 0; +// byte msgType = 0; +// byte[] useless = new byte[2]; +// try { +// inStream.read(magic); +// proto = inStream.readByte(); +// msgType = inStream.readByte(); +// inStream.read(useless); +// } catch (IOException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// +// Utils.log(Utils.VERBOSE, "Received magic: " + magic[0] + magic[1] + magic[2] + magic[3]); +// Utils.log(Utils.VERBOSE, "Received proto: " + proto); +// Utils.log(Utils.VERBOSE, "Received msgType: " + msgType); +// Utils.log(Utils.VERBOSE, "Received useless: " + useless[0] + useless[1]); // TODO check Protocol // switch(proto){ @@ -219,53 +243,61 @@ // break; // } - String msg = null; - JSONObject jo = null; - try { - msg = inStream.readUTF(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - // TODO error handling - if(msg != null){ - Log("The not null msg is: " + msg); - jo = new JSONObject(msg); - } - else return; +// String msg = null; +// JSONObject jo = null; +// try { +// msg = inStream.readUTF(); +// } catch (IOException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// // TODO error handling +// if(msg != null){ +// Utils.log(Utils.VERBOSE, "The not null msg is: " + msg); +// jo = new JSONObject(msg); +// } +// else return; // TODO MOVE_DRY - switch(msgType){ - case CONNECTION_VALID: - isConnected = true; - ownId = jo.getInt("id"); - break; - case MOVE_UPDATE: - Log("received update"); - if(!initGameReceived){ - Log("first update!"); - init(); - updateGame(jo); - createUi(); - initGameReceived = true; - break; - } - updateGame(jo); - updateUi(); - break; - } +// switch(msgType){ +// case CONNECTION_VALID: +// isConnected = true; +// ownId = jo.getInt("id"); +// break; +// case Move.UPDATE: +// Utils.log(Utils.VERBOSE, "received update"); +// if(!initGameReceived){ +// Utils.log(Utils.VERBOSE, "first update!"); +// init(); +// updateGame(jo); +// createUi(); +// initGameReceived = true; +// break; +// } +// updateGame(jo); +// updateUi(); +// break; +// } } - protected int getPlayersSightId(int id){ + protected int getPlayersSightId(int id) { // TODO get the id of a player with ownId as 0-based index return (id + (nPlayers - ownId)) % nPlayers; } - protected int getRealId(int id){ + protected int getRealId(int id) { // TODO get the real id of a player return (id + ownId) % nPlayers; } - protected void updateGame(JSONObject jo){ + private void updateGame(Move m) { + try { + updateGame(m.getJson()); + } catch(JSONException ex) { + throw new InternalHanabiError(ex); + } + } + + private void updateGame(JSONObject jo) throws JSONException { nPlayers = jo.getInt("nPlayers"); if(!initGameReceived){ createPlayers(); @@ -273,12 +305,12 @@ // TODO movesLeft = jo.getInt("movesLeft"); nCardsInDeck = jo.getInt("nCardsInDeck"); hints = jo.getInt("hints"); - flashs = jo.getInt("flashs"); + flashs = jo.getInt("nFlashs"); JSONArray trashArray = jo.getJSONArray("trash"); trash.clear(); // TODO depends on protocol version for(int i=0; i= META_FIRST_META; + } + public void setMoveType(byte msgType) { + this.moveType = msgType; + try { + jo.put("msgType", msgType); + } catch (JSONException ex) { + throw new InternalHanabiError(ex); + } + } +} diff --git a/src/server/ServerGame.java b/src/server/ServerGame.java index eabfdf3..fc522a6 100644 --- a/src/server/ServerGame.java +++ b/src/server/ServerGame.java @@ -1,7 +1,7 @@ package server; -import java.io.DataInputStream; -import java.io.DataOutputStream; +import java.io.PrintStream; + import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; @@ -11,77 +11,198 @@ import org.json.JSONArray; import org.json.JSONObject; +import org.json.JSONException; import card.Card; import player.BasePlayer; public class ServerGame extends BaseGame { - ServerSocket server; + public static final byte PROTO_VIBE_DUMB = 1; - public enum PROTOCOL { - VIBE_DUMB - }; + private List cardsInDeck; + private long seed; + private Random rand; - List cardsInDeck; - long seed; - Random rand; + private boolean gameStarted; - int movesLeft; - boolean won; - boolean lost; + private int movesLeft; + private boolean won; + private boolean lost; - List sockets; - - - public static void main(String[] args){ - ServerGame sg = new ServerGame(); - sg.establishConnection(); - while(sg.sockets.size() > 0){ - try { - Thread.sleep(500); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + private ServerSocket tcpServerSocket; + private List 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); } } - public ServerGame() { - } - - protected void establishConnection(){ - port = 1337; - sockets = new ArrayList<>(); + private void startGame() { 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++; - Log(nPlayers + " players connected!"); - // TODO do this somewhere else - Log("Creating new Socket Listener for #" + (sockets.size()-1)); - Log("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"); + tcpServerSocket.close(); + } catch(IOException ex) { + // TODO + Utils.log(Utils.DEBUG, "Could not close tcpServerSocket due to IO Error."); + System.exit(0); } - Log("Initalizing game..."); - init(); - Log("Sending game to clients"); - Log("game: " + toJson().toString()); - String msg = toJson().toString(); - sendToAll(MOVE_UPDATE, msg); - addToHistory(msg); + 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(); @@ -103,84 +224,8 @@ //TODO shuffle } - protected void receive(DataInputStream inStream) { - Log("Server - Receiving"); - byte[] magic = new byte[4]; - byte proto = 0; - byte msgType = 0; - byte[] useless = new byte[2]; - try { - inStream.read(magic); - proto = inStream.readByte(); - msgType = inStream.readByte(); - inStream.read(useless); - } catch (IOException e) { - // TODO Auto-generated catch block - // e.printStackTrace(); - System.err.println("Exception is currently suppressed"); - } - - Log("Received magic: " + magic[0] + magic[1] + magic[2] + magic[3]); - Log("Received proto: " + proto); - Log("Received msgType: " + msgType); - Log("Received useless: " + useless[0] + useless[1]); - - // TODO check Protocol -// switch(proto){ -// case VIBE_DUMB: -// break; -// } - - String msg = null; - JSONObject jo = null; - boolean isValid = true; - boolean wasMove = false; - try { - msg = inStream.readUTF(); - } catch (IOException e) { - // TODO Auto-generated catch block - // e.printStackTrace(); - System.err.println("Exception is currently suppressed"); - } - if(msg != null) jo = new JSONObject(msg); - Log("Server received: " + msg); - // TODO MOVE_DRY - switch(msgType){ - case MOVE_PLACE_CARD: - isValid = placeCard(new Card(jo)); - wasMove = true; - break; - case MOVE_TRASH_CARD: - isValid = moveCardToTrash(new Card(jo)); - wasMove = true; - break; - case MOVE_HINT: - int hintType = jo.getInt("type"); - int value = jo.getInt("value"); - int player = jo.getInt("playerId"); - isValid = giveHint(hintType, value, player); - wasMove = true; - break; - } - Log("game: " + toJson().toString()); - if(!isValid){ - Log("was invalid move"); - sendToAll(MOVE_INVALID); // TODO only send to person who did the move - return; - } - if(wasMove){ - Log("was VALID move"); - currentPlayer++; - if(currentPlayer == nPlayers) currentPlayer = 0; - // TODO notify players - if(movesLeft >= 0) movesLeft--; - String moveMsg = toJson().toString(); - sendToAll(MOVE_UPDATE, moveMsg); - addToHistory(moveMsg); - } - } - protected Card getRealCard(Card c, int[] index){ + // TODO: Get rid of this ridiculous function. Card[] cards = getPlayer(currentPlayer).getCards(); for(int i=0; i MAX_HINTS) - hints = MAX_HINTS; + if(hints > MAX_HINTS) hints = MAX_HINTS; } } - protected void addFlash() { - flashs++; - } + protected void addFlash() { flashs++; } /** * Stores the current state of the game in the history @@ -390,15 +405,18 @@ * @param json * the current state as json object */ - protected void addToHistory(String json) { - history.add(json); +// protected void addToHistory(String json) { +// history.add(json); +// } + protected void addToHistory(Move m) { + history.add(m); } - protected JSONObject toJson(){ + protected JSONObject toJson() throws JSONException { JSONObject root = new JSONObject(); root.put("nCardsInDeck", nCardsInDeck); root.put("hints", hints); - root.put("flashs", flashs); + root.put("nFlashs", flashs); root.put("movesLeft", movesLeft); JSONArray trashArray = new JSONArray(); for(Card c : trash){ @@ -442,4 +460,89 @@ 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); + } } diff --git a/src/server/SwingClientGame.java b/src/server/SwingClientGame.java index 4575a8f..be8b228 100644 --- a/src/server/SwingClientGame.java +++ b/src/server/SwingClientGame.java @@ -8,6 +8,9 @@ import java.awt.Image; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.io.IOException; +import java.net.UnknownHostException; import java.util.List; import javax.swing.BoxLayout; @@ -20,7 +23,6 @@ import javax.swing.SwingUtilities; import org.json.JSONArray; -import org.json.JSONObject; import card.Card; import card.CardInfo; @@ -28,6 +30,9 @@ public class SwingClientGame extends ClientGame { //UI STUFF + JFrame readyFrame; + JButton readyButton; + JFrame mainFrame; JPanel gamePanel; JPanel hintPanel; @@ -51,7 +56,93 @@ private static final ImageIcon FLASH_1 = new ImageIcon(new ImageIcon("flash_1.png").getImage().getScaledInstance(40, 40, Image.SCALE_SMOOTH)); public static void main(String[] args){ - new SwingClientGame(); + int port = 1337; + String hostname = "localhost"; + Connector c = null; + try { + c = new TcpConnector(hostname, port); + } catch(UnknownHostException uhe) { + //FIXME + Utils.log(Utils.DEBUG, "Host not known!"); + System.exit(0); + } catch(IOException ex) { + //FIXME + Utils.log(Utils.DEBUG, "IOException!"); + System.exit(0); + } + Communicator comm = new VibeDumb(c); + Utils.log(Utils.DEBUG, comm.getConnector().toString()); + try { + new SwingClientGame(comm); + } catch(IOException ex) { + //TODO inform user + // comm = ... + // new SwingClientGame(comm); + } + } + + public SwingClientGame(Communicator comm) throws IOException { + super(comm); + readyFrame = new JFrame("Are you ready?"); + readyFrame.setSize(100, 100); + + readyButton = new JButton("Ready!"); + readyButton.addMouseListener(new MouseListener() { + @Override + public void mouseReleased(MouseEvent e) { + if(readyButton.getText().equals("Ready!")){ + readyButton.setText("You are ready."); + sendReady(); + } else { + readyButton.setText("Ready!"); + sendUnReady(); + } + } + + @Override + public void mousePressed(MouseEvent e) { + } + + @Override + public void mouseExited(MouseEvent e) { + } + + @Override + public void mouseEntered(MouseEvent e) { + } + + @Override + public void mouseClicked(MouseEvent e) { + } + }); + + readyFrame.add(readyButton); + readyFrame.validate(); + readyFrame.setVisible(true); + } + + @Override + protected void sendReady() { + while(true) { + try { + super.sendReady(); + break; + } catch(IOException ex) { + if(!handleIOException(ex)) break; + } + } + } + + @Override + protected void sendUnReady() { + while(true) { + try { + super.sendUnReady(); + break; + } catch(IOException ex) { + if(!handleIOException(ex)) break; + } + } } private void createHintUi(){ @@ -106,6 +197,7 @@ @Override protected void createUi() { + readyFrame.setVisible(false); clickedPlayer = 0; layoutConstr = new GridBagConstraints(); mainFrame = new JFrame("Hanabi - Player " + (ownId+1) + " (" + players[ownId].getName() + ")"); @@ -263,8 +355,8 @@ } @Override - protected BasePlayer createPlayer(JSONObject jo){ - return new SwingPlayer(jo); + protected BasePlayer createPlayer(Move m){ + return new SwingPlayer(m); } @Override @@ -341,8 +433,8 @@ } } - public SwingPlayer(JSONObject jo) { - super(jo); + public SwingPlayer(Move m) { + super(m); playerUi = new JPanel(); playerUi.setBackground(BACKGROUND); playerName = new JButton(name); @@ -363,7 +455,6 @@ @Override public void setCards(JSONArray ja){ super.setCards(ja); - Log("set Swing cards"); for(int i=0; i logLevel) return; + if(logTimeStamp) { + SimpleDateFormat fmt = new SimpleDateFormat("[HH:mm:ss]: "); + str.print( fmt.format(new Date()) ); + } + str.println(msg); + } + + public static void sleep(long ms) { + try { + Thread.sleep(ms); + } catch(InterruptedException ex) { + log(INFO, Msgs.sleepInterrupted()); + } + } +} diff --git a/src/server/VibeDumb.java b/src/server/VibeDumb.java new file mode 100644 index 0000000..858c408 --- /dev/null +++ b/src/server/VibeDumb.java @@ -0,0 +1,86 @@ +package server; + +import java.io.IOException; +import java.io.DataInputStream; +import java.io.DataOutputStream; + +import org.json.JSONObject; +import org.json.JSONException; + +import java.util.Arrays; + +public class VibeDumb implements Communicator { + public static final byte[] MAGIC = new byte[]{ 1, 3, 3, 7 }; + + private Connector conn; + private DataInputStream inStream; + private DataOutputStream outStream; + + public VibeDumb(Connector c) { + conn = c; + } + + public void sendMove(Move m) throws IOException { + if(outStream == null) { + outStream = conn.getOutputStream(); + } + byte version = 0; + byte msgType = m.getMoveType(); + byte[] unused = new byte[]{ 0, 0 }; + String msg = m.toJsonString(); + outStream.write(MAGIC); + outStream.write(version); + outStream.write(msgType); + outStream.write(unused); + outStream.writeUTF(msg); + } + public Move receiveMove() throws IOException { + if(inStream == null) { + inStream = conn.getInputStream(); + } + byte[] magic = new byte[4]; + byte version = 0; + byte msgType = 0; + byte[] unused = new byte[2]; + inStream.read(magic); + if(!isMagicCorrect(magic)) { + throw new InternalHanabiError(); // TODO + } + version = inStream.readByte(); + switch(version) { + case 0: // TODO magic number? + break; + default: + throw new InternalHanabiError(); // TODO + } + msgType = inStream.readByte(); + inStream.read(unused); + String msg = inStream.readUTF(); // TODO ddos? + if(msg == null) throw new InternalHanabiError(); // TODO + try { + Move m; + if(msg.equals("")) { + m = new Move(msgType); + } else { + JSONObject jo = new JSONObject(msg); + m = new Move(jo); + m.setMoveType(msgType); + } + return m; + } catch(JSONException ex) { + throw new InternalHanabiError(ex); + } + } + public void establish() throws IOException { + // FIXME + conn.establishConnection(); + } + private static boolean isMagicCorrect(byte[] magic) { + return Arrays.equals(MAGIC, magic); + } + + @Override + public Connector getConnector() { + return conn; + } +}