diff --git a/net/RemotePlayerClient.java b/net/RemotePlayerClient.java new file mode 100644 index 0000000..a6e338f --- /dev/null +++ b/net/RemotePlayerClient.java @@ -0,0 +1,157 @@ +package ch.epfl.javass.net; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.UncheckedIOException; +import java.net.Socket; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import ch.epfl.javass.jass.Card; +import ch.epfl.javass.jass.CardSet; +import ch.epfl.javass.jass.Player; +import ch.epfl.javass.jass.PlayerId; +import ch.epfl.javass.jass.Score; +import ch.epfl.javass.jass.TeamId; +import ch.epfl.javass.jass.Trick; +import ch.epfl.javass.jass.TurnState; +import ch.epfl.javass.jass.Card.Color; +import static ch.epfl.javass.net.StringSerializer.*; + +/** + * A player that plays on a remote server. + * @author Charles BEAUVILLE + * @author Celia HOUSSIAUX + * + */ +public final class RemotePlayerClient implements Player, AutoCloseable{ + + private final static int PORT_NUMBER = 5108; + + private final Socket s; + private final BufferedReader r; + private final BufferedWriter w; + + /** + * Creates a player that plays on a given host server. + * @param host a string the adress of the host server. + * @throws UnknownHostException if the host ip is wrong. + * @throws IOException if the signal has been interrupted. + */ + public RemotePlayerClient(String host) throws UnknownHostException, IOException { + + this.s = new Socket(host, PORT_NUMBER); + this.r = + new BufferedReader( + new InputStreamReader(s.getInputStream(), + StandardCharsets.UTF_8)); + this.w = + new BufferedWriter( + new OutputStreamWriter(s.getOutputStream(), + StandardCharsets.UTF_8)); + } + + @Override + public void setPlayers(PlayerId ownId, Map playerNames) { + String cmd = JassCommand.PLRS.toString(); + String id = serializeInt(ownId.ordinal()); + String[] names = new String[PlayerId.COUNT]; + + for (int i = 0; i < names.length; i++) + names[i] = serializeString(playerNames.get(PlayerId.ALL.get(i))); + + String combinedNames = combine(',', names); + String[] msg = new String[]{cmd, id, combinedNames}; + + sendMsg(combineMsg(msg)); + } + + @Override + public void setWinningTeam(TeamId winningTeam) { + String cmd = JassCommand.WINR.toString(); + String winningTeamIndex = serializeInt(winningTeam.ordinal()); + String[] msg = new String[]{cmd, winningTeamIndex}; + + sendMsg(combineMsg(msg)); + } + + @Override + public void setTrump(Color trump) { + String cmd = JassCommand.TRMP.toString(); + String trumpIndex = serializeInt(trump.ordinal()); + String[] msg = new String[]{cmd, trumpIndex}; + + sendMsg(combineMsg(msg)); + } + + @Override + public void updateHand(CardSet newHand) { + String cmd = JassCommand.HAND.toString(); + String hand = serializeLong(newHand.packed()); + String[] msg = new String[]{cmd, hand}; + + sendMsg(combineMsg(msg)); + } + + @Override + public void updateScore(Score score) { + String cmd = JassCommand.SCOR.toString(); + String s = serializeLong(score.packed()); + String[] msg = new String[]{cmd, s}; + + sendMsg(combineMsg(msg)); + } + + @Override + public void updateTrick(Trick newTrick) { + String cmd = JassCommand.TRCK.toString(); + String trick = serializeInt(newTrick.packed()); + String[] msg = new String[]{cmd, trick}; + + sendMsg(combineMsg(msg)); + } + + @Override + public Card cardToPlay(TurnState state, CardSet hand) { + String cmd = JassCommand.CARD.toString(); + String s = combine(',', new String[]{serializeLong(state.packedScore()), serializeLong(state.packedUnplayedCards()), serializeInt(state.packedTrick())}); + String h = serializeLong(hand.packed()); + String[] msg = new String[]{cmd, s, h}; + String c = "0"; + + sendMsg(combineMsg(msg)); + + try { + c = r.readLine(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return Card.ofPacked(deserializeInt(c)); + } + + @Override + public void close() throws Exception { + w.close(); + r.close(); + s.close(); + } + + private String combineMsg(String[] strings) { + return combine(' ', strings); + } + + private void sendMsg(String msg) { + try { + w.write(msg); + w.newLine(); + w.flush(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/net/RemotePlayerServer.java b/net/RemotePlayerServer.java new file mode 100644 index 0000000..0094dce --- /dev/null +++ b/net/RemotePlayerServer.java @@ -0,0 +1,132 @@ +package ch.epfl.javass.net; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import static ch.epfl.javass.net.StringSerializer.*; + +import ch.epfl.javass.jass.Card; +import ch.epfl.javass.jass.Card.Color; +import ch.epfl.javass.jass.CardSet; +import ch.epfl.javass.jass.Player; +import ch.epfl.javass.jass.PlayerId; +import ch.epfl.javass.jass.Score; +import ch.epfl.javass.jass.TeamId; +import ch.epfl.javass.jass.Trick; +import ch.epfl.javass.jass.TurnState; + +/** + * Connects to a remote client and executes functions according to the data + * sent. + * + * @author Charles BEAUVILLE + * @author Celia HOUSSIAUX + * + */ +public final class RemotePlayerServer { + + private static final int PORT_NUMBER = 5108; + private static final int COMMAND_INDEX = 0; + private static final int ARG_0_INDEX = 0; + private static final int ARG_1_INDEX = 1; + private static final int ARG_2_INDEX = 2; + + private Player localPlayer; + + /** + * Connects to a remote client and executes functions of the given player + * according to the data sent. + * + * @param p + * the underlying player. + */ + public RemotePlayerServer(Player p) { + this.localPlayer = p; + } + + /** + * Runs the server on the port number. + * @throws IOException + */ + public void run() throws IOException { + @SuppressWarnings("resource") + //Listens for a connection on the port number. + ServerSocket s0 = new ServerSocket(PORT_NUMBER); + Socket s = s0.accept(); + + //Sets up the buffers to read and to write on the socket. + BufferedReader r = new BufferedReader(new InputStreamReader( + s.getInputStream(), StandardCharsets.UTF_8)); + BufferedWriter w = new BufferedWriter(new OutputStreamWriter( + s.getOutputStream(), StandardCharsets.UTF_8)); + + + String command; + + //Reads the commands wrote on the socket line per line + while ((command = r.readLine()) != null) { + String[] msg = split(' ', command); + + switch (JassCommand.valueOf(msg[COMMAND_INDEX])) { + + case PLRS: + + //Creates the players map with the arguments and calls the setPlayers function of the underlyingPlayer. + Map players = new HashMap<>(); + for (int i = 0; i < PlayerId.COUNT; i++) { + players.put(PlayerId.ALL.get(i), + deserializeString(split(',', msg[ARG_2_INDEX])[i])); + } + localPlayer.setPlayers(PlayerId.ALL.get(deserializeInt(msg[ARG_1_INDEX])), + players); + break; + + case CARD: + + //Calls the cardToPlay function of the underlying function with the arguments of the message. + String[] st = split(',', msg[ARG_1_INDEX]); + TurnState state = TurnState.ofPackedComponents( + deserializeLong(st[ARG_0_INDEX]), deserializeLong(st[ARG_1_INDEX]), + deserializeInt(st[ARG_2_INDEX])); + Card c = localPlayer.cardToPlay(state, + CardSet.ofPacked(deserializeLong(msg[ARG_2_INDEX]))); + w.write(serializeInt(c.packed())); + w.newLine(); + w.flush(); + break; + + case HAND: + localPlayer + .updateHand(CardSet.ofPacked(deserializeLong(msg[ARG_1_INDEX]))); + break; + + case TRMP: + localPlayer.setTrump(Color.ALL.get(deserializeInt(msg[ARG_1_INDEX]))); + break; + + case SCOR: + localPlayer + .updateScore(Score.ofPacked(deserializeLong(msg[ARG_1_INDEX]))); + break; + + case TRCK: + localPlayer.updateTrick(Trick.ofPacked(deserializeInt(msg[ARG_1_INDEX]))); + break; + + case WINR: + localPlayer + .setWinningTeam(TeamId.ALL.get(deserializeInt(msg[ARG_1_INDEX]))); + break; + + default: + } + } + } +} diff --git a/net/StringSerializer.java b/net/StringSerializer.java new file mode 100644 index 0000000..8fde84f --- /dev/null +++ b/net/StringSerializer.java @@ -0,0 +1,93 @@ +package ch.epfl.javass.net; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * Methods to serialize object into byte sequences to transmit them in a network. + * @author Charles BEAUVILLE + * @author Celia HOUSSIAUX + * + */ +public final class StringSerializer { + + private static final int HEX_RADIX = 16; + + private StringSerializer() {} + + /** + * Serializes a given {@link Integer} into its base 16 textual representation. + * @param i the {@link Integer} to serialize + * @return a {@link String} the base 16 representation of the given int. + */ + public static String serializeInt(int i) { + return Integer.toUnsignedString(i, HEX_RADIX); + } + + /** + * Takes a the string representation of an int and returns the actual {@link Integer}. + * @param s the string that needs to be deserialized into an {@link Integer}. + * @return the {@link Integer} represented by the string. + */ + public static int deserializeInt(String s) { + return Integer.parseUnsignedInt(s, HEX_RADIX); + } + + /** + * Serializes a given {@link Long} into its base 16 textual representation. + * @param l the {@link Long} to serialize. + * @return a {@link String} the base 16 representation of the given long. + */ + public static String serializeLong(long l) { + return Long.toUnsignedString(l, HEX_RADIX); + } + + /** + * Takes a the string representation of an int and returns the actual {@link Long}. + * @param s the string that needs to be deserialized into an {@link Long}. + * @return the {@link Long} represented by the string. + */ + public static long deserializeLong(String s) { + return Long.parseLong(s, HEX_RADIX); + } + + /** + * Encodes the given string to base 64. + * @param s the string to encode. + * @return the encoded string. + */ + public static String serializeString(String s) { + Base64.Encoder e = Base64.getEncoder(); + return e.encodeToString(s.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Decodes the given string from base 64. + * @param s the string to decode. + * @return the decoded string. + */ + public static String deserializeString(String s) { + Base64.Decoder d = Base64.getDecoder(); + return new String(d.decode(s), StandardCharsets.UTF_8); + } + + /** + * Combines the strings given in the array, separated by the given character. + * @param c the character to separate the strings. + * @param strings the array of strings to be combined. + * @return a string constiting of the strings of the array separated by the given character. + */ + public static String combine(char c, String[] strings) { + return String.join(String.valueOf(c), strings); + } + + /** + * Splits at the given character a string into an array of strings. + * @param c the character that separates the strings. + * @param s the string to be split. + * @return the array of the strings in between the given character. + */ + public static String[] split(char c, String s) { + return s.split(String.valueOf(c)); + } +}