From 26458eafb530582b744929e6b03b0042c977f7cd Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Tue, 10 May 2016 20:31:10 +0200 Subject: Create entry points structures --- src/ch/epfl/xblast/ArgumentChecker.java | 29 +++++++++++++++++++++++++++++ src/ch/epfl/xblast/client/Client.java | 30 ++++++++++++++++++++++++++++++ src/ch/epfl/xblast/client/Main.java | 23 +++++++++++++++++++++++ src/ch/epfl/xblast/server/Main.java | 24 ++++++++++++++++++++++++ src/ch/epfl/xblast/server/Server.java | 30 ++++++++++++++++++++++++++++++ 5 files changed, 136 insertions(+) create mode 100644 src/ch/epfl/xblast/client/Client.java create mode 100644 src/ch/epfl/xblast/client/Main.java create mode 100644 src/ch/epfl/xblast/server/Main.java create mode 100644 src/ch/epfl/xblast/server/Server.java (limited to 'src/ch/epfl') diff --git a/src/ch/epfl/xblast/ArgumentChecker.java b/src/ch/epfl/xblast/ArgumentChecker.java index 7e77b61..56f8acd 100644 --- a/src/ch/epfl/xblast/ArgumentChecker.java +++ b/src/ch/epfl/xblast/ArgumentChecker.java @@ -59,4 +59,33 @@ public final class ArgumentChecker { return s; } + /** + * Returns the element from the array at the requested index, or null if it cannot be retrieved. + * + * @param array the array + * @param index the index + * @param the type of element + * @return the requested element, or null + */ + public static T getOrNull(T[] array, int index) { + if (Objects.isNull(array) || index < 0 || index >= array.length) + return null; + else + return array[index]; + } + + /** + * Parses and returns an integer, or return null on error. + * + * @param str the integer to parse + * @return the parsed integer, or null + */ + public static Integer parseIntOrNull(String str) { + try { + return Integer.parseInt(str); + } catch (NumberFormatException | NullPointerException e) { + return null; + } + } + } diff --git a/src/ch/epfl/xblast/client/Client.java b/src/ch/epfl/xblast/client/Client.java new file mode 100644 index 0000000..dc08f1b --- /dev/null +++ b/src/ch/epfl/xblast/client/Client.java @@ -0,0 +1,30 @@ +package ch.epfl.xblast.client; + +import ch.epfl.xblast.server.Server; + +import java.net.InetSocketAddress; +import java.util.Optional; + +/** + * The Client class. + * + * @author Pacien TRAN-GIRARD (261948) + */ +public class Client { + + private static final String DEFAULT_SERVER_HOST = "localhost"; + private static final int DEFAULT_SERVER_PORT = Server.DEFAULT_PORT; + + private final InetSocketAddress serverAddr; + + public Client(String host, Integer port) { + this.serverAddr = new InetSocketAddress( + Optional.ofNullable(host).orElse(DEFAULT_SERVER_HOST), + Optional.ofNullable(port).orElse(DEFAULT_SERVER_PORT)); + } + + public void run() { + // TODO + } + +} diff --git a/src/ch/epfl/xblast/client/Main.java b/src/ch/epfl/xblast/client/Main.java new file mode 100644 index 0000000..19145c5 --- /dev/null +++ b/src/ch/epfl/xblast/client/Main.java @@ -0,0 +1,23 @@ +package ch.epfl.xblast.client; + +import ch.epfl.xblast.ArgumentChecker; + +/** + * Entry point class of the client. + * + * @author Pacien TRAN-GIRARD (261948) + */ +public final class Main { + + private Main() { + // Static class + } + + public static void main(String[] args) { + String host = ArgumentChecker.getOrNull(args, 0); + Integer port = ArgumentChecker.parseIntOrNull(ArgumentChecker.getOrNull(args, 1)); + + (new Client(host, port)).run(); + } + +} diff --git a/src/ch/epfl/xblast/server/Main.java b/src/ch/epfl/xblast/server/Main.java new file mode 100644 index 0000000..98bbb3d --- /dev/null +++ b/src/ch/epfl/xblast/server/Main.java @@ -0,0 +1,24 @@ +package ch.epfl.xblast.server; + +import ch.epfl.xblast.ArgumentChecker; + +/** + * Entry point class of the server. + * + * @author Pacien TRAN-GIRARD (261948) + */ +public final class Main { + + private Main() { + // Static class + } + + public static void main(String[] args) { + Integer expectedClients = ArgumentChecker.parseIntOrNull(ArgumentChecker.getOrNull(args, 0)); + String iface = ArgumentChecker.getOrNull(args, 1); + Integer port = ArgumentChecker.parseIntOrNull(ArgumentChecker.getOrNull(args, 2)); + + (new Server(iface, port, expectedClients)).run(); + } + +} diff --git a/src/ch/epfl/xblast/server/Server.java b/src/ch/epfl/xblast/server/Server.java new file mode 100644 index 0000000..76f332e --- /dev/null +++ b/src/ch/epfl/xblast/server/Server.java @@ -0,0 +1,30 @@ +package ch.epfl.xblast.server; + +import ch.epfl.xblast.PlayerID; + +import java.net.InetSocketAddress; +import java.util.Optional; + +/** + * The Server class. + * + * @author Pacien TRAN-GIRARD (261948) + */ +public class Server { + + public static final int DEFAULT_PORT = 2016; + public static final int DEFAULT_EXPECTED_CLIENTS = PlayerID.values().length; + + private final InetSocketAddress iface; + private final int expectedClients; + + public Server(String iface, Integer port, Integer expectedClients) { + this.iface = new InetSocketAddress(iface, Optional.ofNullable(port).orElse(DEFAULT_PORT)); + this.expectedClients = Optional.ofNullable(expectedClients).orElse(DEFAULT_EXPECTED_CLIENTS); + } + + public void run() { + // TODO + } + +} -- cgit v1.2.3 From f213ff8993588a9a0d63946e8a4f9b6975de3c95 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Wed, 11 May 2016 13:41:07 +0200 Subject: Add lists utils --- src/ch/epfl/xblast/Lists.java | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) (limited to 'src/ch/epfl') diff --git a/src/ch/epfl/xblast/Lists.java b/src/ch/epfl/xblast/Lists.java index 777fab2..7e3ba5e 100644 --- a/src/ch/epfl/xblast/Lists.java +++ b/src/ch/epfl/xblast/Lists.java @@ -2,7 +2,6 @@ package ch.epfl.xblast; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.IntStream; import java.util.stream.Stream; /** @@ -207,10 +206,36 @@ public final class Lists { if (Objects.isNull(kl) || Objects.isNull(vl) || kl.size() != vl.size()) throw new IllegalArgumentException(); - return Collections.unmodifiableMap(IntStream - .range(0, kl.size()).mapToObj(i -> i) - .filter(i -> Objects.nonNull(vl.get(i))) - .collect(Collectors.toMap(kl::get, vl::get))); + Map m = new HashMap<>(); + Iterator ki = kl.iterator(); + Iterator vi = vl.iterator(); + + while (ki.hasNext() && vi.hasNext()) + m.put(ki.next(), vi.next()); + + return Collections.unmodifiableMap(m); + } + + /** + * Maps linearly two lists, adjusting their respective size by truncating the key list or completing the value list + * with null values. + * + * @param kl the keys list + * @param vl the values list + * @param the key type + * @param the value type + * @return the map + */ + public static Map linearAdjustedMap(List kl, List vl) { + if (Objects.isNull(kl) || Objects.isNull(vl)) + throw new IllegalArgumentException(); + + if (kl.size() <= vl.size()) + return Lists.linearMap(kl, vl.subList(0, kl.size())); + else + return Lists.linearMap( + kl, + concatenated(Arrays.asList(vl, Collections.nCopies(kl.size() - vl.size(), null)))); } /** -- cgit v1.2.3 From 2392d36dfa15a262d69aeb6356408d90d5a32ab2 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Wed, 11 May 2016 13:41:25 +0200 Subject: Add PlayerAction and PlayerID byte conversion --- src/ch/epfl/xblast/PlayerAction.java | 13 ++++++++++++- src/ch/epfl/xblast/PlayerID.java | 13 +++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) (limited to 'src/ch/epfl') diff --git a/src/ch/epfl/xblast/PlayerAction.java b/src/ch/epfl/xblast/PlayerAction.java index 703bc9d..67920eb 100644 --- a/src/ch/epfl/xblast/PlayerAction.java +++ b/src/ch/epfl/xblast/PlayerAction.java @@ -14,6 +14,17 @@ public enum PlayerAction { MOVE_S, MOVE_W, STOP, - DROP_BOMB + DROP_BOMB; + + public static PlayerAction fromByte(byte b) { + if ((int) b < 0 || (int) b >= PlayerAction.values().length) + throw new IllegalArgumentException(); + + return PlayerAction.values()[(int) b]; + } + + public byte toByte() { + return (byte) this.ordinal(); + } } diff --git a/src/ch/epfl/xblast/PlayerID.java b/src/ch/epfl/xblast/PlayerID.java index 9377fdf..96b98e4 100644 --- a/src/ch/epfl/xblast/PlayerID.java +++ b/src/ch/epfl/xblast/PlayerID.java @@ -13,6 +13,8 @@ public enum PlayerID { PLAYER_3, PLAYER_4; + public static final byte OBSERVER = -1; + public Cell initialPosition() { switch (this) { case PLAYER_1: @@ -28,4 +30,15 @@ public enum PlayerID { } } + public static PlayerID fromByte(byte b) { + if ((int) b < 0 || (int) b >= PlayerAction.values().length) + throw new IllegalArgumentException(); + + return PlayerID.values()[(int) b]; + } + + public byte toByte() { + return (byte) this.ordinal(); + } + } -- cgit v1.2.3 From ff03812ce4ccdfb09fd5ac464ff1d993ccc16cb0 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Wed, 11 May 2016 13:41:49 +0200 Subject: Implement server client registration --- src/ch/epfl/xblast/server/Server.java | 83 +++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 4 deletions(-) (limited to 'src/ch/epfl') diff --git a/src/ch/epfl/xblast/server/Server.java b/src/ch/epfl/xblast/server/Server.java index 76f332e..10d5a85 100644 --- a/src/ch/epfl/xblast/server/Server.java +++ b/src/ch/epfl/xblast/server/Server.java @@ -1,9 +1,16 @@ package ch.epfl.xblast.server; +import ch.epfl.xblast.Lists; +import ch.epfl.xblast.PlayerAction; import ch.epfl.xblast.PlayerID; +import java.io.IOException; import java.net.InetSocketAddress; -import java.util.Optional; +import java.net.SocketAddress; +import java.net.StandardProtocolFamily; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; +import java.util.*; /** * The Server class. @@ -13,18 +20,86 @@ import java.util.Optional; public class Server { public static final int DEFAULT_PORT = 2016; - public static final int DEFAULT_EXPECTED_CLIENTS = PlayerID.values().length; + private static final int DEFAULT_EXPECTED_CLIENTS = PlayerID.values().length; + + private static InetSocketAddress listeningInterface(String host, int port) { + if (Objects.isNull(host)) + return new InetSocketAddress(port); + else + return new InetSocketAddress(host, port); + } + + private static DatagramChannel openChannel(InetSocketAddress iface) { + try { + DatagramChannel chan = DatagramChannel.open(StandardProtocolFamily.INET); + chan.bind(iface); + return chan; + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + return null; + } + } + + private static Optional> receiveAction(DatagramChannel chan, boolean block) { + try { + ByteBuffer buf = ByteBuffer.allocate(1); + chan.configureBlocking(block); + SocketAddress client = chan.receive(buf); + PlayerAction action = PlayerAction.fromByte(buf.get(0)); + return Optional.of(new AbstractMap.SimpleImmutableEntry<>(client, action)); + } catch (IOException | IllegalArgumentException e) { + return Optional.empty(); + } + } + + private static Map.Entry acceptAction(DatagramChannel chan) { + Optional> action; + + do { + action = receiveAction(chan, true); + } while (!action.isPresent()); + + return action.get(); + } + + private static SocketAddress acceptRegistration(DatagramChannel chan) { + Map.Entry clientAction; + + do { + clientAction = acceptAction(chan); + } while (clientAction.getValue() != PlayerAction.JOIN_GAME); + + return clientAction.getKey(); + } + + private static List acceptRegistrations(DatagramChannel chan, int registrations) { + List clients = new ArrayList<>(registrations); + + while (clients.size() < registrations) { + SocketAddress client = acceptRegistration(chan); + if (!clients.contains(client)) + clients.add(client); + } + + return Collections.unmodifiableList(clients); + } private final InetSocketAddress iface; private final int expectedClients; + private Map registeredClients; public Server(String iface, Integer port, Integer expectedClients) { - this.iface = new InetSocketAddress(iface, Optional.ofNullable(port).orElse(DEFAULT_PORT)); + this.iface = listeningInterface(iface, Optional.ofNullable(port).orElse(DEFAULT_PORT)); this.expectedClients = Optional.ofNullable(expectedClients).orElse(DEFAULT_EXPECTED_CLIENTS); } public void run() { - // TODO + DatagramChannel chan = openChannel(this.iface); + List clients = acceptRegistrations(chan, this.expectedClients); + this.registeredClients = Lists.linearAdjustedMap(clients, Arrays.asList(PlayerID.values())); + + System.out.println(this.registeredClients); } } -- cgit v1.2.3 From 42098c03b4296e440e2825b7d41965da72a6f970 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Wed, 11 May 2016 15:38:15 +0200 Subject: Implement action collection --- src/ch/epfl/xblast/server/Server.java | 43 +++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) (limited to 'src/ch/epfl') diff --git a/src/ch/epfl/xblast/server/Server.java b/src/ch/epfl/xblast/server/Server.java index 10d5a85..58f3c07 100644 --- a/src/ch/epfl/xblast/server/Server.java +++ b/src/ch/epfl/xblast/server/Server.java @@ -41,14 +41,27 @@ public class Server { } } - private static Optional> receiveAction(DatagramChannel chan, boolean block) { + private static Optional> receiveByte(DatagramChannel chan, boolean block) { try { ByteBuffer buf = ByteBuffer.allocate(1); chan.configureBlocking(block); SocketAddress client = chan.receive(buf); - PlayerAction action = PlayerAction.fromByte(buf.get(0)); - return Optional.of(new AbstractMap.SimpleImmutableEntry<>(client, action)); - } catch (IOException | IllegalArgumentException e) { + + if (Objects.isNull(client) || buf.position() == 0) + throw new IOException(); + + return Optional.of(new AbstractMap.SimpleImmutableEntry<>(client, buf.get(0))); + } catch (IOException e) { + return Optional.empty(); + } + } + + private static Optional> receiveAction(DatagramChannel chan, boolean block) { + try { + Map.Entry actionByte = receiveByte(chan, block).get(); + PlayerAction playerAction = PlayerAction.fromByte(actionByte.getValue()); + return Optional.of(new AbstractMap.SimpleImmutableEntry<>(actionByte.getKey(), playerAction)); + } catch (NoSuchElementException | IllegalArgumentException e) { return Optional.empty(); } } @@ -63,6 +76,19 @@ public class Server { return action.get(); } + private static Map collectActions(DatagramChannel chan) { + Map actions = new HashMap<>(); + Optional> action; + + while (true) { + action = receiveAction(chan, false); + if (!action.isPresent()) break; + actions.put(action.get().getKey(), action.get().getValue()); + } + + return Collections.unmodifiableMap(actions); + } + private static SocketAddress acceptRegistration(DatagramChannel chan) { Map.Entry clientAction; @@ -100,6 +126,15 @@ public class Server { this.registeredClients = Lists.linearAdjustedMap(clients, Arrays.asList(PlayerID.values())); System.out.println(this.registeredClients); + + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + Map actions = collectActions(chan); + System.out.println(actions); } } -- cgit v1.2.3 From 21b25ef18f9a4c11cf0066b2d750a36e7fad2533 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Wed, 11 May 2016 16:05:05 +0200 Subject: Refactor server class --- src/ch/epfl/xblast/server/Server.java | 177 ++++++++++++++++++++-------------- 1 file changed, 103 insertions(+), 74 deletions(-) (limited to 'src/ch/epfl') diff --git a/src/ch/epfl/xblast/server/Server.java b/src/ch/epfl/xblast/server/Server.java index 58f3c07..6d1d62f 100644 --- a/src/ch/epfl/xblast/server/Server.java +++ b/src/ch/epfl/xblast/server/Server.java @@ -22,119 +22,148 @@ public class Server { public static final int DEFAULT_PORT = 2016; private static final int DEFAULT_EXPECTED_CLIENTS = PlayerID.values().length; - private static InetSocketAddress listeningInterface(String host, int port) { - if (Objects.isNull(host)) - return new InetSocketAddress(port); - else - return new InetSocketAddress(host, port); - } + private static class Channel { - private static DatagramChannel openChannel(InetSocketAddress iface) { - try { - DatagramChannel chan = DatagramChannel.open(StandardProtocolFamily.INET); - chan.bind(iface); - return chan; - } catch (IOException e) { - e.printStackTrace(); - System.exit(1); - return null; + private static InetSocketAddress listeningInterface(String host, int port) { + if (Objects.isNull(host)) + return new InetSocketAddress(port); + else + return new InetSocketAddress(host, port); } - } - private static Optional> receiveByte(DatagramChannel chan, boolean block) { - try { - ByteBuffer buf = ByteBuffer.allocate(1); - chan.configureBlocking(block); - SocketAddress client = chan.receive(buf); + private static DatagramChannel openChannel(InetSocketAddress iface) { + try { + DatagramChannel chan = DatagramChannel.open(StandardProtocolFamily.INET); + chan.bind(iface); + return chan; + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + return null; + } + } - if (Objects.isNull(client) || buf.position() == 0) - throw new IOException(); + private final DatagramChannel channel; - return Optional.of(new AbstractMap.SimpleImmutableEntry<>(client, buf.get(0))); - } catch (IOException e) { - return Optional.empty(); + Channel(InetSocketAddress iface) { + this.channel = openChannel(iface); } - } - private static Optional> receiveAction(DatagramChannel chan, boolean block) { - try { - Map.Entry actionByte = receiveByte(chan, block).get(); - PlayerAction playerAction = PlayerAction.fromByte(actionByte.getValue()); - return Optional.of(new AbstractMap.SimpleImmutableEntry<>(actionByte.getKey(), playerAction)); - } catch (NoSuchElementException | IllegalArgumentException e) { - return Optional.empty(); + Channel(String host, Integer port) { + this(listeningInterface(host, Optional.ofNullable(port).orElse(DEFAULT_PORT))); + } + + void closeChannel() { + try { + this.channel.close(); + } catch (IOException e) { + e.printStackTrace(); + } } - } - private static Map.Entry acceptAction(DatagramChannel chan) { - Optional> action; + List acceptRegistrations(int registrations) { + List clients = new ArrayList<>(registrations); - do { - action = receiveAction(chan, true); - } while (!action.isPresent()); + while (clients.size() < registrations) { + SocketAddress client = this.acceptRegistration(); + if (!clients.contains(client)) + clients.add(client); + } - return action.get(); - } + return Collections.unmodifiableList(clients); + } - private static Map collectActions(DatagramChannel chan) { - Map actions = new HashMap<>(); - Optional> action; + Map collectActions() { + Map actions = new HashMap<>(); + Optional> action; - while (true) { - action = receiveAction(chan, false); - if (!action.isPresent()) break; - actions.put(action.get().getKey(), action.get().getValue()); + while (true) { + action = this.receiveAction(false); + if (!action.isPresent()) break; + actions.put(action.get().getKey(), action.get().getValue()); + } + + return Collections.unmodifiableMap(actions); } - return Collections.unmodifiableMap(actions); - } + private Optional> receiveByte(boolean block) { + try { + ByteBuffer buf = ByteBuffer.allocate(1); + this.channel.configureBlocking(block); + SocketAddress client = this.channel.receive(buf); - private static SocketAddress acceptRegistration(DatagramChannel chan) { - Map.Entry clientAction; + if (Objects.isNull(client) || buf.position() == 0) + throw new IOException(); - do { - clientAction = acceptAction(chan); - } while (clientAction.getValue() != PlayerAction.JOIN_GAME); + return Optional.of(new AbstractMap.SimpleImmutableEntry<>(client, buf.get(0))); + } catch (IOException e) { + return Optional.empty(); + } + } - return clientAction.getKey(); - } + private Optional> receiveAction(boolean block) { + try { + Map.Entry actionByte = this.receiveByte(block).get(); + PlayerAction playerAction = PlayerAction.fromByte(actionByte.getValue()); + return Optional.of(new AbstractMap.SimpleImmutableEntry<>(actionByte.getKey(), playerAction)); + } catch (NoSuchElementException | IllegalArgumentException e) { + return Optional.empty(); + } + } + + private Map.Entry acceptAction() { + Optional> action; - private static List acceptRegistrations(DatagramChannel chan, int registrations) { - List clients = new ArrayList<>(registrations); + do { + action = this.receiveAction(true); + } while (!action.isPresent()); - while (clients.size() < registrations) { - SocketAddress client = acceptRegistration(chan); - if (!clients.contains(client)) - clients.add(client); + return action.get(); + } + + private SocketAddress acceptRegistration() { + Map.Entry clientAction; + + do { + clientAction = this.acceptAction(); + } while (clientAction.getValue() != PlayerAction.JOIN_GAME); + + return clientAction.getKey(); } - return Collections.unmodifiableList(clients); } - private final InetSocketAddress iface; + private final Channel channel; private final int expectedClients; - private Map registeredClients; + + private Map registeredClientsMap; + private Map playersAddressMap; public Server(String iface, Integer port, Integer expectedClients) { - this.iface = listeningInterface(iface, Optional.ofNullable(port).orElse(DEFAULT_PORT)); + this.channel = new Channel(iface, port); this.expectedClients = Optional.ofNullable(expectedClients).orElse(DEFAULT_EXPECTED_CLIENTS); } public void run() { - DatagramChannel chan = openChannel(this.iface); - List clients = acceptRegistrations(chan, this.expectedClients); - this.registeredClients = Lists.linearAdjustedMap(clients, Arrays.asList(PlayerID.values())); + this.acceptClientRegistrations(); + this.runGame(); + this.channel.closeChannel(); + } - System.out.println(this.registeredClients); + private void acceptClientRegistrations() { + List clients = this.channel.acceptRegistrations(this.expectedClients); + this.registeredClientsMap = Lists.linearAdjustedMap(clients, Arrays.asList(PlayerID.values())); + this.playersAddressMap = Lists.invertMap(this.registeredClientsMap); + } + private void runGame() { try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } - Map actions = collectActions(chan); - System.out.println(actions); + Map actions = this.channel.collectActions(); } } -- cgit v1.2.3 From e4b599c9bb3f2c1bfcebadc832f9d291ab8e4e93 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Wed, 11 May 2016 17:14:34 +0200 Subject: Implement game state updating --- src/ch/epfl/xblast/PlayerAction.java | 19 +++++++++++++++++++ src/ch/epfl/xblast/server/GameState.java | 17 +++++++++++++++++ src/ch/epfl/xblast/server/Server.java | 21 ++++++++++++++++----- 3 files changed, 52 insertions(+), 5 deletions(-) (limited to 'src/ch/epfl') diff --git a/src/ch/epfl/xblast/PlayerAction.java b/src/ch/epfl/xblast/PlayerAction.java index 67920eb..6ce12ba 100644 --- a/src/ch/epfl/xblast/PlayerAction.java +++ b/src/ch/epfl/xblast/PlayerAction.java @@ -1,5 +1,7 @@ package ch.epfl.xblast; +import java.util.Optional; + /** * The player action enum. * @@ -27,4 +29,21 @@ public enum PlayerAction { return (byte) this.ordinal(); } + public Optional associatedSpeedChangeEvent() { + switch (this) { + case MOVE_N: + return Optional.of(Direction.N); + case MOVE_E: + return Optional.of(Direction.E); + case MOVE_S: + return Optional.of(Direction.S); + case MOVE_W: + return Optional.of(Direction.W); + case STOP: + return Optional.empty(); + default: + return null; + } + } + } diff --git a/src/ch/epfl/xblast/server/GameState.java b/src/ch/epfl/xblast/server/GameState.java index 16cb01b..2aea7f2 100644 --- a/src/ch/epfl/xblast/server/GameState.java +++ b/src/ch/epfl/xblast/server/GameState.java @@ -207,6 +207,23 @@ public final class GameState { return new GameState(this.ticks + 1, board1, players1, bombs1, explosions1, blasts1); } + /** + * Computes and returns the game state for the next tick, given the current state and the given events. + * + * @param playerActions the player actions map + * @return the next game state + */ + public GameState next(Map playerActions) { + Map> speedChangeEvents = playerActions.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().associatedSpeedChangeEvent())); + + Set bombDropEvents = playerActions.entrySet().stream() + .filter(e -> e.getValue() == PlayerAction.DROP_BOMB) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + + return this.next(speedChangeEvents, bombDropEvents); + } /** * Returns a mapping of players from their location. diff --git a/src/ch/epfl/xblast/server/Server.java b/src/ch/epfl/xblast/server/Server.java index 6d1d62f..0989d55 100644 --- a/src/ch/epfl/xblast/server/Server.java +++ b/src/ch/epfl/xblast/server/Server.java @@ -157,13 +157,24 @@ public class Server { } private void runGame() { - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); + GameState gameState = GameState.DEFAULT_GAME_STATE; + + while (!gameState.isGameOver()) { + gameState = updateGameState(updateGameState(gameState)); + // TODO: send updated game state to clients + + try { + Thread.sleep(10000); + // TODO: adapt sleeping time + } catch (InterruptedException e) { + e.printStackTrace(); + } } + } - Map actions = this.channel.collectActions(); + private GameState updateGameState(GameState gs) { + Map events = Lists.traverseMaps(this.playersAddressMap, this.channel.collectActions()); + return gs.next(events); } } -- cgit v1.2.3 From a43d1f0013c7f2cb7595a1fe1a5cfd99a441d0b4 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Wed, 11 May 2016 17:39:14 +0200 Subject: Implement delay management --- src/ch/epfl/xblast/server/Server.java | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'src/ch/epfl') diff --git a/src/ch/epfl/xblast/server/Server.java b/src/ch/epfl/xblast/server/Server.java index 0989d55..6d8db2c 100644 --- a/src/ch/epfl/xblast/server/Server.java +++ b/src/ch/epfl/xblast/server/Server.java @@ -20,6 +20,8 @@ import java.util.*; public class Server { public static final int DEFAULT_PORT = 2016; + + private static final long REFRESH_RATE = (long) (50 * 1E6); // nanosecond private static final int DEFAULT_EXPECTED_CLIENTS = PlayerID.values().length; private static class Channel { @@ -133,6 +135,14 @@ public class Server { } + private static void delay(long ns) { + try { + Thread.sleep((long) (ns / 1E6), (int) (ns % 1E6)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + private final Channel channel; private final int expectedClients; @@ -160,15 +170,11 @@ public class Server { GameState gameState = GameState.DEFAULT_GAME_STATE; while (!gameState.isGameOver()) { + long computationStartTime = System.nanoTime(); + gameState = updateGameState(updateGameState(gameState)); - // TODO: send updated game state to clients - try { - Thread.sleep(10000); - // TODO: adapt sleeping time - } catch (InterruptedException e) { - e.printStackTrace(); - } + delay(REFRESH_RATE - (computationStartTime - System.nanoTime())); } } -- cgit v1.2.3 From 4ca823eb709a00ff93c1fdeae7f2bab6865cb73f Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Wed, 11 May 2016 17:44:57 +0200 Subject: Discard null keys and values on common map operations --- src/ch/epfl/xblast/Lists.java | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/ch/epfl') diff --git a/src/ch/epfl/xblast/Lists.java b/src/ch/epfl/xblast/Lists.java index 7e3ba5e..9bd527b 100644 --- a/src/ch/epfl/xblast/Lists.java +++ b/src/ch/epfl/xblast/Lists.java @@ -250,6 +250,8 @@ public final class Lists { */ public static Map traverseMaps(Map m1, Map m2) { return Collections.unmodifiableMap(m1.entrySet().stream() + .filter(e -> Objects.nonNull(e.getValue())) + .filter(e -> Objects.nonNull(m2.get(e.getValue()))) .collect(Collectors.toMap(Map.Entry::getKey, e -> m2.get(e.getValue())))); } @@ -263,6 +265,8 @@ public final class Lists { */ public static Map invertMap(Map m) { return Collections.unmodifiableMap(m.entrySet().stream() + .filter(e -> Objects.nonNull(e.getKey())) + .filter(e -> Objects.nonNull(e.getValue())) .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey))); } -- cgit v1.2.3 From 33bc4cab3f2a00db14e6d055d6cd55781c9dd2b4 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Wed, 11 May 2016 18:44:21 +0200 Subject: Implement game state broadcasting --- src/ch/epfl/xblast/server/Server.java | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) (limited to 'src/ch/epfl') diff --git a/src/ch/epfl/xblast/server/Server.java b/src/ch/epfl/xblast/server/Server.java index 6d8db2c..4929dd9 100644 --- a/src/ch/epfl/xblast/server/Server.java +++ b/src/ch/epfl/xblast/server/Server.java @@ -3,6 +3,7 @@ package ch.epfl.xblast.server; import ch.epfl.xblast.Lists; import ch.epfl.xblast.PlayerAction; import ch.epfl.xblast.PlayerID; +import ch.epfl.xblast.server.painter.BoardPainter; import java.io.IOException; import java.net.InetSocketAddress; @@ -26,6 +27,15 @@ public class Server { private static class Channel { + private static byte[] toByteArray(List l) { + byte[] arr = new byte[l.size()]; + + for (int i = 0; i < l.size(); ++i) + arr[i] = l.get(i); + + return arr; + } + private static InetSocketAddress listeningInterface(String host, int port) { if (Objects.isNull(host)) return new InetSocketAddress(port); @@ -88,6 +98,15 @@ public class Server { return Collections.unmodifiableMap(actions); } + void send(List content, SocketAddress dst) { + try { + ByteBuffer buf = ByteBuffer.wrap(toByteArray(content)); + this.channel.send(buf, dst); + } catch (IOException e) { + e.printStackTrace(); + } + } + private Optional> receiveByte(boolean block) { try { ByteBuffer buf = ByteBuffer.allocate(1); @@ -171,9 +190,8 @@ public class Server { while (!gameState.isGameOver()) { long computationStartTime = System.nanoTime(); - gameState = updateGameState(updateGameState(gameState)); - + broadcastGameState(gameState); delay(REFRESH_RATE - (computationStartTime - System.nanoTime())); } } @@ -183,4 +201,13 @@ public class Server { return gs.next(events); } + private void broadcastGameState(GameState gs) { + List serialized = GameStateSerializer.serialize(BoardPainter.DEFAULT_BOARD_PAINTER, gs); + + for (Map.Entry client : this.registeredClientsMap.entrySet()) { + byte activePlayer = Objects.nonNull(client) ? client.getValue().toByte() : PlayerID.OBSERVER; + this.channel.send(Lists.prepended(serialized, activePlayer), client.getKey()); + } + } + } -- cgit v1.2.3 From 48d8fc2efc91f5a02391fb1031e7a662509f5630 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Wed, 11 May 2016 22:45:02 +0200 Subject: Implement client --- src/ch/epfl/xblast/Time.java | 13 ++ src/ch/epfl/xblast/client/Client.java | 188 ++++++++++++++++++++- .../epfl/xblast/client/KeyboardEventHandler.java | 29 ++++ src/ch/epfl/xblast/server/GameState.java | 1 + src/ch/epfl/xblast/server/Server.java | 11 +- 5 files changed, 228 insertions(+), 14 deletions(-) (limited to 'src/ch/epfl') diff --git a/src/ch/epfl/xblast/Time.java b/src/ch/epfl/xblast/Time.java index 7c84257..c53f1ef 100644 --- a/src/ch/epfl/xblast/Time.java +++ b/src/ch/epfl/xblast/Time.java @@ -28,4 +28,17 @@ public interface Time { */ int NS_PER_S = 1000 * US_PER_S; + /** + * Pauses the current thread for the given duration. + * + * @param ns the sleep duration (ns) + */ + static void sleep(long ns) { + try { + Thread.sleep((long) (ns / 1E6), (int) (ns % 1E6)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } diff --git a/src/ch/epfl/xblast/client/Client.java b/src/ch/epfl/xblast/client/Client.java index dc08f1b..5b331f2 100644 --- a/src/ch/epfl/xblast/client/Client.java +++ b/src/ch/epfl/xblast/client/Client.java @@ -1,9 +1,25 @@ package ch.epfl.xblast.client; +import ch.epfl.xblast.Lists; +import ch.epfl.xblast.PlayerAction; +import ch.epfl.xblast.PlayerID; +import ch.epfl.xblast.Time; import ch.epfl.xblast.server.Server; +import javax.swing.*; +import java.awt.*; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.StandardProtocolFamily; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Optional; +import java.util.function.Consumer; /** * The Client class. @@ -14,17 +30,179 @@ public class Client { private static final String DEFAULT_SERVER_HOST = "localhost"; private static final int DEFAULT_SERVER_PORT = Server.DEFAULT_PORT; + private static final int PACKET_MAX_SIZE = 1000; + private static final long REGISTER_PING_INTERVAL = 1 * Time.NS_PER_S; // ns - private final InetSocketAddress serverAddr; + private static class Channel { + + private static List bufferToList(ByteBuffer buf) { + List l = new ArrayList<>(buf.remaining()); + + while (buf.hasRemaining()) + l.add(buf.get()); + + return Collections.unmodifiableList(l); + } + + private static DatagramChannel openChannel() { + try { + return DatagramChannel.open(StandardProtocolFamily.INET); + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + return null; + } + } + + private final SocketAddress serverAddr; + private final DatagramChannel channel; + + Channel(InetSocketAddress iface) { + this.serverAddr = iface; + this.channel = openChannel(); + } + + Channel(String host, Integer port) { + this(new InetSocketAddress( + Optional.ofNullable(host).orElse(DEFAULT_SERVER_HOST), + Optional.ofNullable(port).orElse(DEFAULT_SERVER_PORT))); + } + + void closeChannel() { + try { + this.channel.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + void sendAction(PlayerAction action) { + this.sendByte(action.toByte()); + } + + List receiveGameState(boolean block) { + Optional> state; + + do { + state = this.receive(block); + } while (!state.isPresent()); + + return state.get(); + } + + private void sendByte(byte b) { + this.send(ByteBuffer.wrap(new byte[]{b})); + } + + private void send(ByteBuffer b) { + try { + this.channel.send(b, this.serverAddr); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private Optional> receive(boolean block) { + try { + ByteBuffer buf = ByteBuffer.allocate(PACKET_MAX_SIZE); + this.channel.configureBlocking(block); + this.channel.receive(buf); + buf.flip(); + return Optional.of(bufferToList(buf)); + } catch (IOException e) { + e.printStackTrace(); + return Optional.empty(); + } + } + + } + + private static class GUI { + + private static JFrame buildFrame(Container content) { + JFrame frame = new JFrame(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + + frame.setContentPane(content); + frame.pack(); + return frame; + } + + private static void attachKeyboardHandler(Component comp, Consumer actionConsumer) { + comp.addKeyListener(new KeyboardEventHandler(actionConsumer)); + comp.requestFocusInWindow(); + } + + private final XBlastComponent gameComponent; + private final Consumer actionConsumer; + + GUI(Consumer actionConsumer) { + this.gameComponent = new XBlastComponent(); + this.actionConsumer = actionConsumer; + } + + public void display() { + buildFrame(this.gameComponent); + attachKeyboardHandler(this.gameComponent, this.actionConsumer); + } + + public void setGameState(GameState gs, PlayerID p) { + this.gameComponent.setGameState(gs, p); + } + + } + + private final Channel channel; + private final GUI gui; public Client(String host, Integer port) { - this.serverAddr = new InetSocketAddress( - Optional.ofNullable(host).orElse(DEFAULT_SERVER_HOST), - Optional.ofNullable(port).orElse(DEFAULT_SERVER_PORT)); + this.channel = new Channel(host, port); + this.gui = new GUI(this.channel::sendAction); } public void run() { - // TODO + this.displayGUI(); + this.establishConnection(); + this.runGame(); + this.channel.closeChannel(); + } + + private void displayGUI() { + try { + SwingUtilities.invokeAndWait(this.gui::display); + } catch (InterruptedException | InvocationTargetException e) { + e.printStackTrace(); + System.exit(1); + } + } + + private void runGame() { + while (true) + updateGameState(); + } + + private void establishConnection() { + Thread joinThread = new Thread(this::sendJoinRequest); + joinThread.start(); + updateGameState(); + joinThread.interrupt(); + } + + private void updateGameState() { + List serializedGameState = this.channel.receiveGameState(true); + if (serializedGameState.size() < 1) return; + PlayerID player = PlayerID.fromByte(serializedGameState.get(0)); + GameState gameState = GameStateDeserializer.deserialize(Lists.firstDropped(serializedGameState, 1)); + this.gui.setGameState(gameState, player); + } + + private void sendJoinRequest() { + while (!Thread.currentThread().isInterrupted()) { + this.channel.sendAction(PlayerAction.JOIN_GAME); + Time.sleep(REGISTER_PING_INTERVAL); + } } } diff --git a/src/ch/epfl/xblast/client/KeyboardEventHandler.java b/src/ch/epfl/xblast/client/KeyboardEventHandler.java index 2060959..ab64c80 100644 --- a/src/ch/epfl/xblast/client/KeyboardEventHandler.java +++ b/src/ch/epfl/xblast/client/KeyboardEventHandler.java @@ -5,6 +5,8 @@ import ch.epfl.xblast.PlayerAction; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; @@ -17,6 +19,24 @@ import java.util.function.Consumer; */ public class KeyboardEventHandler extends KeyAdapter { + /** + * The default key->action mapping. + */ + private static final Map DEFAULT_KEY_MAP = buildDefaultKeyMap(); + + private static Map buildDefaultKeyMap() { + Map playerActionMap = new HashMap<>(); + + playerActionMap.put(KeyEvent.VK_UP, PlayerAction.MOVE_N); + playerActionMap.put(KeyEvent.VK_RIGHT, PlayerAction.MOVE_E); + playerActionMap.put(KeyEvent.VK_DOWN, PlayerAction.MOVE_S); + playerActionMap.put(KeyEvent.VK_LEFT, PlayerAction.MOVE_W); + playerActionMap.put(KeyEvent.VK_SPACE, PlayerAction.DROP_BOMB); + playerActionMap.put(KeyEvent.VK_SHIFT, PlayerAction.STOP); + + return Collections.unmodifiableMap(playerActionMap); + } + private final Map playerActionMap; private final Consumer consumer; @@ -31,6 +51,15 @@ public class KeyboardEventHandler extends KeyAdapter { this.consumer = consumer; } + /** + * Instantiates a new KeyboardEventHandler with the default key mapping. + * + * @param consumer consumer related to the EventHandler + */ + public KeyboardEventHandler(Consumer consumer) { + this(DEFAULT_KEY_MAP, consumer); + } + /** * Executes the command related to the given key code. * diff --git a/src/ch/epfl/xblast/server/GameState.java b/src/ch/epfl/xblast/server/GameState.java index 2aea7f2..a5e1378 100644 --- a/src/ch/epfl/xblast/server/GameState.java +++ b/src/ch/epfl/xblast/server/GameState.java @@ -215,6 +215,7 @@ public final class GameState { */ public GameState next(Map playerActions) { Map> speedChangeEvents = playerActions.entrySet().stream() + .filter(e -> Objects.nonNull(e.getValue().associatedSpeedChangeEvent())) .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().associatedSpeedChangeEvent())); Set bombDropEvents = playerActions.entrySet().stream() diff --git a/src/ch/epfl/xblast/server/Server.java b/src/ch/epfl/xblast/server/Server.java index 4929dd9..1f29607 100644 --- a/src/ch/epfl/xblast/server/Server.java +++ b/src/ch/epfl/xblast/server/Server.java @@ -3,6 +3,7 @@ package ch.epfl.xblast.server; import ch.epfl.xblast.Lists; import ch.epfl.xblast.PlayerAction; import ch.epfl.xblast.PlayerID; +import ch.epfl.xblast.Time; import ch.epfl.xblast.server.painter.BoardPainter; import java.io.IOException; @@ -154,14 +155,6 @@ public class Server { } - private static void delay(long ns) { - try { - Thread.sleep((long) (ns / 1E6), (int) (ns % 1E6)); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - private final Channel channel; private final int expectedClients; @@ -192,7 +185,7 @@ public class Server { long computationStartTime = System.nanoTime(); gameState = updateGameState(updateGameState(gameState)); broadcastGameState(gameState); - delay(REFRESH_RATE - (computationStartTime - System.nanoTime())); + Time.sleep(REFRESH_RATE - (computationStartTime - System.nanoTime())); } } -- cgit v1.2.3 From c095405dd09105be86a25987e3607be10fb12647 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Thu, 12 May 2016 14:58:43 +0200 Subject: Fix last dying image selection --- src/ch/epfl/xblast/server/painter/PlayerPainter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/ch/epfl') diff --git a/src/ch/epfl/xblast/server/painter/PlayerPainter.java b/src/ch/epfl/xblast/server/painter/PlayerPainter.java index 043cb50..a366918 100644 --- a/src/ch/epfl/xblast/server/painter/PlayerPainter.java +++ b/src/ch/epfl/xblast/server/painter/PlayerPainter.java @@ -59,7 +59,7 @@ public final class PlayerPainter { * @return the dying image ID */ private static byte byteForDyingState(int lives) { - return lives == 0 ? LAST_DYING_IMAGE_ID : DYING_IMAGE_ID; + return lives == 1 ? LAST_DYING_IMAGE_ID : DYING_IMAGE_ID; } /** -- cgit v1.2.3 From 8fb64e8969f093c896ad9b5e327738e82941d30c Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Thu, 12 May 2016 15:04:55 +0200 Subject: Properly handle invulnerability state image --- src/ch/epfl/xblast/server/painter/PlayerPainter.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'src/ch/epfl') diff --git a/src/ch/epfl/xblast/server/painter/PlayerPainter.java b/src/ch/epfl/xblast/server/painter/PlayerPainter.java index a366918..79b954c 100644 --- a/src/ch/epfl/xblast/server/painter/PlayerPainter.java +++ b/src/ch/epfl/xblast/server/painter/PlayerPainter.java @@ -16,6 +16,8 @@ public final class PlayerPainter { private static final byte DYING_IMAGE_ID = 12; private static final byte LAST_DYING_IMAGE_ID = 13; + private static final int BLANK_PLAYER_GROUP = 4; + private static final int PLAYER_MULTIPLIER = 20; private static final int DIRECTION_MULTIPLIER = 3; @@ -52,6 +54,15 @@ public final class PlayerPainter { return (byte) (pid.ordinal() * PLAYER_MULTIPLIER); } + /** + * Returns the player image byte for the blank player. + * + * @return the image byte for the player + */ + private static byte byteForPlayerID() { + return (byte) (BLANK_PLAYER_GROUP * PLAYER_MULTIPLIER); + } + /** * Returns the image ID for the dying state according to the number of remaining lives. * @@ -78,6 +89,12 @@ public final class PlayerPainter { return (byte) (byteForPlayerID(player.id()) + byteForDyingState(player.lives())); + case INVULNERABLE: + if (tick % 2 == 1) + return (byte) (byteForPlayerID() + + byteForDirection(player.direction()) + + byteForFrame(tick)); + default: return (byte) (byteForPlayerID(player.id()) + byteForDirection(player.direction()) -- cgit v1.2.3 From 060d36a02e06fdc2943c8ff0b915057d50a13cb7 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Thu, 12 May 2016 15:43:28 +0200 Subject: Properly handle observer mode --- src/ch/epfl/xblast/PlayerID.java | 2 -- src/ch/epfl/xblast/client/Client.java | 30 +++++++++++++++++++++----- src/ch/epfl/xblast/client/XBlastComponent.java | 3 +++ src/ch/epfl/xblast/server/Server.java | 5 +++-- 4 files changed, 31 insertions(+), 9 deletions(-) (limited to 'src/ch/epfl') diff --git a/src/ch/epfl/xblast/PlayerID.java b/src/ch/epfl/xblast/PlayerID.java index 96b98e4..72a48b9 100644 --- a/src/ch/epfl/xblast/PlayerID.java +++ b/src/ch/epfl/xblast/PlayerID.java @@ -13,8 +13,6 @@ public enum PlayerID { PLAYER_3, PLAYER_4; - public static final byte OBSERVER = -1; - public Cell initialPosition() { switch (this) { case PLAYER_1: diff --git a/src/ch/epfl/xblast/client/Client.java b/src/ch/epfl/xblast/client/Client.java index 5b331f2..b522312 100644 --- a/src/ch/epfl/xblast/client/Client.java +++ b/src/ch/epfl/xblast/client/Client.java @@ -15,10 +15,8 @@ import java.net.SocketAddress; import java.net.StandardProtocolFamily; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; -import java.util.ArrayList; -import java.util.Collections; +import java.util.*; import java.util.List; -import java.util.Optional; import java.util.function.Consumer; /** @@ -154,6 +152,25 @@ public class Client { } + private static PlayerID deserializePlayerID(byte b) { + if (b == Server.OBSERVER) + return null; + + try { + return PlayerID.fromByte(b); + } catch (IllegalArgumentException e) { + return null; + } + } + + private static GameState deserializeGameState(List b) { + try { + return GameStateDeserializer.deserialize(b); + } catch (IllegalArgumentException e) { + return null; + } + } + private final Channel channel; private final GUI gui; @@ -193,8 +210,11 @@ public class Client { private void updateGameState() { List serializedGameState = this.channel.receiveGameState(true); if (serializedGameState.size() < 1) return; - PlayerID player = PlayerID.fromByte(serializedGameState.get(0)); - GameState gameState = GameStateDeserializer.deserialize(Lists.firstDropped(serializedGameState, 1)); + + PlayerID player = deserializePlayerID(serializedGameState.get(0)); + GameState gameState = deserializeGameState(Lists.firstDropped(serializedGameState, 1)); + if (Objects.isNull(gameState)) return; + this.gui.setGameState(gameState, player); } diff --git a/src/ch/epfl/xblast/client/XBlastComponent.java b/src/ch/epfl/xblast/client/XBlastComponent.java index 0ef8c5c..dfc0be5 100644 --- a/src/ch/epfl/xblast/client/XBlastComponent.java +++ b/src/ch/epfl/xblast/client/XBlastComponent.java @@ -121,6 +121,9 @@ public final class XBlastComponent extends JComponent { } private static List sortPlayers(List players, PlayerID currentPlayerID) { + if (Objects.isNull(currentPlayerID)) + return players; + return Lists.sorted( players, GameState.Player diff --git a/src/ch/epfl/xblast/server/Server.java b/src/ch/epfl/xblast/server/Server.java index 1f29607..8a4b09b 100644 --- a/src/ch/epfl/xblast/server/Server.java +++ b/src/ch/epfl/xblast/server/Server.java @@ -22,9 +22,10 @@ import java.util.*; public class Server { public static final int DEFAULT_PORT = 2016; - private static final long REFRESH_RATE = (long) (50 * 1E6); // nanosecond + private static final int DEFAULT_EXPECTED_CLIENTS = PlayerID.values().length; + public static final byte OBSERVER = -1; private static class Channel { @@ -198,7 +199,7 @@ public class Server { List serialized = GameStateSerializer.serialize(BoardPainter.DEFAULT_BOARD_PAINTER, gs); for (Map.Entry client : this.registeredClientsMap.entrySet()) { - byte activePlayer = Objects.nonNull(client) ? client.getValue().toByte() : PlayerID.OBSERVER; + byte activePlayer = Objects.nonNull(client) ? client.getValue().toByte() : OBSERVER; this.channel.send(Lists.prepended(serialized, activePlayer), client.getKey()); } } -- cgit v1.2.3 From 1ab03f95d9dc7dd25a775dd17e1f3977bb987dd2 Mon Sep 17 00:00:00 2001 From: Timothée Floure Date: Fri, 20 May 2016 13:25:42 +0200 Subject: Improve the documentation for the server package --- src/ch/epfl/xblast/server/Main.java | 5 ++ src/ch/epfl/xblast/server/Server.java | 125 +++++++++++++++++++++++++++++++++- 2 files changed, 128 insertions(+), 2 deletions(-) (limited to 'src/ch/epfl') diff --git a/src/ch/epfl/xblast/server/Main.java b/src/ch/epfl/xblast/server/Main.java index 98bbb3d..aff499b 100644 --- a/src/ch/epfl/xblast/server/Main.java +++ b/src/ch/epfl/xblast/server/Main.java @@ -13,6 +13,11 @@ public final class Main { // Static class } + /** + * Starts a new server. + * + * @param args arguments given to the server (i.e. number of players required for the game to start) + */ public static void main(String[] args) { Integer expectedClients = ArgumentChecker.parseIntOrNull(ArgumentChecker.getOrNull(args, 0)); String iface = ArgumentChecker.getOrNull(args, 1); diff --git a/src/ch/epfl/xblast/server/Server.java b/src/ch/epfl/xblast/server/Server.java index 8a4b09b..015d771 100644 --- a/src/ch/epfl/xblast/server/Server.java +++ b/src/ch/epfl/xblast/server/Server.java @@ -21,14 +21,29 @@ import java.util.*; */ public class Server { - public static final int DEFAULT_PORT = 2016; + /** + * Default parameters of the server. + */ private static final long REFRESH_RATE = (long) (50 * 1E6); // nanosecond - private static final int DEFAULT_EXPECTED_CLIENTS = PlayerID.values().length; + public static final int DEFAULT_PORT = 2016; + + /** + * ID of an observer. + */ public static final byte OBSERVER = -1; + /** + * A Channel. + */ private static class Channel { + /** + * Transform a list of bytes to an array of bytes. + * + * @param l the given list of bytes + * @return the array built from the given list + */ private static byte[] toByteArray(List l) { byte[] arr = new byte[l.size()]; @@ -38,6 +53,13 @@ public class Server { return arr; } + /** + * Create the socket which will be listened. + * + * @param host hostname + * @param port port to be listened + * @return the socket build from the given params + */ private static InetSocketAddress listeningInterface(String host, int port) { if (Objects.isNull(host)) return new InetSocketAddress(port); @@ -45,6 +67,12 @@ public class Server { return new InetSocketAddress(host, port); } + /** + * Open a UDP channel. + * + * @param iface listened socket + * @return a UDP channel + */ private static DatagramChannel openChannel(InetSocketAddress iface) { try { DatagramChannel chan = DatagramChannel.open(StandardProtocolFamily.INET); @@ -57,16 +85,33 @@ public class Server { } } + /** + * The UDP communication channel. + */ private final DatagramChannel channel; + /** + * Instantiates a new Channel. + * + * @param iface listened socket + */ Channel(InetSocketAddress iface) { this.channel = openChannel(iface); } + /** + * Instantiates a new Channel. + * + * @param host hostname + * @param port port to be listened + */ Channel(String host, Integer port) { this(listeningInterface(host, Optional.ofNullable(port).orElse(DEFAULT_PORT))); } + /** + * Close the channel. + */ void closeChannel() { try { this.channel.close(); @@ -75,6 +120,12 @@ public class Server { } } + /** + * Accept the registration of a new client. + * + * @param registrations + * @return a list of the clients. + */ List acceptRegistrations(int registrations) { List clients = new ArrayList<>(registrations); @@ -87,6 +138,11 @@ public class Server { return Collections.unmodifiableList(clients); } + /** + * Collect actions from the players. + * + * @return a list map containing the actions related to each player + */ Map collectActions() { Map actions = new HashMap<>(); Optional> action; @@ -100,6 +156,12 @@ public class Server { return Collections.unmodifiableMap(actions); } + /** + * Send data through the socket. + * + * @param content data to be send + * @param dst recepient of the data + */ void send(List content, SocketAddress dst) { try { ByteBuffer buf = ByteBuffer.wrap(toByteArray(content)); @@ -109,6 +171,12 @@ public class Server { } } + /** + * Receive a Byte from the socket. + * + * @param block + * @return an Optional containing the received data + */ private Optional> receiveByte(boolean block) { try { ByteBuffer buf = ByteBuffer.allocate(1); @@ -124,6 +192,12 @@ public class Server { } } + /** + * Receive a player action from the socket. + * + * @param block + * @return an Optional containing the received player action + */ private Optional> receiveAction(boolean block) { try { Map.Entry actionByte = this.receiveByte(block).get(); @@ -134,6 +208,11 @@ public class Server { } } + /** + * Accept an action from the socket. + * + * @return the accepted action + */ private Map.Entry acceptAction() { Optional> action; @@ -144,6 +223,11 @@ public class Server { return action.get(); } + /** + * Accept the registration of a player + * + * @return the address of the new player + */ private SocketAddress acceptRegistration() { Map.Entry clientAction; @@ -156,29 +240,55 @@ public class Server { } + /** + * The communication channel. + */ private final Channel channel; + + /** + * Required number of player to start a game (up to 4). + */ private final int expectedClients; + /** + * Maps of the players and their addresses. + */ private Map registeredClientsMap; private Map playersAddressMap; + /** + * Instantiates a new server. + * + * @param iface the interface to be listened + * @param port the port to be listened + * @param expectedClients required number of player to start a game + */ public Server(String iface, Integer port, Integer expectedClients) { this.channel = new Channel(iface, port); this.expectedClients = Optional.ofNullable(expectedClients).orElse(DEFAULT_EXPECTED_CLIENTS); } + /** + * Run the whole server. + */ public void run() { this.acceptClientRegistrations(); this.runGame(); this.channel.closeChannel(); } + /** + * Handle the registration of new clients. + */ private void acceptClientRegistrations() { List clients = this.channel.acceptRegistrations(this.expectedClients); this.registeredClientsMap = Lists.linearAdjustedMap(clients, Arrays.asList(PlayerID.values())); this.playersAddressMap = Lists.invertMap(this.registeredClientsMap); } + /** + * Launch the Game. + */ private void runGame() { GameState gameState = GameState.DEFAULT_GAME_STATE; @@ -190,11 +300,22 @@ public class Server { } } + /** + * Update the given GameState to the next tick. + * + * @param gs the given GameState + * @return the updated GameState + */ private GameState updateGameState(GameState gs) { Map events = Lists.traverseMaps(this.playersAddressMap, this.channel.collectActions()); return gs.next(events); } + /** + * Send