View Javadoc

1   /*
2    Cervii -- Cervi Worms game
3    Copyright (C) 2007 Martin Vysny
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  package net.sf.btw.cervii;
16  
17  import java.io.DataInputStream;
18  import java.util.Random;
19  import java.util.Vector;
20  
21  import javax.bluetooth.UUID;
22  import javax.microedition.lcdui.Alert;
23  import javax.microedition.lcdui.AlertType;
24  import javax.microedition.lcdui.Command;
25  import javax.microedition.lcdui.CommandListener;
26  import javax.microedition.lcdui.Display;
27  import javax.microedition.lcdui.Displayable;
28  import javax.microedition.midlet.MIDlet;
29  import javax.microedition.midlet.MIDletStateChangeException;
30  
31  import net.sf.btw.btlib.Client;
32  import net.sf.btw.btlib.IClientListener;
33  import net.sf.btw.btlib.IServerListener;
34  import net.sf.btw.btlib.Peer;
35  import net.sf.btw.btlib.Server;
36  import net.sf.btw.btlib.ServerInfo;
37  import net.sf.btw.btlib.ui.ConnectorCanvas;
38  import net.sf.btw.btlib.ui.IConnectorListener;
39  import net.sf.btw.cervii.comm.Packet;
40  import net.sf.btw.cervii.game.GameController;
41  import net.sf.btw.cervii.menu.PlayerSetupCanvas;
42  import net.sf.btw.cervii.menu.SplashScreen;
43  import net.sf.btw.commons.Logger;
44  
45  /***
46   * The main Midlet of the Cervii game. Handles canvas management and messaging
47   * management.
48   * 
49   * @author Martin Vysny
50   */
51  public final class Cervii extends MIDlet implements IClientListener,
52  		IServerListener, IConnectorListener, CommandListener {
53  
54  	/***
55  	 * The UUID of the game.
56  	 */
57  	public static final UUID CERVII_UUID = new UUID(
58  			"c2b6f262c8f411db88c00016d45fb918", false); //$NON-NLS-1$
59  
60  	/***
61  	 * Constructor.
62  	 */
63  	public Cervii() {
64  		super();
65  		instance = this;
66  	}
67  
68  	/***
69  	 * The singleton instance.
70  	 */
71  	private static Cervii instance = null;
72  
73  	/***
74  	 * Returns the instance of main class.
75  	 * 
76  	 * @return instance of Cervii, never <code>null</code>.
77  	 */
78  	public static Cervii getInstance() {
79  		return instance;
80  	}
81  
82  	/*
83  	 * (non-Javadoc)
84  	 * 
85  	 * @see javax.microedition.midlet.MIDlet#destroyApp(boolean)
86  	 */
87  	protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
88  		// do nothing
89  	}
90  
91  	protected void pauseApp() {
92  		// TODO Auto-generated method stub
93  	}
94  
95  	/***
96  	 * Player setup screen, valid always.
97  	 */
98  	private PlayerSetupCanvas playerSetupCanvas;
99  
100 	/***
101 	 * Connector canvas, valid always.
102 	 */
103 	private ConnectorCanvas connectorCanvas;
104 
105 	/***
106 	 * Game controller, valid only while playing.
107 	 */
108 	private GameController gameController;
109 
110 	/***
111 	 * The communicating peer. Valid after the connector form closes.
112 	 */
113 	private Peer peer;
114 
115 	/***
116 	 * The X screen size, valid always.
117 	 */
118 	private int screenxsize;
119 
120 	/***
121 	 * The Y screen size, always valid.
122 	 */
123 	private int screenysize;
124 
125 	/*
126 	 * (non-Javadoc)
127 	 * 
128 	 * @see javax.microedition.midlet.MIDlet#startApp()
129 	 */
130 	protected void startApp() throws MIDletStateChangeException {
131 		// initialize bluetooth subsystem
132 		try {
133 			Peer.initializeBluetooth();
134 		} catch (Exception e) {
135 			Logger.error("Failed to initialize bluetooth", e); //$NON-NLS-1$
136 			final Alert error = new Alert(I18n.getMessage("ERROR"), //$NON-NLS-1$
137 					I18n.getMessage("BT_ERROR"), //$NON-NLS-1$
138 					null, AlertType.ERROR);
139 			error.setCommandListener(this);
140 			error.setTimeout(Alert.FOREVER);
141 			Display.getDisplay(this).setCurrent(error);
142 			return;
143 		}
144 		// show splash
145 		final SplashScreen splash=new SplashScreen();
146 		// detect the screenx/screeny
147 		connectorCanvas = new ConnectorCanvas(CERVII_UUID, Display
148 				.getDisplay(this), this, I18n.getMessage("START_SERVER"), I18n //$NON-NLS-1$
149 				.getMessage("CONNECT_TO")); //$NON-NLS-1$
150 		splash.setNextDisplayable(connectorCanvas);
151 		connectorCanvas.setFullScreenMode(true);
152 		screenxsize = connectorCanvas.getWidth();
153 		screenysize = connectorCanvas.getHeight();
154 		connectorCanvas.setFullScreenMode(false);
155 	}
156 
157 	/***
158 	 * Private method, not intended to be called by clients.
159 	 * 
160 	 * @see net.sf.btw.btlib.IClientListener#connected(byte)
161 	 */
162 	public void connected(byte peerId) {
163 		Logger.info("Cervii.connected(" + peerId + ")", null); //$NON-NLS-1$ //$NON-NLS-2$
164 		playerSetupCanvas.setThisPlayerId(peerId, screenxsize, screenysize);
165 		final Packet initPacket = new Packet(Packet.PACKET_INIT);
166 		initPacket.resolutionX = screenxsize;
167 		initPacket.resolutionY = screenysize;
168 		((Client) peer).send(initPacket.toByteArray());
169 	}
170 
171 	/***
172 	 * Private method, not intended to be called by clients.
173 	 * 
174 	 * @see net.sf.btw.btlib.IClientListener#disconnected()
175 	 */
176 	public void disconnected() {
177 		Logger.info("Cervii.disconnected()", null); //$NON-NLS-1$
178 		handleCriticalError(I18n.getMessage("DISCONNECTED"), null); //$NON-NLS-1$
179 	}
180 
181 	/***
182 	 * Private method, not intended to be called by clients.
183 	 * 
184 	 * @see net.sf.btw.btlib.IClientListener#messageArrived(byte[], int,
185 	 *      java.io.DataInputStream)
186 	 */
187 	public void messageArrived(byte[] message, int messageLength,
188 			DataInputStream stream) {
189 		final Packet packet;
190 		try {
191 			packet = Packet.fromStream(stream);
192 		} catch (Exception ex) {
193 			clientCriticalError(ex);
194 			return;
195 		}
196 		if ((packet.packetType == Packet.PACKET_PLAYER_JOINED)
197 				|| (packet.packetType == Packet.PACKET_PLAYER_LEFT)) {
198 			playerSetupCanvas.clientOnPacket(packet);
199 		} else if (packet.packetType == Packet.PACKET_PING) {
200 			((Client) peer).send(new Packet(Packet.PACKET_PONG).toByteArray());
201 		} else if (packet.packetType == Packet.PACKET_GAMEINIT) {
202 			// immediately start the game
203 			final byte thisPlayerId = playerSetupCanvas.getThisPlayerId();
204 			final Vector players = playerSetupCanvas.getPlayers();
205 			gameController = new GameController(peer, packet, players,
206 					thisPlayerId);
207 			return;
208 		} else if (packet.packetType == Packet.PACKET_SYNC) {
209 			if (gameController != null)
210 				gameController.clientOnPacket(packet);
211 			return;
212 		} else
213 			throw new IllegalArgumentException("Invalid packet: " //$NON-NLS-1$
214 					+ packet.packetType);
215 	}
216 
217 	/***
218 	 * Not intended to be called by clients.
219 	 * 
220 	 * @see net.sf.btw.btlib.IErrorListener#errorOccured(byte, int, boolean,
221 	 *      java.lang.Exception)
222 	 */
223 	public void errorOccured(byte clientID, int errorHint,
224 			boolean listenerError, Exception ex) {
225 		if (peer instanceof Client) {
226 			clientCriticalError(ex);
227 		}
228 	}
229 
230 	/***
231 	 * A critical error occured. The client will disconnect and return to
232 	 * connector form.
233 	 * 
234 	 * @param ex
235 	 *            the exception.
236 	 */
237 	private void clientCriticalError(Exception ex) {
238 		((Client) peer).disconnect();
239 		handleCriticalError(I18n.getMessage("COMM_ERR"), ex); //$NON-NLS-1$
240 	}
241 
242 	/***
243 	 * Private method not intended to be called by clients.
244 	 * @see net.sf.btw.btlib.IServerListener#clientConnected(byte, java.lang.String)
245 	 */
246 	public void clientConnected(byte id, String name) {
247 		playerSetupCanvas.serverOnClientConnect(id, name, true);
248 	}
249 
250 	/***
251 	 * Private method not intended to be called by clients.
252 	 * @see net.sf.btw.btlib.IServerListener#clientDisconnected(byte, java.lang.String)
253 	 */
254 	public void clientDisconnected(byte id, String name) {
255 		playerSetupCanvas.serverOnClientConnect(id, name, false);
256 	}
257 
258 	/***
259 	 * Private method not intended to be called by clients.
260 	 * @see net.sf.btw.btlib.IServerListener#messageArrived(byte, java.lang.String, byte[], int, java.io.DataInputStream)
261 	 */
262 	public void messageArrived(byte clientID, String name, byte[] message,
263 			int messageLength, DataInputStream stream) {
264 		try {
265 			final Packet packet = Packet.fromStream(stream);
266 			if ((packet.packetType == Packet.PACKET_INIT)
267 					|| (packet.packetType == Packet.PACKET_PONG)) {
268 				playerSetupCanvas.serverOnPacket(clientID, packet);
269 				return;
270 			} else if (packet.packetType == Packet.PACKET_TURN_REQUEST) {
271 				gameController.serverOnPacket(packet, clientID);
272 				return;
273 			} else
274 				throw new IllegalArgumentException(
275 						"Cervii.messageArrived(): Invalid packet type " //$NON-NLS-1$
276 								+ packet.packetType);
277 		} catch (Exception ex) {
278 			Logger.error("Error deserializing packet", ex); //$NON-NLS-1$
279 			((Server) peer).disconnectClient(clientID);
280 		}
281 	}
282 
283 	/***
284 	 * Private method not intended to be called by clients.
285 	 */
286 	public void closeConnector() {
287 		notifyDestroyed();
288 	}
289 
290 	/***
291 	 * Private method, not intended to be called by clients.
292 	 * 
293 	 * @see net.sf.btw.ui.IConnectorListener#connectToServer(net.sf.btw.btlib.ServerInfo)
294 	 */
295 	public void connectToServer(ServerInfo info) {
296 		peer = new Client(CERVII_UUID, Packet.MAX_PACKET_SIZE,
297 				Packet.MAX_PACKET_SIZE, this);
298 		playerSetupCanvas = new PlayerSetupCanvas(peer);
299 		Display.getDisplay(this).setCurrent(playerSetupCanvas);
300 		((Client) peer).connect(info);
301 	}
302 
303 	/***
304 	 * Private method, not intended to be called by clients.
305 	 * 
306 	 * @see net.sf.btw.ui.IConnectorListener#startServer()
307 	 */
308 	public void startServer() {
309 		peer = new Server(Packet.MAX_PACKET_SIZE, Packet.MAX_PACKET_SIZE,
310 				CERVII_UUID, this);
311 		playerSetupCanvas = new PlayerSetupCanvas(peer);
312 		playerSetupCanvas.setThisPlayerId(Peer.SERVER_ID, screenxsize,
313 				screenysize);
314 		Display.getDisplay(this).setCurrent(playerSetupCanvas);
315 		try {
316 			((Server) peer).startServer();
317 		} catch (Exception ex) {
318 			handleCriticalError(I18n.getMessage("ERR_CREATING_SERVER"), ex); //$NON-NLS-1$
319 		}
320 	}
321 
322 	/***
323 	 * Returns the display instance.
324 	 * 
325 	 * @return the display instance, never <code>null</code>.
326 	 */
327 	public static Display getDisplay() {
328 		return Display.getDisplay(getInstance());
329 	}
330 
331 	/***
332 	 * Shows a big error dialog, logs the error and bails out back to the
333 	 * connector canvas.
334 	 * 
335 	 * @param message
336 	 *            the message to show
337 	 * @param ex
338 	 *            error.
339 	 */
340 	public void handleCriticalError(final String message, final Exception ex) {
341 		Logger.error(message, ex);
342 		String msg = message;
343 		if (ex != null) {
344 			msg += ": " + ex.getClass().getName() + ":" + ex.getMessage(); //$NON-NLS-1$ //$NON-NLS-2$
345 		}
346 		getDisplay().setCurrent(new Alert(I18n.getMessage("ERROR"), msg, //$NON-NLS-1$
347 				null, AlertType.ERROR), connectorCanvas);
348 		if (gameController != null) {
349 			gameController.cancel();
350 			gameController = null;
351 		}
352 		if (playerSetupCanvas != null) {
353 			playerSetupCanvas.cancelPingTimers();
354 			playerSetupCanvas = null;
355 		}
356 		peer = null;
357 	}
358 
359 	/***
360 	 * Private method, not intended to be called by clients.
361 	 * 
362 	 * @see javax.microedition.lcdui.CommandListener#commandAction(javax.microedition.lcdui.Command,
363 	 *      javax.microedition.lcdui.Displayable)
364 	 */
365 	public void commandAction(Command c, Displayable d) {
366 		if (c == Alert.DISMISS_COMMAND) {
367 			notifyDestroyed();
368 		}
369 	}
370 
371 	/***
372 	 * Private method, not intended to be called by clients.
373 	 * Called by {@link #playerSetupCanvas} when the server chose to start the
374 	 * game.
375 	 */
376 	public void gameStart() {
377 		// broadcast the PACKET_GAMEINIT packet.
378 		final Packet packet = playerSetupCanvas.getGameInitPacket();
379 		final byte thisPlayerId = playerSetupCanvas.getThisPlayerId();
380 		final Vector players = playerSetupCanvas.getPlayers();
381 		((Server) peer).sendBuffer(((Server) peer).getClientIDs().keys(),
382 				(byte) -1, packet.toByteArray());
383 		// begin the game!
384 		gameController = new GameController(peer, packet, players, thisPlayerId);
385 	}
386 
387 	/***
388 	 * Not a public method.
389 	 * Called by {@link #playerSetupCanvas} when back button is pressed.
390 	 */
391 	public void showConnectorCanvas() {
392 		getDisplay().setCurrent(connectorCanvas);
393 		playerSetupCanvas = null;
394 		peer = null;
395 	}
396 
397 	/***
398 	 * <p>
399 	 * Do not call.
400 	 * </p>
401 	 * <p>
402 	 * Called by {@link GameController} when the game finishes.
403 	 * </p>
404 	 */
405 	public void gameOver() {
406 		gameController = null;
407 		getDisplay().setCurrent(playerSetupCanvas);
408 	}
409 
410 	/***
411 	 * Returns next random int in the range 0 (including) .. maxValue
412 	 * (excluding).
413 	 * 
414 	 * @param random
415 	 *            the random instance.
416 	 * @param maxValue
417 	 *            max. value. returned value is always less than this value.
418 	 * @return the value.
419 	 */
420 	public static int nextRandomInt(final Random random, final int maxValue) {
421 		int result = random.nextInt();
422 		if (result < 0)
423 			result = -result;
424 		if (result < 0)
425 			result = Integer.MAX_VALUE;
426 		return result % maxValue;
427 	}
428 
429 }