Subversion Repositories sokoban

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 daniel-mar 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
/* ============================================================================ */