www.pudn.com > Bluegammon蓝牙的应用编程.rar > Bluegammon.java
// Copyright (c) 2005 Sony Ericsson Mobile Communications AB // // This software is provided "AS IS," without a warranty of any kind. // ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, // INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. // // THIS SOFTWARE IS COMPLEMENTARY OF JAYWAY AB (www.jayway.se) package bluegammon; import bluegammon.gui.MenuCanvas; import bluegammon.gui.PopupCanvas; import bluegammon.gui.StringInputHandler; import bluegammon.gui.popup.Popup; import bluegammon.gui.popup.PopupListener; import bluegammon.io.BackgammonConnection; import bluegammon.io.Handshake; import bluegammon.io.PlayerListenerProxy; import bluegammon.logic.BoardMediator; import bluegammon.logic.GameRecord; import bluegammon.logic.LocalPlayer; import bluegammon.logic.Player; import bluegammon.logic.RemotePlayer; import bluegammon.logic.Rules; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import javax.microedition.lcdui.Command; import javax.microedition.lcdui.CommandListener; import javax.microedition.lcdui.Display; import javax.microedition.lcdui.Displayable; import javax.microedition.lcdui.TextBox; import javax.microedition.lcdui.TextField; /** ** The
Bluegammonclass represents the Bluegammon MIDlet * game altogether. It coordinates the macro state of the application. ** This class collects common functionality as starting and stopping a * backgammon game, showing a popup, or getting string input from user. It is * invoked statically since it mainly is a mediator. *
* * @author Peter Andersson */ public class Bluegammon implements PopupListener, CommandListener { /** Current canvas that is displayed */ protected static PopupCanvas m_currentCanvas; /** Current listener to popups */ protected static PopupListener m_popupListener; /** Popup instance cache */ protected static Popup m_popupCache; /** Text box used for collecting user string input */ protected static TextBox m_textBox; /** Handler when user has entered string input */ protected static StringInputHandler m_inputHandler; /** The connection used in remote games */ protected static BackgammonConnection m_remoteConnection; /** Instance of this class, used as PopupListener or CommandListener */ protected static Bluegammon m_inst; /** Key for device id (int)*/ public static final int DEVICE_ID = 0; /** Key for player name (char[])*/ public static final int PLAYER_NAME = 1; /** Key for saved game boolean flag (boolean)*/ public static final int HAS_SAVED_LOCAL_GAME = 2; /** Key for saved game data (byte[])*/ public static final int SAVED_GAME_DATA = 3; /** Key for preferred color (boolean)*/ public static final int BLACK_PREFERRED = 4; /** Key for audio off (boolean)*/ // Negative flag because default boolean = false public static final int AUDIO_OFF = 5; /** Key for vibration off (boolean)*/ public static final int VIBRA_OFF = 6; /** Key for preferred rules (int)*/ public static final int RULES_PREFERRED = 7; /** Max number of opponent scores that can be persistent */ public static final int GAMERECORDS_SIZE = 16; /** First key in gamerecords, device ids (int) */ public static final int GAMEREC_OP_ID = 8; /** First key in gamerecords, opponent name (char[]) */ public static final int GAMEREC_OP_NAME = GAMEREC_OP_ID + GAMERECORDS_SIZE; /** First key in gamerecords, player score (int) */ public static final int GAMEREC_MY_SCORE = GAMEREC_OP_NAME + GAMERECORDS_SIZE; /** First key in gamerecords, opponent score (int) */ public static final int GAMEREC_OP_SCORE = GAMEREC_MY_SCORE + GAMERECORDS_SIZE; /** First key in gamerecords, number of games against this opponent (int) */ public static final int GAMEREC_GAME_COUNT = GAMEREC_OP_SCORE + GAMERECORDS_SIZE; /** First key in gamerecords, timestamp (long) */ public static final int GAMEREC_TIMESTAMP = GAMEREC_GAME_COUNT + GAMERECORDS_SIZE; /** First key in gamerecords, saved game data (byte[]) */ public static final int GAMEREC_SAVED_GAME_DATA = GAMEREC_TIMESTAMP + GAMERECORDS_SIZE; /** Nbr of keys */ protected static final int NBR_OF_KEYS = 8 + 7 * GAMERECORDS_SIZE; /** Command used during user string input */ protected static final Command OK = new Command(Resources.getString(Resources.TXT_C_OK), Command.OK, 1); /** Command used during user string input */ protected static final Command CANCEL = new Command(Resources.getString(Resources.TXT_C_CANCEL), Command.CANCEL, 1); /** Represents a game that is only played on this device */ public static int GAME_TYPE_LOCAL = 0; /** Represents a distributed game where this device acts server */ public static int GAME_TYPE_REMOTE_SERVER = 1; /** Represents a distributed game where this device acts client */ public static int GAME_TYPE_REMOTE_CLIENT = 2; /** Current game type */ protected static int m_gameType; /** * Sets current game type, one ofGAME_TYPE_LOCAL, *GAME_TYPE_REMOTE_SERVER,GAME_TYPE_REMOTE_CLIENT. * @param gameType The current game type. */ protected static void setGameType(int gameType) { m_gameType = gameType; } /** * Returns current game type, one ofGAME_TYPE_LOCAL, *GAME_TYPE_REMOTE_SERVER,GAME_TYPE_REMOTE_CLIENT. * @return The current game type. */ public static int getGameType() { return m_gameType; } /** * Sets the connection used in remote game. Must be set before invoking *startRemoteGame* @param conn The connection used with remote player. */ public synchronized static void setBackgammonConnection(BackgammonConnection conn) { m_remoteConnection = conn; } /** * Returns true if there exists a saved phone game * * @return true if saved game exists, false otherwise */ public static boolean hasSavedLocalGame() { return RmsFacade.getBoolean(HAS_SAVED_LOCAL_GAME); } /** * Start a new game for two players on one phone. */ public synchronized static void startLocalGame() { setGameType(GAME_TYPE_LOCAL); Audio.stopSound(Audio.MUSIC); Player p1 = new LocalPlayer(1, "Whitey".toCharArray(), true, BoardMediator.getCanvas()); Player p2 = new LocalPlayer(2, "Blackie".toCharArray(), false, BoardMediator.getCanvas()); Audio.stopSound(Audio.MUSIC); BoardMediator.init(p1, p2, false); setCanvas(BoardMediator.getCanvas()); } /** * Starts a remote game, either as server or as client. This method uses the * connection set in methodsetBackgammonConnection. It handshakes * with the other device and starts a new game, or resumes a game if any of the * devices has a saved game. On conflicting preferred settings (color and rules) * the server wins. * * @param server True if server, false if client. * @param localName The name of this player. * @see Bluegammon#setBackgammonConnection(BackgammonConnection) */ public static void startRemoteGame(boolean server, char[] localName) { DataInputStream in = m_remoteConnection.getInput(); DataOutputStream out = m_remoteConnection.getOutput(); if (in == null || out == null) { // Occurs when connecting to a closed connection // i.e. the remote device pressed cancel during connecting phase return; } Bluegammon.showPopup(Resources.getChars(Resources.TXT_STARTING_GAME), null, 10, 0, 0, null); setGameType(server ? GAME_TYPE_REMOTE_SERVER : GAME_TYPE_REMOTE_CLIENT); Handshake handshake = new Handshake(); boolean fail = false; try { if (server) { handshake.serverHandshake(in, out, new String(localName)); } else { handshake.clientHandshake(in, out, new String(localName)); } } catch (Throwable t) { fail = true; Bluegammon.showPopup(Resources.getChars(Resources.TXT_HANDSHAKE_FAIL), Popup.ALT_OK, 0, 0, 0, null); System.err.println("Handshake failed"); t.printStackTrace(); return; } if (!fail) { boolean resumed = handshake.getSavedGame() != null; Player p1 = new LocalPlayer(Device.getDeviceId(), localName, handshake.isWhite(), BoardMediator.getCanvas()); PlayerListenerProxy proxy = new PlayerListenerProxy(out); p1.addListener(proxy); Player p2 = new RemotePlayer(handshake.getRemoteId(), handshake.getRemoteName(), !handshake.isWhite(), in); BoardMediator.init(p1, p2, resumed); if (resumed) { boolean remoteResume = handshake.isRemoteResume(); byte[] b = handshake.getSavedGame(); ByteArrayInputStream bais = new ByteArrayInputStream(b); try { BoardMediator.loadGame(bais, remoteResume); bais.close(); } catch (IOException e) { e.printStackTrace(); } b = null; bais = null; System.gc(); } setCanvas(BoardMediator.getCanvas()); Audio.stopSound(Audio.MUSIC); // Show the rules of this game if connecting as client or // if this is a resumed game if (!server || resumed) { showRules(); } else { Popup p = Bluegammon.getCurrentPopup(); if (p != null) p.dispose(); } } getCanvas().repaint(); } /** * Resumes a saved game */ public static void resumeSavedLocalGame() { Player p1 = new LocalPlayer(1, "Whitey".toCharArray(), true, BoardMediator.getCanvas()); Player p2 = new LocalPlayer(2, "Blackey".toCharArray(), false, BoardMediator.getCanvas()); BoardMediator.init(p1, p2, true); setGameType(GAME_TYPE_LOCAL); byte[] b = RmsFacade.get(SAVED_GAME_DATA); ByteArrayInputStream bais = new ByteArrayInputStream(b); try { BoardMediator.loadGame(bais, false); bais.close(); } catch (IOException e) { e.printStackTrace(); } b = null; bais = null; System.gc(); Audio.stopSound(Audio.MUSIC); setCanvas(BoardMediator.getCanvas()); showRules(); } /** * Exits a game. If the game is not finished it is stored, * so it can be resumed later. */ public synchronized static void exitGame() { // Local game if (getGameType() == GAME_TYPE_LOCAL) { if (!BoardMediator.isGameFinished()) { // Local game is not finished, save it try { if (BoardMediator.getCurrentPlayer() != null) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BoardMediator.saveGame(baos); baos.close(); RmsFacade.set(SAVED_GAME_DATA, baos.toByteArray()); RmsFacade.setBoolean(HAS_SAVED_LOCAL_GAME, true); } } catch (IOException e) { e.printStackTrace(); } } else { // Local game is finished, remove any local saved game RmsFacade.set(SAVED_GAME_DATA, null); RmsFacade.setBoolean(HAS_SAVED_LOCAL_GAME, false); } } // Remote game else if (getGameType() == GAME_TYPE_REMOTE_CLIENT || getGameType() == GAME_TYPE_REMOTE_SERVER) { Player opponent = BoardMediator.getOpponentPlayer(); if (!BoardMediator.isGameFinished()) { // Remote game is not finished, save it try { if (BoardMediator.getCurrentPlayer() != null) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BoardMediator.saveGame(baos); baos.close(); GameRecord.saveGame(opponent, baos.toByteArray()); } } catch (IOException e) { e.printStackTrace(); } } else { // Remove the saved game data for this opponent GameRecord.saveGame(opponent, null); } BoardMediator.forceGameFinished(); if (m_remoteConnection != null) { try { m_remoteConnection.close(); m_remoteConnection = null; } catch (IOException e) {} } } // Load preferred rules Rules.setRuleFlags(RmsFacade.getInt(Bluegammon.RULES_PREFERRED)); // Show menu MenuCanvas.getInstance().initShow(); setCanvas(MenuCanvas.getInstance()); } /** * Shuts down the MIDlet. */ public synchronized static void shutdown() { BoardMediator.shutdown(); RmsFacade.shutdown(); Audio.shutdown(); if (m_popupCache != null) { m_popupCache.dispose(); m_popupCache = null; } m_inst = null; if (m_remoteConnection != null) { try { m_remoteConnection.close(); m_remoteConnection = null; } catch (IOException e) {} } System.gc(); Device.getMidlet().notifyDestroyed(); } /** * Displays a popup with current rule settings. */ public static void showRules() { String str = Resources.getString(Resources.TXT_T_RULES) + "\n\n"; if (Rules.isAnyRuleSet()) { if (Rules.isSet(Rules.EVEN_OUT)) { str += Resources.getString(Resources.TXT_I_R_EVENOUT) + "\n"; } if (Rules.isSet(Rules.MAX_FIVE)) { str += Resources.getString(Resources.TXT_I_R_MAXFIVE) + "\n"; } } else { str += Resources.getString(Resources.TXT_NO_RULES); } str += "\n"; Bluegammon.showPopup(str.toCharArray(), null, 10, 0, 0, null); } /** * Gets input from the user. Runs asynchronously, i.e. this method does not * lock. User input is reported to specifiedInputHandler. * * @param title The title of the input dialog. * @param defaultText The default text presented in the input dialog. * @param length The maximum length of the input. * @param constraints The constraints of the text as defined inTextField. * @param ih The input handler, which handles the input when the user * commits the text. */ public static void getStringInput(String title, char[] defaultText, int length, int constraints, StringInputHandler ih) { m_textBox.setString(""); m_textBox.setMaxSize(length); m_textBox.setTitle(title); if (defaultText != null) { try { m_textBox.insert(defaultText, 0, defaultText.length, 0); m_textBox.setConstraints(constraints); } catch (Throwable t) { // Failed setting default text, may happen // due to constraints. Set to null text. try { m_textBox.setChars(null, 0, 0); m_textBox.setConstraints(constraints); } catch (Throwable t2) {} } } Device.getDisplay().setCurrent(m_textBox); m_inputHandler = ih; } /** * Returns the locking object of the user input mechanism * * @return The input monitor */ protected static Object getInputLock() { return m_textBox; } /** * Sets specified canvas as current * * @param c The canvas to set */ public synchronized static void setCanvas(PopupCanvas c) { m_currentCanvas = c; Device.getDisplay().setCurrent(c); c.repaint(); } /** * Returns current canvas * * @return Current canvas */ public synchronized static PopupCanvas getCanvas() { return m_currentCanvas; } /** * Returns true if a popup is currently displayed to the user * * @return true if a popup is displayed */ public synchronized static boolean isShowingPopup() { if (getCanvas() != null && getCanvas().getPopup() != null && getCanvas().getPopup().isActive()) { return true; } else { return false; } } /** * Returns current displayed popup * * @return Current popup or null if no popup is displayed */ public synchronized static Popup getCurrentPopup() { if (!isShowingPopup()) return null; else return getCanvas().getPopup(); } /** * Shows a popup on the device display, enabling the user to make a choice * amongst specified alternatives. Selected alternative is reported to * specified listener. The popup has a timeout - if this timeout is reached * the listener receives the specified timeout choice. If the user already has * a popup open, a call to this method makes current popup go away and the * listener to current popup is reported with the timeout choice. After this, * specified popup is presented. Thus, it is the responsibility of the server * or proxy layer to buffer multiple incoming popups. To check if a popup is * currently displayed, useBluegammon.getCurrentPopup()or *Bluegammon..isShowingPopup()* * @param text Character array with text presented in popup. * @param altTexts Array of character arrays with alternatives presented in popup. If * this argument is null, no alternatives are presented. * @param timeOutInSeconds The timeout in seconds. If this argument is set to zero, * the popup is displayed until the a user makes a choice. If no * choices are given and the timeout is zero, this popup will not show. * @param defaultChoice The default selection when the popup appears. * @param timeOutChoice The choice reported if the popup times out or is overridden by * another popup. * @param listener The listener to this popup. */ public synchronized static Popup showPopup(char[] text, char[][] altTexts, int timeOutInSeconds, int defaultChoice, int timeOutChoice, PopupListener listener) { if (getCanvas() == null) return null; if (getCanvas().getPopup() != null && getCanvas().getPopup().isActive()) { Popup p = getCanvas().getPopup(); getPopupListener().selectedChoice(p.getTimeOutChoice(), true); p.dispose(); m_popupCache = p; } Popup p = getPopupInstance(); p.init(text, altTexts, (byte) timeOutInSeconds, (byte) defaultChoice, (byte) timeOutChoice, getPopupListener(), getCanvas().getWidth(), getCanvas().getHeight()); m_popupListener = listener; getCanvas().setPopup(p); getCanvas().repaint(); return p; } /** * Returns a popup instance from cache. * @return A popup instance */ protected synchronized static Popup getPopupInstance() { if (m_popupCache == null) { m_popupCache = new Popup(); } return m_popupCache; } /** * Returns a popuplistener that can be used for dispatching * popup events, used internally only. * @return A popuplistener dispatching events. */ protected synchronized static PopupListener getPopupListener() { return getInstance(); } /** * Returns a commandlistener that can be used for dispatching * user string input, used internally only. * @return A command listener for reporting user string input. */ protected synchronized static CommandListener getInputCommandListener() { return getInstance(); } /** * Returns an instance ofBluegammon, * used internally only for listener implementations. * @return A bluegammon instance */ private static Bluegammon getInstance() { return m_inst; } /** * Initializes this class. * @param bg The bluegammon midlet. * @param display The display. */ public synchronized static void init(BluegammonMIDlet bg, Display display) { if (m_inst == null) { // Creates singleton listeners m_inst = new Bluegammon(); } // Initialize device facade Device.init(bg, display); // Initialize the RMS persistence facade RmsFacade.init(NBR_OF_KEYS); // Set preferred rules Rules.setRuleFlags(RmsFacade.getInt(Bluegammon.RULES_PREFERRED)); // Initialize the menu control canvas MenuCanvas.getInstance().initShow(); setCanvas(MenuCanvas.getInstance()); // Startup the backgammon board logic BoardMediator.startup(); // Initialize generic string input handling m_textBox = new TextBox(null, null, 1, TextField.ANY); m_textBox.addCommand(OK); m_textBox.addCommand(CANCEL); m_textBox.setCommandListener(getInputCommandListener()); } // PopupListener implementation public void selectedChoice(byte choice, boolean timeOut) { // Just forward to registered listener and repaint if (m_popupListener != null) m_popupListener.selectedChoice(choice, timeOut); if (getCanvas() != null) getCanvas().repaint(); } // CommandListener implementation for input textbox public void commandAction(Command c, Displayable d) { if (d == m_textBox) { synchronized (getInputLock()) { if (c != CANCEL && m_textBox.getString().length() > 0 && m_inputHandler != null) { final String input = m_textBox.getString(); new Thread(new Runnable() { public void run() { m_inputHandler.handleStringInput(input); } }, "InputHandler").start(); } setCanvas(getCanvas()); } } } // Prevent external construction private Bluegammon() {} }