www.pudn.com > Bluegammon蓝牙的应用编程.rar > BoardCanvas.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.gui; 
 
import java.io.DataInputStream; 
import java.io.DataOutputStream; 
import java.io.IOException; 
 
import javax.microedition.lcdui.Canvas; 
import javax.microedition.lcdui.Command; 
import javax.microedition.lcdui.CommandListener; 
import javax.microedition.lcdui.Displayable; 
import javax.microedition.lcdui.Font; 
import javax.microedition.lcdui.Graphics; 
import javax.microedition.lcdui.Image; 
import javax.microedition.lcdui.TextField; 
import javax.microedition.m3g.Graphics3D; 
import javax.microedition.m3g.Transform; 
 
import bluegammon.Bluegammon; 
import bluegammon.Resources; 
import bluegammon.gui.animation.Animation; 
import bluegammon.gui.animation.AnimationEngine; 
import bluegammon.gui.animation.CommitMoveAnim; 
import bluegammon.gui.animation.CursorAnim; 
import bluegammon.gui.animation.DicesNewTurnAnim; 
import bluegammon.gui.animation.DicesSelectTurnAnim; 
import bluegammon.gui.animation.PieceMoveAnim; 
import bluegammon.gui.animation.RepaintRequestable; 
import bluegammon.gui.animation.WinnerAnim; 
import bluegammon.gui.animation.WinnerPieceMoveAnim; 
import bluegammon.logic.Board; 
import bluegammon.logic.BoardMediator; 
import bluegammon.logic.BoardState; 
import bluegammon.logic.LocalPlayer; 
 
/** 
 

* Singleton class handling all graphical details of a backgammon view. * There can only be one BoardCanvas per device, thus * the singleton pattern.

* BoardCanvas handles the BackGammon board gui view. * Contains functionality for signalling gui changes on * user interaction. Uses a Board instance for * representation of gui state of the backgammon board. *

* The BoardCanvas also contains drawing primitives for * the backgammon board. *

* * @see bluegammon.logic.BoardMediator * @see bluegammon.logic.BoardState * @author Peter Andersson */ public class BoardCanvas extends PopupCanvas implements CommandListener, RepaintRequestable { /** The 3d z depth of a piece lying on backgammon board */ public static final float PIECE_Z = -55f; /** Singleton instance */ protected static BoardCanvas m_inst; // Gui colors protected static final int COL_BOARD = 0x008800; protected static final int COL_BOARD_LT = 0x00aa00; protected static final int COL_BOARD_DK = 0x006600; protected static final int COL_WHITE_DIAG = 0xdddddd; protected static final int COL_BLACK_DIAG = 0x222222; protected static final int COL_WHITE_PIECE = 0xffffff; protected static final int COL_BLACK_PIECE = 0x000000; protected static final int COL_WHITE_PIECE_DK = 0xbbbbbb; protected static final int COL_BLACK_PIECE_DK = 0x444444; protected static final int[] COL_BLACK_DICE_VAL = {0x000000, 0x333333, 0x666666, 0x000000}; protected static final int[] COL_WHITE_DICE_VAL = {0xffffff, 0xcccccc, 0x999999, 0x666666}; /** Dice value unit, will be set depending on screen size */ protected static int m_diceValueUnit = 1; /** Undo command */ public static final Command CMD_UNDO = new Command(Resources.getString(Resources.TXT_C_UNDO), Command.CANCEL, 1); /** Exit command */ public static final Command CMD_EXIT = new Command(Resources.getString(Resources.TXT_C_QUIT), Command.BACK, 1); /** Represents the gui state of backgammon board */ protected Board m_board = new Board(); /** Board's softbutton control */ protected SoftButtonControl m_softbuttons = new SoftButtonControl(); /** Canvas width in pixels */ protected int m_width; /** Canvas height in pixels */ protected int m_height; /** Backgammon diagonal width in pixels */ protected int m_diagW; /** Backgammon diagonal height in pixels */ protected int m_diagH; /** Backgammon diagonal deltaX/deltaY value */ protected int m_triangK; /** Backgammon board height in pixels */ protected int m_boardH; /** Backgammon piece size in pixels */ protected int m_pieceDiameter; /** * Cache of backgammon board regions per index, * defined by [index][x = 0, y = 1, width = 2, height = 3] */ protected int[][] m_boardRegions = new int[Board.MAX_POS][4]; /** * Point coordinates for dicevalue graphics, specified in an array * as [diceValue][Xn][Yn], where n can be 0..diceValue, and diceValue * is 0..5 representing different faces of a dice. */ protected int[][][] m_diceValPts = new int[6][][]; // The board graphics consists of a two level buffer /** First buffer level of board graphics, a clean board without pieces */ protected Image m_cleanBoardImg; /** * Second buffer level of board graphics, the board with pieces of * current state, i.e. all static pieces. */ protected Image m_pieceBoardImg; /** Flags indicating what backgammon board indices needs to be repainted */ protected boolean[] m_dirtyRows = new boolean[Board.MAX_POS]; protected boolean m_undoOn = false; /** The animation engine */ protected AnimationEngine m_animationEngine; /** Current local player */ protected LocalPlayer m_player; /** * Current index in possible movement vector of * user piece movement cursor */ protected int m_cursor = 0; /** User piece movement cursor animation */ protected CursorAnim m_cursorAnimation; /** Dice value vector, one value for each dice */ protected int[] m_diceValues = new int[2]; /** * Dice consume status vector, one status for each dicevalue * in m_diceValues. 0 indicates unconsumed, 1 indicates semiconsumed * (used for double throws), 2 means consumed. */ protected int[] m_consumeStatuses = new int[2]; /** Flag indicating if user can interact */ protected boolean m_allowInteraction = false; /** Flag indicating if user is queried of committing moves */ protected boolean m_queryCommit = false; /** Dice animation */ protected Animation m_diceAnimation = null; /** Flag indicating if dicevalues should be drawn */ protected boolean m_drawDiceValues = false; /** Request repaint flag */ protected volatile boolean m_repaint = false; /** 3d transform cache instance */ protected Transform m_transform = new Transform(); // Control methods /** * Sets up start positions for white and black player */ public void setStartPositions() { m_board.setStartPositions(); for (int i = 0; i < Board.MAX_POS; i++) { m_dirtyRows[i] = true; } } /** * Called from logic on a new game, when * players toss one dice each and the one with * highest value starts. * @param wDice value of white dice * @param bDice value of black dice */ public void selectTurns(int wDice, int bDice) { m_animationEngine.addAnimation( new DicesSelectTurnAnim(wDice, bDice, getWidth(), getBoardHeight())); } /** * Called from logic when dices are thrown on a new turn, * starts the dice animation. * @param white the color of dices */ public void throwDices(boolean white) { if (m_diceAnimation != null) m_animationEngine.remove(m_diceAnimation); m_diceAnimation = new DicesNewTurnAnim( white, BoardMediator.getDiceValue(0), BoardMediator.getDiceValue(1)); m_animationEngine.addAnimation(m_diceAnimation); setDiceValues( BoardMediator.getDiceValue(0), BoardMediator.getDiceValue(1)); updateCursor(); } /** * Called from logic when a piece is moved. * @param white Color of piece * @param from From index. * @param to To index. * @param piecesOnSrc Number of pieces on source index * @param piecesOnDst Number of pieces on dest index */ public void movePiece(boolean white, int from, int to, int piecesOnSrc, int piecesOnDst) { m_animationEngine.addAnimation(new PieceMoveAnim(white, from, to, piecesOnSrc, piecesOnDst)); } /** * Called from logic when user has made a move * using specified dice value. Updates consume status. * @param diceVal The dice value used */ public void consumeDiceValue(int diceVal) { if (m_diceValues[0] == m_diceValues[1]) { if (m_consumeStatuses[0] == 2) m_consumeStatuses[1]++; else m_consumeStatuses[0]++; } else { if (m_diceValues[0] == diceVal) m_consumeStatuses[0] = 2; else m_consumeStatuses[1] = 2; } } /** * Called from logic when user has undoed a * move based on specified dice value. Updates * specified dicevalue consume status. * @param diceVal The dice value being undoed */ public void undoConsumedDiceValue(int diceVal) { if (m_diceValues[0] == m_diceValues[1]) { if (m_consumeStatuses[1] == 0) m_consumeStatuses[0]--; else m_consumeStatuses[1]--; } else { if (m_diceValues[0] == diceVal) m_consumeStatuses[0] = 0; else m_consumeStatuses[1] = 0; } } /** * Called from logic when the user should commit his/her moves * or when the user has commited. * @param commit True if user should commit, false if user has commited. */ public void setQueryCommit(boolean commit) { m_queryCommit = commit; if (commit) { m_cursor = -1; m_animationEngine.addAnimation(new CommitMoveAnim(!BoardMediator.isCurrentPlayerWhite())); } else { m_cursor = 0; updateUndoCommand(); } } /** * Setups gui when there is a winner. * @param white The color of the winner. * @param pieces Pieces left for loser. * @param pts Points per piece, */ public void finishGame(boolean white, int pieces, int pts) { m_animationEngine.addAnimation(new WinnerAnim(white, pieces, pts)); } /** * Invalidates this canvas, forcing a full repaint on * next call to paint() */ public void invalidate() { m_pieceBoardImg.getGraphics().drawImage( getBoardImage(), 0, 0, Graphics.TOP | Graphics.LEFT); // repaint all rows for (int i = 0; i < Board.MAX_POS; i++) { m_dirtyRows[i] = true; } drawBoard(null); // clean up animation engine if (m_cursorAnimation != null) m_cursorAnimation.stop(); m_cursorAnimation = null; m_diceAnimation = null; m_animationEngine.removeAll(); updateUndoCommand(); if (BoardMediator.countPossibleMoves() > 0) { cursorNearestIndex(0, 0); } else { m_cursor = -1; } updateCursor(); } /** * Shuts down the canvas logic. */ public void shutdown() { m_inst = null; m_animationEngine.shutdown(); } // BoardCanvas state manipulation methods /** * Set the local player that will receive interaction * calls made on this canvas. * @param player The local player, or null if current player is not local. */ public void setCurrentLocalPlayer(LocalPlayer player) { m_player = player; updateCursor(); } /** * Adds a new piece to the canvas * @param white true if white piece, false if black * @param index where to place the piece */ public void addPiece(boolean white, int index) { m_board.addPiece(white, index); m_dirtyRows[index] = true; requestRepaint(); } /** * Removes a piece from the canvas * @param white true if white piece, false if black * @param index where from to remove the piece */ public void removePiece(boolean white, int index) { m_board.removePiece(white, index); m_dirtyRows[index] = true; requestRepaint(); } /** * Sets number of pieces on specified index * @param white true if white piece, false if black * @param index The board index * @param pieces Number of pieces */ public void setPieces(boolean white, int index, int pieces) { m_board.setPieces(white, index, pieces); m_dirtyRows[index] = true; requestRepaint(); } /** * Returns number pieces on specified index * @param index The board index * @return Number of pieces */ public int getPieces(int index) { return m_board.countPieces(index); } /** * Returns number of pieces on specified index of specified color * @param white true if white piece, false if black * @param index The board index * @return Number of pieces */ public int getPieces(boolean white, int index) { return m_board.countPieces(white, index); } /** * Returns true if there are white pieces on specified index, * returns false if there are black or no pieces. * @param index The board index * @return true if white, false if black. */ public boolean isWhite(int index) { return m_board.isWhite(index); } // Low level canvas state methods /** * Returns whether the player can move any pieces or not. * @return true if user can move pieces, false if not. */ public boolean isUserMovable() { return m_allowInteraction && !BoardMediator.isRemoteTurn(); } /** * Returns current cursor index of possible moves. * @return the cursor index. */ public int getCurrentCursorIndex() { return m_cursor; } /** * Returns whether the canvas is waiting for the user * to commit his/her moves. * @return true if waiting for commit, false if not. */ public boolean waitingForCommit() { return m_queryCommit; } /** * Updates the cursor. Called when possible moves has been altered. * Stops any current cursor animation and creates a new * one if there are possible moves. */ public void updateCursor() { if (m_cursorAnimation != null) { m_cursorAnimation.stop(); m_cursorAnimation = null; } if (m_cursor >= 0 && BoardMediator.countPossibleMoves() > 0 && isUserMovable()) { int src = BoardMediator.getPossibleMoves()[m_cursor][BoardState.PM_SOUR]; int dst = BoardMediator.getPossibleMoves()[m_cursor][BoardState.PM_DEST]; m_cursorAnimation = new CursorAnim( src, dst, BoardMediator.countStatePieces(src), BoardMediator.countStatePieces(dst), BoardMediator.isCurrentPlayerWhite()); m_animationEngine.addAnimation(m_cursorAnimation); } } /** * Enables/disables the possibility for user to perform a undo, * depending on game state. */ public void updateUndoCommand() { if (!BoardMediator.isGameFinished() && !m_undoOn && BoardMediator.countUndoableMoves() > 0) { m_undoOn = true; } else if (BoardMediator.isGameFinished() || m_undoOn && BoardMediator.countUndoableMoves() == 0) { m_undoOn = false; } m_softbuttons.enable(CMD_UNDO, m_undoOn); } /** * Sets whether the canvas accepts any interaction or not. * @param allow enable/disable interaction. */ public void allowInteraction(boolean allow) { m_allowInteraction = allow; } /** * Sets the dice values to be drawn and * resets consume status * @param dice1 value of dice 1 (0-5) * @param dice2 value of dice 2 (0-5) */ public void setDiceValues(int dice1, int dice2) { m_diceValues[0] = dice1; m_diceValues[1] = dice2; m_consumeStatuses[0] = 0; m_consumeStatuses[1] = 0; } /** * Enables/Disables drawing of dice values. * @param enable True when dice values should be drawn, * falsw when dice values should not be drawn. */ public void setDrawDiceValues(boolean enable) { m_drawDiceValues = enable; repaint(); } // Interaction /** * Handles user interaction. * @param keyCode Representation of pressed key. */ protected void keyPressed(int keyCode) { try { // Do softbuttons m_softbuttons.keyPressed(keyCode); // Check if any popup is open if (m_popup != null && m_popup.isActive()) { getPopup().keyPressed(keyCode, getGameAction(keyCode)); repaint(); return; } // Report to current local player boolean consumed = false; int gameCode = getGameAction(keyCode); if (m_player != null) { consumed = m_player.keyPressed(keyCode, gameCode); } if (keyCode == Canvas.KEY_POUND && Bluegammon.getGameType() != Bluegammon.GAME_TYPE_LOCAL) { LocalPlayer p = BoardMediator.getLocalPlayer(); Bluegammon.getStringInput( Resources.getString(Resources.TXT_MESSAGE) + " " + new String(BoardMediator.getOpponentPlayer().getName()), null, 1024, TextField.ANY, p); } repaint(); } catch (Throwable t) { System.err.println("Error in board interaction thread"); t.printStackTrace(); } } /** * Handles command invokations from SoftButtonControl. * @param c the command. * @param d the displayable. */ public void commandAction(Command c, Displayable d) { boolean consumed = false; // Report to current local player if (!BoardMediator.isRemoteTurn()) { consumed = BoardMediator.getLocalPlayer().commandAction(c); } if (!consumed && c == CMD_EXIT) { // Exit, can be invoked without being user's turn consumed = BoardMediator.getLocalPlayer().commandAction(c); } repaint(); } // Cursor interaction logic methods /** * Moves to nearest cursor index after a change in possible * moves. * @param oldIndex old index before change * @param oldTarget old target index before change */ public void cursorNearestIndex(int oldIndex, int oldTarget) { int posMoves[][] = BoardMediator.getPossibleMoves(); int diffSource = Integer.MAX_VALUE, curDiffSource; int diffTarget = Integer.MAX_VALUE, curDiffTarget; int newIndex = -1; for (int i = 0; i < BoardMediator.countPossibleMoves(); i++) { curDiffSource = Math.abs(posMoves[i][BoardState.PM_SOUR] - oldIndex); if (curDiffSource <= diffSource) { curDiffTarget = Math.abs(posMoves[i][BoardState.PM_SOUR] - oldTarget); if (diffSource == curDiffSource) { if (curDiffTarget < diffTarget) { diffSource = curDiffSource; diffTarget = curDiffTarget; newIndex = i; } } else { diffSource = curDiffSource; diffTarget = curDiffTarget; newIndex = i; } } } m_cursor = newIndex; } /** * Moves cursor to nearest matching cursor index on opposite * side of board, given current cursor position. */ public void cursorSwapSide() { if (m_cursor < 0) return; int posMoves[][] = BoardMediator.getPossibleMoves(); int diffSource = Integer.MAX_VALUE, curDiffSource; int diffTarget = Integer.MAX_VALUE, curDiffTarget; int newIndex = m_cursor; int oldIndex = Board.POS_BOARD - posMoves[m_cursor][BoardState.PM_SOUR]; int oldTarget = Board.POS_BOARD - posMoves[m_cursor][BoardState.PM_DEST]; for (int i = 0; i < BoardMediator.countPossibleMoves(); i++) { if ((oldIndex < 12 && posMoves[i][BoardState.PM_SOUR] > 11 || oldIndex > 11 && posMoves[i][BoardState.PM_SOUR] < 12) && (oldTarget < 12 && posMoves[i][BoardState.PM_DEST] > 11 || oldTarget > 11 && posMoves[i][BoardState.PM_DEST] < 12)) { continue; } curDiffSource = Math.abs(posMoves[i][BoardState.PM_SOUR] - oldIndex); if (curDiffSource <= diffSource) { curDiffTarget = Math.abs(posMoves[i][BoardState.PM_SOUR] - oldTarget); if (diffSource == curDiffSource) { if (curDiffTarget < diffTarget) { diffSource = curDiffSource; diffTarget = curDiffTarget; newIndex = i; } } else { diffSource = curDiffSource; diffTarget = curDiffTarget; newIndex = i; } } } m_cursor = newIndex; } /** * Steps forward amongst cursor indices. */ public void cursorNextIndex() { if (BoardMediator.countPossibleMoves() > 0) m_cursor = (m_cursor + 1) % BoardMediator.countPossibleMoves(); else m_cursor = -1; } /** * Steps backward amongst cursor indices. */ public void cursorBackIndex() { int newIndex = m_cursor - 1; if (newIndex < 0) newIndex = BoardMediator.countPossibleMoves() - 1; m_cursor = newIndex; } /** * Returns whether there is a valid cursor or not. * * @return True if there is a valid cursor, false otherwise. */ public boolean isCursorValid() { return m_cursor >= 0; } // Graphic methods /** * Paints the backgammon board, any surrounding * graphics and all current animations. * * @param g Graphics context. */ protected void paint(Graphics g) { try { // Clear background g.setColor(COL_BOARD_DK); g.fillRect(0,m_boardH,m_width, m_height - m_boardH); // Draw backgammon board drawBoard(g); // Draw dice values if necessary if (!BoardMediator.isGameFinished() && m_drawDiceValues) { drawDiceValues(g, BoardMediator.isCurrentPlayerWhite(), m_diceValues, m_consumeStatuses); } // Draw animations m_animationEngine.paint(g); // Draw softbuttons m_softbuttons.paint(g); // Draw popup if necessary if (m_popup != null && m_popup.isActive()) { m_popup.paint(g); } } catch (Throwable t) { System.err.println("Error in board paint thread"); t.printStackTrace(); } } /** * Returns the actual height of the board in pixels * @return the board height */ public int getBoardHeight() { return m_boardH; } /** * Adds a piece animation when there is a winner. Selects * among existing pieces on board. * @param white True to animate white pieces, false for black. * @param winAnim The comprising winning animation. */ public void addWinningPieceAnimation(boolean white, WinnerAnim winAnim) { // Find a piece to animate int src = -1; if (getPieces(white, Board.POS_GUARD) > 0) { src = Board.POS_GUARD; } else { for (int i = 0; src == -1 && i <= Board.POS_BOARD; i++) { if (getPieces(white, i) > 0) { src = i; } } } if (src >= 0) { int x = (src > Board.POS_BOARD / 2) ? 0 : getWidth(); m_animationEngine.addAnimation( new WinnerPieceMoveAnim(winAnim, white, src, x, getHeight()/2, getPieces(white, src)-1) ); } } /** * Draws the backgammon game board. Does partial redraw depending on * what is changed since last draw. * @param g Graphics context to draw to or null if just * updating static graphics. */ protected void drawBoard(Graphics g) { Graphics staticGraphics = m_pieceBoardImg.getGraphics(); for (int i = 0; i < Board.MAX_POS; i++) { if (m_dirtyRows[i]) { // Repaint dirty region staticGraphics.setClip( m_boardRegions[i][0], m_boardRegions[i][1], m_boardRegions[i][2], m_boardRegions[i][3]); staticGraphics.drawImage(m_cleanBoardImg, 0, 0, Graphics.TOP | Graphics.LEFT); staticGraphics.setClip(0,0,m_width, m_height); drawPieces(staticGraphics, i); } } if (g != null) { g.drawImage(m_pieceBoardImg, 0, 0, Graphics.TOP | Graphics.LEFT); } } /** * Draws both dice values. * @param g Graphics context to draw to. * @param white True for white dicevalues, false for vlack. * @param diceVals Array for the actual dice values. * @param consumeStatus Array of the consume status of each dice value. */ protected void drawDiceValues(Graphics g, boolean white, int[] diceVals, int[] consumeStatus) { int totalW = 2 * (m_diceValueUnit * 8 ) - m_diceValueUnit; int x = (m_width - totalW) / 2; int y = m_boardH - m_diceValueUnit * 7 - 1; for (int i = 0; i < 2; i++) { drawDiceValue(g, white, diceVals[i], consumeStatus[i], x, y); x += m_diceValueUnit * 8; } } /** * Draws one dice value. * @param g Graphics context to draw to. * @param white True for white dicevalues, false for vlack. * @param diceVal The actual dice value (0-5). * @param consumeStatu The consume status of the dice value. * @param x X-coordinate of the dicevalue. * @param y Y-coordinate of the dicevalue. */ protected void drawDiceValue( Graphics g, boolean white, int diceVal, int consumeStatus, int x, int y) { int[] backCols = COL_WHITE_DICE_VAL, ptCols = COL_BLACK_DICE_VAL; if (!white) { backCols = COL_BLACK_DICE_VAL; ptCols = COL_WHITE_DICE_VAL; } int backCol = backCols[consumeStatus]; int fxCol = backCols[3]; int ptCol = ptCols[consumeStatus]; g.setColor(fxCol); g.fillRect(x+1,y+1,m_diceValueUnit * 7, m_diceValueUnit * 7); g.setColor(backCol); g.fillRect(x,y,m_diceValueUnit * 7, m_diceValueUnit * 7); g.setColor(ptCol); int[][] pts = m_diceValPts[diceVal-1]; for (int i = 0; i < pts.length; i++) { g.fillRect(x + pts[i][0], y + pts[i][1], m_diceValueUnit, m_diceValueUnit); } } /** * Returns an image of a clean backgammon board (without pieces). * @return A backgammon board image. */ public synchronized Image getBoardImage() { if (m_cleanBoardImg == null) { m_cleanBoardImg = Image.createImage(m_width, m_boardH); Graphics ig = m_cleanBoardImg.getGraphics(); ig.setColor(COL_BOARD); ig.fillRect(0,0,m_width,m_boardH); int diag = 0; int index = 0; int tmpCol, col1 = COL_WHITE_DIAG, col2 = COL_BLACK_DIAG; for (int y = 0; y < m_height - m_diagH; y += m_diagH) { diag ++; switch (diag) { // out case 1: // guard case 8: // bottom case 15: { y++; ig.setColor(COL_BOARD_LT); ig.drawLine(0,y,m_width-1,y); ig.drawLine(0,y,0,y+m_diagH); ig.setColor(COL_BOARD_DK); ig.drawLine(0,y+m_diagH,m_width-1,y+m_diagH); ig.drawLine(m_width-1,y,m_width-1,y+m_diagH); y++; break; } default: { m_boardRegions[index][0] = 0; m_boardRegions[index][1] = y; m_boardRegions[index][2] = m_width/2; m_boardRegions[index][3] = m_diagH; m_boardRegions[Board.POS_BOARD - index][0] = m_width/2 + 1; m_boardRegions[Board.POS_BOARD - index][1] = y; m_boardRegions[Board.POS_BOARD - index][2] = m_width/2; m_boardRegions[Board.POS_BOARD - index][3] = m_diagH; index ++; tmpCol = col1; col1 = col2; col2 = tmpCol; int x = 0; int k = (2 * m_triangK) / 2; for (int yy = y; yy < y + m_diagH/2; yy++) { drawAntialiasedLine(0,x,yy, col1, COL_BOARD, ig, k); drawAntialiasedLine(m_width, m_width - x, yy, col2, COL_BOARD, ig, k); x += k; } for (int yy = y + m_diagH / 2; yy < y + m_diagH; yy++) { drawAntialiasedLine(m_width, m_width - x, yy, col2, COL_BOARD, ig, k); drawAntialiasedLine(0,x,yy, col1, COL_BOARD, ig, k); x -= k; } } } } m_boardRegions[Board.POS_OUT][0] = 0; m_boardRegions[Board.POS_OUT][1] = 0; m_boardRegions[Board.POS_OUT][2] = m_width; m_boardRegions[Board.POS_OUT][3] = m_boardRegions[0][1] - 1; m_boardRegions[Board.POS_GUARD][0] = 0; m_boardRegions[Board.POS_GUARD][1] = m_boardRegions[5][1] + m_boardRegions[5][3]; m_boardRegions[Board.POS_GUARD][2] = m_width; m_boardRegions[Board.POS_GUARD][3] = m_boardRegions[6][1] - m_boardRegions[5][1] - m_boardRegions[5][3] - 1; } return m_cleanBoardImg; } /** * Draws pieces for a specified row-index. * @param g Graphics context to draw to. * @param index The row-index. */ protected void drawPieces(Graphics g, int index) { Context3D c3d = Context3D.getInstance(); Graphics3D g3d = c3d.bindScene(g); int x,y; // on board if (index <= Board.POS_BOARD) { for (int j = 0; j < getPieces(index); j++) { drawPiece3D(getPieceX(index, j, isWhite(index)), getPieceY(index, j, isWhite(index)), 0, false, 0, isWhite(index), c3d, g3d); } } // on guard else if (index == Board.POS_GUARD) { for (int i = 0; i < getPieces(true, Board.POS_GUARD); i++) { drawPiece3D(getPieceX(Board.POS_GUARD, i, true), getPieceY(Board.POS_GUARD, i, true), 0, false, 0, true, c3d, g3d); } for (int i = 0; i < getPieces(false, Board.POS_GUARD); i++) { drawPiece3D(getPieceX(Board.POS_GUARD, i, false), getPieceY(Board.POS_GUARD, i, false), 0, false, 0, false, c3d, g3d); } } // out else if (index == Board.POS_OUT) { for (int i = 0; i < getPieces(true, Board.POS_OUT); i++) { drawPiece3D(getPieceX(Board.POS_OUT, i, true), getPieceY(Board.POS_OUT, i, true), -60, false, 0, true, c3d, g3d); } for (int i = 0; i < getPieces(false, Board.POS_OUT); i++) { drawPiece3D(getPieceX(Board.POS_OUT, i, false), getPieceY(Board.POS_OUT, i, false), 60, false, 0, false, c3d, g3d); } } m_dirtyRows[index] = false; g3d.releaseTarget(); } /** * Returns x coordinate for a piece on specified row-index. * @param index The row-index. * @param pieceNo The piece number on this index. * @param white true if white, false if black. * @return the x-coordinate. */ public int getPieceX(int index, int pieceNo, boolean white) { int x = 0; if (index == Board.POS_GUARD) { if (white) { x = m_width / 2 + (1 * m_pieceDiameter) / 2 + 1; } else { x = m_width / 2 - (3 * m_pieceDiameter) / 2 - 1; } x -= 2 * pieceNo; } else if (index == Board.POS_OUT) { if (white) { x = m_width - m_pieceDiameter - 1 - pieceNo * 2; } else { x = 1 + pieceNo * 2; } } else { if (index < 12) { x = 0; } else { x = m_width - m_pieceDiameter - 1; } int dx = Math.min(pieceNo, 4) * (m_diagH + 1); dx += (pieceNo - Math.min(pieceNo,4)) * 2; if (index <= BoardState.POS_BOARD / 2) { x += dx; } else { x -= dx; } } return x; } /** * Returns y coordinate for a piece on specified row-index. * @param index The row-index. * @param pieceNo The piece number on this index. * @param white true if white, false if black. * @return the y-coordinate. */ public int getPieceY(int index, int pieceNo, boolean white) { int y = 0; if (index == Board.POS_GUARD) { y = 2 + 7 * m_diagH; } else if (index == Board.POS_OUT) { y = 1; } else { if (index < 6) // 0 - 5 upper left y = 2 + (index+1) * m_diagH; else if (index < 12) // 6 - 11 bottom left y = 4 + (index+2) * m_diagH; else if (index < 18) // 12 - 17 bottom right y = m_boardH - 2 - (index - 10) * m_diagH; else // 18 - 23 upper right y = m_boardH - 4 - (index - 9) * m_diagH; } return y; } /** * Draws a piece on specified coordinates. * @param x x-coordinate. * @param y y-coordinate. * @param angle Piece angle. * @param horizTilt True for horizontal tilt, false for vertical. * @param dz Piece delta z. * @param white True for white piece, false for black. * @param g Graphics context to draw to. */ public void drawPiece(int x, int y, float angle, boolean horizTilt, float dz, boolean white, Graphics g) { Context3D c3d = Context3D.getInstance(); Graphics3D g3d = c3d.bindScene(g); drawPiece3D(x,y,angle,horizTilt,dz,white,c3d,g3d); g3d.releaseTarget(); } /** * Draws a piece on specified coordinates. * @param x x-coordinate. * @param y y-coordinate. * @param angle Piece angle. * @param horizTilt True for horizontal tilt, false for vertical. * @param dz Piece delta z. * @param white True for white piece, false for black. * @param g3d 3D Graphics context to draw to. */ public void drawPiece3D(int x, int y, float angle, boolean horizTilt, float dz, boolean white, Context3D c3d, Graphics3D g3d) { float z = PIECE_Z + dz; int size = (int)(c3d.toScreenCoordinateX(2f,z)+0.5f); x += size/2; y += size/2; m_transform.setIdentity(); m_transform.postTranslate( c3d.to3DCoordinateX(x,z-dz), c3d.to3DCoordinateY(y+1,z-dz), z); if (horizTilt) { m_transform.postRotate(angle,1f,0,0); } else { m_transform.postRotate(angle,0,1f,0); } g3d.render(c3d.getPieceVertexBuffer(), c3d.getPieceIndexBuffer(), c3d.getPieceAppearance(white), m_transform); } /** * Draws a horizontal antialiased line. * @param x1 Start x - coordinate. * @param x2 End x - coordinate. * @param y Y - coordinate. * @param col1 Starting color. * @param col2 End color. * @param g Graphics context to draw to. */ protected void drawAntialiasedLine(int x1, int x2, int y, int col1, int col2, Graphics g, int k) { if (x1 == x2) return; g.setColor(col1); int col; if (x1 > x2) k = -k; int x = (x2 - k) << 8; int dx = (k << 8) >> 3; g.drawLine(x1,y,x>>8,y); for (int i = 0; i < 8; i++) { col = blendColors(col1, col2, i); g.setColor(col); g.drawLine(x>>8,y,(x+dx)>>8,y); x += dx; } } /** * Blends two colors. * @param col1 Color 1 * @param col2 Color 2 * @param i A number between 0 and 7, 0 means color 1, 7 means color 2. * @return The blended color. */ protected int blendColors(int col1, int col2, int i) { if (i < 0) i = 0; else if (i > 8) i = 8; int ii = 8 - i; return (((i * (col2 & 0xff0000)) >>> 3) & 0xff0000) + (((ii * (col1 & 0xff0000)) >>> 3) & 0xff0000) | (((i * (col2 & 0x00ff00)) >>> 3) & 0x00ff00) + (((ii * (col1 & 0x00ff00)) >>> 3) & 0x00ff00) | (((i * (col2 & 0x0000ff)) >>> 3) & 0x0000ff) + (((ii * (col1 & 0x0000ff)) >>> 3) & 0x0000ff); } // RequestRepaintable impl /** * Requests a repaint of whole canvas. */ public void requestRepaint() { m_repaint = true; } /** * Committs all repaint request in an actual repaint. * Called from animation logic. */ public void commitRepaint() { if (m_repaint) { repaint(); m_repaint = false; } } // I/O /** * Saves the state of the graphical representation of the board. * @param dos The output stream. * @return Number of bytes written. * @throws IOException if save failed. */ public int saveCanvas(DataOutputStream dos) throws IOException { int len = 0; dos.writeBoolean(m_queryCommit); len += 1; dos.writeBoolean(m_undoOn); len += 1; dos.writeInt(m_diceValues[0]); len += 4; dos.writeInt(m_diceValues[1]); len += 4; dos.writeInt(m_consumeStatuses[0]); len += 4; dos.writeInt(m_consumeStatuses[1]); len += 4; return len; } /** * Loads the state of a graphical representation of the board. * @param dis The input stream. * @return Number of bytes read. * @throws IOException if load failed. */ public int loadCanvas(DataInputStream dis) throws IOException { int len = 0; m_queryCommit = dis.readBoolean(); len += 1; m_undoOn = dis.readBoolean(); len += 1; m_diceValues[0] = dis.readInt(); len += 4; m_diceValues[1] = dis.readInt(); len += 4; m_consumeStatuses[0] = dis.readInt(); len += 4; m_consumeStatuses[1] = dis.readInt(); len += 4; m_animationEngine.removeAll(); allowInteraction(true); setDrawDiceValues(true); setQueryCommit(m_queryCommit); updateUndoCommand(); return len; } /** * C'tor and initialization */ protected BoardCanvas() { setFullScreenMode(true); m_width = getWidth(); m_height = getHeight(); // calculate common graphic properties on given width and height m_diagW = (3 * m_width) / 8; m_diagH = (m_height-6) / 15; m_triangK = (2 * m_diagW) / m_diagH; m_boardH = m_diagH * 15 + 6; m_pieceDiameter = m_diagH - 2; m_diceValueUnit = 1 + m_pieceDiameter / 7; for (int i = 0; i < 6; i++) { m_diceValPts[i] = new int[i + 1][2]; switch (i) { case 0: m_diceValPts[0][0][0] = 3 * m_diceValueUnit; m_diceValPts[0][0][1] = 3 * m_diceValueUnit; break; case 1: m_diceValPts[1][0][0] = 1 * m_diceValueUnit; m_diceValPts[1][0][1] = 1 * m_diceValueUnit; m_diceValPts[1][1][0] = 5 * m_diceValueUnit; m_diceValPts[1][1][1] = 5 * m_diceValueUnit; break; case 2: m_diceValPts[2][0][0] = 5 * m_diceValueUnit; m_diceValPts[2][0][1] = 1 * m_diceValueUnit; m_diceValPts[2][1][0] = 3 * m_diceValueUnit; m_diceValPts[2][1][1] = 3 * m_diceValueUnit; m_diceValPts[2][2][0] = 1 * m_diceValueUnit; m_diceValPts[2][2][1] = 5 * m_diceValueUnit; break; case 3: m_diceValPts[3][0][0] = 1 * m_diceValueUnit; m_diceValPts[3][0][1] = 1 * m_diceValueUnit; m_diceValPts[3][1][0] = 5 * m_diceValueUnit; m_diceValPts[3][1][1] = 1 * m_diceValueUnit; m_diceValPts[3][2][0] = 1 * m_diceValueUnit; m_diceValPts[3][2][1] = 5 * m_diceValueUnit; m_diceValPts[3][3][0] = 5 * m_diceValueUnit; m_diceValPts[3][3][1] = 5 * m_diceValueUnit; break; case 4: m_diceValPts[4][0][0] = 1 * m_diceValueUnit; m_diceValPts[4][0][1] = 1 * m_diceValueUnit; m_diceValPts[4][1][0] = 5 * m_diceValueUnit; m_diceValPts[4][1][1] = 1 * m_diceValueUnit; m_diceValPts[4][2][0] = 1 * m_diceValueUnit; m_diceValPts[4][2][1] = 5 * m_diceValueUnit; m_diceValPts[4][3][0] = 5 * m_diceValueUnit; m_diceValPts[4][3][1] = 5 * m_diceValueUnit; m_diceValPts[4][4][0] = 3 * m_diceValueUnit; m_diceValPts[4][4][1] = 3 * m_diceValueUnit; break; case 5: m_diceValPts[5][0][0] = 1 * m_diceValueUnit; m_diceValPts[5][0][1] = 1 * m_diceValueUnit; m_diceValPts[5][1][0] = 1 * m_diceValueUnit; m_diceValPts[5][1][1] = 3 * m_diceValueUnit; m_diceValPts[5][2][0] = 1 * m_diceValueUnit; m_diceValPts[5][2][1] = 5 * m_diceValueUnit; m_diceValPts[5][3][0] = 5 * m_diceValueUnit; m_diceValPts[5][3][1] = 1 * m_diceValueUnit; m_diceValPts[5][4][0] = 5 * m_diceValueUnit; m_diceValPts[5][4][1] = 3 * m_diceValueUnit; m_diceValPts[5][5][0] = 5 * m_diceValueUnit; m_diceValPts[5][5][1] = 5 * m_diceValueUnit; break; } } // Initialize 3d context and animations Context3D.getInstance().init(getWidth(), getHeight()); CursorAnim.init(m_pieceDiameter); PieceMoveAnim.init(m_pieceDiameter); DicesNewTurnAnim.init(m_diceValueUnit); // Start animation engine m_animationEngine = new AnimationEngine(this); new Thread(m_animationEngine, "AnimationThread").start(); // Create image buffer m_pieceBoardImg = Image.createImage(m_width, m_boardH); // Initialize softbuttons m_softbuttons.init(this, Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_SMALL), CMD_UNDO, CMD_EXIT); m_softbuttons.setCommandListener(this); } /** * Returns singleton instance. * @return The singleton instance. */ public static BoardCanvas getInstance() { if (m_inst == null) { m_inst = new BoardCanvas(); m_inst.setFullScreenMode(true); } return m_inst; } }