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 }