001    /**
002     * Jetrix TetriNET Server
003     * Copyright (C) 2001-2003  Emmanuel Bourg
004     *
005     * This program is free software; you can redistribute it and/or
006     * modify it under the terms of the GNU General Public License
007     * as published by the Free Software Foundation; either version 2
008     * of the License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU General Public License for more details.
014     *
015     * You should have received a copy of the GNU General Public License
016     * along with this program; if not, write to the Free Software
017     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
018     */
019    
020    package net.jetrix;
021    
022    import static net.jetrix.GameState.*;
023    
024    import java.io.*;
025    import java.util.*;
026    import java.util.concurrent.*;
027    import java.util.logging.*;
028    import java.net.*;
029    
030    import net.jetrix.config.*;
031    import net.jetrix.filter.*;
032    import net.jetrix.messages.*;
033    import net.jetrix.messages.channel.*;
034    import net.jetrix.messages.channel.specials.*;
035    import net.jetrix.winlist.*;
036    import net.jetrix.clients.TetrinetClient;
037    
038    /**
039     * Game channel.
040     *
041     * @author Emmanuel Bourg
042     * @version $Revision: 868 $, $Date: 2010-08-27 13:41:18 +0200 (ven., 27 août 2010) $
043     */
044    public class Channel extends Thread implements Destination
045    {
046        /** The maximum number of players per channel. */
047        public static final int MAX_PLAYERS = 6;
048        
049        private ChannelConfig channelConfig;
050        private ServerConfig serverConfig;
051        private Logger log = Logger.getLogger("net.jetrix");
052    
053        private BlockingQueue<Message> queue = new LinkedBlockingQueue<Message>();
054    
055        private boolean open;
056        private GameState gameState = STOPPED;
057        private boolean running = true;
058        private GameResult result;
059    
060        /** The start time of the channel */
061        private long startTime;
062    
063        /** set of clients connected to this channel */
064        private Set<Client> clients = new HashSet<Client>();
065    
066        /** slot/player mapping */
067        private List<Client> slots = Arrays.asList(new Client[MAX_PLAYERS]);
068        private Field[] fields = new Field[MAX_PLAYERS];
069    
070        private List<MessageFilter> filters = new ArrayList<MessageFilter>();
071    
072        public Channel()
073        {
074            this(new ChannelConfig());
075        }
076    
077        public Channel(ChannelConfig channelConfig)
078        {
079            this.channelConfig = channelConfig;
080            this.serverConfig = Server.getInstance().getConfig();
081            
082            // initialize the players' fields
083            for (int i = 0; i < MAX_PLAYERS; i++)
084            {
085                fields[i] = new Field();
086            }
087            
088            /**
089             * Loading filters
090             */
091    
092            // global filters
093            if (serverConfig != null)
094            {
095                Iterator<FilterConfig> globalFilters = serverConfig.getGlobalFilters();
096                while (globalFilters.hasNext())
097                {
098                    addFilter(globalFilters.next());
099                }
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    }