View Javadoc

1   // START LICENSE
2   // JSet - a Java JSet card board game implementation
3   // Copyright (C) 2004 Jerome Lacoste
4   //
5   // This program is free software; you can redistribute it and/or modify
6   // it under the terms of the GNU General Public License as published by
7   // the Free Software Foundation; either version 2 of the License, or (at
8   // your option) any later version.
9   //
10  // This program is distributed in the hope that it will be useful, but
11  // WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // General Public License for more details.
14  //
15  // You should have received a copy of the GNU General Public License
16  // along with this program; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18  // END LICENSE
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             // FIXME check that strange test
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       // not found?
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 //      endTurn();  // FIXME - not exactly the spec...
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       // check that game is being closed?
199 
200       // note: going reverse on the _nbPlayers index, as it is being modified
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     // FIXME we probably don't need threads in local connections...
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       //            _me.start();
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     // FIXME not clean: if called directly, _players doens't know about it
277     // add a parameter: with call back.
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       // final Thread meTemp = _me;
291       synchronized (this)
292       {
293         _me = null;
294         _isClosed = true;
295         notify();
296       }
297       /*
298       while (meTemp.isAlive()) {
299     try{
300         _logger.info("Wait for thread to Die...");
301         Thread.currentThread().sleep(500);
302     } catch  (InterruptedException e) {}
303       }
304       */
305 
306       // FIXME we could avoid calling back those who asked for disconnection or encountered a failure.
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         // precondition
377         if (! _players.allConnected()) {
378 	String msg = "Implementation error: Not all players registered!";
379 	if (_logger.isInfoEnabled())
380 	_logger.info(msg);
381 	throw new IllegalStateException(msg);
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       // FIXME we probably don't want to terminate here.
420       // we want the user to play until he is tired or there is a timeout.
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 //        _turnTimer.cancel(); //Terminate the timer thread
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   private void waitForAllPlayersToBeDisconnected() {
502       if (_logger.isInfoEnabled())
503           _logger.info("waitForAllPlayersToBeDisconnected");
504       synchronized  (this) {
505           while (! _players.noneConnected()) {
506               if (_logger.isInfoEnabled())
507                   _logger.info("nbPlayersConnected:" + _players.getCurrentNbPlayers());
508               try {
509                   wait();
510               } catch (InterruptedException e) {
511                   _logger.error("IE",e);  // FIXME - cleanup
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);   // FIXME - cleanup
530         }
531       }
532     }
533   }
534 
535   private void pushCardOnBoard(final int nbCards)
536   {
537     // precondition
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 //            if (_logger.isInfoEnabled())
569 //                _logger.info("CardSet removed");
570   }
571 
572   public void terminate()
573   {
574     if (_logger.isInfoEnabled())
575     {
576       _logger.info("Closing connections");
577     }
578     _players.close();
579     //         waitForAllPlayersToBeDisconnected();
580     // disconnecting from JSetGameServer?
581     // FIXME
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 }