diff options
Diffstat (limited to 'src/ch/epfl/xblast/client/Client.java')
-rw-r--r-- | src/ch/epfl/xblast/client/Client.java | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/src/ch/epfl/xblast/client/Client.java b/src/ch/epfl/xblast/client/Client.java new file mode 100644 index 0000000..24ca727 --- /dev/null +++ b/src/ch/epfl/xblast/client/Client.java | |||
@@ -0,0 +1,360 @@ | |||
1 | package ch.epfl.xblast.client; | ||
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; | ||
7 | import ch.epfl.xblast.server.Server; | ||
8 | |||
9 | import javax.swing.*; | ||
10 | import java.awt.*; | ||
11 | import java.io.IOException; | ||
12 | import java.lang.reflect.InvocationTargetException; | ||
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.*; | ||
19 | import java.util.List; | ||
20 | import java.util.function.Consumer; | ||
21 | |||
22 | /** | ||
23 | * The Client class. | ||
24 | * | ||
25 | * @author Pacien TRAN-GIRARD (261948) | ||
26 | */ | ||
27 | public class Client { | ||
28 | |||
29 | /** | ||
30 | * Client's parameters. | ||
31 | */ | ||
32 | private static final String DEFAULT_SERVER_HOST = "localhost"; | ||
33 | private static final int DEFAULT_SERVER_PORT = Server.DEFAULT_PORT; | ||
34 | private static final int PACKET_MAX_SIZE = 1000; | ||
35 | private static final long REGISTER_PING_INTERVAL = 1 * Time.NS_PER_S; // ns | ||
36 | |||
37 | /** | ||
38 | * Communication channel. | ||
39 | */ | ||
40 | private static class Channel { | ||
41 | |||
42 | /** | ||
43 | * Transforms a buffer to a list of bytes. | ||
44 | * | ||
45 | * @param buf given buffer | ||
46 | * @return the list of bytes built from the given buffer | ||
47 | */ | ||
48 | private static List<Byte> bufferToList(ByteBuffer buf) { | ||
49 | List<Byte> l = new ArrayList<>(buf.remaining()); | ||
50 | |||
51 | while (buf.hasRemaining()) | ||
52 | l.add(buf.get()); | ||
53 | |||
54 | return Collections.unmodifiableList(l); | ||
55 | } | ||
56 | |||
57 | /** | ||
58 | * Creates the UDP communication Channel. | ||
59 | * | ||
60 | * @return the UDP communication Channel | ||
61 | */ | ||
62 | private static DatagramChannel openChannel() { | ||
63 | try { | ||
64 | return DatagramChannel.open(StandardProtocolFamily.INET); | ||
65 | } catch (IOException e) { | ||
66 | e.printStackTrace(); | ||
67 | System.exit(1); | ||
68 | return null; | ||
69 | } | ||
70 | } | ||
71 | |||
72 | /** | ||
73 | * Parameters of the Channel. | ||
74 | */ | ||
75 | private final SocketAddress serverAddr; | ||
76 | private final DatagramChannel channel; | ||
77 | |||
78 | /** | ||
79 | * Instantiates a new Channel. | ||
80 | * | ||
81 | * @param iface socket | ||
82 | */ | ||
83 | Channel(InetSocketAddress iface) { | ||
84 | this.serverAddr = iface; | ||
85 | this.channel = openChannel(); | ||
86 | } | ||
87 | |||
88 | /** | ||
89 | * Instantiates a new Channel. | ||
90 | * | ||
91 | * @param host hostname | ||
92 | * @param port port | ||
93 | */ | ||
94 | Channel(String host, Integer port) { | ||
95 | this(new InetSocketAddress( | ||
96 | Optional.ofNullable(host).orElse(DEFAULT_SERVER_HOST), | ||
97 | Optional.ofNullable(port).orElse(DEFAULT_SERVER_PORT))); | ||
98 | } | ||
99 | |||
100 | /** | ||
101 | * Closes the Channel. | ||
102 | */ | ||
103 | void closeChannel() { | ||
104 | try { | ||
105 | this.channel.close(); | ||
106 | } catch (IOException e) { | ||
107 | e.printStackTrace(); | ||
108 | } | ||
109 | } | ||
110 | |||
111 | /** | ||
112 | * Sends an action through the Channel. | ||
113 | * | ||
114 | * @param action action to send | ||
115 | */ | ||
116 | void sendAction(PlayerAction action) { | ||
117 | this.sendByte(action.toByte()); | ||
118 | } | ||
119 | |||
120 | /** | ||
121 | * Receives a GameState through the Channel. | ||
122 | * | ||
123 | * @param block wait for an incoming game state before returning | ||
124 | * @return the received GameState | ||
125 | */ | ||
126 | List<Byte> receiveGameState(boolean block) { | ||
127 | Optional<List<Byte>> state; | ||
128 | |||
129 | do { | ||
130 | state = this.receive(block); | ||
131 | } while (!state.isPresent()); | ||
132 | |||
133 | return state.get(); | ||
134 | } | ||
135 | |||
136 | /** | ||
137 | * Sends a byte through the Channel. | ||
138 | * | ||
139 | * @param b byte to send | ||
140 | */ | ||
141 | private void sendByte(byte b) { | ||
142 | this.send(ByteBuffer.wrap(new byte[]{b})); | ||
143 | } | ||
144 | |||
145 | /** | ||
146 | * Sends the content of a buffer. | ||
147 | * | ||
148 | * @param b buffer containing the bytes to send | ||
149 | */ | ||
150 | private void send(ByteBuffer b) { | ||
151 | try { | ||
152 | this.channel.send(b, this.serverAddr); | ||
153 | } catch (IOException e) { | ||
154 | e.printStackTrace(); | ||
155 | } | ||
156 | } | ||
157 | |||
158 | /** | ||
159 | * Receives data from the Channel. | ||
160 | * | ||
161 | * @param block wait for an incoming byte before returning | ||
162 | * @return the received data | ||
163 | */ | ||
164 | private Optional<List<Byte>> receive(boolean block) { | ||
165 | try { | ||
166 | ByteBuffer buf = ByteBuffer.allocate(PACKET_MAX_SIZE); | ||
167 | this.channel.configureBlocking(block); | ||
168 | this.channel.receive(buf); | ||
169 | buf.flip(); | ||
170 | return Optional.of(bufferToList(buf)); | ||
171 | } catch (IOException e) { | ||
172 | e.printStackTrace(); | ||
173 | return Optional.empty(); | ||
174 | } | ||
175 | } | ||
176 | |||
177 | } | ||
178 | |||
179 | /** | ||
180 | * The Graphical User Interface. | ||
181 | */ | ||
182 | private static class GUI { | ||
183 | |||
184 | /** | ||
185 | * Builds the window. | ||
186 | * | ||
187 | * @param content content of the window | ||
188 | * @return the frame | ||
189 | */ | ||
190 | private static JFrame buildFrame(Container content) { | ||
191 | JFrame frame = new JFrame(); | ||
192 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | ||
193 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | ||
194 | frame.setVisible(true); | ||
195 | |||
196 | frame.setContentPane(content); | ||
197 | frame.pack(); | ||
198 | return frame; | ||
199 | } | ||
200 | |||
201 | /** | ||
202 | * Attaches the keyboard event handler to the component. | ||
203 | * | ||
204 | * @param comp component | ||
205 | * @param actionConsumer actionConsumer corresponding to the keyboardEventHandler | ||
206 | */ | ||
207 | private static void attachKeyboardHandler(Component comp, Consumer<PlayerAction> actionConsumer) { | ||
208 | comp.addKeyListener(new KeyboardEventHandler(actionConsumer)); | ||
209 | comp.requestFocusInWindow(); | ||
210 | } | ||
211 | |||
212 | /** | ||
213 | * GUI's parameters. | ||
214 | */ | ||
215 | private final XBlastComponent gameComponent; | ||
216 | private final Consumer<PlayerAction> actionConsumer; | ||
217 | |||
218 | /** | ||
219 | * Instantiates a new GUI. | ||
220 | * | ||
221 | * @param actionConsumer actionConsumer corresponding to the keyboardEventHandler | ||
222 | */ | ||
223 | GUI(Consumer<PlayerAction> actionConsumer) { | ||
224 | this.gameComponent = new XBlastComponent(); | ||
225 | this.actionConsumer = actionConsumer; | ||
226 | } | ||
227 | |||
228 | /** | ||
229 | * Builds and display the GUI. | ||
230 | */ | ||
231 | public void display() { | ||
232 | buildFrame(this.gameComponent); | ||
233 | attachKeyboardHandler(this.gameComponent, this.actionConsumer); | ||
234 | } | ||
235 | |||
236 | /** | ||
237 | * Updates the displayed GameState. | ||
238 | * | ||
239 | * @param gs new GameState to be displayed | ||
240 | * @param p Player ID corresponding to the client | ||
241 | */ | ||
242 | public void setGameState(GameState gs, PlayerID p) { | ||