View Javadoc

1   /***
2    * Jetrix TetriNET Server
3    * Copyright (C) 2001-2010  Emmanuel Bourg
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18   */
19  
20  package net.jetrix;
21  
22  import static net.jetrix.config.Special.*;
23  
24  import java.io.*;
25  import java.util.*;
26  import java.util.logging.*;
27  
28  import net.jetrix.config.*;
29  import net.jetrix.messages.channel.*;
30  
31  /***
32   * A game field.
33   *
34   * @author Emmanuel Bourg
35   * @version $Revision: 870 $, $Date: 2010-09-16 22:50:33 +0200 (jeu., 16 sept. 2010) $
36   */
37  public class Field
38  {
39      private static Logger log = Logger.getLogger("net.jetrix");
40  
41      public static final int WIDTH = 12;
42      public static final int HEIGHT = 22;
43  
44      public static final byte BLOCK_VOID     = '0';
45      public static final byte BLOCK_BLUE     = '1';
46      public static final byte BLOCK_YELLOW   = '2';
47      public static final byte BLOCK_GREEN    = '3';
48      public static final byte BLOCK_PURPLE   = '4';
49      public static final byte BLOCK_RED      = '5';
50      public static final byte BLOCK_PREVIOUS = '6';
51  
52      /*** The index of blocks used in a partial update. */
53      private static final byte[] BLOCKS = {
54              BLOCK_VOID, BLOCK_BLUE, BLOCK_YELLOW, BLOCK_GREEN, BLOCK_PURPLE, BLOCK_RED,
55              (byte) ADDLINE.getLetter(),
56              (byte) CLEARLINE.getLetter(),
57              (byte) NUKEFIELD.getLetter(),
58              (byte) RANDOMCLEAR.getLetter(),
59              (byte) SWITCHFIELD.getLetter(),
60              (byte) CLEARSPECIAL.getLetter(),
61              (byte) GRAVITY.getLetter(),
62              (byte) QUAKEFIELD.getLetter(),
63              (byte) BLOCKBOMB.getLetter()};
64  
65      /*** Array of blocks. (0, 0) is the bottom left block, and (11, 21) is the upper right block. */
66      private byte[][] field = new byte[WIDTH][HEIGHT];
67  
68      public Field()
69      {
70          clear();
71      }
72  
73      public Field(byte[][] field)
74      {
75          this.field = field;
76      }
77  
78      /***
79       * Check if the field is empty.
80       */
81      public boolean isEmpty()
82      {
83          for (int i = HEIGHT - 1; i >= 0; i--)
84          {
85              for (int j = 0; j < WIDTH; j++)
86              {
87                  if (field[j][i] != BLOCK_VOID)
88                  {
89                      return false;
90                  }
91              }
92          }
93  
94          return true;
95      }
96  
97      /***
98       * Check if the field contains the specified special block.
99       *
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 }