diff options
-rw-r--r-- | src/ch/epfl/xblast/Time.java | 13 | ||||
-rw-r--r-- | src/ch/epfl/xblast/client/Client.java | 188 | ||||
-rw-r--r-- | src/ch/epfl/xblast/client/KeyboardEventHandler.java | 29 | ||||
-rw-r--r-- | src/ch/epfl/xblast/server/GameState.java | 1 | ||||
-rw-r--r-- | src/ch/epfl/xblast/server/Server.java | 11 | ||||
-rw-r--r-- | test/ch/epfl/xblast/simulation/GraphicalSimulation.java | 19 |
6 files changed, 229 insertions, 32 deletions
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 { | |||
28 | */ | 28 | */ |
29 | int NS_PER_S = 1000 * US_PER_S; | 29 | int NS_PER_S = 1000 * US_PER_S; |
30 | 30 | ||
31 | /** | ||
32 | * Pauses the current thread for the given duration. | ||
33 | * | ||
34 | * @param ns the sleep duration (ns) | ||
35 | */ | ||
36 | static void sleep(long ns) { | ||
37 | try { | ||
38 | Thread.sleep((long) (ns / 1E6), (int) (ns % 1E6)); | ||
39 | } catch (InterruptedException e) { | ||
40 | Thread.currentThread().interrupt(); | ||
41 | } | ||
42 | } | ||
43 | |||
31 | } | 44 | } |
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 @@ | |||
1 | package ch.epfl.xblast.client; | 1 | package ch.epfl.xblast.client; |
2 | 2 | ||
3 | import ch.epfl.xblast.Lists; | ||
4 | import ch.epfl.xblast.PlayerAction; | ||
5 | import ch.epfl.xblast.PlayerID; | ||
6 | import ch.epfl.xblast.Time; | ||
3 | import ch.epfl.xblast.server.Server; | 7 | import ch.epfl.xblast.server.Server; |
4 | 8 | ||
9 | import javax.swing.*; | ||
10 | import java.awt.*; | ||
11 | import java.io.IOException; | ||
12 | import java.lang.reflect.InvocationTargetException; | ||
5 | import java.net.InetSocketAddress; | 13 | import java.net.InetSocketAddress; |
14 | import java.net.SocketAddress; | ||
15 | import java.net.StandardProtocolFamily; | ||
16 | import java.nio.ByteBuffer; | ||
17 | import java.nio.channels.DatagramChannel; | ||
18 | import java.util.ArrayList; | ||
19 | import java.util.Collections; | ||
20 | import java.util.List; | ||
6 | import java.util.Optional; | 21 | import java.util.Optional; |
22 | import java.util.function.Consumer; | ||
7 | 23 | ||
8 | /** | 24 | /** |
9 | * The Client class. | 25 | * The Client class. |
@@ -14,17 +30,179 @@ public class Client { | |||
14 | 30 | ||
15 | private static final String DEFAULT_SERVER_HOST = "localhost"; | 31 | private static final String DEFAULT_SERVER_HOST = "localhost"; |
16 | private static final int DEFAULT_SERVER_PORT = Server.DEFAULT_PORT; | 32 | private static final int DEFAULT_SERVER_PORT = Server.DEFAULT_PORT; |
33 | private static final int PACKET_MAX_SIZE = 1000; | ||
34 | private static final long REGISTER_PING_INTERVAL = 1 * Time.NS_PER_S; // ns | ||
17 | 35 | ||
18 | private final InetSocketAddress serverAddr; | 36 | private static class Channel { |
37 | |||
38 | private static List<Byte> bufferToList(ByteBuffer buf) { | ||
39 | List<Byte> l = new ArrayList<>(buf.remaining()); | ||
40 | |||
41 | while (buf.hasRemaining()) | ||
42 | l.add(buf.get()); | ||
43 | |||
44 | return Collections.unmodifiableList(l); | ||
45 | } | ||
46 | |||
47 | private static DatagramChannel openChannel() { | ||
48 | try { | ||
49 | return DatagramChannel.open(StandardProtocolFamily.INET); | ||
50 | } catch (IOException e) { | ||
51 | e.printStackTrace(); | ||
52 | System.exit(1); | ||
53 | return null; | ||
54 | } | ||
55 | } | ||
56 | |||
57 | private final SocketAddress serverAddr; | ||
58 | private final DatagramChannel channel; | ||
59 | |||
60 | Channel(InetSocketAddress iface) { | ||
61 | this.serverAddr = iface; | ||
62 | this.channel = openChannel(); | ||
63 | } | ||
64 | |||
65 | Channel(String host, Integer port) { | ||
66 | this(new InetSocketAddress( | ||
67 | Optional.ofNullable(host).orElse(DEFAULT_SERVER_HOST), | ||
68 | Optional.ofNullable(port).orElse(DEFAULT_SERVER_PORT))); | ||
69 | } | ||
70 | |||
71 | void closeChannel() { | ||
72 | try { | ||
73 | this.channel.close(); | ||
74 | } catch (IOException e) { | ||
75 | e.printStackTrace(); | ||
76 | } | ||
77 | } | ||
78 | |||
79 | void sendAction(PlayerAction action) { | ||
80 | this.sendByte(action.toByte()); | ||
81 | } | ||
82 | |||
83 | List<Byte> receiveGameState(boolean block) { | ||
84 | Optional<List<Byte>> state; | ||
85 | |||
86 | do { | ||
87 | state = this.receive(block); | ||
88 | } while (!state.isPresent()); | ||
89 | |||
90 | return state.get(); | ||
91 | } | ||
92 | |||
93 | private void sendByte(byte b) { | ||
94 | this.send(ByteBuffer.wrap(new byte[]{b})); | ||
95 | } | ||
96 | |||
97 | private void send(ByteBuffer b) { | ||
98 | try { | ||
99 | this.channel.send(b, this.serverAddr); | ||
100 | } catch (IOException e) { | ||
101 | e.printStackTrace(); | ||
102 | } | ||
103 | } | ||
104 | |||
105 | private Optional<List<Byte>> receive(boolean block) { | ||
106 | try { | ||
107 | ByteBuffer buf = ByteBuffer.allocate(PACKET_MAX_SIZE); | ||
108 | this.channel.configureBlocking(block); | ||
109 | this.channel.receive(buf); | ||
110 | buf.flip(); | ||
111 | return Optional.of(bufferToList(buf)); | ||
112 | } catch (IOException e) { | ||
113 | e.printStackTrace(); | ||
114 | return Optional.empty(); | ||
115 | } | ||
116 | } | ||
117 | |||
118 | } | ||
119 | |||
120 | private static class GUI { | ||
121 | |||
122 | private static JFrame buildFrame(Container content) { | ||
123 | JFrame frame = new JFrame(); | ||
124 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | ||
125 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | ||
126 | frame.setVisible(true); | ||
127 | |||
128 | frame.setContentPane(content); | ||
129 | frame.pack(); | ||
130 | return frame; | ||
131 | } | ||
132 | |||
133 | private static void attachKeyboardHandler(Component comp, Consumer<PlayerAction> actionConsumer) { | ||
134 | comp.addKeyListener(new KeyboardEventHandler(actionConsumer)); | ||
135 | comp.requestFocusInWindow(); | ||
136 | } | ||
137 | |||
138 | private final XBlastComponent gameComponent; | ||
139 | private final Consumer<PlayerAction> actionConsumer; | ||
140 | |||
141 | GUI(Consumer<PlayerAction> actionConsumer) { | ||
142 | this.gameComponent = new XBlastComponent(); | ||
143 | this.actionConsumer = actionConsumer; | ||
144 | } | ||
145 | |||
146 | public void display() { | ||
147 | buildFrame(this.gameComponent); | ||
148 | attachKeyboardHandler(this.gameComponent, this.actionConsumer); | ||
149 | } | ||
150 | |||
151 | public void setGameState(GameState gs, PlayerID p) { | ||
152 | this.gameComponent.setGameState(gs, p); | ||
153 | } | ||
154 | |||
155 | } | ||
156 | |||
157 | private final Channel channel; | ||
158 | private final GUI gui; | ||
19 | 159 | ||
20 | public Client(String host, Integer port) { | 160 | public Client(String host, Integer port) { |
21 | this.serverAddr = new InetSocketAddress( | 161 | this.channel = new Channel(host, port); |
22 | Optional.ofNullable(host).orElse(DEFAULT_SERVER_HOST), | 162 | this.gui = new GUI(this.channel::sendAction); |
23 | Optional.ofNullable(port).orElse(DEFAULT_SERVER_PORT)); | ||
24 | } | 163 | } |
25 | 164 | ||
26 | public void run() { | 165 | public void run() { |
27 | // TODO | 166 | this.displayGUI(); |
167 | this.establishConnection(); | ||
168 | this.runGame(); | ||
169 | this.channel.closeChannel(); | ||
170 | } | ||
171 | |||
172 | private void displayGUI() { | ||
173 | try { | ||
174 | SwingUtilities.invokeAndWait(this.gui::display); | ||
175 | } catch (InterruptedException | InvocationTargetException e) { | ||
176 | e.printStackTrace(); | ||
177 | System.exit(1); | ||
178 | } | ||
179 | } | ||
180 | |||
181 | private void runGame() { | ||
182 | while (true) | ||
183 | updateGameState(); | ||
184 | } | ||
185 | |||
186 | private void establishConnection() { | ||
187 | Thread joinThread = new Thread(this::sendJoinRequest); | ||
188 | joinThread.start(); | ||
189 | updateGameState(); | ||
190 | joinThread.interrupt(); | ||
191 | } | ||
192 | |||
193 | private void updateGameState() { | ||
194 | List<Byte> serializedGameState = this.channel.receiveGameState(true); | ||
195 | if (serializedGameState.size() < 1) return; | ||
196 | PlayerID player = PlayerID.fromByte(serializedGameState.get(0)); | ||
197 | GameState gameState = GameStateDeserializer.deserialize(Lists.firstDropped(serializedGameState, 1)); | ||
198 | this.gui.setGa |