1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.cb.jset;
21
22 import org.apache.log4j.Logger;
23 import org.cb.util.Assert;
24
25 import java.io.Serializable;
26 import java.util.Timer;
27 import java.util.TimerTask;
28
29 /***
30 * @author jerome@coffeebreaks.org - last modified by $LastChangedBy: jerome $
31 * @version $Id: JSetGameImpl.java 129 2004-04-15 05:00:43Z jerome $
32 */
33 public class JSetGameImpl implements SetGame
34 {
35 private Logger _logger = Logger.getLogger(this.getClass().getName());
36 private static int gameId = 0;
37
38 private String _name;
39 private int _id;
40 private Players _players;
41 private final CardStack _cardStack = new CardStack();
42 private SetGameBoard _board = new BoardImpl();
43
44 private GameThread _gameThread;
45 private Timer _turnTimer;
46 private boolean _endedTurn = false;
47 private int _turnLengthInSeconds = 60 * 3;
48
49 /***
50 * Keeps track of the various connected players for that game.
51 */
52 class Players
53 {
54 private GameConnectionImpl[] _players;
55 private int _nbPlayers;
56
57 /***
58 * Contructs an instance of Players, given the specified required number of playes.
59 * @param nbPlayers the number of players for that game.
60 */
61 Players(final int nbPlayers)
62 {
63 _players = new GameConnectionImpl[nbPlayers];
64 _nbPlayers = 0;
65 }
66
67 /***
68 * @return the maximum number of players.
69 */
70 synchronized int getMaxNbPlayers()
71 {
72 return _players.length;
73 }
74
75 /***
76 * @return the current number of registered players.
77 */
78 synchronized int getCurrentNbPlayers()
79 {
80 return _nbPlayers;
81 }
82
83 /***
84 * Adds the specified player to the game.
85 * @param player the player to add
86 * @return the game connection related to that player.
87 */
88 synchronized GameConnectionImpl addPlayer(final SetGamePlayer player)
89 {
90 Assert.assertTrue(_nbPlayers < _players.length, "All players are already registered.");
91
92 if (_logger.isInfoEnabled())
93 {
94 _logger.info("Players.addPlayer: current (" + _nbPlayers + " of " + _players.length + ")");
95 }
96 final GameConnectionImpl connection = new GameConnectionImpl(player);
97 _players[_nbPlayers] = connection;
98 _nbPlayers++;
99 return connection;
100 }
101
102 /***
103 * Removes the specified player from the game.
104 * @param player the player to remove
105 */
106 synchronized void removePlayer(final SetGamePlayer player)
107 {
108 if (_logger.isInfoEnabled())
109 {
110 _logger.info("Players.removePlayer: current (" + _nbPlayers + " of " + _players.length + ")");
111 }
112 for (int i = 0; i < _players.length; i++)
113 {
114 final GameConnectionImpl gameConnection = _players[i];
115 if (gameConnection != null)
116 {
117 if (gameConnection.getPlayer() == player)
118 {
119
120 if (!gameConnection.isClosed() && !(gameConnection.getPlayer() == null))
121 {
122 final String msg = "Implementation error: removing player while GC is non closed and/or gameConnection.getPlayer() not null";
123 _logger.error(msg);
124 throw new IllegalStateException(msg);
125 }
126 _players[i] = null;
127 _nbPlayers--;
128 return;
129 }
130 }
131 }
132
133 }
134
135 /***
136 * Fire a cards added event for all registered players.
137 * @see SetGamePlayer#cardsAdded(org.cb.jset.CardProperties[])
138 * @param cards the added cards
139 */
140 void fireAddedCardsEvent(final CardProperties[] cards)
141 {
142 if (_logger.isInfoEnabled())
143 {
144 _logger.info("Notifying players of added cards on board " + CardProperties.toString(cards));
145 }
146 for (int i = 0; i < _nbPlayers; i++)
147 {
148 Assert.assertTrue(_players[i] != null && _players[i].getPlayer() != null,
149 "Null player or null GameConnectionImpl");
150 final SetGamePlayer player = _players[i].getPlayer();
151 player.cardsAdded(cards);
152 }
153 }
154
155 /***
156 * Fire a set removed event for all registered players.
157 * @see SetGamePlayer#setRemoved(org.cb.jset.CardSet)
158 * @param set the removed set
159 */
160 void fireRemovedSetEvent(final CardSet set)
161 {
162 if (_logger.isInfoEnabled())
163 {
164 _logger.info("Notifying players of removed set from board" + CardProperties.toString(set.getCards()));
165 }
166 for (int i = 0; i < _nbPlayers; i++)
167 {
168 Assert.assertTrue(_players[i] != null && _players[i].getPlayer() != null,
169 "Null player or null GameConnectionImpl");
170 final SetGamePlayer player = _players[i].getPlayer();
171 player.setRemoved(set);
172 }
173
174 }
175
176 /***
177 * @return <code>true</code> if all expected players are connected.
178 */
179 synchronized boolean allConnected()
180 {
181 return _nbPlayers == _players.length;
182 }
183
184 /***
185 * @return <code>true</code> if no players are connected.
186 */
187 synchronized boolean noneConnected()
188 {
189 return _nbPlayers == 0;
190 }
191
192 /***
193 * Force the closing of the connections for all registered players.
194 * @see GameConnectionImpl#internalClose()
195 */
196 synchronized void close()
197 {
198
199
200
201 for (int i = _nbPlayers - 1; i >= 0; i--)
202 {
203 synchronized (_players[i])
204 {
205 if (!_players[i].isClosed())
206 {
207 _players[i].internalClose();
208 }
209 _nbPlayers--;
210 }
211 }
212 }
213 }
214
215 /***
216 * A GameConnectionImpl instance acts as a broker between a Player and the Game
217 * Useful to keep track which player does what.
218 * @todo we should probably turn this inner into a static inner class and make the reference
219 * to the associated Game transient
220 */
221 public class GameConnectionImpl implements SetGameConnection, Serializable, Runnable
222 {
223 private transient SetGamePlayer _player;
224 private boolean _isClosed = false;
225
226 private Thread _me;
227 private final transient Logger _logger;
228
229 /***
230 * Creates a new instance of GameConnectionImpl
231 */
232 public GameConnectionImpl(final SetGamePlayer player)
233 {
234 _logger = Logger.getLogger("GCon-" + player.getName());
235 _player = player;
236 _me = new Thread(this);
237 init();
238 }
239
240 void init()
241 {
242
243 }
244
245 public void matchSet(final CardSet set) throws MatchingException
246 {
247 if (_logger.isInfoEnabled())
248 {
249 _logger.info(getPlayer().getName() + " try to match a set: " + set);
250 }
251 try
252 {
253 removeMatchedSet(set);
254 }
255 catch (MatchingException e)
256 {
257 _logger.error("removeSet() failed: ", e);
258 _logger.error("Blocking player");
259 getPlayer().block();
260 throw e;
261 }
262 catch(BoardException e)
263 {
264 _logger.error("removeSet() failed: ", e);
265 _logger.error("Blocking player");
266 getPlayer().block();
267 throw new MatchingException(e.getMessage());
268 }
269 }
270
271 SetGamePlayer getPlayer()
272 {
273 return _player;
274 }
275
276
277
278 public void close()
279 {
280 internalClose();
281 endGame();
282 }
283
284 void internalClose()
285 {
286 if (_logger.isInfoEnabled())
287 {
288 _logger.info("Closing connection for player - " + this);
289 }
290
291 synchronized (this)
292 {
293 _me = null;
294 _isClosed = true;
295 notify();
296 }
297
298
299
300
301
302
303
304
305
306
307 _logger.info("Trying to disconnect properly....");
308 _player.disconnect();
309 _logger.info("Disconnected properly....");
310 }
311
312 public synchronized boolean isClosed()
313 {
314 return _isClosed;
315 }
316
317 public void run()
318 {
319 if (_logger.isInfoEnabled())
320 {
321 _logger.info("Starting GameConnectionImpl thread");
322 }
323 final Thread thisThread = Thread.currentThread();
324 while (_me == thisThread)
325 {
326 synchronized (this)
327 {
328 try
329 {
330 wait();
331 }
332 catch (InterruptedException e)
333 {
334 _logger.error("boum ", e);
335 }
336 }
337 }
338 if (_logger.isInfoEnabled())
339 {
340 _logger.info("Ending GameConnectionImpl thread");
341 }
342 }
343 }
344
345
346 public JSetGameImpl(final String name, final int nbPlayers)
347 {
348 _name = name;
349 _players = new Players(nbPlayers);
350 _id = gameId++;
351 _gameThread = new GameThread();
352
353 _board.addBoardListener(new SetGameBoardListener()
354 {
355 public void cardsAdded(final CardProperties[] cards)
356 {
357 notifyAddedCards(cards);
358 }
359
360 public void setRemoved(final CardSet set)
361 {
362 notifyRemovedSet(set);
363 }
364 });
365 }
366
367 class GameThread extends Thread
368 {
369 public void run()
370 {
371 final Thread thisThread = Thread.currentThread();
372
373 waitForAllPlayersToConnect();
374
375
376
377
378
379
380
381
382
383
384 if (_logger.isInfoEnabled())
385 {
386 _logger.info("-- starting game --");
387 }
388
389 int turn = 0;
390 while (_gameThread == thisThread &&
391 !_cardStack.empty() && _players.allConnected())
392 {
393
394 if (_logger.isInfoEnabled())
395 {
396 _logger.info("-- starting turn " + turn++ + " --");
397 }
398
399 int nbCardToAdd = 3;
400 if (_board.getNbCards() < 12)
401 {
402 nbCardToAdd = Math.min(12 - _board.getNbCards(), _cardStack.size());
403 }
404 pushCardOnBoard(nbCardToAdd);
405
406 startTurn();
407
408 waitForTurnToEnd();
409
410 if (_logger.isInfoEnabled())
411 {
412 _logger.info("-- ending turn --");
413 }
414 }
415 if (_logger.isInfoEnabled())
416 {
417 _logger.info("-- Ending Game -- ");
418 }
419
420
421 terminate();
422 }
423 }
424
425 public void start()
426 {
427 _cardStack.shuffle();
428 _gameThread.setDaemon(true);
429 _gameThread.start();
430 }
431
432 public int getTurnLength()
433 {
434 return _turnLengthInSeconds;
435 }
436
437 public void setTurnLength(final int turnInSeconds)
438 {
439 _turnLengthInSeconds = turnInSeconds;
440 }
441
442 void startTurn()
443 {
444 _endedTurn = false;
445 final int seconds = getTurnLength();
446 _turnTimer = new Timer();
447 _turnTimer.schedule(new TimerTask()
448 {
449 public void run()
450 {
451 endTurn();
452
453 }
454 }, seconds * 1000);
455 }
456
457 synchronized boolean endedTurn()
458 {
459 return _endedTurn;
460 }
461
462 synchronized void endGame()
463 {
464 if (_logger.isInfoEnabled())
465 {
466 _logger.info("endGame()!");
467 }
468 _gameThread = null;
469 notifyAll();
470 }
471
472 synchronized void endTurn()
473 {
474 if (_logger.isInfoEnabled())
475 {
476 _logger.info("Turn's up!");
477 }
478 _endedTurn = true;
479 _turnTimer.cancel();
480 notifyAll();
481 }
482
483 private void waitForAllPlayersToConnect()
484 {
485 synchronized (this)
486 {
487 while (!_players.allConnected())
488 {
489 try
490 {
491 wait();
492 }
493 catch (InterruptedException e)
494 {
495 }
496 }
497 }
498 }
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517 void waitForTurnToEnd()
518 {
519 while (!endedTurn())
520 {
521 synchronized (this)
522 {
523 try
524 {
525 wait();
526 }
527 catch (InterruptedException e)
528 {
529 _logger.error("IE", e);
530 }
531 }
532 }
533 }
534
535 private void pushCardOnBoard(final int nbCards)
536 {
537
538 if (nbCards > _cardStack.size())
539 {
540 final String msg = "Can't add more cards than present in the stack!";
541 if (_logger.isInfoEnabled())
542 {
543 _logger.info(msg);
544 }
545 throw new IllegalStateException(msg);
546 }
547
548 final CardProperties[] cardsToAdd = _cardStack.pop(nbCards);
549 if (_logger.isInfoEnabled())
550 {
551 _logger.info("-- adding " + nbCards + " on board --");
552 _logger.info("-- " + CardProperties.toString(_board.getCards()) +
553 " - " + CardProperties.toString(cardsToAdd) + " --");
554 }
555 _board.addCards(cardsToAdd);
556 }
557
558 private void removeMatchedSet(final CardSet set) throws MatchingException, BoardException
559 {
560 if (_logger.isInfoEnabled())
561 {
562 if (_logger.isInfoEnabled())
563 {
564 _logger.info("trying to remove a set:" + CardProperties.toString(set.getCards()));
565 }
566 }
567 _board.matchSet(set);
568
569
570 }
571
572 public void terminate()
573 {
574 if (_logger.isInfoEnabled())
575 {
576 _logger.info("Closing connections");
577 }
578 _players.close();
579
580
581
582 }
583
584 public String getName()
585 {
586 return _name;
587 }
588
589 public int getCurrentNbPlayers()
590 {
591 return _players.getCurrentNbPlayers();
592 }
593
594 public int getMaxNbPlayers()
595 {
596 return _players.getMaxNbPlayers();
597 }
598
599 public SetGameConnection connect(final SetGamePlayer player)
600 {
601 if (_logger.isInfoEnabled())
602 {
603 _logger.info("Connecting player:" + player.getName() + ">");
604 }
605 final GameConnectionImpl connection;
606 synchronized (this)
607 {
608 connection = _players.addPlayer(player);
609 if (_logger.isInfoEnabled())
610 {
611 _logger.info("Added player " + player);
612 }
613 notifyAll();
614 }
615 return connection;
616 }
617
618 void notifyAddedCards(final CardProperties[] cards)
619 {
620 _players.fireAddedCardsEvent(cards);
621 }
622
623 void notifyRemovedSet(final CardSet set)
624 {
625 _players.fireRemovedSetEvent(set);
626 }
627
628 public int getID()
629 {
630 return _id;
631 }
632 }