/*============================================================================*/
package gdi1sokoban.gui;
/*============================================================================*/
import java.awt.GridLayout;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.HashMap;
import java.util.Vector;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import gdi1sokoban.exceptions.InternalFailureException;
import gdi1sokoban.exceptions.InvalidOperationException;
import gdi1sokoban.exceptions.ParameterOutOfRangeException;
/*============================================================================*/
/**
* Class representing a basic Sokoban game field display that you can extend for
* your own solution by deriving from it.
*
* @author Steven Arzt, Oren Avni
* @version 1.0
*/
/* ======================================================================== */
private static final long serialVersionUID = -1037100806444236904L;
/* ======================================================================== */
// the list of entities
// the loaded images, accessed by name
// the parent window
private SokobanWindow parentWindow = null;
// The current layout width and height based on the level
private int layoutWidth = 0, layoutHeight = 0;
/* ======================================================================== */
/**
* Creates a new instance of the GamePanel class
*
* @param theParentWindow
* The parent window on which this panel is placed
*/
public GamePanel(final SokobanWindow theParentWindow) {
super();
// Set the reference
parentWindow = theParentWindow;
// Create the internal objects
}
/* ======================================================================== */
/**
* Resizes the game panel to match the current contents
*/
private void resizePanel ()
{
final int oldWidth = getWidth (), oldHeight = getHeight ();
int width = 0, height = 0;
for (int i = 0; i < entities.size (); i++)
{
final JButton btn = entities.
get (i
);
final int icoWidth = btn.getIcon ().getIconWidth ();
final int icoHeight = btn.getIcon ().getIconHeight ();
width =
Math.
max (width, icoWidth
);
height =
Math.
max (height, icoHeight
);
btn.setSize (icoWidth, icoHeight);
}
width = layoutWidth * width;
height = layoutHeight * height;
setSize (width, height);
if ((oldWidth != width) || (oldHeight != height))
panelResized ();
}
/* ======================================================================== */
/**
* Draws the game field once again and updates its graphical representation
* @throws InternalFailureException
* Thrown if an uncorrectable internal error occurs
*/
public void redraw() throws InternalFailureException {
// The following code contains a few tricks concerning Swing and
// threading. I do not require anyone to understand it - mainly
// workarounds for the mess Sun has done...
if (Thread.
currentThread ().
getName ().
contains ("AWT-EventQueue"))
{
clearFrame();
setGamePanelContents();
resizePanel ();
}
else
try
{
{
//@Override
public void run ()
{
clearFrame();
setGamePanelContents();
resizePanel ();
}
});
}
{
throw new InternalFailureException (ex);
}
}
/* ======================================================================== */
/**
* Method for setting the game panel's contents. Override this method to
* place your game entities like walls, crates etc. on the game field.
*/
protected abstract void setGamePanelContents();
/* ======================================================================== */
/**
* Clears the game frame by removing all entity buttons and recreating the
* corresponding internal data structures. This method can also be used for
* initialization purposes.
*/
private void clearFrame() {
for (int i = 0; i < entities.size(); i++) {
final JButton btn = entities.
get(i
);
btn.setVisible(false);
remove(btn);
synchronized (entities)
{
entities.remove(btn);
}
clearFrame();
return;
}
}
/* ======================================================================== */
/**
* Notifies the game panel that a new level has been loaded
*
* @param width
* The width of the level just loaded
* @param height
* The height if the level just loaded
* @throws ParameterOutOfRangeException
* Thrown if one of the parameters falls out of the range of
* acceptable values
* @throws InternalFailureException
* Thrown if an uncorrectable internal error occurs
*/
void notifyLevelLoaded(final int width, final int height)
throws ParameterOutOfRangeException, InternalFailureException {
// Check the parameters
if (width <= 0)
throw new ParameterOutOfRangeException("Game Panel width negative");
if (height <= 0)
throw new ParameterOutOfRangeException("Game Panel height negative");
// Initialize the layout
layoutWidth = width;
layoutHeight = height;
redraw();
}
/* ======================================================================== */
/**
* Returns whether there are already entities on this game panel
*
* @return True if there are already entities on this game panel, otherwise
* false
*/
protected boolean hasEntities() {
return entities.size() > 0;
}
/* ======================================================================== */
/**
* Checks whether a specific image has already been registered with the game
* panel
*
* @param identifier
* The unique image identifier
* @throws ParameterOutOfRangeException
* Thrown if one of the parameters falls out of the range of
* acceptable values
*/
protected boolean isImageRegistered
(final String identifier
)
throws ParameterOutOfRangeException {
// Check the parameter
if ((identifier == null) || identifier.equals(""))
throw new ParameterOutOfRangeException("Identifier invalid");
return images.containsKey(identifier);
}
/* ======================================================================== */
/**
* Registers a new image in this game panel. Please note that the identifier
* must be unique, so the operation will fail if an image with the specified
* identifier has already been registered.
*
* @param identifier
* The new image's unique identifier
* @param fileName
* The file name from which to load the image file
* @throws ParameterOutOfRangeException
* Thrown if one of the parameters falls out of the range of
* acceptable values
* @throws InvalidOperationException
* Thrown if this operation is not permitted due to the object's
* current state
*/
protected void registerImage
(final String identifier,
final String fileName
)
throws ParameterOutOfRangeException, InvalidOperationException {
// Check the parameters
if ((identifier == null) || identifier.equals(""))
throw new ParameterOutOfRangeException("Identifier invalid");
if ((fileName == null) || fileName.equals(""))
throw new ParameterOutOfRangeException("FileName invalid");
if (isImageRegistered(identifier))
throw new InvalidOperationException(
"An image with this identifier "
+ "has already been registered");
images.
put(identifier,
new ImageIcon(fileName
));
}
/* ======================================================================== */
/**
* Unregisters a previously registered image from this game panel. If the
* specified identifier does not exist, an exception is thrown.
* @param identifier
* The image's unique identifier
* @throws ParameterOutOfRangeException
* Thrown if one of the parameters falls out of the range of
* acceptable values
* @throws InvalidOperationException
* Thrown if this operation is not permitted due to the object's
* current state
*/
protected void unregisterImage
(final String identifier
)
throws ParameterOutOfRangeException, InvalidOperationException {
// Check the parameters
if ((identifier == null) || identifier.equals(""))
throw new ParameterOutOfRangeException("Identifier invalid");
if (!isImageRegistered(identifier))
throw new InvalidOperationException(
"An image with this identifier "
+ "has not been registered");
images.remove (identifier);
}
/* ======================================================================== */
/**
* Places a graphical entity on the game panel
*
* @param imageIdentifier
* The identifier of a previously registered image that will be
* used for rendering the entity
* @throws ParameterOutOfRangeException
* Thrown if one of the parameters falls out of the range of
* acceptable values
*/
protected void placeEntity
(final String imageIdentifier
)
throws ParameterOutOfRangeException {
// Check the parameters
if ((imageIdentifier == null) || imageIdentifier.equals(""))
throw new ParameterOutOfRangeException("ImageIdentifier invalid");
// Get the image icon
final ImageIcon img = images.
get(imageIdentifier
);
if (img == null)
+ imageIdentifier + "could not be found");
// Create the visual entity
synchronized (entities)
{
entities.add (btn);
}
btn.setIcon (img);
btn.addKeyListener (parentWindow);
btn.addMouseListener (this);
// add it
add (btn);
btn.requestFocus();
}
/* ======================================================================== */
/**
* This method is called whenever an entity on the game field is clicked
*
* @param positionX
* The x coordinate of the entity that was clicked
* @param positionY
* The y coordinate of the entity that was clicked
*/
protected abstract void entityClicked(int positionX, int positionY);
/* ======================================================================== */
/**
* This method is called whenever the game panel is resized
*/
protected abstract void panelResized ();
/* ======================================================================== */
// @Override
/**
* This method handles the "mouse clicked" mouse event by converting the
* event into a call to <em>entityClicked(int, int)</em>.
*
* @param evt the mouse event caused by clicking "somewhere" on the screen
* @see #entityClicked(int, int)
*/
if (!hasEntities())
return;
// retrieve first button
final JButton refBtn = entities.
get(0);
// iterate buttons until right one was found
for (int i = 0; i < entities.size(); i++) {
final JButton btn = entities.
get(i
);
if (evt.getSource() == btn) {
// determine x and y position
int posX = evt.getXOnScreen();
posX = posX - (int) this.getLocationOnScreen().getX();
int posY = evt.getYOnScreen();
posY = posY - (int) this.getLocationOnScreen().getY();
// pass message along
entityClicked(posX / refBtn.getWidth(), posY
/ refBtn.getHeight());
// done!
evt.consume();
break;
}
}
}
/* ======================================================================== */
// nothing to be done here
}
/* ======================================================================== */
public void mouseReleased
(final MouseEvent arg0
) {
// nothing to be done here
}
/* ======================================================================== */
// nothing to be done here
}
/* ======================================================================== */
// nothing to be done here
}
/* ======================================================================== */
/**
* Gets the parent window for this game field
*
* @return This game field's parent window
*/
public SokobanWindow getParentWindow() {
return parentWindow;
}
/* ======================================================================== */
}
/* ============================================================================ */