-
Notifications
You must be signed in to change notification settings - Fork 1
/
PlayerThread.java
408 lines (332 loc) · 13.2 KB
/
PlayerThread.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class PlayerThread implements Runnable {
private String name = null;
private int turn = -1;
private static CyclicBarrier barrier;
private static Suit selectedSuit = null;
private static CardCollection pile;
private static GameLogger logger;
private static int numOfPlayers = 0;
private static int currentTurn = 1;
private static Object roundOneLock = new Object();
private static Object roundTwoLock = new Object();
private static final int CARDS_IN_FIRST_TURN = 5;
private static final int CARDS_IN_SECOND_TURN = 2;
private ClientInterface clientInterface;
private class ClientInterface {
private ObjectOutputStream out;
private ObjectInputStream in;
public ClientInterface(ObjectOutputStream out, ObjectInputStream in) {
this.out = out;
this.in = in;
}
// __Methods for communicating with the client__
/**
* Requests a resource from the client over the socket.
* @param resType -> Specifies the type of resource required as defined by GameProtocol.
* @return The requested resource is returned as an Object. Caller must apply appropriate cast to convert the object into the
* required type.
* @throws IOException
* @throws ClassNotFoundException
*/
private Object retrieveClientResource(GameProtocol resType) throws IOException, ClassNotFoundException {
out.writeObject(resType);
out.reset();
return in.readObject();
}
/**
* Requests some number of resources from the client over the socket.
* @param resType -> Specifies the type of resource required as defined by GameProtocol.
* @param count -> Specifies the how many resources are required.
* @return The requested resource is returned as an Object. Caller must apply appropriate cast to convert the object into the
* required type.
* @throws IOException
* @throws ClassNotFoundException
*/
private Object retrieveClientResource(GameProtocol resType, int count) throws IOException, ClassNotFoundException {
out.writeObject(resType);
out.writeObject(count);
out.reset();
return in.readObject();
}
/**
* Sends some resource to the client.
* @param resType -> Specifies which resource is being sent to the client.
* @param res -> Represents the actual resource.
* @throws IOException
*/
private void sendClientResource(GameProtocol resType, Object res) throws IOException {
out.writeObject(resType);
out.writeObject(res);
out.reset();
}
/**
* Send some number of resources to the client.
* @param resType -> Specifies which resource is being requested from the client.
* @param res -> Represents the actual resource.
* @param count -> Specifies how many resource of resType are needed.
* @throws IOException
*/
private void sendClientResource(GameProtocol resType, Object res, int count) throws IOException {
out.writeObject(resType);
out.writeObject(count);
out.writeObject(res);
out.reset();
}
/**
* Tells client to perform some operation on its end.
* @param cmd -> Specifies the operation which the client should perform as specified by GameProtocol.
* @throws IOException
*/
private void issueClientCommand(GameProtocol cmd) throws IOException {
out.writeObject(cmd);
out.reset();
}
}
// ______________PUBLIC______________
public PlayerThread(ObjectOutputStream out, ObjectInputStream in) {
clientInterface = new ClientInterface(out, in);
numOfPlayers++;
}
// __Setters__
public static void setBarrier(CyclicBarrier barrier) {
PlayerThread.barrier = barrier;
}
public static void setLogger(GameLogger logger) {
PlayerThread.logger = logger;
}
public static void setPile(CardCollection pile) {
PlayerThread.pile = pile;
}
public void setTurn(int turn) {
if (this.turn == -1) {
this.turn = turn;
}
else {
System.err.println("Player turn has already been set and cannot be changed");
}
}
// __Getters__
public static Suit getSelectedSuit() {
return selectedSuit;
}
/*
* Returns the CardCollection representing the players current hand.
*/
public CardCollection getHand() throws ClassNotFoundException, IOException {
return (CardCollection) clientInterface.retrieveClientResource(GameProtocol.SEND_HAND);
}
public int getHandSize() throws ClassNotFoundException, IOException {
return (Integer) clientInterface.retrieveClientResource(GameProtocol.SEND_HAND_SIZE);
}
public int getTurn() {
return turn;
}
public String getPlayerName() {
return name;
}
// __Methods__
/*
* Runs the player thread which completes it first turn, waits for all other players to complete their
* first turns and then proceeds to complete its second turn.
*/
@Override
public void run() {
try {
firstTurn();
}
catch (ClassNotFoundException | IOException e1) {
System.err.println("Error exchanging cards b/w client and player during first turn of " + name);
e1.printStackTrace();
}
waitForFirstRoundToComplete();
try {
secondTurn();
}
catch (IOException | ClassNotFoundException e) {
System.err.println("Error exchanging cards b/w client and player during second turn of " + name);
e.printStackTrace();
}
}
// __Client interaction methods__
/*
* Send the specified card to the player client so that he/she can
* add it to their hand.
*/
public void pickupCard(Card card) throws IOException {
clientInterface.sendClientResource(GameProtocol.RECEIVE_CARD, card);
}
/*
* Removes the specified number of cards from the players hand. The removed cards
* are selected randomly.
*/
public CardCollection dumpRandomCards(int numOfCards) throws IOException, ClassNotFoundException {
// Request PlayerClient to return the specified number of Card's in a random fashion (i.e. Card's are in a random order)
return (CardCollection) clientInterface.retrieveClientResource(GameProtocol.SEND_CARDS_RANDOMLY_HAND, numOfCards);
}
/*
* Removes the specified number of cards from the players hand. The removed cards
* are lowest score card that the player currently has.
*/
public CardCollection dumpCardsStrategically(int numOfCards) throws ClassNotFoundException, IOException {
return (CardCollection) clientInterface.retrieveClientResource(GameProtocol.SEND_CARDS_STRATEGICALLY_HAND, numOfCards);
}
/*
* Prints the names of all the cards currently in the players hand.
*/
public void printHand() throws ClassNotFoundException, IOException {
CardCollection collec = (CardCollection) clientInterface.retrieveClientResource(GameProtocol.SEND_HAND);
collec.printCollection();
}
/*
* Sorts the hand according to suit and card value. Sorting criteria specified in
* the assignment file.
*/
public void sortHand() throws IOException {
clientInterface.issueClientCommand(GameProtocol.SORT_HAND);
}
public CardCollection retrievePlayerHand() throws ClassNotFoundException, IOException {
return (CardCollection) clientInterface.retrieveClientResource(GameProtocol.SEND_HAND);
}
public int retrievePlayerHandSize() throws ClassNotFoundException, IOException {
return (Integer) clientInterface.retrieveClientResource(GameProtocol.SEND_HAND_SIZE);
}
/*
* Asks client to send player name. If name has already been sent previously,
* returns that instead.
*/
public void setPlayerName() throws IOException, ClassNotFoundException {
name = (String) clientInterface.retrieveClientResource(GameProtocol.SEND_NAME);
}
public void closeConnection(GameProtocol status) throws IOException {
clientInterface.issueClientCommand(GameProtocol.GAME_OVER);
clientInterface.issueClientCommand(status);
}
/*
* Ask client to select a suit and send it back to the Player thread over the socket.
*/
public Suit getSelectedSuitFromClient() throws IOException, ClassNotFoundException {
if (this.selectedSuit == null) {
return (Suit) clientInterface.retrieveClientResource(GameProtocol.SEND_SUIT);
}
else {
return this.selectedSuit;
}
}
/*
* Sends the specified suit to the client.
*/
public void sendSelectedSuit(Suit suit) throws IOException {
clientInterface.sendClientResource(GameProtocol.RECEIVE_SUIT, suit);
}
public CardCollection drawCardsFromPile(int numOfCards) throws IOException, ClassNotFoundException {
return (CardCollection) clientInterface.retrieveClientResource(GameProtocol.SEND_CARDS_STRATEGICALLY_PILE, numOfCards);
}
// ______________PRIVATE______________
private void firstTurn() throws ClassNotFoundException, IOException {
// Player thread checks if it is its turn. If it's not, it goes into a waiting state
// using roundOneLock. If it is its turn, it finishes its first turn and then uses
// notifyAll to wake up all the other waiting Player threads. This loop runs again
// in all the newly awoken threads and the Player thread with the next turn gets to
// complete its first turn whilst the rest of threads go into a waiting state again.
// Once the running thread completes its turn, it wakes up all the waiting threads
// again and this loop repeats itself. This ensures that the Player threads complete
// their turn in the assigned order.
synchronized (roundOneLock) {
while (turn != currentTurn) {
try {
roundOneLock.wait();
}
catch (InterruptedException e) {
System.out.println(this.name + "'s thread got interrupted!");
}
}
sortHand();
logger.logHeading(name + "'s first turn");
logger.log("\n--" + name + "'s hand before first turn (" + retrievePlayerHandSize() + ")");
logger.logCards(retrievePlayerHand());
CardCollection dumpedCards = dumpCardsStrategically(CARDS_IN_FIRST_TURN);
logger.log("--Following five cards dumped into pile:");
logger.logCards(dumpedCards);
logger.log("--Pile before dumping:");
logger.logCards(pile);
pile.addCardCollection(dumpedCards);
logger.log("--Pile after dumping:");
logger.logCards(pile);
// If last turn of first round
if (turn == numOfPlayers) {
currentTurn = 1;
}
else {
currentTurn++;
}
roundOneLock.notifyAll();
sortHand();
logger.log("--" + name + "'s hand after first turn (" + retrievePlayerHandSize() + ")");
logger.logCards(retrievePlayerHand());
logger.addNewLine();
}
}
private void secondTurn() throws IOException, ClassNotFoundException {
// The same code is used as that in firstTurn(). Only the lock variable is changed.
synchronized (roundTwoLock) {
while (turn != currentTurn) {
try {
roundTwoLock.wait();
}
catch (InterruptedException e) {
System.out.println(this.name + "'s thread got interrupted!");
}
}
logger.logHeading(name + "'s second turn");
logger.log("\n--" + name + "'s hand before second turn (" + retrievePlayerHandSize() + ")");
logger.logCards(retrievePlayerHand());
CardCollection dumpedCards = dumpCardsStrategically(CARDS_IN_SECOND_TURN);
logger.log("--Following two cards dumped into pile:");
logger.logCards(dumpedCards);
logger.log("--Pile before dumping:");
logger.logCards(pile);
pile.addCardCollection(dumpedCards);
logger.log("--Pile after dumping:");
logger.logCards(pile);
// Send the client the current pile
clientInterface.sendClientResource(GameProtocol.RECEIVE_CARD_COLLECTION, pile);
// Tell client to draw 2 cards from the sent pile and add them to their hand and then send those
// added cards back so the pile can be updated.
CardCollection drawnCards = drawCardsFromPile(CARDS_IN_SECOND_TURN);;
pile.removeCards(drawnCards);
logger.log("--Cards drawn from the pile:");
logger.logCards(drawnCards);
logger.log("--Pile after drawing cards:");
logger.logCards(pile);
roundTwoLock.notifyAll();
currentTurn++;
sortHand();
logger.log("--" + name + "'s hand after second turn (" + retrievePlayerHandSize() + ")");
logger.logCards(retrievePlayerHand());
}
}
private void waitForFirstRoundToComplete() {
// As each player completes its first turn, it begins to wait on the barrier.
// The barrier will only open when all the player threads call barrier.await().
// The number of threads needed to trip the barrier is specified when the
// barrier is created. In our case, it is equal to the number of players, meaning
// that only when all the players have completed their first turn will they
// proceed towards their second turn.
try {
barrier.await();
}
catch (InterruptedException e) {
System.out.println(this.name + "'s thread got interrupted!");
e.printStackTrace();
}
catch (BrokenBarrierException e) {
System.out.println(this.name + " still waiting for completion of first round!");
e.printStackTrace();
}
}
}