001    /**
002     * Jetrix TetriNET Server
003     * Copyright (C) 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.config.Block.*;
023    
024    import java.io.*;
025    import java.util.logging.*;
026    import java.util.*;
027    
028    import net.jetrix.config.*;
029    import net.jetrix.*;
030    import org.apache.commons.lang.*;
031    
032    /**
033     * Puzzle generator supporting the puzzle format used by the 2ice server,
034     * aka downstack.com. Each puzzle consists in two files, a <tt>.field</tt> file
035     * containing the description of the field, and a <tt>.settings<tt> file
036     * containing the game settings and the puzzle description. By default the
037     * puzzle files are looked for in the <tt>data/puzzle</tt> sub directory, this
038     * can be overridden by setting the <tt>path</tt> parameter.
039     *
040     * @since 0.3
041     *
042     * @author Emmanuel Bourg
043     * @version $Revision: 836 $, $Date: 2010-04-11 23:25:35 +0200 (dim., 11 avr. 2010) $
044     */
045    public class DownstackPuzzleGenerator implements PuzzleGenerator
046    {
047        private Logger log = Logger.getLogger("net.jetrix");
048    
049        private static final String DEFAULT_PATH = "data/puzzle";
050    
051        /** The path where the puzzle fields and settings are located. */
052        private String path = DEFAULT_PATH;
053    
054        /** The current level played. */
055        private int level;
056    
057        /** The order or the specials used in the settings file. */
058        public static final Block[] BLOCKS = { LEFTL, LEFTZ, SQUARE, RIGHTL, RIGHTZ, HALFCROSS, LINE };
059    
060        public void init(Configuration config)
061        {
062            path = config.getString("path", DEFAULT_PATH);
063        }
064    
065        public Puzzle getNextPuzzle()
066        {
067            try
068            {
069                // load the field
070                File[] levels = getLevels();
071                File file = levels[level % levels.length];
072                level = level + 1;
073                
074                Puzzle puzzle = loadPuzzle(new File(path), file.getName().substring(0, file.getName().lastIndexOf(".")));
075                puzzle.setKey(String.valueOf(level));
076                
077                return puzzle;
078            }
079            catch (IOException e)
080            {
081                log.log(Level.WARNING, e.getMessage(), e);
082            }
083            
084            return null;
085        }
086    
087        /**
088         * Load a puzzle from the filesystem.
089         * 
090         * @param directory the directory containing the puzzle file
091         * @param name      the name of the puzzle
092         */
093        protected Puzzle loadPuzzle(File directory, String name) throws IOException
094        {
095            Puzzle puzzle = new Puzzle();
096    
097            // load the field
098            Field field = new Field();
099            field.load(new File(directory, name + ".field").getAbsolutePath());
100            puzzle.setField(field);
101    
102            // load the settings
103            readSettings(puzzle, new File(directory, name + ".settings").getAbsolutePath());
104            
105            return puzzle;
106        }
107    
108        /**
109         * Find all levels in the puzzle directory.
110         */
111        protected File[] getLevels()
112        {
113            File directory = new File(path);
114            File[] files = directory.listFiles(new FilenameFilter()
115            {
116                public boolean accept(File dir, String name)
117                {
118                    return name.endsWith(".field");
119                }
120            });
121    
122            // sort the list of files
123    
124            Arrays.sort(files, new FilenameComparator());
125    
126            return files;
127        }
128    
129        /**
130         * Parse the specified .settings file and update the puzzle.
131         *
132         * @param puzzle   the puzzle to update
133         * @param filename the settings file to parse
134         */
135        private void readSettings(Puzzle puzzle, String filename) throws IOException
136        {
137            Settings settings = new Settings();
138            puzzle.setSettings(settings);
139    
140            File file = new File(filename);
141            if (file.exists())
142            {
143                BufferedReader in = null;
144    
145                try
146                {
147                    in = new BufferedReader(new FileReader(file));
148    
149                    String line;
150                    while ((line = in.readLine()) != null)
151                    {
152                        if (line.startsWith("NAME"))
153                        {
154                            // parse the name and remove the leading and trailing quotes
155                            String name = line.substring(4).trim();
156                            if (name.startsWith("\"") && name.endsWith("\""))
157                            {
158                                name = name.substring(1, name.length() - 1);
159                            }
160    
161                            puzzle.setName(name);
162                        }
163                        else if (line.startsWith("COMMENT"))
164                        {
165                            puzzle.setComment(line.substring(7).trim());
166                        }
167                        else if (line.startsWith("DESIGNER"))
168                        {
169                            puzzle.setAuthor(line.substring(8).trim());
170                        }
171                        else if (line.startsWith("SPECIAL"))
172                        {
173                            // parse the special occurancies
174                            String[] occurancy = StringUtils.split(line.substring(7).trim(), ' ');
175    
176                            for (Special special : Special.values())
177                            {
178                                settings.setOccurancy(special, Integer.parseInt(occurancy[special.ordinal()]));
179                            }
180                        }
181                        else if (line.startsWith("BLOCK"))
182                        {
183                            // parse the block occurancies
184                            String[] occurancy = StringUtils.split(line.substring(5).trim(), ' ');
185    
186                            // careful, it doesn't follow the standard order
187                            for (int i = 0; i < BLOCKS.length; i++)
188                            {
189                                settings.setOccurancy(BLOCKS[i], Integer.parseInt(occurancy[i]));
190                            }
191                        }
192                        else if (line.startsWith("SUDDENDEATHMSG"))
193                        {
194                            settings.setSuddenDeathMessage(line.substring(14).trim());
195                        }
196                        else if (line.startsWith("SUDDENDEATH"))
197                        {
198                            String[] params = StringUtils.split(line.substring(11).trim(), ' ');
199    
200                            settings.setSuddenDeathTime(Integer.parseInt(params[0]));
201                            settings.setSuddenDeathLinesAdded(Integer.parseInt(params[1]));
202                            settings.setSuddenDeathDelay(Integer.parseInt(params[2]));
203                        }
204                        else if (line.startsWith("RULES"))
205                        {
206                            String[] params = StringUtils.split(line.substring(5).trim(), ' ');
207    
208                            settings.setStartingLevel(Integer.parseInt(params[0]));
209                            settings.setLinesPerLevel(Integer.parseInt(params[1]));
210                            settings.setLevelIncrease(Integer.parseInt(params[2]));
211                            settings.setLinesPerSpecial(Integer.parseInt(params[3]));
212                            settings.setSpecialAdded(Integer.parseInt(params[4]));
213                            settings.setSpecialCapacity(Integer.parseInt(params[5]));
214                            settings.setClassicRules(!"0".equals(params[6]));
215                            settings.setAverageLevels(!"0".equals(params[7]));
216                        }
217                    }
218                }
219                finally
220                {
221                    // close the file
222                    if (in != null)
223                    {
224                        in.close();
225                    }
226                }
227            }
228        }
229    }