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 }