/** * $RCSfile: ,v $ * $Revision: $ * $Date: $ * * Copyright (C) 2004-2011 Jive Software. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.game.reversi; import javax.swing.*; import java.util.*; import java.util.List; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.filter.PacketExtensionFilter; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.DefaultPacketExtension; /** * The game UI, which is created after both players have accepted a new game. * * @author Bill Lynch */ public class ReversiPanel extends JPanel { private static final long serialVersionUID = 3591458286918924065L; private static final int BOARD_SIZE = 320; private static final int INFO_PANEL_HEIGHT = 50; private static final int BORDER_SIZE = 5; public static final int TOTAL_WIDTH = BOARD_SIZE + (BORDER_SIZE*2); public static final int TOTAL_HEIGHT = TOTAL_WIDTH + INFO_PANEL_HEIGHT; // frame width + 50 for the info panel private static final int NUM_BLOCKS = 8; private static final int BLOCK_SIZE = BOARD_SIZE/NUM_BLOCKS; private static final int DISC_SIZE = (int)(BLOCK_SIZE*0.8); // 80% of block size private XMPPConnection connection; private int otherPlayer; private int gameID; private String opponentJID; private PacketListener gameMoveListener; private List<ReversiBlock> blocks = new ArrayList<ReversiBlock>(); // Main game object private ReversiModel reversi; // All images used by the game. ImageIcon imageIcon = ReversiRes.getImageIcon(ReversiRes.REVERSI_ICON); private Image imageBackground = ReversiRes.getImageIcon(ReversiRes.REVERSI_BOARD).getImage(); private Image imageScoreWhite = ReversiRes.getImageIcon(ReversiRes.REVERSI_SCORE_WHITE).getImage(); private Image imageScoreBlack = ReversiRes.getImageIcon(ReversiRes.REVERSI_SCORE_BLACK).getImage(); private Image imageTurnBlack = ReversiRes.getImageIcon(ReversiRes.REVERSI_LABEL_BLACK).getImage(); private Image imageTurnWhite = ReversiRes.getImageIcon(ReversiRes.REVERSI_LABEL_WHITE).getImage(); private Image imageButtonResign = ReversiRes.getImageIcon(ReversiRes.REVERSI_RESIGN).getImage(); private Image imageYou = ReversiRes.getImageIcon(ReversiRes.REVERSI_YOU).getImage(); private Image imageThem = ReversiRes.getImageIcon(ReversiRes.REVERSI_THEM).getImage(); /** * Creates a new Reversi panel. * * @param connection Connection associated. * @param gameID Game ID number * @param startingPlayer Whether we are the starting player or not * @param opponentJID JID of opponent */ public ReversiPanel(XMPPConnection connection, final int gameID, boolean startingPlayer, String opponentJID) { this.connection = connection; this.gameID = gameID; this.opponentJID = opponentJID; otherPlayer = startingPlayer? ReversiModel.WHITE : ReversiModel.BLACK; // Load all images. // Start the game reversi = new ReversiModel(); if (connection != null) { gameMoveListener = new PacketListener() { public void processPacket(Packet packet) { GameMove move = (GameMove)packet.getExtension(GameMove.ELEMENT_NAME, GameMove.NAMESPACE); // If this is a move for the current game. if (move.getGameID() == gameID) { int position = move.getPosition(); // Make sure that the other player is allowed to make the move right now. if (reversi.getCurrentPlayer() == otherPlayer && reversi.isValidMove(position)) { reversi.makeMove(position); // Redraw board. ReversiPanel.this.repaint(); } else { // TODO: other user automatically forfeits! } // Execute move. } } }; connection.addPacketListener(gameMoveListener, new PacketExtensionFilter(GameMove.ELEMENT_NAME, GameMove.NAMESPACE)); // TODO: at end of game, remove listener. } setOpaque(false); // Use absolute layout setLayout(null); // Set its size: setPreferredSize(new Dimension(TOTAL_WIDTH, TOTAL_HEIGHT)); // Make a new panel which is the game board grid: JPanel reversiBoard = new JPanel(new GridLayout(NUM_BLOCKS,NUM_BLOCKS,0,0)); reversiBoard.setOpaque(false); for (int i=0; i<NUM_BLOCKS*NUM_BLOCKS; i++) { ReversiBlock block = new ReversiBlock(this, i); blocks.add(block); reversiBoard.add(block); } // Add the reversi board to the main panel: add(reversiBoard); // Position it: reversiBoard.setBounds(BORDER_SIZE, BORDER_SIZE, BOARD_SIZE, BOARD_SIZE); // TODO: listen for click on resign button!! } /** * Sends a forfeit message to the other player. */ public void sendForfeit() { DefaultPacketExtension forfeit = new DefaultPacketExtension(GameForfeit.ELEMENT_NAME, GameForfeit.NAMESPACE); forfeit.setValue("gameID", Integer.toString(gameID)); Message message = new Message(); message.setTo(opponentJID); message.addExtension(forfeit); connection.sendPacket(message); connection.removePacketListener(gameMoveListener); } public void paintComponent(Graphics g) { // Turn on anti-aliasing. Graphics2D g2d = (Graphics2D)g; g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); // Background g.drawImage(imageBackground, 0, 0, null); // Draw info panel components. // Draw the score. g.drawImage(imageScoreWhite, 3, BOARD_SIZE + BORDER_SIZE*2 + 7, null); g.drawImage(imageScoreBlack, 3, BOARD_SIZE + BORDER_SIZE*2 + 27, null); g.setFont(new Font("SansSerif", Font.BOLD, 12)); String whiteScore = String.valueOf(reversi.getWhiteScore()); String blackScore = String.valueOf(reversi.getBlackScore()); FontMetrics fm = g.getFontMetrics(); int width = Math.max(fm.stringWidth(whiteScore), fm.stringWidth(blackScore)); g.drawString(whiteScore, imageScoreBlack.getWidth(null) + 7 + width - fm.stringWidth(whiteScore), BOARD_SIZE + BORDER_SIZE*2 + 22); g.drawString(blackScore, imageScoreWhite.getWidth(null) + 7 + width - fm.stringWidth(blackScore), BOARD_SIZE + BORDER_SIZE*2 + 42); // Draw who's turn it is. if (!reversi.isGameFinished()) { if (reversi.getCurrentPlayer() == ReversiModel.BLACK) { g.drawImage(imageTurnBlack, 116, BOARD_SIZE + BORDER_SIZE*2 + 11, null); } else { g.drawImage(imageTurnWhite, 116, BOARD_SIZE + BORDER_SIZE*2 + 11, null); } } else { int me = otherPlayer==ReversiModel.BLACK?ReversiModel.WHITE:ReversiModel.BLACK; String whoWins = "Draw"; if (reversi.getBlackScore() > reversi.getWhiteScore()) { if (me == ReversiModel.BLACK) whoWins = "YOU WIN!"; else whoWins = "YOU LOST!"; } else if(reversi.getBlackScore() < reversi.getWhiteScore()) { if (me == ReversiModel.WHITE) whoWins = "YOU WIN!"; else whoWins = "YOU LOST!"; } g.drawString(whoWins, 130, BOARD_SIZE + BORDER_SIZE*2 + 20); } if (reversi.getCurrentPlayer() == otherPlayer) { g.drawImage(imageThem, 163, BOARD_SIZE + BORDER_SIZE*2 + 31, null); } else { g.drawImage(imageYou, 163, BOARD_SIZE + BORDER_SIZE*2 + 31, null); } // The resign button. g.drawImage(imageButtonResign, 281, BOARD_SIZE + BORDER_SIZE*2 + 17, null); } /** * A Reversi block (one of the squares of the grid). */ public class ReversiBlock extends JPanel { private static final long serialVersionUID = -8504469339731900770L; private ReversiPanel ui; private int index; public ReversiBlock(ReversiPanel ui, int index) { super(); this.ui = ui; this.index = index; setPreferredSize(new Dimension(BLOCK_SIZE,BLOCK_SIZE)); addMouseListener(new ReversiBlockMouseListener(this)); setOpaque(false); } /** * Returns a handle on the game UI. * * @return ReversiPanel of block. */ public ReversiPanel getReversiUI() { return ui; } /** * This block's index (0->63). * * @return Index of block */ public int getIndex() { return index; } protected void paintComponent(Graphics g) { super.paintComponent(g); // Turn on anti-aliasing: ((Graphics2D)g).setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); // Draw a disc in the block if the game says we should. int boardValue = reversi.getBoardValue(index); if (boardValue == org.jivesoftware.game.reversi.ReversiModel.BLACK) { drawDisc(g, Color.BLACK); } else if (reversi.getBoardValue(index) == org.jivesoftware.game.reversi.ReversiModel.WHITE) { drawDisc(g, Color.WHITE); } } /** * Draws the disc. * * @param g Graphics to draw * @param color Color */ private void drawDisc(Graphics g, Color color) { int position = BLOCK_SIZE - ((BLOCK_SIZE+DISC_SIZE)/2); g.setColor(color); g.fillOval(position, position, DISC_SIZE, DISC_SIZE); } } /** * A mouse listener for a Reversi block. */ public class ReversiBlockMouseListener extends MouseAdapter { private ReversiBlock block; public ReversiBlockMouseListener(ReversiBlock block) { this.block = block; } /** * Highlight the block if this block is a valid move. */ public void mouseEntered(MouseEvent e) { super.mouseEntered(e); if (reversi.getCurrentPlayer() != otherPlayer && reversi.isValidMove(block.getIndex())) { block.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); block.setBorder(BorderFactory.createLineBorder(Color.WHITE)); } } /** * Set the block color back to the default. */ public void mouseExited(MouseEvent e) { super.mouseExited(e); block.setCursor(Cursor.getDefaultCursor()); block.setBorder(null); } /** * If the click box is a valid move, register a move in this box. */ public void mouseClicked(MouseEvent e) { super.mouseClicked(e); // Make sure that it's our turn and that it's a valid move. if (reversi.getCurrentPlayer() != otherPlayer && reversi.isValidMove(block.getIndex())) { // Update the game model. reversi.makeMove(block.getIndex()); // Send the move to the other player. Message message = new Message(opponentJID); GameMove move = new GameMove(); move.setGameID(gameID); move.setPosition(block.getIndex()); message.addExtension(move); connection.sendPacket(message); // Repaint board. ReversiPanel.this.repaint(); // Repaint all blocks. // for (Iterator it = block.getReversiUI().getBlocks().iterator(); it.hasNext();) { // ReversiBlock component = (ReversiBlock)it.next(); // component.repaint(); // } } } } public List<ReversiBlock> getBlocks() { return blocks; } }