www.pudn.com > Bluegammon蓝牙的应用编程.rar > BoardState.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.logic; 
 
import java.io.DataInputStream; 
import java.io.DataOutputStream; 
import java.io.IOException; 
 
/** 
 * 

* Singleton class holding game logic and state of a game. There can * only be one state per device, thus the singleton pattern.

* Extends the Board class with player-, turn-, dice- and rule-logic. * The BoardState is detached from gui. State reports when * pieces are moved etc can be retreived by implementing the * BoardStateListener interface and registering this instance * in the BoardState. *

* * @see bluegammon.logic.BoardMediator * @author Peter Andersson */ public class BoardState extends Board { // Interaction game states /** Flag indicating if turns should be chosen */ protected boolean m_chooseTurns = true; /** Flag indicating if dice is thrown */ protected boolean m_diceThrown = false; // Player and turn state /** Player one */ protected static Player m_player1; /** Player two */ protected static Player m_player2; /** Current player */ protected static Player m_currentPlayer; // Rule game states /** Flag indicating if game is finished */ protected boolean m_gameFinished = true; /** Integer array of the dice values */ protected int[] m_dice = new int[4]; /** Number of dice values (2 for different dice values, 4 for double throw) */ protected int m_diceVals = 2; /** Flag indicating if possible moves for current player has been calculated */ protected boolean m_possibleMovesCalculated = false; /** Integer array of possible moves */ protected int[][] m_possibleMoves = new int[256][3]; /** Possible move board source index */ public static final int PM_SOUR = 0; /** Possible move board destination index */ public static final int PM_DEST = 1; /** Possible move dice value index */ public static final int PM_DICE = 2; /** Number of possible moves for current player */ protected int m_possibleMoveCount = 0; /** Integer array of undoable moves */ protected int[][] m_undoableMoves = new int[5][4]; /** Undoable move board source index */ protected static final int UNDO_SOURCE = 0; /** Undoable move board destination index */ protected static final int UNDO_DEST = 1; /** Undoable move on guard flag (if >=0, guard will be moved to this pos ) */ protected static final int UNDO_GUARD = 2; /** The dicevalue that should be put back to dicevalues */ protected static final int UNDO_DICEVALUE = 3; /** Number of undoable moves */ protected int m_undoableMoveCount = 0; /** Listener of this state. For compact framework, only allow one listener. */ protected BoardStateListener m_listener; /** BoardState instance */ protected static BoardState m_instance; /** * Registers a boardstate-listener that will receive * events upon state changes. * * @param listener The listener. */ public void setGameListener(BoardStateListener listener) { m_listener = listener; } // Player stats /** * Defines the two backgammon players. * @param player1 Player one. * @param player2 Player two. */ public void setPlayers(Player player1, Player player2) { m_player1 = player1; m_player2 = player2; } /** * Returns whether the game is finished or not * @return true if finished, false otherwise */ public boolean isGameFinished() { return m_gameFinished; } /** * Marks this game as finished * @param b true for finished, false otherwise */ public void setGameFinished(boolean b) { m_gameFinished = b; m_possibleMovesCalculated = false; } /** * Returns the color of current player. * @return true for white player, false for black player */ public boolean isCurrentPlayerWhite() { return m_currentPlayer.isWhite(); } /** * Returns current player. * @return current player. */ public Player getCurrentPlayer() { return m_currentPlayer; } /** * Returns waiting player. * @return noncurrent player. */ public Player getWaitingPlayer() { if (m_player1 == m_currentPlayer) { return m_player2; } else { return m_player1; } } /** * Sets the turn to the player having specified color * without notifying listener about the change. * @param white True for white player, false for black player */ public void setCurrentPlayer(boolean white) { Player p = m_player1; if (white && !m_player1.isWhite() || !white && m_player1.isWhite()) { p = m_player2; } m_currentPlayer = p; } /** * Sets the turn to the player having specified color * and notifies listener about the change. * @param whiteTurn True for white player, false for black player */ public void setTurn(boolean whiteTurn) { setCurrentPlayer(whiteTurn); if (m_listener != null) m_listener.turnChange(whiteTurn); } // Movement /** * Moves a piece of specified color, from specified index * to specified destination on board. Handles specific backgammon rules, i.e. * if white moves to an index where one black piece resides, whereas the black * piece is put under guard. Step (1) of performing a move. * @param index The source index to move from * @param dest The destination index to move to * @param white The color of the piece to move */ public synchronized void makeMove(int index, int dest, boolean white) { int[] player = WHITE, opponent = BLACK; if (!white) { player = BLACK; opponent = WHITE; } player[index] -= 1; player[dest] += 1; m_undoableMoves[m_undoableMoveCount][UNDO_SOURCE] = dest; m_undoableMoves[m_undoableMoveCount][UNDO_DEST] = index; m_undoableMoves[m_undoableMoveCount][UNDO_GUARD] = -1; if (m_listener != null) m_listener.pieceMoved(white, index, dest); if (dest != POS_OUT && opponent[dest] == 1) { m_undoableMoves[m_undoableMoveCount][UNDO_GUARD] = dest; opponent[dest] = 0; opponent[POS_GUARD]++; if (m_listener != null) m_listener.pieceMoved(!white, dest, POS_GUARD); } if (player[POS_OUT] == 15) { setGameFinished(true); if (m_listener != null) m_listener.gameFinished( white, calculatePiecesLeft(!white), calculatePoints(!white)); } } /** * Consumes a dice value, called when a player moves a piece. * Populates undoable moves vector and consumes the dice value * used for move. * Step (2) of performing a move. * @param diceIndex The diceindex that was used for the move. */ public synchronized void consumeDice(int diceIndex) { m_undoableMoves[m_undoableMoveCount][UNDO_DICEVALUE] = m_dice[diceIndex]; for (int d = diceIndex; d < m_dice.length-1; d++) { m_dice[d] = m_dice[d+1]; } m_diceVals--; m_possibleMovesCalculated = false; } /** * Commits a move, step (3) of performing a move. Updates * undoable move count and checks if game is ended because of this * move. * @param white The color of the pieces whose moves are to be commited. * @param diceValue The dicevalue that was used for the move. */ public synchronized void commitMove(boolean white, int diceValue) { int[] player = WHITE, opponent = BLACK; if (!white) { player = BLACK; opponent = WHITE; } m_undoableMoveCount++; if (m_listener != null) m_listener.undoAdded(m_undoableMoveCount, diceValue); } /** * Undoes the last move. Does nothing if there are no moves to undo. * @param white The color of the player that undoes * @return the source index of the undoed move. */ public synchronized int undoLastMove(boolean white) { int oldSource = -1; if (m_undoableMoveCount > 0) { int[] player = WHITE, opponent = BLACK; if (!white) { player = BLACK; opponent = WHITE; } m_undoableMoveCount--; int source = m_undoableMoves[m_undoableMoveCount][UNDO_SOURCE]; int dest = m_undoableMoves[m_undoableMoveCount][UNDO_DEST]; int guard = m_undoableMoves[m_undoableMoveCount][UNDO_GUARD]; int diceVal = m_undoableMoves[m_undoableMoveCount][UNDO_DICEVALUE]; oldSource = dest; player[source]--; player[dest]++; if (m_listener != null) m_listener.pieceMoved(white, source, dest); if (guard >= 0) { opponent[POS_GUARD]--; opponent[guard]++; if (m_listener != null) m_listener.pieceMoved(!white, POS_GUARD, guard); } m_dice[m_diceVals++] = diceVal; m_possibleMovesCalculated = false; if (m_listener != null) m_listener.undoPerformed(m_undoableMoveCount, diceVal); } return oldSource; } // Game intelligence /** * Calculates possible moves and returns array, composed as * int[possibleMoveIndex][PM_SOUR | PM_DEST | PM_DICE] where PM_SOUR * denotes the source index, PM_DEST denotes the destination index, and * PM_DICE denotes the dice value index that is used for the move.

* The array is sorted depending on player color, allowing consistent * movement of source and destination cursors by simply increasing or * decreasing the move index.

* To get number of possible moves, i.e. the length of the array, see * countPossibleMoves()

* This method caches results of possible moves calculation for performance. * @param white The color of the player * @return An array with possible moves, containing source indices, destination indices, * and the dice index used for specific move. */ public int[][] getPossibleMoves(boolean white) { if (!m_possibleMovesCalculated) calcPossibleMoves(white); return m_possibleMoves; } /** * Returns number of possible moves for current game state. * See getPossibleMoves.

* This method caches results of possible moves calculation for performance. * @param white The color of the player * @return Number of possible moves. */ public int countPossibleMoves(boolean white) { if (!m_possibleMovesCalculated) calcPossibleMoves(white); return m_possibleMoveCount; } /** * Throws dices, and puts new values in the dice value vector. * If both dices are of same value, the dice value vector is * expanded to contain four values. The dice value vector is * sorted ascending. * * @param dice1 value of dice one * @param dice2 value of dice two */ public synchronized void newDiceValues(int dice1, int dice2) { m_possibleMovesCalculated = false; m_undoableMoveCount = 0; m_dice[0] = dice1; m_dice[1] = dice2; m_diceVals = 2; if (dice1 == dice2) { m_dice[2] = dice1; m_dice[3] = dice1; m_diceVals = 4; } else if (dice1 > dice2) { // Sort dice value vector to make movement more logical m_dice[0] = dice2; m_dice[1] = dice1; } } /** * Returns value of specified dice * @param diceIndex the dice, 0 or 1 * @return a value between 1 and 6 */ public int getDiceValue(int diceIndex) { return m_dice[diceIndex]; } /** * Returns number of possible undoable moves * @return number of possible undoable moves */ public synchronized int countUndoableMoves() { return m_undoableMoveCount; } /** * Calculates possible moves for specified player. * @param white True for white player, false for black. */ protected void calcPossibleMoves(boolean white) { m_possibleMoveCount = 0; int[] player = WHITE, opponent = BLACK; if (!white) { player = BLACK; opponent = WHITE; } // calculate number of unique dicevalues int includeDices = m_diceVals > 2 ? 1 : m_diceVals; if (includeDices == 2 && (m_dice[0] == m_dice[1])) includeDices = 1; // Check pieces under guard, first preference if (player[POS_GUARD] > 0) { // try all dicevalues for (int d = 0; d < includeDices; d++) { int toIndex; if (white) toIndex = m_dice[d] - 1; else toIndex = POS_BOARD - m_dice[d] + 1; if (Rules.isValidFromGuard(this, toIndex, player, opponent)) { // found a valid move, add to array m_possibleMoves[m_possibleMoveCount][PM_SOUR] = POS_GUARD; m_possibleMoves[m_possibleMoveCount][PM_DEST] = toIndex; m_possibleMoves[m_possibleMoveCount++][PM_DICE] = d; } } } else // No pieces under guard { int fromIndex, diceIndex; boolean inverseDiceValues = false; // Try all positions where pieces are found for (int i = 0; i <= Board.POS_BOARD; i++) { // Flip fromindex after half board // to make cursor interaction more intuitive if (i < 12) fromIndex = i; else fromIndex = Board.POS_BOARD - (i-12); if (player[fromIndex] > 0) { if (includeDices > 0) { inverseDiceValues = ( white & fromIndex + m_dice[0] > 11) | (!white & fromIndex - m_dice[0] < 12); } // Found some pieces, try all unique dicevalues for (int d = 0; d < includeDices; d++) { if (inverseDiceValues) diceIndex = includeDices - d - 1; else diceIndex = d; int toIndex = fromIndex; if (white) { toIndex += m_dice[diceIndex]; } else { toIndex -= m_dice[diceIndex]; if (toIndex < 0) toIndex = -toIndex + POS_BOARD; } if (Rules.isValidMove(this, fromIndex, toIndex, white, player, opponent)) { // found a valid move, add to array m_possibleMoves[m_possibleMoveCount][PM_SOUR] = fromIndex; m_possibleMoves[m_possibleMoveCount][PM_DEST] = toIndex > POS_BOARD ? POS_OUT : toIndex; m_possibleMoves[m_possibleMoveCount++][PM_DICE] = diceIndex; } } // per unique diceval } } // per pos on board } m_possibleMovesCalculated = true; } /** * Returns true if there are no pieces of specified color * before specified index. * @param white True for white, false for black * @param index The index * @return true if no pieces before, false otherwise */ public boolean isNoneBefore(boolean white, int index) { boolean none = true; int dir = -1; int[] player = WHITE; if (!white) { player = BLACK; dir = 1; } index += dir; while (none && index > 0 && index <= POS_BOARD) { none = player[index] == 0; index += dir; } return none; } /** * Checks if there are any pieces outside home - in that case pieces * must not be moved off the boards. * @param white true to check if all white is home, * false to check if all black is home. * @return true if no pieces outside home, false otherwise. */ public boolean areAllPiecesHome(boolean white) { boolean homeOk = true; int[] player = WHITE; if (!white) { player = BLACK; } for (int pos = 0; homeOk && pos <= POS_BOARD-6; pos++) { int index = getPlayerIndex(white, pos,0); homeOk = player[index] == 0; } return homeOk; } /** * Loads state from specified input stream. See saveState for format. * @param dis the input stream * @throws IOException * @return size of data in bytes */ public int loadState(DataInputStream dis) throws IOException { int len = 0; m_chooseTurns = dis.readBoolean(); len += 1; m_diceThrown = dis.readBoolean(); len += 1; boolean whiteTurn = dis.readBoolean(); len += 1; m_undoableMoveCount = dis.readInt(); len += 4; for (int a = 0; a < 5; a++) { for (int b = 0; b < 4; b++) { m_undoableMoves[a][b] = dis.readInt(); len += 4; } } m_diceVals = dis.readInt(); len += 4; for (int a = 0; a < 4; a++) { m_dice[a] = dis.readInt(); len += 4; } len += loadBoard(dis); m_possibleMovesCalculated = false; setCurrentPlayer(whiteTurn); return len; } /** * Saves current state to specified outputstream. Format: * boolean m_chooseTurns * boolean m_diceThrown * boolean whiteTurn; * int m_undoableMoveCount * int[][] m_undoableMoves[5][4] * int m_diceVals * int[] m_dice[4] * Board this * @param dos the output stream * @throws IOException * @return size of data in bytes */ public int saveState(DataOutputStream dos) throws IOException { int len = 0; dos.writeBoolean(m_chooseTurns); len += 1; dos.writeBoolean(m_diceThrown); len += 1; dos.writeBoolean(m_currentPlayer.isWhite()); len += 1; dos.writeInt(m_undoableMoveCount); len += 4; for (int a = 0; a < 5; a++) { for (int b = 0; b < 4; b++) { dos.writeInt(m_undoableMoves[a][b]); len += 4; } } dos.writeInt(m_diceVals); len += 4; for (int a = 0; a < 4; a++) { dos.writeInt(m_dice[a]); len += 4; } len += saveBoard(dos); return len; } private BoardState() {} public static BoardState getInstance() { if (m_instance == null) { m_instance = new BoardState(); } return m_instance; } }