001    /**
002     * Jetrix TetriNET Server
003     * Copyright (C) 2001-2010  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.config.Special.*;
023    
024    import java.io.*;
025    import java.util.*;
026    import java.util.logging.*;
027    
028    import net.jetrix.config.*;
029    import net.jetrix.messages.channel.*;
030    
031    /**
032     * A game field.
033     *
034     * @author Emmanuel Bourg
035     * @version $Revision: 870 $, $Date: 2010-09-16 22:50:33 +0200 (jeu., 16 sept. 2010) $
036     */
037    public class Field
038    {
039        private static Logger log = Logger.getLogger("net.jetrix");
040    
041        public static final int WIDTH = 12;
042        public static final int HEIGHT = 22;
043    
044        public static final byte BLOCK_VOID     = '0';
045        public static final byte BLOCK_BLUE     = '1';
046        public static final byte BLOCK_YELLOW   = '2';
047        public static final byte BLOCK_GREEN    = '3';
048        public static final byte BLOCK_PURPLE   = '4';
049        public static final byte BLOCK_RED      = '5';
050        public static final byte BLOCK_PREVIOUS = '6';
051    
052        /** The index of blocks used in a partial update. */
053        private static final byte[] BLOCKS = {
054                BLOCK_VOID, BLOCK_BLUE, BLOCK_YELLOW, BLOCK_GREEN, BLOCK_PURPLE, BLOCK_RED,
055                (byte) ADDLINE.getLetter(),
056                (byte) CLEARLINE.getLetter(),
057                (byte) NUKEFIELD.getLetter(),
058                (byte) RANDOMCLEAR.getLetter(),
059                (byte) SWITCHFIELD.getLetter(),
060                (byte) CLEARSPECIAL.getLetter(),
061                (byte) GRAVITY.getLetter(),
062                (byte) QUAKEFIELD.getLetter(),
063                (byte) BLOCKBOMB.getLetter()};
064    
065        /** Array of blocks. (0, 0) is the bottom left block, and (11, 21) is the upper right block. */
066        private byte[][] field = new byte[WIDTH][HEIGHT];
067    
068        public Field()
069        {
070            clear();
071        }
072    
073        public Field(byte[][] field)
074        {
075            this.field = field;
076        }
077    
078        /**
079         * Check if the field is empty.
080         */
081        public boolean isEmpty()
082        {
083            for (int i = HEIGHT - 1; i >= 0; i--)
084            {
085                for (int j = 0; j < WIDTH; j++)
086                {
087                    if (field[j][i] != BLOCK_VOID)
088                    {
089                        return false;
090                    }
091                }
092            }
093    
094            return true;
095        }
096    
097        /**
098         * Check if the field contains the specified special block.
099         *
100         * @param special the special to check
101         * @since 0.3
102         */
103        public boolean contains(Special special)
104        {
105            for (int i = HEIGHT - 1; i >= 0; i--)
106            {
107                for (int j = 0; j < WIDTH; j++)
108                {
109                    if (field[j][i] == special.getLetter())
110                    {
111                        return true;
112                    }
113                }
114            }
115    
116            return false;
117        }
118    
119        /**
120         * Check if the field contains a special block.
121         */
122        public boolean containsSpecialBlock()
123        {
124            for (int i = HEIGHT - 1; i >= 0; i--)
125            {
126                for (int j = 0; j < WIDTH; j++)
127                {
128                    if ('a' <= field[j][i] && field[j][i] <= 'z')
129                    {
130                        return true;
131                    }
132                }
133            }
134    
135            return false;
136        }
137    
138        /**
139         * Check if the field has holes (i.e. if a gravity block may have an effect).
140         *
141         * @since 0.3
142         */
143        public boolean hasHoles()
144        {
145            for (int i = 1; i <= HEIGHT - 1; i++)
146            {
147                for (int j = 0; j < WIDTH; j++)
148                {
149                    if (field[j][i] != BLOCK_VOID && field[j][i - 1] == BLOCK_VOID)
150                    {
151                        return true;
152                    }
153                }
154            }
155    
156            return false;
157        }
158    
159        /**
160         * Return the height of the highest block.
161         *
162         * @since 0.3
163         */
164        public int getHighest()
165        {
166            for (int i = HEIGHT - 1; i >= 1; i--)
167            {
168                for (int j = 0; j < WIDTH; j++)
169                {
170                    if (field[j][i] != BLOCK_VOID)
171                    {
172                        return i;
173                    }
174                }
175            }
176    
177            return 0;
178        }
179    
180        /**
181         * Update the field with the specified FieldMessage.
182         */
183        public void update(FieldMessage message)
184        {       
185            if (message.isPartialUpdate())
186            {
187                StringTokenizer tokenizer = new StringTokenizer(message.getField(), "!\"#$%&'()*+,-./", true);
188                
189                while (tokenizer.hasMoreTokens())
190                {
191                    // block type
192                    String type = tokenizer.nextToken();
193                    byte color = BLOCKS[type.charAt(0) - 0x21];
194                    
195                    // locations
196                    String locations = tokenizer.nextToken();
197                    for (int i = 0; i < locations.length(); i = i + 2)
198                    {
199                        int x = locations.charAt(i) - '3';
200                        int y = HEIGHT - (locations.charAt(i + 1) - '3') - 1;
201                        field[x][y] = color;
202                    }
203                }
204            }
205            else if (message.isFullUpdate())
206            {
207                String fieldString = message.getField();
208                for (int i = 0; i < fieldString.length(); i++)
209                {
210                    char c = fieldString.charAt(i);
211                    if (c != BLOCK_PREVIOUS)
212                    {
213                        field[i % WIDTH][HEIGHT - i / WIDTH - 1] = (byte) c;
214                    }
215                }
216            }
217            else if (!message.isEmpty())
218            {
219                // malformed message
220                log.warning("Malformed field update received from " + message.getSource());
221            }
222        }
223    
224        /**
225         * Return the string representing this field as used in the
226         * {@link net.jetrix.messages.channel.FieldMessage} messages.
227         */
228        public String getFieldString()
229        {
230            byte[] buffer = new byte[WIDTH * HEIGHT];
231            int k = 0;
232            for (int j = HEIGHT - 1; j >= 0; j--)
233            {
234                for (int i = 0; i < WIDTH; i++)
235                {
236                    buffer[k++] = field[i][j];
237                }
238            }
239    
240            return new String(buffer);
241        }
242    
243        /**
244         * Return the block at the specified location. (0, 0) is the bottom left
245         * block, and (11, 21) is the upper right block.
246         *
247         * @param x
248         * @param y
249         */
250        public byte getBlock(int x, int y)
251        {
252            return field[x][y];
253        }
254    
255        /**
256         * Clear the field.
257         */
258        public void clear()
259        {
260            for (int i = 0; i < WIDTH; i++)
261            {
262                for (int j = 0; j < HEIGHT; j++)
263                {
264                    field[i][j] = BLOCK_VOID;
265                }
266            }
267        }
268        
269        /**
270         * Load the field from the specified stream
271         *
272         * @since 0.3
273         */
274        public void load(InputStream in) throws IOException
275        {
276            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
277            
278            for (int i = 0; i < HEIGHT; i++)
279            {
280                String line = reader.readLine();
281    
282                if (line == null || line.length() != WIDTH)
283                {
284                    throw new IOException("Field format error at line " + i);
285                }
286                
287                for (int j = 0; j < WIDTH; j++)
288                {
289                    byte block = (byte) line.charAt(j);
290    
291                    if (isValidBlock(block))
292                    {
293                        field[j][HEIGHT - i - 1] = block;
294                    }
295                    else
296                    {
297                        throw new IOException("Illegal block '" + block + "' at line " + i + " , column " + j);
298                    }
299                }
300            }
301        }
302    
303        /**
304         * Load the field from the specified file
305         *
306         * @since 0.3
307         */
308        public void load(String file) throws IOException
309        {
310            InputStream in = null;
311            
312            try
313            {
314                in = new FileInputStream(file);
315                load(in);
316            }
317            finally
318            {
319                if (in != null)
320                {
321                    in.close();
322                }
323            }
324        }
325    
326        /**
327         * Save the field to the specified file.
328         *
329         * @since 0.3
330         */
331        public void save(String file) throws IOException
332        {
333            FileOutputStream out = null;
334    
335            try
336            {
337                out = new FileOutputStream(file);
338    
339                for (int i = 0; i < WIDTH; i++)
340                {
341                    for (int j = 0; j < HEIGHT; j++)
342                    {
343                        out.write(field[i][HEIGHT - j - 1]);
344                    }
345                    out.write('\n');
346                }
347            }
348            finally
349            {
350                if (out != null)
351                {
352                    out.close();
353                }
354            }
355        }
356    
357        private boolean isValidBlock(byte block)
358        {
359            if (block >= '0' && block <= '6')
360            {
361                return true;
362            }
363            else if (Special.valueOf((char) block) != null)
364            {
365                return true;
366            }
367            else
368            {
369                return false;
370            }
371        }
372    }