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.protocols;
021    
022    import java.util.*;
023    
024    import net.jetrix.*;
025    import net.jetrix.winlist.*;
026    import net.jetrix.config.*;
027    import net.jetrix.messages.*;
028    import net.jetrix.messages.channel.*;
029    import net.jetrix.messages.channel.specials.*;
030    import org.apache.commons.lang.StringUtils;
031    
032    /**
033     * Protocol to communicate with TetriNET 1.13 compatible clients.
034     *
035     * @author Emmanuel Bourg
036     * @version $Revision: 863 $, $Date: 2010-08-20 12:26:25 +0200 (ven., 20 août 2010) $
037     */
038    public class TetrinetProtocol extends AbstractProtocol
039    {
040        private static Map<String, String> styles = new HashMap<String, String>();
041    
042        static
043        {
044            styles.put("red", "\u0014");
045            styles.put("black", "\u0004");
046            styles.put("green", "\u000c");
047            styles.put("lightGreen", "\u000e");
048            styles.put("darkBlue", "\u0011");
049            styles.put("blue", "\u0005");
050            styles.put("cyan", "\u0003");
051            styles.put("aqua", "\u0017");
052            styles.put("yellow", "\u0019");
053            styles.put("kaki", "\u0012");
054            styles.put("brown", "\u0010");
055            styles.put("lightGray", "\u000f");
056            styles.put("gray", "\u0006");
057            styles.put("magenta", "\u0008");
058            styles.put("purple", "\u0013");
059            styles.put("b", "\u0002");
060            styles.put("i", "\u0016");
061            styles.put("u", "\u001f");
062            styles.put("white", "\u0018");
063        }
064    
065        private static Map<String, Class> specials = new TreeMap<String, Class>();
066    
067        static
068        {
069            specials.put("cs1", OneLineAddedMessage .class);
070            specials.put("cs2", TwoLinesAddedMessage.class);
071            specials.put("cs4", FourLinesAddedMessage.class);
072            specials.put("a", AddLineMessage.class);
073            specials.put("c", ClearLineMessage.class);
074            specials.put("n", NukeFieldMessage.class);
075            specials.put("r", RandomClearMessage.class);
076            specials.put("s", SwitchFieldsMessage.class);
077            specials.put("b", ClearSpecialsMessage.class);
078            specials.put("g", GravityMessage.class);
079            specials.put("q", BlockQuakeMessage.class);
080            specials.put("o", BlockBombMessage.class);
081        }
082    
083        /** Initialization token */
084        public static final String INIT_TOKEN = "tetrisstart";
085    
086        /**
087         * Return the name of this protocol
088         */
089        public String getName()
090        {
091            return "tetrinet";
092        }
093    
094        /**
095         * Parse the specified string and return the corresponding server
096         * message for this protocol.
097         */
098        public Message getMessage(String line)
099        {
100            StringTokenizer st = new StringTokenizer(line);
101            if (!st.hasMoreTokens())
102            {
103                // skip empty lines
104                return null;
105            }
106    
107            String cmd = st.nextToken();
108            Message m = null;
109    
110            // f <slot> <field>
111            if ("f".equals(cmd))
112            {
113                FieldMessage field = new FieldMessage();
114                field.setSlot(Integer.parseInt(st.nextToken()));
115                field.setField((st.hasMoreTokens()) ? st.nextToken() : null);
116                m = field;
117                m.setRawMessage(this, line);
118            }
119            // sb <to> <bonus> <from>
120            else if ("sb".equals(cmd))
121            {
122                int to = Integer.parseInt(st.nextToken());
123                String special = st.nextToken();
124                int from = Integer.parseInt(st.nextToken());
125    
126                Class cls = specials.get(special);
127    
128                SpecialMessage spmsg = null;
129                if (specials.keySet().contains(special))
130                {
131                    try
132                    {
133                        spmsg = (SpecialMessage) cls.newInstance();
134                    }
135                    catch (Exception e)
136                    {
137                        e.printStackTrace();
138                    }
139                }
140                else
141                {
142                    throw new IllegalArgumentException("Forged special detected from " + this);
143                }
144    
145                spmsg.setSlot(to);
146                spmsg.setFromSlot(from);
147                m = spmsg;
148                m.setRawMessage(this, line);
149            }
150            // team <slot> teamname
151            else if ("team".equals(cmd))
152            {
153                TeamMessage team = new TeamMessage();
154                team.setSlot(Integer.parseInt(st.nextToken()));
155                if (st.hasMoreTokens())
156                {
157                    team.setName(line.substring("team".length() + 3).trim());
158                }
159                m = team;
160                m.setRawMessage(this, line);
161            }
162            // pline <slot> text
163            else if ("pline".equals(cmd))
164            {
165                if (st.hasMoreTokens())
166                {
167                    String slot = st.nextToken();
168                    if (st.hasMoreTokens())
169                    {
170                        String firstWord = st.nextToken();
171    
172                        if (firstWord.startsWith("/") && !firstWord.startsWith("//"))
173                        {
174                            CommandMessage command = new CommandMessage();
175                            command.setSlot(Integer.parseInt(slot));
176                            command.setCommand(firstWord.substring(1));
177                            command.setText(line.substring(line.indexOf(" ", 9) + 1));
178                            while (st.hasMoreTokens())
179                            {
180                                command.addParameter(st.nextToken());
181                            }
182                            m = command;
183                        }
184                        else
185                        {
186                            PlineMessage pline = new PlineMessage();
187                            pline.setSlot(Integer.parseInt(slot));
188                            pline.setText(line.substring("pline".length() + 3));
189                            m = pline;
190                            m.setRawMessage(this, line);
191                        }
192                    }
193                }
194            }
195            // gmsg <playername> text
196            else if ("gmsg".equals(cmd))
197            {
198                GmsgMessage gmsg = new GmsgMessage();
199                gmsg.setText(line.substring(line.indexOf(" ") + 1));
200                m = gmsg;
201                m.setRawMessage(this, line);
202            }
203            // plineact <slot> emote
204            else if ("plineact".equals(cmd))
205            {
206                PlineActMessage plineAct = new PlineActMessage();
207                plineAct.setSlot(Integer.parseInt(st.nextToken()));
208                plineAct.setText(line.substring("plineact".length() + 3));
209                m = plineAct;
210                m.setRawMessage(this, line);
211            }
212            // startgame <0 = stop | 1 = start> <playernumber>
213            else if ("startgame".equals(cmd))
214            {
215                if ("1".equals(st.nextToken()))
216                {
217                    StartGameMessage start = new StartGameMessage();
218                    start.setSlot(Integer.parseInt(st.nextToken()));
219                    m = start;
220                }
221                else
222                {
223                    StopGameMessage stop = new StopGameMessage();
224                    stop.setSlot(Integer.parseInt(st.nextToken()));
225                    m = stop;
226                }
227            }
228            // pause <0 = resume | 1 = pause> <playernumber>
229            else if ("pause".equals(cmd))
230            {
231                ChannelMessage msg;
232                if ("1".equals(st.nextToken()))
233                {
234                    msg = new PauseMessage();
235                }
236                else
237                {
238                    msg = new ResumeMessage();
239                }
240                m = msg;
241            }
242            // newgame
243            else if ("newgame".equals(cmd))
244            {
245                m = new NewGameMessage();
246    
247                // todo parse the game settings
248            }
249            // endgame
250            else if ("endgame".equals(cmd))
251            {
252                m = new EndGameMessage();
253            }
254            // ingame
255            else if ("ingame".equals(cmd))
256            {
257                m = new IngameMessage();
258            }
259            // playerlost <slot>
260            else if ("playerlost".equals(cmd))
261            {
262                PlayerLostMessage lost = new PlayerLostMessage();
263                lost.setSlot(Integer.parseInt(st.nextToken()));
264                m = lost;
265                m.setRawMessage(this, line);
266            }
267            // lvl <playernumber> <level>
268            else if ("lvl".equals(cmd))
269            {
270                LevelMessage level = new LevelMessage();
271                level.setSlot(Integer.parseInt(st.nextToken()));
272                level.setLevel(Integer.parseInt(st.nextToken()));
273                m = level;
274                m.setRawMessage(this, line);
275            }
276            // clientinfo <name> <version>
277            else if ("clientinfo".equals(cmd))
278            {
279                ClientInfoMessage info = new ClientInfoMessage();
280                if (st.hasMoreTokens())
281                {
282                    info.setName(st.nextToken());
283                }
284                if (st.hasMoreTokens())
285                {
286                    info.setVersion(st.nextToken());
287                }
288                m = info;
289            }
290            // playernum <num>
291            else if ("playernum".equals(cmd))
292            {
293                PlayerNumMessage num = new PlayerNumMessage();
294                num.setSlot(Integer.parseInt(st.nextToken()));
295                m = num;
296            }
297            // playerjoin <num> <name>
298            else if ("playerjoin".equals(cmd))
299            {
300                JoinMessage join = new JoinMessage();
301                join.setSlot(Integer.parseInt(st.nextToken()));
302                join.setName(st.nextToken());
303                join.setRawMessage(this, line);
304                m = join;
305            }
306            // playerleave <num>
307            else if ("playerleave".equals(cmd))
308            {
309                LeaveMessage leave = new LeaveMessage();
310                leave.setSlot(Integer.parseInt(st.nextToken()));
311                leave.setRawMessage(this, line);
312                m = leave;
313            }
314            else if ("noconnecting".equals(cmd))
315            {
316                NoConnectingMessage noconnecting = new NoConnectingMessage();
317                noconnecting.setText(line.substring(cmd.length() + 1));
318                m = noconnecting;
319            }
320            else if ("winlist".equals(cmd))
321            {
322                WinlistMessage winlist = new WinlistMessage();
323                winlist.setRawMessage(this, line);
324                List<Score> scores = new ArrayList<Score>();
325                String[] tokens = line.split(" ");
326                for (int i = 1; i < tokens.length; i++)
327                {
328                    String token = tokens[i];
329                    String[] values = token.split(";");
330    
331                    Score score = new Score();
332                    score.setName(values[0].substring(1));
333                    score.setType(values[0].charAt(0) == 'p' ? Score.TYPE_PLAYER : Score.TYPE_TEAM);
334                    score.setScore(Long.parseLong(values[1]));
335    
336                    scores.add(score);
337                }
338                winlist.setScores(scores);
339    
340                m = winlist;
341            }
342    
343            return m;
344        }
345    
346    
347        /**
348         * Translate the specified message into a string that will be sent
349         * to a client using this protocol.
350         */
351        public String translate(Message m, Locale locale)
352        {
353            if (m instanceof SpecialMessage)            { return translate((SpecialMessage) m); }
354            else if (m instanceof FieldMessage)         { return translate((FieldMessage) m); }
355            else if (m instanceof PlineMessage)         { return translate((PlineMessage) m, locale); }
356            else if (m instanceof LevelMessage)         { return translate((LevelMessage) m); }
357            else if (m instanceof PlayerLostMessage)    { return translate((PlayerLostMessage) m); }
358            else if (m instanceof PlineActMessage)      { return translate((PlineActMessage) m, locale); }
359            else if (m instanceof TeamMessage)          { return translate((TeamMessage) m); }
360            else if (m instanceof JoinMessage)          { return translate((JoinMessage) m, locale); }
361            else if (m instanceof LeaveMessage)         { return translate((LeaveMessage) m, locale); }
362            else if (m instanceof PlayerNumMessage)     { return translate((PlayerNumMessage) m); }
363            else if (m instanceof StartGameMessage)     { return translate((StartGameMessage) m); }
364            else if (m instanceof StopGameMessage)      { return translate((StopGameMessage) m); }
365            else if (m instanceof NewGameMessage)       { return translate((NewGameMessage) m); }
366            else if (m instanceof EndGameMessage)       { return translate((EndGameMessage) m); }
367            else if (m instanceof PauseMessage)         { return translate((PauseMessage) m); }
368            else if (m instanceof ResumeMessage)        { return translate((ResumeMessage) m); }
369            else if (m instanceof IngameMessage)        { return translate((IngameMessage) m); }
370            else if (m instanceof GmsgMessage)          { return translate((GmsgMessage) m, locale); }
371            else if (m instanceof PlayerWonMessage)     { return translate((PlayerWonMessage) m); }
372            else if (m instanceof NoConnectingMessage)  { return translate((NoConnectingMessage) m); }
373            else if (m instanceof SpectatorListMessage) { return translate((SpectatorListMessage) m, locale); }
374            else if (m instanceof SmsgMessage)          { return translate((SmsgMessage) m, locale); }
375            else if (m instanceof WinlistMessage)       { return translate((WinlistMessage) m, locale); }
376            else if (m instanceof NoopMessage)          { return translate((NoopMessage) m); }
377            else if (m instanceof CommandMessage)       { return translate((CommandMessage) m); }
378            else if (m instanceof ClientInfoMessage)    { return translate((ClientInfoMessage) m); }
379            else
380            {
381                return null;
382            }
383        }
384    
385        public String translate(SpecialMessage m)
386        {
387            if (m instanceof LinesAddedMessage)          { return translate((LinesAddedMessage) m); }
388            else if (m instanceof AddLineMessage)        { return translate((AddLineMessage) m); }
389            else if (m instanceof ClearLineMessage)      { return translate((ClearLineMessage) m); }
390            else if (m instanceof ClearSpecialsMessage)  { return translate((ClearSpecialsMessage) m); }
391            else if (m instanceof RandomClearMessage)    { return translate((RandomClearMessage) m); }
392            else if (m instanceof BlockQuakeMessage)     { return translate((BlockQuakeMessage) m); }
393            else if (m instanceof BlockBombMessage)      { return translate((BlockBombMessage) m); }
394            else if (m instanceof GravityMessage)        { return translate((GravityMessage) m); }
395            else if (m instanceof NukeFieldMessage)      { return translate((NukeFieldMessage) m); }
396            else if (m instanceof SwitchFieldsMessage)   { return translate((SwitchFieldsMessage) m); }
397            else
398            {
399                return null;
400            }
401        }
402    
403        public String translate(PlineMessage m, Locale locale)
404        {
405            StringBuilder message = new StringBuilder();
406            message.append("pline ");
407            message.append(m.getSlot());
408            message.append(" ");
409            message.append(applyStyle(m.getText(locale)));
410            return message.toString();
411        }
412    
413        public String translate(PlineActMessage m, Locale locale)
414        {
415            StringBuilder message = new StringBuilder();
416            message.append("plineact ");
417            message.append(m.getSlot());
418            message.append(" ");
419            message.append(m.getText(locale));
420            return message.toString();
421        }
422    
423        public String translate(CommandMessage m)
424        {
425            StringBuilder message = new StringBuilder();
426            message.append("pline ");
427            message.append(m.getSlot());
428            message.append(" /");
429            message.append(m.getCommand());
430    
431            for (int i = 0; i < m.getParameterCount(); i++)
432            {
433                message.append(" ");
434                message.append(m.getParameter(i));
435            }
436            return message.toString();
437        }
438    
439        public String translate(TeamMessage m)
440        {
441            StringBuilder message = new StringBuilder();
442            message.append("team ");
443            message.append(m.getSlot());
444            message.append(" ");
445            if (m.getName() != null)
446            {
447                message.append(m.getName());
448            }
449            return message.toString();
450        }
451    
452        public String translate(JoinMessage m, Locale locale)
453        {
454            if (m.getSlot() == 0)
455            {
456                // spectator joining
457                PlineMessage announce = new PlineMessage();
458                announce.setKey("channel.spectator.join", m.getName());
459                return translate(announce, locale);
460            }
461            else
462            {
463                StringBuilder message = new StringBuilder();
464                message.append("playerjoin ");
465                message.append(m.getSlot());
466                message.append(" ");
467                message.append(m.getName());
468                return message.toString();
469            }
470        }
471    
472        public String translate(LeaveMessage m, Locale locale)
473        {
474            if (m.getSlot() == 0)
475            {
476                // spectator leaving
477                PlineMessage announce = new PlineMessage();
478                announce.setKey("channel.spectator.leave", m.getName());
479                return translate(announce, locale);
480            }
481            else
482            {
483                StringBuilder message = new StringBuilder();
484                message.append("playerleave ");
485                message.append(m.getSlot());
486                return message.toString();
487            }
488        }
489    
490        public String translate(PlayerNumMessage m)
491        {
492            StringBuilder message = new StringBuilder();
493            message.append("playernum ");
494            message.append(m.getSlot());
495            return message.toString();
496        }
497    
498        public String translate(StartGameMessage m)
499        {
500            StringBuilder message = new StringBuilder();
501            message.append("startgame 1 ");
502            message.append(m.getSlot());
503            return message.toString();
504        }
505    
506        public String translate(StopGameMessage m)
507        {
508            StringBuilder message = new StringBuilder();
509            message.append("startgame 0 ");
510            message.append(m.getSlot());
511            return message.toString();
512        }
513    
514        public String translate(NewGameMessage m)
515        {
516            Settings s = m.getSettings();
517            StringBuilder message = new StringBuilder();
518            message.append("newgame ");
519            message.append(s.getStackHeight());
520            message.append(" ");
521            message.append(s.getStartingLevel());
522            message.append(" ");
523            message.append(s.getLinesPerLevel());
524            message.append(" ");
525            message.append(s.getLevelIncrease());
526            message.append(" ");
527            message.append(s.getLinesPerSpecial());
528            message.append(" ");
529            message.append(s.getSpecialAdded());
530            message.append(" ");
531            message.append(s.getSpecialCapacity());
532            message.append(" ");
533    
534            // blocks frequency
535            for (Block block : Block.values())
536            {
537                for (int j = 0; j < s.getOccurancy(block); j++)
538                {
539                    message.append(Integer.toString(block.ordinal() + 1));
540                }
541            }
542    
543            message.append(" ");
544    
545            // specials frequency
546            for (Special special : Special.values())
547            {
548                for (int j = 0; j < s.getOccurancy(special); j++)
549                {
550                    message.append(Integer.toString(special.ordinal() + 1));
551                }
552            }
553    
554            message.append(" ");
555            message.append(s.getAverageLevels() ? "1" : "0");
556            message.append(" ");
557            message.append(s.getClassicRules() ? "1" : "0");
558    
559            // extended parameter for 1.14 clients
560            if (s.getSameBlocks())
561            {
562                message.append(" ");
563                String hexstring = Integer.toHexString(m.getSeed()).toUpperCase();
564                // padding to 8 digits
565                for (int i = hexstring.length(); i < 8; i++)
566                {
567                    message.append("0");
568                }
569    
570                message.append(hexstring);
571            }
572    
573            return message.toString();
574        }
575    
576        public String translate(EndGameMessage m)
577        {
578            return "endgame";
579        }
580    
581        public String translate(PauseMessage m)
582        {
583            return "pause 1 ";
584        }
585    
586        public String translate(ResumeMessage m)
587        {
588            return "pause 0 ";
589        }
590    
591        public String translate(IngameMessage m)
592        {
593            return "ingame";
594        }
595    
596        public String translate(GmsgMessage m, Locale locale)
597        {
598            StringBuilder message = new StringBuilder();
599            message.append("gmsg ");
600            message.append(stripStyle(m.getText(locale)));
601            return message.toString();
602        }
603    
604        public String translate(LevelMessage m)
605        {
606            StringBuilder message = new StringBuilder();
607            message.append("lvl ");
608            message.append(m.getSlot());
609            message.append(" ");
610            message.append(m.getLevel());
611            return message.toString();
612        }
613    
614        public String translate(FieldMessage m)
615        {
616            StringBuilder message = new StringBuilder();
617            message.append("f ");
618            message.append(m.getSlot());
619            if (m.getField() != null)
620            {
621                message.append(" ");
622                message.append(m.getField());
623            }
624            return message.toString();
625        }
626    
627        public String translate(PlayerLostMessage m)
628        {
629            StringBuilder message = new StringBuilder();
630            message.append("playerlost ");
631            message.append(m.getSlot());
632            return message.toString();
633        }
634    
635        public String translate(PlayerWonMessage m)
636        {
637            StringBuilder message = new StringBuilder();
638            message.append("playerwon ");
639            message.append(m.getSlot());
640            return message.toString();
641        }
642    
643        public String translate(NoConnectingMessage m)
644        {
645            StringBuilder message = new StringBuilder();
646            message.append("noconnecting ");
647            message.append(m.getText());
648            return message.toString();
649        }
650    
651        public String translate(LinesAddedMessage m)
652        {
653            StringBuilder message = new StringBuilder();
654            message.append("sb ");
655            message.append(m.getSlot());
656            message.append(" cs").append(m.getLinesAdded()).append(" ");
657            message.append(m.getFromSlot());
658            return message.toString();
659        }
660    
661        public String translate(AddLineMessage m)
662        {
663            StringBuilder message = new StringBuilder();
664            message.append("sb ");
665            message.append(m.getSlot());
666            message.append(" a ");
667            message.append(m.getFromSlot());
668            return message.toString();
669        }
670    
671        public String translate(ClearLineMessage m)
672        {
673            StringBuilder message = new StringBuilder();
674            message.append("sb ");
675            message.append(m.getSlot());
676            message.append(" c ");
677            message.append(m.getFromSlot());
678            return message.toString();
679        }
680    
681        public String translate(NukeFieldMessage m)
682        {
683            StringBuilder message = new StringBuilder();
684            message.append("sb ");
685            message.append(m.getSlot());
686            message.append(" n ");
687            message.append(m.getFromSlot());
688            return message.toString();
689        }
690    
691        public String translate(RandomClearMessage m)
692        {
693            StringBuilder message = new StringBuilder();
694            message.append("sb ");
695            message.append(m.getSlot());
696            message.append(" r ");
697            message.append(m.getFromSlot());
698            return message.toString();
699        }
700    
701        public String translate(SwitchFieldsMessage m)
702        {
703            StringBuilder message = new StringBuilder();
704            message.append("sb ");
705            message.append(m.getSlot());
706            message.append(" s ");
707            message.append(m.getFromSlot());
708            return message.toString();
709        }
710    
711        public String translate(ClearSpecialsMessage m)
712        {
713            StringBuilder message = new StringBuilder();
714            message.append("sb ");
715            message.append(m.getSlot());
716            message.append(" b ");
717            message.append(m.getFromSlot());
718            return message.toString();
719        }
720    
721        public String translate(GravityMessage m)
722        {
723            StringBuilder message = new StringBuilder();
724            message.append("sb ");
725            message.append(m.getSlot());
726            message.append(" g ");
727            message.append(m.getFromSlot());
728            return message.toString();
729        }
730    
731        public String translate(BlockQuakeMessage m)
732        {
733            StringBuilder message = new StringBuilder();
734            message.append("sb ");
735            message.append(m.getSlot());
736            message.append(" q ");
737            message.append(m.getFromSlot());
738            return message.toString();
739        }
740    
741        public String translate(BlockBombMessage m)
742        {
743            StringBuilder message = new StringBuilder();
744            message.append("sb ");
745            message.append(m.getSlot());
746            message.append(" o ");
747            message.append(m.getFromSlot());
748            return message.toString();
749        }
750    
751        public String translate(SpectatorListMessage m, Locale locale)
752        {
753            PlineMessage pline = new PlineMessage();
754            pline.setKey("command.speclist.format", StringUtils.join(m.getSpectators().iterator(), ", "));
755    
756            return translate(pline, locale);
757        }
758    
759        public String translate(SmsgMessage m, Locale locale)
760        {
761            StringBuilder message = new StringBuilder();
762            String name = ((Client) m.getSource()).getUser().getName();
763    
764            PlineMessage pline = new PlineMessage();
765            pline.setKey("channel.spectator.message", name, m.getText());
766            message.append(translate(pline, locale));
767    
768            return message.toString();
769        }
770    
771        public String translate(WinlistMessage m, Locale locale)
772        {
773            StringBuilder message = new StringBuilder();
774            message.append("winlist");
775    
776            for (Score score : m.getScores())
777            {           
778                message.append(" ");
779                message.append(score.getType() == Score.TYPE_PLAYER ? "p" : "t");
780                message.append(score.getName());
781                message.append(";");
782                message.append(score.getScore());
783            }
784            return message.toString();
785        }
786    
787        public String translate(ClientInfoMessage m)
788        {
789            StringBuilder message = new StringBuilder();
790            message.append("clientinfo ");
791            message.append(m.getName());
792            message.append(" ");
793            message.append(m.getVersion());
794            return message.toString();
795        }
796    
797        public String translate(NoopMessage m)
798        {
799            return "";
800        }
801    
802        /**
803         * Return the map of the color and style codes for this protocol.
804         */
805        public Map<String, String> getStyles()
806        {
807            return styles;
808        }
809    
810        public String applyStyle(String text)
811        {
812            // to be optimized later
813            Map<String, String> styles = getStyles();
814            if (styles == null) {
815                return text;
816            }
817    
818            for (String key : styles.keySet())
819            {
820                String value = styles.get(key);
821                if (value == null)
822                {
823                    value = "";
824                }
825                text = text.replaceAll("<" + key + ">", value);
826                text = text.replaceAll("</" + key + ">", value);
827            }
828            return text;
829        }
830    
831        /**
832         * Removes the style tags from the specified text.
833         */
834        public String stripStyle(String text)
835        {
836            Map<String, String> styles = getStyles();
837            if (styles == null) {
838                return text;
839            }
840    
841            for (String key : styles.keySet())
842            {
843                text = text.replaceAll("<" + key + ">", "");
844                text = text.replaceAll("</" + key + ">", "");
845            }
846            return text;
847        }
848    
849        public char getEOL()
850        {
851            return 0xFF;
852        }
853    
854    
855        /**
856         * Decodes TetriNET client initialization string
857         *
858         * @param initString initialization string
859         * @return decoded string
860         * @throws IllegalArgumentException thrown if the string can't be decoded
861         */
862        public static String decode(String initString)
863        {
864            // check the size of the init string
865            if (initString.length() % 2 != 0)
866            {
867                throw new IllegalArgumentException("Invalid initialization string, the length is not even (" + initString + ")");
868            }
869    
870            // parse the hex values from the init string
871            int[] dec = new int[initString.length() / 2];
872    
873            try
874            {
875                for (int i = 0; i < dec.length; i++)
876                {
877                    dec[i] = Integer.parseInt(initString.substring(i * 2, i * 2 + 2), 16);
878                }
879            }
880            catch (NumberFormatException e)
881            {
882                throw new IllegalArgumentException("Invalid initialization string, illegal characters found (" + initString + ")", e);
883            }
884    
885            // find the hash pattern for a tetrinet client
886            String pattern = findHashPattern(dec, false);
887    
888            // find the hash pattern for a tetrifast client
889            if (pattern.length() == 0)
890            {
891                pattern = findHashPattern(dec, true);
892            }
893    
894            // check the size of the pattern found
895            if (pattern.length() == 0)
896            {
897                throw new IllegalArgumentException("Invalid initialization string, unable to find the pattern (" + initString + ")");
898            }
899    
900            // decode the string
901            StringBuilder s = new StringBuilder();
902    
903            for (int i = 1; i < dec.length; i++)
904            {
905                s.append((char) (((dec[i] ^ pattern.charAt((i - 1) % pattern.length())) + 255 - dec[i - 1]) % 255));
906            }
907    
908            return s.toString().replace((char) 0, (char) 255);
909        }
910    
911        private static String findHashPattern(int[] dec, boolean tetrifast)
912        {
913            // the first characters from the decoded string
914            char[] data = (tetrifast ? TetrifastProtocol.INIT_TOKEN : INIT_TOKEN).substring(0, 10).toCharArray();
915    
916            // compute the full hash
917            int[] hash = new int[data.length];
918    
919            for (int i = 0; i < data.length; i++)
920            {
921                hash[i] = ((data[i] + dec[i]) % 255) ^ dec[i + 1];
922            }
923    
924            // find the length of the hash
925            int length = 5;
926    
927            for (int i = 5; i == length && i > 0; i--)
928            {
929                for (int j = 0; j < data.length - length; j++)
930                {
931                    if (hash[j] != hash[j + length])
932                    {
933                        length--;
934                    }
935                }
936            }
937    
938            return new String(hash, 0, length);
939        }
940    
941        /**
942         * Return the initialization string for the specified user.
943         *
944         * @param nickname  the nickname of the client
945         * @param version   the version of the client
946         * @param ip        the IP of the server
947         * @param tetrifast is this a tetrifast client ?
948         */
949        public static String encode(String nickname, String version, byte[] ip, boolean tetrifast)
950        {
951            // compute the pattern
952            int p = 54 * (ip[0] & 0xFF) + 41 * (ip[1] & 0xFF) + 29 * (ip[2] & 0xFF) + 17 * (ip[3] & 0xFF);
953            char[] pattern = String.valueOf(p).toCharArray();
954    
955            // build the string to encode
956            char[] data = ((tetrifast ? TetrifastProtocol.INIT_TOKEN : INIT_TOKEN) + " " + nickname + " " + version).toCharArray();
957    
958            // build the encoded string
959            StringBuilder result = new StringBuilder();
960            char offset = 0x80;
961            result.append(toHex(offset));
962    
963            char previous = offset;
964    
965            for (int i = 0; i < data.length; i++)
966            {
967                char current = (char) (((previous + data[i]) % 255) ^ pattern[i % pattern.length]);
968                result.append(toHex(current));
969                previous = current;
970            }
971    
972            return result.toString().toUpperCase();
973        }
974    
975        /**
976         * Return the hex value of the specified byte on 2 digits.
977         */
978        private static String toHex(char c)
979        {
980            String h = Integer.toHexString(c);
981    
982            return h.length() > 1 ? h : "0" + h;
983        }
984    
985    }