Subversion Repositories sokoban

Rev

Blame | Last modification | View Log | RSS feed

  1. /*============================================================================*/
  2.  
  3. package gdi1sokoban.gui;
  4.  
  5. /*============================================================================*/
  6.  
  7. import java.awt.GridLayout;
  8. import java.awt.event.MouseEvent;
  9. import java.awt.event.MouseListener;
  10. import java.util.HashMap;
  11. import java.util.Vector;
  12.  
  13. import javax.swing.ImageIcon;
  14. import javax.swing.JButton;
  15. import javax.swing.JPanel;
  16. import javax.swing.SwingUtilities;
  17.  
  18. import gdi1sokoban.exceptions.InternalFailureException;
  19. import gdi1sokoban.exceptions.InvalidOperationException;
  20. import gdi1sokoban.exceptions.ParameterOutOfRangeException;
  21.  
  22. /*============================================================================*/
  23.  
  24. /**
  25.  * Class representing a basic Sokoban game field display that you can extend for
  26.  * your own solution by deriving from it.
  27.  *
  28.  * @author Steven Arzt, Oren Avni
  29.  * @version 1.0
  30.  */
  31. public abstract class GamePanel extends JPanel implements MouseListener {
  32.        
  33.         /* ======================================================================== */
  34.  
  35.         private static final long serialVersionUID = -1037100806444236904L;
  36.  
  37.         /* ======================================================================== */
  38.  
  39.         // the list of entities
  40.         private Vector<JButton> entities = null;
  41.        
  42.         // the loaded images, accessed by name
  43.         private HashMap<String, ImageIcon> images = null;
  44.        
  45.         // the parent window
  46.         private SokobanWindow parentWindow = null;
  47.        
  48.         // The current layout width and height based on the level
  49.         private int layoutWidth = 0, layoutHeight = 0;
  50.  
  51.         /* ======================================================================== */
  52.  
  53.         /**
  54.          * Creates a new instance of the GamePanel class
  55.          *
  56.          * @param theParentWindow
  57.          *            The parent window on which this panel is placed
  58.          */
  59.         public GamePanel(final SokobanWindow theParentWindow) {
  60.                 super();
  61.  
  62.                 // Set the reference
  63.                 parentWindow = theParentWindow;
  64.  
  65.                 // Create the internal objects
  66.                 entities = new Vector<JButton>();
  67.                 images = new HashMap<String, ImageIcon>();
  68.         }
  69.  
  70.         /* ======================================================================== */
  71.        
  72.         /**
  73.          * Resizes the game panel to match the current contents
  74.          */
  75.         private void resizePanel ()
  76.         {
  77.                
  78.                 final int oldWidth = getWidth (), oldHeight = getHeight ();
  79.                 int width = 0, height = 0;
  80.                 for (int i = 0; i < entities.size (); i++)
  81.                 {
  82.                         final JButton btn = entities.get (i);
  83.                         final int icoWidth = btn.getIcon ().getIconWidth ();
  84.                         final int icoHeight = btn.getIcon ().getIconHeight ();
  85.                        
  86.                         width = Math.max (width, icoWidth);
  87.                         height = Math.max (height, icoHeight);
  88.                        
  89.                         btn.setSize (icoWidth, icoHeight);
  90.                 }
  91.                
  92.                 width = layoutWidth * width;
  93.                 height = layoutHeight * height;
  94.                 setSize (width, height);
  95.                 if ((oldWidth != width) || (oldHeight != height))
  96.                         panelResized ();
  97.         }
  98.        
  99.         /* ======================================================================== */
  100.  
  101.         /**
  102.          * Draws the game field once again and updates its graphical representation
  103.          * @throws InternalFailureException
  104.          *                         Thrown if an uncorrectable internal error occurs
  105.          */
  106.         public void redraw() throws InternalFailureException {
  107.                 // The following code contains a few tricks concerning Swing and
  108.                 // threading. I do not require anyone to understand it - mainly
  109.                 // workarounds for the mess Sun has done...
  110.                
  111.                 if (Thread.currentThread ().getName ().contains ("AWT-EventQueue"))
  112.                 {
  113.                         clearFrame();
  114.                         setGamePanelContents();
  115.                         resizePanel ();
  116.                 }
  117.                 else
  118.                         try
  119.                         {
  120.                                 SwingUtilities.invokeAndWait (new Runnable ()
  121.                                 {
  122.                                         //@Override
  123.                                         public void run ()
  124.                                         {
  125.                                                 clearFrame();
  126.                                                 setGamePanelContents();
  127.                                                 resizePanel ();
  128.                                         }      
  129.                                 });
  130.                         }
  131.                         catch (final Exception ex)
  132.                         {
  133.                                 throw new InternalFailureException (ex);
  134.                         }
  135.  
  136.         }
  137.  
  138.         /* ======================================================================== */
  139.  
  140.         /**
  141.          * Method for setting the game panel's contents. Override this method to
  142.          * place your game entities like walls, crates etc. on the game field.
  143.          */
  144.         protected abstract void setGamePanelContents();
  145.  
  146.         /* ======================================================================== */
  147.        
  148.         /**
  149.          * Clears the game frame by removing all entity buttons and recreating the
  150.          * corresponding internal data structures. This method can also be used for
  151.          * initialization purposes.
  152.          */
  153.         private void clearFrame() {
  154.                 for (int i = 0; i < entities.size(); i++) {
  155.                         final JButton btn = entities.get(i);
  156.                         btn.setVisible(false);
  157.                         remove(btn);
  158.                         synchronized (entities)
  159.                         {
  160.                                 entities.remove(btn);
  161.                         }
  162.                         clearFrame();
  163.                         return;
  164.                 }                      
  165.         }
  166.  
  167.         /* ======================================================================== */
  168.  
  169.         /**
  170.          * Notifies the game panel that a new level has been loaded
  171.          *
  172.          * @param width
  173.          *            The width of the level just loaded
  174.          * @param height
  175.          *            The height if the level just loaded
  176.          * @throws ParameterOutOfRangeException
  177.          *             Thrown if one of the parameters falls out of the range of
  178.          *             acceptable values
  179.          * @throws InternalFailureException
  180.          *                         Thrown if an uncorrectable internal error occurs
  181.          */
  182.         void notifyLevelLoaded(final int width, final int height)
  183.                         throws ParameterOutOfRangeException, InternalFailureException {
  184.                 // Check the parameters
  185.                 if (width <= 0)
  186.                         throw new ParameterOutOfRangeException("Game Panel width negative");
  187.                 if (height <= 0)
  188.                         throw new ParameterOutOfRangeException("Game Panel height negative");
  189.  
  190.                 // Initialize the layout
  191.                 layoutWidth = width;
  192.                 layoutHeight = height;
  193.                 setLayout(new GridLayout(height, width));
  194.                 redraw();
  195.         }
  196.  
  197.         /* ======================================================================== */
  198.  
  199.         /**
  200.          * Returns whether there are already entities on this game panel
  201.          *
  202.          * @return True if there are already entities on this game panel, otherwise
  203.          *         false
  204.          */
  205.         protected boolean hasEntities() {
  206.                 return entities.size() > 0;
  207.         }
  208.  
  209.         /* ======================================================================== */
  210.  
  211.         /**
  212.          * Checks whether a specific image has already been registered with the game
  213.          * panel
  214.          *
  215.          * @param identifier
  216.          *            The unique image identifier
  217.          * @throws ParameterOutOfRangeException
  218.          *             Thrown if one of the parameters falls out of the range of
  219.          *             acceptable values
  220.          */
  221.         protected boolean isImageRegistered(final String identifier)
  222.                         throws ParameterOutOfRangeException {
  223.                 // Check the parameter
  224.                 if ((identifier == null) || identifier.equals(""))
  225.                         throw new ParameterOutOfRangeException("Identifier invalid");
  226.                 return images.containsKey(identifier);
  227.         }
  228.  
  229.         /* ======================================================================== */
  230.  
  231.         /**
  232.          * Registers a new image in this game panel. Please note that the identifier
  233.          * must be unique, so the operation will fail if an image with the specified
  234.          * identifier has already been registered.
  235.          *
  236.          * @param identifier
  237.          *            The new image's unique identifier
  238.          * @param fileName
  239.          *            The file name from which to load the image file
  240.          * @throws ParameterOutOfRangeException
  241.          *             Thrown if one of the parameters falls out of the range of
  242.          *             acceptable values
  243.          * @throws InvalidOperationException
  244.          *             Thrown if this operation is not permitted due to the object's
  245.          *             current state
  246.          */
  247.         protected void registerImage(final String identifier, final String fileName)
  248.                         throws ParameterOutOfRangeException, InvalidOperationException {
  249.                 // Check the parameters
  250.                 if ((identifier == null) || identifier.equals(""))
  251.                         throw new ParameterOutOfRangeException("Identifier invalid");
  252.                 if ((fileName == null) || fileName.equals(""))
  253.                         throw new ParameterOutOfRangeException("FileName invalid");
  254.  
  255.                 if (isImageRegistered(identifier))
  256.                         throw new InvalidOperationException(
  257.                                         "An image with this identifier "
  258.                                                         + "has already been registered");
  259.                 images.put(identifier, new ImageIcon(fileName));
  260.         }
  261.        
  262.         /* ======================================================================== */
  263.        
  264.         /**
  265.          * Unregisters a previously registered image from this game panel. If the
  266.          * specified identifier does not exist, an exception is thrown.
  267.          * @param identifier
  268.          *            The image's unique identifier
  269.          * @throws ParameterOutOfRangeException
  270.          *             Thrown if one of the parameters falls out of the range of
  271.          *             acceptable values
  272.          * @throws InvalidOperationException
  273.          *             Thrown if this operation is not permitted due to the object's
  274.          *             current state
  275.          */
  276.         protected void unregisterImage(final String identifier)
  277.                         throws ParameterOutOfRangeException, InvalidOperationException {
  278.                 // Check the parameters
  279.                 if ((identifier == null) || identifier.equals(""))
  280.                         throw new ParameterOutOfRangeException("Identifier invalid");
  281.  
  282.                 if (!isImageRegistered(identifier))
  283.                         throw new InvalidOperationException(
  284.                                         "An image with this identifier "
  285.                                                         + "has not been registered");
  286.                 images.remove (identifier);
  287.         }
  288.        
  289.         /* ======================================================================== */
  290.  
  291.         /**
  292.          * Places a graphical entity on the game panel
  293.          *
  294.          * @param imageIdentifier
  295.          *            The identifier of a previously registered image that will be
  296.          *            used for rendering the entity
  297.          * @throws ParameterOutOfRangeException
  298.          *             Thrown if one of the parameters falls out of the range of
  299.          *             acceptable values
  300.          */
  301.         protected void placeEntity(final String imageIdentifier)
  302.                         throws ParameterOutOfRangeException {
  303.                 // Check the parameters
  304.                 if ((imageIdentifier == null) || imageIdentifier.equals(""))
  305.                         throw new ParameterOutOfRangeException("ImageIdentifier invalid");
  306.  
  307.                 // Get the image icon
  308.                 final ImageIcon img = images.get(imageIdentifier);
  309.                 if (img == null)
  310.                         throw new RuntimeException("An image with the identifier "
  311.                                         + imageIdentifier + "could not be found");
  312.  
  313.                 // Create the visual entity
  314.                 final JButton btn = new JButton();
  315.                 synchronized (entities)
  316.                 {
  317.                         entities.add (btn);
  318.                 }
  319.                 btn.setIcon (img);
  320.                 btn.addKeyListener (parentWindow);
  321.                 btn.addMouseListener (this);
  322.  
  323.                 // add it
  324.                 add (btn);
  325.                 btn.requestFocus();
  326.         }
  327.  
  328.         /* ======================================================================== */
  329.  
  330.         /**
  331.          * This method is called whenever an entity on the game field is clicked
  332.          *
  333.          * @param positionX
  334.          *            The x coordinate of the entity that was clicked
  335.          * @param positionY
  336.          *            The y coordinate of the entity that was clicked
  337.          */
  338.         protected abstract void entityClicked(int positionX, int positionY);
  339.  
  340.         /* ======================================================================== */
  341.        
  342.         /**
  343.          * This method is called whenever the game panel is resized
  344.          */
  345.         protected abstract void panelResized ();
  346.        
  347.         /* ======================================================================== */
  348.  
  349.         // @Override
  350.         /**
  351.          * This method handles the "mouse clicked" mouse event by converting the
  352.          * event into a call to <em>entityClicked(int, int)</em>.
  353.          *
  354.          * @param evt the mouse event caused by clicking "somewhere" on the screen
  355.          * @see #entityClicked(int, int)
  356.          */
  357.         public void mouseClicked(final MouseEvent evt) {
  358.                 if (!hasEntities())
  359.                         return;
  360.                 // retrieve first button
  361.                 final JButton refBtn = entities.get(0);
  362.                
  363.                 // iterate buttons until right one was found
  364.                 for (int i = 0; i < entities.size(); i++) {
  365.                         final JButton btn = entities.get(i);
  366.                         if (evt.getSource() == btn) {
  367.                                 // determine x and y position
  368.                                 int posX = evt.getXOnScreen();
  369.                                 posX = posX - (int) this.getLocationOnScreen().getX();
  370.  
  371.                                 int posY = evt.getYOnScreen();
  372.                                 posY = posY - (int) this.getLocationOnScreen().getY();
  373.  
  374.                                 // pass message along
  375.                                 entityClicked(posX / refBtn.getWidth(), posY
  376.                                                 / refBtn.getHeight());
  377.                                
  378.                                 // done!
  379.                                 evt.consume();
  380.                                 break;
  381.                         }
  382.                 }
  383.         }
  384.  
  385.         /* ======================================================================== */
  386.  
  387.         public void mousePressed(final MouseEvent arg0) {
  388.                 // nothing to be done here
  389.         }
  390.  
  391.         /* ======================================================================== */
  392.  
  393.         public void mouseReleased(final MouseEvent arg0) {
  394.                 // nothing to be done here
  395.         }
  396.  
  397.         /* ======================================================================== */
  398.  
  399.         public void mouseEntered(final MouseEvent arg0) {
  400.                 // nothing to be done here
  401.         }
  402.  
  403.         /* ======================================================================== */
  404.  
  405.         public void mouseExited(final MouseEvent arg0) {
  406.                 // nothing to be done here
  407.         }
  408.  
  409.         /* ======================================================================== */
  410.  
  411.         /**
  412.          * Gets the parent window for this game field
  413.          *
  414.          * @return This game field's parent window
  415.          */
  416.         public SokobanWindow getParentWindow() {
  417.                 return parentWindow;
  418.         }
  419.  
  420.         /* ======================================================================== */
  421.  
  422. }
  423.  
  424. /* ============================================================================ */
  425.