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
192 String type = tokenizer.nextToken();
193 byte color = BLOCKS[type.charAt(0) - 0x21];
194
195
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
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 }