View Javadoc

1   /***
2    * Jetrix TetriNET Server
3    * Copyright (C) 2001-2003  Emmanuel Bourg
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU 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   */
19  
20  package net.jetrix;
21  
22  import static net.jetrix.GameState.*;
23  
24  import java.io.*;
25  import java.util.*;
26  import java.util.concurrent.*;
27  import java.util.logging.*;
28  import java.net.*;
29  
30  import net.jetrix.config.*;
31  import net.jetrix.filter.*;
32  import net.jetrix.messages.*;
33  import net.jetrix.messages.channel.*;
34  import net.jetrix.messages.channel.specials.*;
35  import net.jetrix.winlist.*;
36  import net.jetrix.clients.TetrinetClient;
37  
38  /***
39   * Game channel.
40   *
41   * @author Emmanuel Bourg
42   * @version $Revision: 868 $, $Date: 2010-08-27 13:41:18 +0200 (ven., 27 août 2010) $
43   */
44  public class Channel extends Thread implements Destination
45  {
46      /*** The maximum number of players per channel. */
47      public static final int MAX_PLAYERS = 6;
48      
49      private ChannelConfig channelConfig;
50      private ServerConfig serverConfig;
51      private Logger log = Logger.getLogger("net.jetrix");
52  
53      private BlockingQueue<Message> queue = new LinkedBlockingQueue<Message>();
54  
55      private boolean open;
56      private GameState gameState = STOPPED;
57      private boolean running = true;
58      private GameResult result;
59  
60      /*** The start time of the channel */
61      private long startTime;
62  
63      /*** set of clients connected to this channel */
64      private Set<Client> clients = new HashSet<Client>();
65  
66      /*** slot/player mapping */
67      private List<Client> slots = Arrays.asList(new Client[MAX_PLAYERS]);
68      private Field[] fields = new Field[MAX_PLAYERS];
69  
70      private List<MessageFilter> filters = new ArrayList<MessageFilter>();
71  
72      public Channel()
73      {
74          this(new ChannelConfig());
75      }
76  
77      public Channel(ChannelConfig channelConfig)
78      {
79          this.channelConfig = channelConfig;
80          this.serverConfig = Server.getInstance().getConfig();
81          
82          // initialize the players' fields
83          for (int i = 0; i < MAX_PLAYERS; i++)
84          {
85              fields[i] = new Field();
86          }
87          
88          /***
89           * Loading filters
90           */
91  
92          // global filters
93          if (serverConfig != null)
94          {
95              Iterator<FilterConfig> globalFilters = serverConfig.getGlobalFilters();
96              while (globalFilters.hasNext())
97              {
98                  addFilter(globalFilters.next());
99              }
100         }
101 
102         // channel filters
103         Iterator<FilterConfig> channelFilters = channelConfig.getFilters();
104         while (channelFilters.hasNext())
105         {
106             addFilter(channelFilters.next());
107         }
108     }
109 
110     /***
111      * Enable a new filter for this channel.
112      */
113     public void addFilter(FilterConfig filterConfig)
114     {
115         FilterManager filterManager = FilterManager.getInstance();
116         MessageFilter filter;
117 
118         // add the filter to the channel config if it isn't a global filter
119 
120         try
121         {
122             // getting filter instance
123             if (filterConfig.getClassname() != null)
124             {
125                 filter = filterManager.getFilter(filterConfig.getClassname());
126             }
127             else
128             {
129                 filter = filterManager.getFilterByName(filterConfig.getName());
130             }
131 
132             // initializing filter
133             filter.setChannel(this);
134             filter.setConfig(filterConfig);
135             filter.init();
136 
137             // add the filter to the list
138             filters.add(filter);
139         }
140         catch (FilterException e)
141         {
142             log.log(Level.WARNING, e.getMessage(), e);
143         }
144     }
145 
146     public void removeFilter(String filterName)
147     {
148         // remove the filter from the filter list
149         // @todo filter removal
150 
151         // remove the filter from the channel config
152     }
153 
154     public Iterator<MessageFilter> getFilters()
155     {
156         return filters.iterator();
157     }
158 
159     /***
160      * Returns the filters applied to this channel not defined at the server level.
161      */
162     public Collection<MessageFilter> getLocalFilters()
163     {
164         Collection<MessageFilter> localFilters = new ArrayList<MessageFilter>();
165         for (MessageFilter filter : filters)
166         {
167             if (!filter.getConfig().isGlobal())
168             {
169                 localFilters.add(filter);
170             }
171         }
172         return localFilters;
173     }
174 
175     /***
176      * Main loop. The channel listens for incoming messages until the server
177      * or the channel closes. Every message is first passed through the
178      * registered filters and then handled by the channel.
179      */
180     public void run()
181     {
182         log.info("Channel " + channelConfig.getName() + " opened");
183 
184         startTime = System.currentTimeMillis();
185 
186         while (running && serverConfig.isRunning()
187                 && (getConfig().isPersistent() || !clients.isEmpty() || (System.currentTimeMillis() - startTime < 200)))
188         {
189             LinkedList<Message> list = new LinkedList<Message>();
190 
191             try
192             {
193                 // waiting for new messages
194                 list.add(queue.take());
195 
196                 // filter the message
197                 for (MessageFilter filter : filters)
198                 {
199                     int size = list.size();
200                     for (int i = 0; i < size; i++)
201                     {
202                         filter.process(list.removeFirst(), list);
203                     }
204                 }
205 
206                 // process the message(s)
207                 while (!list.isEmpty())
208                 {
209                     process(list.removeFirst());
210                 }
211             }
212             catch (Exception e)
213             {
214                 log.log(Level.WARNING, e.getMessage(), e);
215             }
216 
217         }
218 
219         // unregister the channel
220         if (serverConfig.isRunning())
221         {
222             ChannelManager.getInstance().removeChannel(this);
223         }
224 
225         log.info("Channel " + channelConfig.getName() + " closed");
226     }
227 
228     /***
229      * Stop the channel.
230      */
231     public void close()
232     {
233         running = false;
234         queue.add(new ShutdownMessage());
235     }
236 
237     private void process(CommandMessage m)
238     {
239         // forwards the command to the server
240         Server.getInstance().send(m);
241     }
242 
243     private void process(TeamMessage m)
244     {
245         Client client = (Client) m.getSource();
246         if (client.getUser().isPlayer())
247         {
248             int slot = m.getSlot();
249             getPlayer(slot).setTeam(m.getName());
250             sendAll(m, slot);
251         }
252     }
253 
254     private void process(GmsgMessage m)
255     {
256         sendAll(m);
257     }
258 
259     private void process(PlineMessage m)
260     {
261         int slot = m.getSlot();
262         String text = m.getText();
263         if (!text.startsWith("/"))
264         {
265             sendAll(m, slot);
266         }
267     }
268 
269     private void process(PlineActMessage m)
270     {
271         int slot = m.getSlot();
272         if (m.getSource() == null)
273         {
274             // forged by the server, send to all
275             sendAll(m);
276         }
277         else
278         {
279             sendAll(m, slot);
280         }
281     }
282 
283     private void process(SmsgMessage m)
284     {
285         if (m.isPrivate())
286         {
287             // send the message to the spectators only
288             for (Client client : clients)
289             {
290                 if (client.getUser().isSpectator() && client != m.getSource())
291                 {
292                     client.send(m);
293                 }
294             }
295         }
296         else
297         {
298             sendAll(m);
299         }
300     }
301 
302     private void process(PauseMessage m)
303     {
304         gameState = PAUSED;
305 
306         // tell who paused the game if the message comes from a client
307         if (m.getSource() instanceof Client)
308         {
309             Client client = (Client) m.getSource();
310             sendAll(new PlineMessage("channel.game.paused-by", client.getUser().getName()));
311         }
312 
313         sendAll(m);
314     }
315 
316     private void process(ResumeMessage m)
317     {
318         gameState = STARTED;
319 
320         // tell who resumed the game if the message comes from a client
321         if (m.getSource() instanceof Client)
322         {
323             Client client = (Client) m.getSource();
324             sendAll(new PlineMessage("channel.game.resumed-by", client.getUser().getName()));
325         }
326 
327         sendAll(m);
328     }
329 
330     private void process(PlayerWonMessage m)
331     {
332         if (m.getSource() == null)
333         {
334             sendAll(m);
335         }
336     }
337 
338     private void process(PlayerLostMessage m)
339     {
340         int slot = m.getSlot();
341         Client client = getClient(slot);
342         User user = client.getUser();
343         sendAll(m);
344 
345         boolean wasPlaying = user.isPlaying();
346         user.setPlaying(false);
347 
348         // update the game result
349         if (wasPlaying)
350         {
351             result.update(user, false);
352         }
353 
354         // check for the end of the game
355         if (wasPlaying && countRemainingTeams() <= 1)
356         {
357             Message endgame = new EndGameMessage();
358             send(endgame);
359             result.setEndTime(new Date());
360 
361             // looking for the slot of the winner
362             slot = 0;
363             for (int i = 0; i < slots.size(); i++)
364             {
365                 client = slots.get(i);
366 
367                 if (client != null && client.getUser().isPlaying())
368                 {
369                     slot = i + 1;
370                     // update the result of the game
371                     result.update(client.getUser(), true);
372                 }
373             }
374 
375             // announcing the winner
376             if (slot != 0)
377             {
378                 PlayerWonMessage playerwon = new PlayerWonMessage();
379                 playerwon.setSlot(slot);
380                 send(playerwon);
381 
382                 User winner = getPlayer(slot);
383                 PlineMessage announce = new PlineMessage();
384                 if (winner.getTeam() == null)
385                 {
386                     announce.setKey("channel.player_won", winner.getName());
387                 }
388                 else
389                 {
390                     announce.setKey("channel.team_won", winner.getTeam());
391                 }
392                 send(announce);
393             }
394 
395             // update the winlist with the final result
396             Winlist winlist = WinlistManager.getInstance().getWinlist(channelConfig.getWinlistId());
397             if (winlist != null)
398             {
399                 winlist.saveGameResult(result);
400 
401                 List<Score> topScores = winlist.getScores(0, 10);
402                 WinlistMessage winlistMessage = new WinlistMessage();
403                 winlistMessage.setScores(topScores);
404                 sendAll(winlistMessage);
405             }
406 
407             // update the server statistics
408             serverConfig.getStatistics().increaseGameCount();
409         }
410     }
411 
412     private void process(SpecialMessage m)
413     {
414         // specials are not forwarded in pure mode
415         if (channelConfig.getSettings().getLinesPerSpecial() > 0)
416         {
417             int slot = m.getFromSlot();
418             sendAll(m, slot);
419         }
420     }
421 
422     private void process(LevelMessage m)
423     {
424         sendAll(m);
425     }
426 
427     private void process(FieldMessage m)
428     {
429         int slot = m.getSlot();
430 
431         // update the field of the player
432         fields[slot - 1].update(m);
433 
434         if (m.getSource() != null)
435         {
436             sendAll(m, slot);
437         }
438         else
439         {
440             if (m.isFullUpdate())
441             {
442                 // rewrite the field to handle properly the BLOCK_PREVIOUS type on non standard client
443                 m.setField(fields[slot - 1].getFieldString());
444             }
445             sendAll(m);
446         }
447     }
448 
449     private void process(StartGameMessage m)
450     {
451         if (gameState == STOPPED)
452         {
453             // change the channel state
454             gameState = STARTED;
455 
456             // tell who started the game if the message comes from a client
457             if (m.getSource() instanceof Client)
458             {
459                 Client client = (Client) m.getSource();
460                 sendAll(new PlineMessage("channel.game.started-by", client.getUser().getName()));
461             }
462 
463             // initialize the game result
464             result = new GameResult();
465             result.setStartTime(new Date());
466             result.setChannel(this);
467 
468             // change the game state of the players
469             for (Client client : slots)
470             {
471                 if (client != null && client.getUser().isPlayer())
472                 {
473                     client.getUser().setPlaying(true);
474                 }
475             }
476 
477             // clear the players' fields
478             for (int i = 0; i < MAX_PLAYERS; i++)
479             {
480                 fields[i].clear();
481             }
482 
483             // send the newgame message
484             NewGameMessage newgame = new NewGameMessage();
485             newgame.setSlot(m.getSlot());
486             newgame.setSettings(channelConfig.getSettings());
487             if (channelConfig.getSettings().getSameBlocks())
488             {
489                 Random random = new Random();
490                 newgame.setSeed(random.nextInt());
491             }
492 
493             sendAll(newgame);
494         }
495     }
496 
497     private void process(StopGameMessage m)
498     {
499         EndGameMessage end = new EndGameMessage();
500         end.setSlot(m.getSlot());
501         end.setSource(m.getSource());
502         send(end);
503     }
504 
505     private void process(EndGameMessage m)
506     {
507         if (gameState != STOPPED)
508         {
509             // tell who stopped the game if the message comes from a client
510             if (m.getSource() instanceof Client)
511             {
512                 Client client = (Client) m.getSource();
513                 sendAll(new PlineMessage("channel.game.stopped-by", client.getUser().getName()));
514             }
515 
516             gameState = STOPPED;
517             sendAll(m);
518 
519             // update the status of the remaining players
520             for (Client client : slots)
521             {
522                 if (client != null)
523                 {
524                     client.getUser().setPlaying(false);
525                 }
526             }
527         }
528     }
529 
530     private void process(DisconnectedMessage m)
531     {
532         Client client = m.getClient();
533         removeClient(client);
534 
535         sendAll(new PlineMessage("channel.disconnected", client.getUser().getName()));
536     }
537 
538     private void process(LeaveMessage m)
539     {
540         removeClient((Client) m.getSource());
541     }
542 
543     private void process(AddPlayerMessage m)
544     {
545         Client client = m.getClient();
546 
547         Channel previousChannel = client.getChannel();
548         client.setChannel(this);
549 
550         // remove the client from the previous channel if it doesn't support multiple channels
551         if (previousChannel != null && !client.supportsMultipleChannels())
552         {
553             // clear the player list
554             for (int j = 1; j <= MAX_PLAYERS; j++)
555             {
556                 if (previousChannel.getPlayer(j) != null)
557                 {
558                     LeaveMessage clear = new LeaveMessage();
559                     clear.setSlot(j);
560                     client.send(clear);
561                 }
562             }
563 
564             previousChannel.removeClient(client);
565 
566             // send a message to the previous channel announcing what channel the player joined
567             PlineMessage announce = new PlineMessage();
568             announce.setKey("channel.join_notice", client.getUser().getName(), channelConfig.getName());
569             previousChannel.send(announce);
570 
571             // clear the game status of the player
572             if (client.getUser().isPlaying())
573             {
574                 client.getUser().setPlaying(false);
575                 client.send(new EndGameMessage());
576             }
577         }
578 
579         // add the client to the channel
580         clients.add(client);
581 
582         if (client.getUser().isSpectator())
583         {
584             // announce the new player to the other clients in the channel
585             JoinMessage mjoin = new JoinMessage();
586             mjoin.setName(client.getUser().getName());
587             //sendAll(mjoin, client);
588             sendAll(mjoin);
589 
590             // send a boggus slot number for gtetrinet
591             PlayerNumMessage mnum = new PlayerNumMessage(1);
592             client.send(mnum);
593         }
594         else
595         {
596             // looking for the first free slot
597             int slot = 0;
598             for (slot = 0; slot < slots.size() && slots.get(slot) != null; slot++) ;
599 
600             if (slot >= MAX_PLAYERS)
601             {
602                 log.warning("[" + getConfig().getName() + "] Panic, no slot available for " + client);
603                 client.getUser().setSpectator();
604             }
605             else
606             {
607                 slots.set(slot, client);
608 
609                 // announce the new player to the other clients in the channel
610                 JoinMessage mjoin = new JoinMessage();
611                 mjoin.setSlot(slot + 1);
612                 mjoin.setName(client.getUser().getName());
613                 sendAll(mjoin, client);
614 
615                 // send the slot number assigned to the new player
616                 PlayerNumMessage mnum = new PlayerNumMessage(slot + 1);
617                 client.send(mnum);
618             }
619 
620             // update the access level of the channel operator
621             updateChannelOperator();
622         }
623 
624         // send the list of spectators
625         if (client.getUser().isSpectator())
626         {
627             sendSpectatorList(client);
628         }
629 
630         // send the list of players
631         for (int i = 0; i < slots.size(); i++)
632         {
633             Client resident = slots.get(i);
634             if (resident != null && resident != client)
635             {
636                 // players...
637                 JoinMessage mjoin2 = new JoinMessage();
638                 mjoin2.setChannelName(getConfig().getName());
639                 mjoin2.setSlot(i + 1);
640                 mjoin2.setName(resident.getUser().getName()); // NPE
641                 client.send(mjoin2);
642 
643                 // ...and teams
644                 TeamMessage mteam = new TeamMessage();
645                 mteam.setChannelName(getConfig().getName());
646                 mteam.setSource(resident);
647                 mteam.setSlot(i + 1);
648                 mteam.setName(resident.getUser().getTeam());
649                 client.send(mteam);
650             }
651         }
652 
653         // send the fields
654         for (int i = 0; i < MAX_PLAYERS; i++)
655         {
656             if (!fields[i].isEmpty() || previousChannel != null)
657             {
658                 FieldMessage message = new FieldMessage(i + 1, fields[i].getFieldString());
659                 client.send(message);
660             }
661         }
662 
663         // send the winlist
664         Winlist winlist = WinlistManager.getInstance().getWinlist(channelConfig.getWinlistId());
665         if (winlist != null)
666         {
667             List<Score> topScores = winlist.getScores(0, 10);
668             WinlistMessage winlistMessage = new WinlistMessage();
669             winlistMessage.setScores(topScores);
670             client.send(winlistMessage);
671         }
672 
673         // send a welcome message to the incoming client
674         PlineMessage mwelcome = new PlineMessage();
675         mwelcome.setKey("channel.welcome", client.getUser().getName(), channelConfig.getName());
676         client.send(mwelcome);
677 
678         // send the topic of the channel
679         if (channelConfig.getTopic() != null)
680         {
681             BufferedReader topic = new BufferedReader(new StringReader(channelConfig.getTopic()));
682             try
683             {
684                 String line;
685                 while ((line = topic.readLine()) != null)
686                 {
687                     PlineMessage message = new PlineMessage();
688                     message.setText("<kaki>" + line);
689                     client.send(message);
690                 }
691                 topic.close();
692             }
693             catch (Exception e)
694             {
695                 log.log(Level.WARNING, e.getMessage(), e);
696             }
697         }
698 
699         // send the list of spectators
700         if (client.getUser().isPlayer())
701         {
702             sendSpectatorList(client);
703         }
704 
705         // send the status of the game to the new client
706         if (gameState != STOPPED)
707         {
708             IngameMessage ingame = new IngameMessage();
709             ingame.setChannelName(getConfig().getName());
710             client.send(ingame);
711 
712             // tell the player if the game is currently paused
713             if (gameState == PAUSED)
714             {
715                 client.send(new PauseMessage());
716             }
717         }
718 
719         // adjust the timeout
720         if (client instanceof TetrinetClient)
721         {
722             int timeout = getConfig().isIdleAllowed() ? 0 : serverConfig.getTimeout() * 1000;
723             try
724             {
725                 ((TetrinetClient) client).getSocket().setSoTimeout(timeout);
726             }
727             catch (SocketException e)
728             {
729                 log.log(Level.WARNING, "Unable to change the timeout", e);
730             }
731         }
732     }
733 
734     /***
735      * Send the list of spectators in this channel to the specified client.
736      */
737     private void sendSpectatorList(Client client)
738     {
739         // extract the list of spectators
740         List<String> specnames = new ArrayList<String>();
741 
742         for (Client c : clients)
743         {
744             if (c.getUser().isSpectator())
745             {
746                 specnames.add(c.getUser().getName());
747             }
748         }
749 
750         // send the list of spectators
751         if (!specnames.isEmpty())
752         {
753             SpectatorListMessage spectators = new SpectatorListMessage();
754             spectators.setDestination(client);
755             spectators.setChannel(getConfig().getName());
756             spectators.setSpectators(specnames);
757             client.send(spectators);
758         }
759     }
760 
761     public void process(PlayerSwitchMessage m)
762     {
763         if (gameState == STOPPED)
764         {
765             // get the players at the specified slots
766             Client player1 = getClient(m.getSlot1());
767             Client player2 = getClient(m.getSlot2());
768 
769             // swap the players
770             slots.set(m.getSlot1() - 1, player2);
771             slots.set(m.getSlot2() - 1, player1);
772 
773             // make the change visible to all clients
774             if (player1 != null)
775             {
776                 LeaveMessage leave1 = new LeaveMessage();
777                 leave1.setSlot(m.getSlot1());
778                 sendAll(leave1);
779             }
780 
781             if (player2 != null)
782             {
783                 LeaveMessage leave2 = new LeaveMessage();
784                 leave2.setSlot(m.getSlot2());
785                 sendAll(leave2);
786             }
787 
788             if (player1 != null)
789             {
790                 JoinMessage mjoin = new JoinMessage();
791                 mjoin.setSlot(m.getSlot2());
792                 mjoin.setName(player1.getUser().getName());
793                 sendAll(mjoin);
794 
795                 PlayerNumMessage mnum = new PlayerNumMessage(m.getSlot2());
796                 player1.send(mnum);
797             }
798 
799             if (player2 != null)
800             {
801                 JoinMessage mjoin = new JoinMessage();
802                 mjoin.setSlot(m.getSlot1());
803                 mjoin.setName(player2.getUser().getName());
804                 sendAll(mjoin);
805 
806                 PlayerNumMessage mnum = new PlayerNumMessage(m.getSlot1());
807                 player2.send(mnum);
808             }
809 
810             // update the access level of the channel operator
811             updateChannelOperator();
812         }
813     }
814 
815     public void process(Message m)
816     {
817         if (log.isLoggable(Level.FINEST))
818         {
819             log.finest("[" + channelConfig.getName() + "] Processing " + m.getClass().getSimpleName() + " from " + m.getSource());
820         }
821 
822         if (m instanceof CommandMessage) process((CommandMessage) m);
823         else if (m instanceof FieldMessage) process((FieldMessage) m);
824         else if (m instanceof SpecialMessage) process((SpecialMessage) m);
825         else if (m instanceof LevelMessage) process((LevelMessage) m);
826         else if (m instanceof PlayerLostMessage) process((PlayerLostMessage) m);
827         else if (m instanceof PlayerWonMessage) process((PlayerWonMessage) m);
828         else if (m instanceof TeamMessage) process((TeamMessage) m);
829         else if (m instanceof PlineMessage) process((PlineMessage) m);
830         else if (m instanceof GmsgMessage) process((GmsgMessage) m);
831         else if (m instanceof SmsgMessage) process((SmsgMessage) m);
832         else if (m instanceof PlineActMessage) process((PlineActMessage) m);
833         else if (m instanceof PauseMessage) process((PauseMessage) m);
834         else if (m instanceof ResumeMessage) process((ResumeMessage) m);
835         else if (m instanceof StartGameMessage) process((StartGameMessage) m);
836         else if (m instanceof StopGameMessage) process((StopGameMessage) m);
837         else if (m instanceof EndGameMessage) process((EndGameMessage) m);
838         else if (m instanceof DisconnectedMessage) process((DisconnectedMessage) m);
839         else if (m instanceof PlayerSwitchMessage) process((PlayerSwitchMessage) m);
840         else if (m instanceof LeaveMessage) process((LeaveMessage) m);
841         else if (m instanceof AddPlayerMessage) process((AddPlayerMessage) m);
842         else
843         {
844             if (log.isLoggable(Level.FINEST))
845             {
846                 log.finest("[" + channelConfig.getName() + "] Message not processed " + m);
847             }
848         }
849     }
850 
851     /***
852      * Remove the specified client from the channel.
853      */
854     public void removeClient(Client client)
855     {
856         if (client != null)
857         {
858             clients.remove(client);
859 
860             LeaveMessage leave = new LeaveMessage();
861             leave.setName(client.getUser().getName());
862 
863             int slot = slots.indexOf(client);
864             if (slot != -1)
865             {
866                 slots.set(slot, null);
867                 leave.setSlot(slot + 1);
868             }
869 
870             // update the result of the game
871             if (gameState != STOPPED && client.getUser().isPlaying())
872             {
873                 result.update(client.getUser(), false);
874             }
875 
876             sendAll(leave);
877         }
878 
879         // update the channel operator is the channel is not empty
880         if (!isEmpty())
881         {
882             updateChannelOperator();
883         }
884 
885         // stop the game if the channel is now empty
886         if (isEmpty() && running)
887         {
888             gameState = STOPPED;
889         }
890 
891         // stop the channel if it's not persistent
892         if (clients.isEmpty() && !getConfig().isPersistent())
893         {
894             send(new ShutdownMessage());
895         }
896     }
897 
898     /***
899      * Add a message to the channel message queue.
900      *
901      * @param message message to add
902      */
903     public void send(Message message)
904     {
905         queue.add(message);
906     }
907 
908     /***
909      * Send a message to all players in this channel.
910      *
911      * @param message the message to send
912      */
913     private void sendAll(Message message)
914     {
915         if (message.getDestination() == null)
916         {
917             message.setDestination(this);
918         }
919 
920         // @todo add a fast mode to iterate on players only for game messages
921         for (Client client : clients)
922         {
923             client.send(message);
924         }
925     }
926 
927     /***
928      * Send a message to all players but the one in the specified slot.
929      *
930      * @param message the message to send
931      * @param slot    the slot to exclude
932      */
933     private void sendAll(Message message, int slot)
934     {
935         Client client = getClient(slot);
936         sendAll(message, client);
937     }
938 
939     /***
940      * Send a message to all players but the specified client.
941      *
942      * @param message the message to send
943      * @param c       the client to exclude
944      */
945     private void sendAll(Message message, Client c)
946     {
947         if (message.getDestination() == null)
948         {
949             message.setDestination(this);
950         }
951 
952         // @todo add a fast mode to iterate on players only for game messages
953         for (Client client : clients)
954         {
955             if (client != c)
956             {
957                 client.send(message);
958             }
959         }
960     }
961 
962     /***
963      * Tell if the channel can accept more players.
964      *
965      * @return <tt>true</tt> if the channel is full, <tt>false</tt> if not
966      */
967     public boolean isFull()
968     {
969         return getPlayerCount() >= channelConfig.getMaxPlayers();
970     }
971 
972     public boolean isEmpty()
973     {
974         return getPlayerCount() == 0;
975     }
976 
977     /***
978      * Returns the number of players currently in this channel.
979      *
980      * @return player count
981      */
982     public int getPlayerCount()
983     {
984         int count = 0;
985         
986         for (Client client : slots)
987         {
988             if (client != null)
989             {
990                 count++;
991             }
992         }
993 
994         return count;
995     }
996 
997     /***
998      * Returns the channel configuration.
999      */
1000     public ChannelConfig getConfig()
1001     {
1002         return channelConfig;
1003     }
1004 
1005     /***
1006      * Returns the game state.
1007      */
1008     public GameState getGameState()
1009     {
1010         return gameState;
1011     }
1012 
1013     /***
1014      * Finds the slot used in the channel by the specified client.
1015      */
1016     public int getClientSlot(Client client)
1017     {
1018         return (slots.indexOf(client) + 1);
1019     }
1020 
1021     /***
1022      * Returns the client in the specified slot.
1023      *
1024      * @param slot slot number between 1 and 6
1025      *
1026      * @return <tt>null</tt> if there is no client in the specified slot, or if the number is out of range
1027      */
1028     public Client getClient(int slot)
1029     {
1030         Client client = null;
1031 
1032         if (slot >= 1 && slot <= slots.size())
1033         {
1034             client = slots.get(slot - 1);
1035         }
1036 
1037         return client;
1038     }
1039 
1040     /***
1041      * Returns the client in the specified slot.
1042      *
1043      * @param slot slot number between 1 and 6
1044      *
1045      * @return <tt>null</tt> if there is no client in the specified slot, or if the number is out of range
1046      */
1047     public User getPlayer(int slot)
1048     {
1049         Client client = getClient(slot);
1050         return (client != null) ? client.getUser() : null;
1051     }
1052 
1053     /***
1054      * Return an iterator of players in this channel.
1055      */
1056     public Iterator<Client> getPlayers()
1057     {
1058         return slots.iterator();
1059     }
1060 
1061     /***
1062      * Return an iterator of spectators observing this channel.
1063      */
1064     public Iterator<Client> getSpectators()
1065     {
1066         List<Client> spectators = new ArrayList<Client>();
1067 
1068         for (Client client : clients)
1069         {
1070             if (client.getUser().isSpectator())
1071             {
1072                 spectators.add(client);
1073             }
1074         }
1075 
1076         return spectators.iterator();
1077     }
1078 
1079     /***
1080      * Count how many teams are still fighting for victory. A teamless player
1081      * is considered as a separate team. The game ends when there is one team
1082      * left in game OR when the last player loose if only one team took part
1083      * in the game.
1084      *
1085      * @return number of teams still playing
1086      */
1087     private int countRemainingTeams()
1088     {
1089         Map<String, String> playingTeams = new HashMap<String, String>();
1090 
1091         int nbTeamsLeft = 0;
1092 
1093         for (Client client : slots)
1094         {
1095             if (client != null && client.getUser().isPlaying())
1096             {
1097                 String team = client.getUser().getTeam();
1098 
1099                 if (team == null)
1100                 {
1101                     nbTeamsLeft++;
1102                 }
1103                 else
1104                 {
1105                     playingTeams.put(team, team);
1106                 }
1107             }
1108         }
1109 
1110         return nbTeamsLeft + playingTeams.size();
1111     }
1112 
1113     /***
1114      * Return the field of the specified slot.
1115      */
1116     public Field getField(int slot)
1117     {
1118         return fields[slot];
1119     }
1120 
1121     /***
1122      * Promote the first player in the channel to the channel operator access
1123      * level if necessary, and demote the former channel operator to the
1124      * player access level.
1125      *
1126      * @since 0.3
1127      */
1128     private void updateChannelOperator()
1129     {
1130         boolean firstFound = false;
1131         for (Client client : slots)
1132         {
1133             if (client != null)
1134             {
1135                 User user = client.getUser();
1136 
1137                 if (user.getAccessLevel() == AccessLevel.PLAYER && !firstFound)
1138                 {
1139                     // promote to channel operator
1140                     user.setAccessLevel(AccessLevel.CHANNEL_OPERATOR);
1141                 }
1142                 else if (user.getAccessLevel() == AccessLevel.CHANNEL_OPERATOR && firstFound)
1143                 {
1144                     // demote the player
1145                     user.setAccessLevel(AccessLevel.PLAYER);
1146                 }
1147 
1148                 firstFound = true;
1149             }
1150         }
1151 
1152     }
1153 
1154 }