diff --git a/src/MainTest.java b/src/MainTest.java deleted file mode 100644 index 73bbcbb..0000000 --- a/src/MainTest.java +++ /dev/null @@ -1,40 +0,0 @@ -import java.io.File; -import java.io.FileReader; -import java.io.IOException; - -import org.json.JSONException; -import org.json.JSONObject; -import org.json.JSONTokener; - -import card.Card; -import player.BasePlayer; - -public class MainTest { - private static class Opts { - String playerFile; - - Opts(String[] args) { - // TODO dummy - // TODO error handling here, ctor is assumed to never fail - playerFile = "test.json"; - } - } - - public static void main(String[] args) { - Opts opts = new Opts(args); - JSONTokener jt = null; - try { - FileReader fr = new FileReader(new File(opts.playerFile)); - jt = new JSONTokener(fr); - } catch (JSONException | IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - JSONObject o = new JSONObject(jt); - - BasePlayer p = new BasePlayer(o); - - System.out.println(p.toJson().toString(4)); - } - -} diff --git a/src/card/Card.java b/src/card/Card.java index 66d50c9..003ab55 100644 --- a/src/card/Card.java +++ b/src/card/Card.java @@ -3,9 +3,6 @@ import java.util.ArrayList; import java.util.List; -import org.json.JSONArray; -import org.json.JSONObject; - /* * COLORS: * GREEN @@ -31,35 +28,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(Message m) { +// this(); +// try { +// fromJson(m.getJson()); +// } catch(JSONException ex) { +// throw new InternalHanabiError(ex); +// } +// } - public Card(JSONObject jo){ - this(); - fromJson(jo); - } - - 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,61 +82,63 @@ 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(){ - // TODO: handle dummy card - JSONObject root = new JSONObject(); - root.put("value", value); - root.put("id", id); - root.put("color", color); - root.put("cardInfos", cardInfosToJson()); - return root; - } + public int getColor() { return color; } + +// public JSONObject toJson() throws JSONException { +// // TODO: handle dummy card +// JSONObject root = new JSONObject(); +// root.put("value", value); +// root.put("id", id); +// root.put("color", color); +// root.put("cardInfos", cardInfosToJson()); +// return root; +// } - private JSONArray cardInfosToJson(){ - JSONArray infoArray = new JSONArray(); - if(cardInfos == null) return infoArray; - for(CardInfo ci : cardInfos){ - infoArray.put(ci.toJson()); - } - return infoArray; - } +// private JSONArray cardInfosToJson() throws JSONException { +// JSONArray infoArray = new JSONArray(); +// if(cardInfos == null) return infoArray; +// for(CardInfo ci : cardInfos){ +// infoArray.put(ci.toJson()); +// } +// return infoArray; +// } - private void fromJson(JSONObject jo){ - // TODO: handle dummy card - cardInfos = new ArrayList<>(); - int color = jo.getInt("color"); - int value = jo.getInt("value"); - int id = jo.getInt("id"); - JSONArray cardInfoArray = jo.getJSONArray("cardInfos"); - for(int i=0; i(); +// int color = jo.getInt("color"); +// int value = jo.getInt("value"); +// int id = jo.getInt("id"); +// 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 processMsg(Message 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..661f999 --- /dev/null +++ b/src/server/Client.java @@ -0,0 +1,75 @@ +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, boolean isServer, int id) { + this(game, ctorHelper(conn, proto, isServer, id), id); + } + + private static Communicator ctorHelper(Connector conn, byte proto, boolean isServer, int id) { + Communicator comm = null; + switch(proto) { + case ServerGame.PROTO_VIBE_DUMB: + if(isServer) { + comm = new ServerGame.VibeDumbServer(conn, id); + } else { + 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(); + } + + private void listen() { + while(keepRunning) { + Message m = null; + boolean retry = true; + while(retry){ + try { + m = comm.receiveMsg(); + break; + } catch(IOException ex) { + retry = game.handleIOException(ex); + if(!keepRunning) return; + } + } + game.processMsg(m); + } + } + + public int getOrigin() { return id; } + public void setOrigin(int newId) { id = newId; } // TODO: remove this method? +} diff --git a/src/server/ClientGame.java b/src/server/ClientGame.java index be62df1..d54db0b 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); + Message m = new Message(jo); + m.setMsgType(Message.MSG_READY); + send(m); + } catch(JSONException ex) { + throw new InternalHanabiError(ex); + } + } + + protected void sendUnReady() throws IOException { + try { + JSONObject jo = new JSONObject(); + jo.put("origin", ownId); + Message m = new Message(jo); + m.setMsgType(Message.MSG_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 ? Message.PLACE_CARD | Message.DRY : Message.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 ? Message.TRASH_CARD | Message.DRY : Message.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 ? Message.HINT|Message.DRY : Message.HINT; JSONObject msg = new JSONObject(); - if(isValue){ - msg.put("type", HINT_VALUE); + try { + if(isValue){ + msg.put("type", Message.HINT_VALUE); + } else { + msg.put("type", Message.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 ? Message.HINT|Message.DRY : Message.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 msgType, 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(msgType, c.toJson()); + } catch(JSONException ex) { + throw new InternalHanabiError(ex); } } + private void send(byte msgType, JSONObject jo) throws IOException { + try { + jo.put("origin", ownId); + Message m = new Message(jo); + m.setMsgType(msgType); + send(m); + } catch(JSONException ex) { + throw new InternalHanabiError(ex); + } + } + + private void send(Message m) throws IOException { + client.comm.sendMsg(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(Message 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 MSG_LAST_MSG) throw new RuntimeException("Invalid message Type " + b + "."); + } + + private byte msgType; + + private Move move; + + /* ClientGame fields: */ + private int ownId; + private int leftId; + private InitialPlayer[] initialPlayers; + private NewCard newCard; + + /* ServerGame fields: */ + private int id; + + public Message(byte msgType) { + throwOnInvalidMsgType(msgType); + setMsgType(msgType); + } + + public Message(Move m) { + setMsgType(MSG_MOVE); + move = m; // TODO: clone? + } + + public static class InitialPlayer { + private int id; + private String name; + private Card[] cards; + InitialPlayer(int i, String n, Card[] cs) { + id = i; + name = n; + cards = cs; + } + int getId() { return id; } + String getName() { return name; } + Card[] getCards() { return cards; } + } + + public static class NewCard { + private int id; + private Card c; + NewCard(int id, Card c) { + this.id = id; + this.c = c; + } + int getId() { return id; } + Card getCard() { return c; } + } + + public Message(InitialPlayer[] players) { + this(MSG_INITIAL_GAME); + initialPlayers = players; + } + + public Message(int id, Card c) { this(new NewCard(id, c)); } + public Message(NewCard c) { this(MSG_CARD); newCard = c; } + + public Message(byte b, int leftId) { + this(b); + if(b != MSG_LEFT) { + throw new InternalHanabiError("Attempt to create non-LEFT message with leftId."); + } + this.leftId = leftId; + } + + public static Message makeInvalidMoveAnswer(Message m) { + return new Message(MSG_INVALID); + } + + public static Message makeItsYourTurnMessage() { + return new Message(MSG_YOURTURN); + } + + //public Message(JSONObject jo) throws JSONException { + //this.jo = (JSONObject)jo.clone(); + //this.jo = jo; // TODO: clone? +// moveType = (byte)jo.getInt("msgType"); + //} + +// public static Message newMove(Card c) { +// try { +// return new Message(c.toJson()); +// } catch(JSONException ex) { +// throw new InternalHanabiError(ex); +// } +// } + + public Message(byte[] bytes) { + throw new RuntimeException("Not implemented. This is a bug."); + } + + public byte[] toBytes() { + return toBytes(ServerGame.PROTO_VIBE_DUMB); + } + + public byte[] toBytes(byte proto) { + switch(proto) { + case ServerGame.PROTO_VIBE_DUMB: + throw new RuntimeException("Not implemented. This is a bug."); + default: + throw new InternalHanabiError("Message.toBytes(byte) called with invalid protocol code " + proto + "."); + } + } + +// public JSONObject getJson() { +// return jo; +// } +// public String toJsonString() { +// if(jo == null) return ""; +// return jo.toString(); +// } + + public byte getMsgType() { return msgType; } + + public int getOwnId() { + if(msgType != MSG_CONNECTION_VALID) { + throw new RuntimeException("Internal Error: ownId of non-connection valid message requested."); + } + return ownId; + } + public void setOwnId(int id) { + if(msgType != MSG_CONNECTION_VALID) { + throw new RuntimeException("Internal Error: Attempt to set ownId of non-connection valid message."); + } + ownId = id; + } + + public int getOrigin() { + // TODO: Should we throw if we're called by ClientGame? (if so, how?) + return id; + } + public void setOrigin(int newId) { + // TODO: cf. getOrigin() + id = newId; + } + + public boolean isNonMove() { + return msgType != MSG_MOVE; + } + + public void setMsgType(byte msgType) { + this.msgType = msgType; + } + + public Move getMove() { + return move; + } + + public void setMove(Move move) { + this.move = move; // TODO clone? + } + + public InitialPlayer[] getInitialPlayers() { + if(msgType != MSG_INITIAL_GAME) { + throw new InternalHanabiError("Attempt to get initial players from non-INITIAL_GAME message."); + } + return initialPlayers; + } + + public int getLeftId() { + if(msgType != MSG_LEFT) { + throw new InternalHanabiError("Attempt to get leftId of non-LEFT message."); + } + return leftId; + } + + public Card getNewCard() { + if(msgType != MSG_CARD) { + throw new InternalHanabiError("Attempt to get card of new card of non-CARD message."); + } + return newCard.getCard(); + } + public int getNewCardPlayerId() { + if(msgType != MSG_CARD) { + throw new InternalHanabiError("Attempt to get player id of new card of non-CARD message."); + } + return newCard.getId(); + } + +// public int getOrigin() { +// return origin; +//// try { +//// return jo.getInt("origin"); +//// } catch(JSONException ex) { +//// throw new InternalHanabiError(ex); +//// } +// } + +// public void setOrigin(int orig) { origin = orig; } + +// public int getHintType() { +// if(msgType != MSG_MOVE) { +// throw new RuntimeException("Internal Error: Hint type of non-hint move requested."); +// } +// return move.getHintType(); +// } +// +// public int getValue() { +// if(msgType != Move.HINT) { +// throw new RuntimeException("Internal Error: Hint value of non-hint message requested."); +// } +// try { +// return jo.getInt("value"); +// } catch(JSONException ex) { +// throw new InternalHanabiError(ex); +// } +// } +// public int getHintRecipient() { +// if(msgType != Move.HINT) { +// throw new RuntimeException("Internal Error: Target player ID of non-hint message requested."); +// } +// try { +// return jo.getInt("playerId"); +// } catch(JSONException ex) { +// throw new InternalHanabiError(ex); +// } +// } +} \ No newline at end of file diff --git a/src/server/Move.java b/src/server/Move.java new file mode 100644 index 0000000..02cdd90 --- /dev/null +++ b/src/server/Move.java @@ -0,0 +1,70 @@ +package server; + +public class Move { + public static final byte[] MAGIC = { 1, 3, 3, 7 }; + + public static final byte DRY = 1 << 5; + public static final byte PLACE_CARD = 3; + public static final byte TRASH_CARD = 4; + public static final byte HINT = 5; + + public static final byte HINT_COLOR = 0; + public static final byte HINT_VALUE = 1; + + private int moveType; + + private int hintType; + private int hintRecipient; + private int value; + private int color; + + public Move(byte mT) { + setMoveType(mT); + } + + public int getMoveType() { return moveType; } + + public int getHintType() { + if(moveType != HINT) { + throw new RuntimeException("Internal Error: Hint type of non-hint move requested."); + } + return hintType; + } + public int getValue() { + if(moveType == HINT && hintType == HINT_COLOR) { + throw new RuntimeException("Internal Error: Value of color hint requested."); + } + return value; + } + + public int getColor() { + if(moveType == HINT && hintType == HINT_VALUE) { + throw new RuntimeException("Internal Error: Color of value hint requested."); + } + return color; + } + + public int getHintRecipient() { + if(moveType != HINT) { + throw new RuntimeException("Internal Error: Target player ID of non-hint move requested."); + } + return hintRecipient; + } + + public void setMoveType(byte moveType) { + this.moveType = moveType; + } + + public void setHintType(int type) { + if(moveType != HINT) throw new InternalHanabiError("Attempt to set hint type of non-hint."); + hintType = type; + } + + public void setColor(int color) { + this.color = color; + } + + public void setValue(int value) { + this.value = value; + } +} diff --git a/src/server/ServerGame.java b/src/server/ServerGame.java index eabfdf3..8561afa 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; @@ -9,79 +9,217 @@ import java.util.List; import java.util.Random; -import org.json.JSONArray; -import org.json.JSONObject; - 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 boolean won; + private boolean lost; - List sockets; + 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 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; +// } +// } - - 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(); - } + 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; } } - public ServerGame() { + 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); + } } - 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 +241,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,48 +422,14 @@ * @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(Message m) { + history.add(m.getMove()); } - protected JSONObject toJson(){ - JSONObject root = new JSONObject(); - root.put("nCardsInDeck", nCardsInDeck); - root.put("hints", hints); - root.put("flashs", 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(); } + + 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); + } } diff --git a/src/server/SwingClientGame.java b/src/server/SwingClientGame.java index 4575a8f..43a340d 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(Message m){ + return new SwingPlayer(m); } @Override @@ -341,8 +433,8 @@ } } - public SwingPlayer(JSONObject jo) { - super(jo); + public SwingPlayer(Message 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..2d20207 --- /dev/null +++ b/src/server/VibeDumb.java @@ -0,0 +1,229 @@ +package server; + +import java.io.IOException; +import java.io.DataInputStream; +import java.io.DataOutputStream; + +import org.json.JSONObject; + +import card.Card; +import card.CardInfo; +import player.BasePlayer; + +import org.json.JSONArray; +import org.json.JSONException; + +import java.util.Arrays; +import java.util.List; + +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 sendMsg(Message m) throws IOException { + if(outStream == null) { + outStream = conn.getOutputStream(); + } + byte version = 0; + byte msgType = m.getMsgType(); + byte[] unused = new byte[]{ 0, 0 }; + outStream.write(MAGIC); + outStream.write(version); + outStream.write(msgType); + outStream.write(unused); + if(msgType == Message.MSG_MOVE) outStream.writeUTF(moveToJsonString(m.getMove())); + else if(msgType == Message.MSG_CONNECTION_VALID) outStream.writeInt(m.getOwnId()); + } + + public Message receiveMsg() 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("Malformed message (wrong magic number)."); + } + version = inStream.readByte(); + switch(version) { + case 0: // TODO magic number? + break; + default: + throw new InternalHanabiError("Version " + version + " of protocol doesn't exist."); + } + msgType = inStream.readByte(); + inStream.read(unused); + Message m = new Message(msgType); + if(msgType == Message.MSG_MOVE) { + String msg = inStream.readUTF(); // TODO ddos? + if(msg == null) throw new InternalHanabiError("Purported move doesn't exist."); + try { + m.setMove(jsonToMove(new JSONObject(msg))); + } catch(JSONException ex) { + throw new InternalHanabiError(ex); + } + } else if(msgType == Message.MSG_CONNECTION_VALID) { + m.setOwnId(inStream.readInt()); + } + return m; + } + + private static String moveToJsonString(Move move) { + try { + JSONObject jo = new JSONObject(); + byte moveType = (byte) move.getMoveType(); + jo.put(Keys.VibeDumb.MOVE_TYPE, moveType); + // TODO: dry moves + switch(moveType) { + case Move.HINT: + int type = move.getHintType(); + jo.put(Keys.VibeDumb.HINT_TYPE, type); + if(type == Move.HINT_COLOR) { + jo.put(Keys.VibeDumb.COLOR, move.getColor()); + } else if(type == Move.HINT_VALUE) { + jo.put(Keys.VibeDumb.VALUE, move.getValue()); + } else { + throw new InternalHanabiError("Unknown hint type."); + } + break; + case Move.PLACE_CARD: + case Move.TRASH_CARD: + jo.put(Keys.VibeDumb.COLOR, move.getColor()); + jo.put(Keys.VibeDumb.VALUE, move.getValue()); + break; + default: + throw new InternalHanabiError("Move is neither hint nor place nor trash."); + } + return jo.toString(); + } catch(JSONException ex) { + throw new InternalHanabiError(ex); + } + } + + private static Move jsonToMove(JSONObject jo) { + try { + byte moveType = (byte)jo.getInt(Keys.VibeDumb.MOVE_TYPE); + Move move = new Move(moveType); + // TODO: dry moves + switch(moveType) { + case Move.HINT: + int type = jo.getInt(Keys.VibeDumb.HINT_TYPE); + move.setHintType(type); + if(type == Move.HINT_COLOR) { + move.setColor(jo.getInt(Keys.VibeDumb.COLOR)); + } else if(type == Move.HINT_VALUE) { + move.setValue(jo.getInt(Keys.VibeDumb.VALUE)); + } else { + throw new InternalHanabiError("Unknown hint type."); + } + break; + case Move.PLACE_CARD: + case Move.TRASH_CARD: + move.setColor(jo.getInt(Keys.VibeDumb.COLOR)); + move.setValue(jo.getInt(Keys.VibeDumb.VALUE)); + break; + default: + throw new InternalHanabiError("Move is neither hint nor place nor trash."); + } + return move; + } 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; + } + + protected JSONObject serverGameToJson(ServerGame sg) throws JSONException { + JSONObject root = new JSONObject(); + root.put("nCardsInDeck", sg.nCardsInDeck); + root.put("hints", sg.hints); + root.put("nFlashs", sg.flashs); + root.put("movesLeft", sg.movesLeft); + JSONArray trashArray = new JSONArray(); + for(Card c : sg.trash){ + trashArray.put(cardToJson(c)); + } + root.put("trash", trashArray); + + JSONObject deckRow = new JSONObject(); + for(int i=0; i cardInfos = c.getCardInfos(); + if(cardInfos == null) return infoArray; + for(CardInfo ci : cardInfos){ + infoArray.put(cardInfoToJson(ci)); + } + return infoArray; + } + + public JSONObject cardInfoToJson(CardInfo ci) throws JSONException { + JSONObject root = new JSONObject(); + root.put("type", ci.getType()); + root.put("is", ci.isIs()); + root.put("what", ci.getWhat()); + root.put("from", ci.getFrom()); + return root; + } + +// public Message jsonToMsg(JSONObject jo) throws JSONException { +// this.jo = jo; // TODO: clone? +// moveType = (byte)jo.getInt("msgType"); +// } +}