001    /**
002     * Jetrix TetriNET Server
003     * Copyright (C) 2001-2005  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.filter;
021    
022    import static net.jetrix.GameState.*;
023    
024    import java.util.*;
025    import java.text.*;
026    
027    import net.jetrix.*;
028    import net.jetrix.messages.channel.*;
029    import net.jetrix.messages.channel.specials.*;
030    import org.apache.commons.lang.time.StopWatch;
031    
032    /**
033     * A filter computing and displaying the number of pieces dropped per minute
034     * by each player.
035     *
036     * @author Emmanuel Bourg
037     * @version $Revision: 799 $, $Date: 2009-02-18 17:28:08 +0100 (Wed, 18 Feb 2009) $
038     */
039    public class StatsFilter extends GenericFilter
040    {
041        private List<PlayerStats> stats;
042        private StopWatch stopWatch;
043    
044        private static DecimalFormat df = new DecimalFormat("0.00");
045    
046        public void init()
047        {
048            stopWatch = new StopWatch();
049            stats = new ArrayList<PlayerStats>(6);
050            for (int i = 0; i < 6; i++)
051            {
052                stats.add(null);
053            }
054        }
055    
056        public void onMessage(StartGameMessage m, List<Message> out)
057        {
058            if (getChannel().getGameState() == STOPPED)
059            {
060                // reset and start the stop watch
061                stopWatch.reset();
062                stopWatch.start();
063    
064                // initialize the stats
065                for (int i = 0; i < 6; i++)
066                {
067                    if (getChannel().getClient(i + 1) != null)
068                    {
069                        stats.set(i, new PlayerStats());
070                    }
071                    else
072                    {
073                        stats.set(i, null);
074                    }
075                }
076            }
077    
078            out.add(m);
079        }
080    
081        public void onMessage(EndGameMessage m, List<Message> out)
082        {
083            // forward the message
084            out.add(m);
085    
086            if (getChannel().getGameState() != STOPPED)
087            {
088                stopWatch.stop();
089                displayStats(out);
090            }
091        }
092    
093        public void onMessage(PauseMessage m, List<Message> out)
094        {
095            if (getChannel().getGameState() == STARTED)
096            {
097                // suspend the stop watch
098                stopWatch.suspend();
099            }
100    
101            out.add(m);
102        }
103    
104        public void onMessage(ResumeMessage m, List<Message> out)
105        {
106            if (getChannel().getGameState() == PAUSED)
107            {
108                // resume the stop watch
109                stopWatch.resume();
110            }
111    
112            out.add(m);
113        }
114    
115        public void onMessage(FieldMessage m, List<Message> out)
116        {
117            // increase the block count for the updated slot
118            PlayerStats playerStats = stats.get(m.getSlot() - 1);
119            if (playerStats != null && (stopWatch.getTime() > 1500))
120            {
121                playerStats.blockCount++;
122            }
123    
124            out.add(m);
125        }
126    
127        public void onMessage(LinesAddedMessage m, List<Message> out)
128        {
129            out.add(m);
130    
131            updateStats(m);
132    
133            // remove 1 block count from any player in an opposite team
134            removeBlock(m);
135        }
136    
137        public void onSpecial(SpecialMessage m, List<Message> out)
138        {
139            if (!(m instanceof LinesAddedMessage))
140            {
141                // add a special received by the target
142                PlayerStats playerStats = stats.get(m.getSlot() - 1);
143                playerStats.specialsReceived++;
144    
145                // remove 2 blocks count from the target
146                playerStats.blockCount = playerStats.blockCount - 2;
147    
148                // add a special sent by the sender
149                playerStats = stats.get(m.getFromSlot() - 1);
150                playerStats.specialsSent++;
151            }
152        }
153    
154        public void onMessage(LevelMessage m, List<Message> out)
155        {
156            out.add(m);
157            PlayerStats playerStats = stats.get(m.getSlot() - 1);
158            if (playerStats != null)
159            {
160                playerStats.level = m.getLevel();
161            }
162        }
163    
164        public void onMessage(LeaveMessage m, List<Message> out)
165        {
166            out.add(m);
167            // remove the stats from the list if a player leave the channel
168            stats.set(m.getSlot() - 1, null);
169        }
170    
171        public void onMessage(PlayerLostMessage m, List<Message> out)
172        {
173            out.add(m);
174            PlayerStats playerStats = stats.get(m.getSlot() - 1);
175            if (playerStats != null)
176            {
177                playerStats.playing = false;
178                playerStats.timePlayed = stopWatch.getTime();
179            }
180        }
181    
182        /**
183         * Decrease the block count of players receiving an add to all message since
184         * they will send back a field message assimilated by mistake as a block fall.
185         */
186        private void removeBlock(SpecialMessage message)
187        {
188            int slot = message.getFromSlot();
189    
190            // find the team of the player sending the message;
191            String team = null;
192    
193            if (message.getSource() != null && message.getSource() instanceof Client)
194            {
195                Client client = (Client) message.getSource();
196                team = client.getUser().getTeam();
197            }
198    
199            // check all players...
200            for (int i = 1; i <= 6; i++)
201            {
202                Client client = getChannel().getClient(i);
203                if (i != slot && client != null)
204                {
205                    User user = client.getUser();
206    
207                    // ...still playing, and team-less or in a different team from the sender
208                    if (user.isPlaying() && (user.getTeam() == null || !user.getTeam().equals(team)))
209                    {
210                        PlayerStats playerStats = stats.get(i - 1);
211                        playerStats.blockCount--;
212                    }
213                }
214            }
215        }
216    
217        /**
218         * Update the stats of the player sending the specified message.
219         *
220         * @param message
221         */
222        private void updateStats(LinesAddedMessage message)
223        {
224            if (message.getFromSlot() > 0) // ignore messages sent by the server
225            {
226                PlayerStats playerStats = stats.get(message.getFromSlot() - 1);
227                if (playerStats != null)
228                {
229                    playerStats.linesAdded += message.getLinesAdded();
230                    if (message.getLinesAdded() == 4)
231                    {
232                        playerStats.tetrisCount++;
233                    }
234                }
235            }
236        }
237    
238        private void displayStats(List<Message> out)
239        {
240            for (int slot = 1; slot <= 6; slot++)
241            {
242                PlayerStats playerStats = stats.get(slot - 1);
243                User user = getChannel().getPlayer(slot);
244    
245                if (playerStats != null && user != null)
246                {
247                    // update the play time of the remaining players
248                    if (playerStats.playing)
249                    {
250                        playerStats.timePlayed = stopWatch.getTime();
251                    }
252    
253                    // display the stats
254                    String bpm = df.format(playerStats.getBlocksPerMinute());
255    
256                    StringBuilder text = new StringBuilder();
257                    text.append("<purple>" + user.getName() + "</purple> : ");
258                    text.append(playerStats.blockCount + " <aqua>blocks @<red>" + bpm + "</red> bpm, ");
259                    text.append("<black>" + playerStats.linesAdded + "</black> added, ");
260                    text.append("<black>" + playerStats.tetrisCount + "</black> tetris");
261                    if (getChannel().getConfig().getSettings().getSpecialAdded() > 0)
262                    {
263                        // stats on special blocks for non pure game only
264                        text.append(", <black>" + playerStats.specialsSent + " / " + playerStats.specialsReceived + "</black> specials");
265                    }
266    
267                    out.add(new PlineMessage(text.toString()));
268                }
269            }
270    
271            // display the total game time
272            PlineMessage time = new PlineMessage();
273            time.setText("<brown>Total game time: <black>" + df.format(stopWatch.getTime() / 1000f) + "</black> seconds"); // todo i18n
274            out.add(time);
275        }
276    
277        public String getName()
278        {
279            return "Stats Filter";
280        }
281    
282        public String getDescription()
283        {
284            return "Displays stats about the game (pieces dropped per minute, lines added to all, time played, etc";
285        }
286    
287        public String getVersion()
288        {
289            return "1.1";
290        }
291    
292        public String getAuthor()
293        {
294            return "Emmanuel Bourg";
295        }
296    
297        private class PlayerStats
298        {
299            long timePlayed;
300            int tetrisCount;
301            int linesAdded;
302            int blockCount;
303            int level;
304            int specialsSent;
305            int specialsReceived;
306            boolean playing = true;
307    
308            public double getBlocksPerMinute()
309            {
310                return (double) blockCount * 60000 / (double) timePlayed;
311            }
312        }
313    
314    }