View Javadoc

1   // START LICENSE
2   // JSet - a Java JSet card board game implementation
3   // Copyright (C) 2004 Jerome Lacoste
4   //
5   // This program is free software; you can redistribute it and/or modify
6   // it under the terms of the GNU General Public License as published by
7   // the Free Software Foundation; either version 2 of the License, or (at
8   // your option) any later version.
9   //
10  // This program is distributed in the hope that it will be useful, but
11  // WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // General Public License for more details.
14  //
15  // You should have received a copy of the GNU General Public License
16  // along with this program; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18  // END LICENSE
19  
20  package org.cb.jset.client.ui;
21  
22  import javax.swing.border.Border;
23  import javax.swing.JFrame;
24  import javax.swing.JLabel;
25  import javax.swing.JPanel;
26  import javax.swing.BorderFactory;
27  import javax.swing.JMenuItem;
28  import javax.swing.JMenuBar;
29  import javax.swing.JMenu;
30  import javax.swing.JComponent;
31  import javax.swing.JOptionPane;
32  import java.awt.event.KeyEvent;
33  import java.awt.event.ActionEvent;
34  import java.awt.event.ActionListener;
35  import java.awt.event.WindowAdapter;
36  import java.awt.event.WindowEvent;
37  import java.awt.HeadlessException;
38  import java.awt.BorderLayout;
39  import java.awt.Dimension;
40  import java.awt.FlowLayout;
41  import java.util.Iterator;
42  import java.util.Map;
43  import java.util.HashSet;
44  import java.util.HashMap;
45  import java.rmi.RMISecurityManager;
46  import java.rmi.NotBoundException;
47  import java.rmi.RemoteException;
48  import java.net.MalformedURLException;
49  import java.io.Serializable;
50  
51  import org.apache.log4j.Logger;
52  import org.cb.jset.client.model.JSetClientBoardModel;
53  import org.cb.jset.server.RemoteSetGame;
54  import org.cb.jset.client.model.SetGameClientCardListener;
55  import org.cb.jset.client.model.CardEvent;
56  import org.cb.jset.client.SetGameListener;
57  import org.cb.jset.client.GameEvent;
58  import org.cb.jset.client.LocalGame;
59  import org.cb.jset.client.NetworkGame;
60  import org.cb.jset.client.Client;
61  import org.cb.jset.CardProperties;
62  import org.cb.jset.CardSet;
63  import org.cb.jset.MatchingSetFinder;
64  import org.cb.jset.JSetCard;
65  import org.cb.jset.SetGameBoardListener;
66  import org.cb.jset.MatchingException;
67  import org.cb.jset.JSetBuild;
68  import org.cb.jset.BoardException;
69  import org.cb.cardboard.CardBoardEvent;
70  import org.cb.cardboard.CardBoardModelListener;
71  
72  /***
73   * The main UI for the game.
74   *
75   * @author jerome@coffeebreaks.org - last modified by $LastChangedBy: jerome $
76   * @version $Id: JSetClientUI.java 128 2004-04-15 04:12:04Z jerome $
77   */
78  public class JSetClientUI extends JFrame
79  {
80    final transient Logger _logger = Logger.getLogger("JSetClientUI");
81  
82    private JFrame _frame = this;
83  
84    private JSetBoardComponent _boardPanel;
85    private final JSetClientBoardModel _boardModel = new JSetClientBoardModel();
86    private final java.util.Set _selectedCards = new HashSet();
87    private final Map _mapCardsToComponents = new HashMap();
88  
89    private final BoardController _boardController = new BoardController();
90    private final GameController _gameController = new GameController();
91  //  private CardStack _cardStack;
92    private JLabel _infoLabel;
93  
94    private org.cb.jset.client.SetGame _game;
95  
96    /*** Wether we ask to confirm close.
97     * @todo make it configurable
98     */
99    private boolean _askCloseConfirm = true;
100 
101   /***
102    * Constructs an instance of JSetClientUI, given the specified title.
103    * @param title the title to put on the UI.
104    * @throws HeadlessException if GraphicsEnvironment.isHeadless() returns true.
105    */
106   public JSetClientUI(final String title)
107       throws HeadlessException
108   {
109     super(title);
110     init();
111   }
112 
113   // debug code.
114   /***
115   public void paint(final Graphics g)
116   {
117     super.paint(g);    //To change body of overridden methods use File | Settings | File Templates.
118     _logger.debug("painting frame: " +  getSize());
119   }
120 
121   public void paintComponents(final Graphics g)
122   {
123     super.paintComponents(g);    //To change body of overridden methods use File | Settings | File Templates
124     _logger.debug("painting component: " +  getSize());
125   }
126   **/
127 
128   /***
129    * Construct the UI.
130    */
131   private void init()
132   {
133     final JPanel panel = new JPanel(new BorderLayout());
134     _boardPanel = new JSetBoardComponent(_boardModel);
135     final Border etchedBorder = BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), "Card Board");
136     _boardPanel.setSize(new Dimension(800, 600));
137     _boardPanel.setBorder(etchedBorder);
138     _boardPanel.setLayout(new FlowLayout(FlowLayout.LEADING, 20, 20));
139 
140     _boardModel.addBoardDataListener(_boardController);
141 //    _boardPanel.addBoardSelectionListener(_boardController);
142 
143     _infoLabel = new JLabel();
144 
145     final JMenuBar menuBar;
146     JMenu menu;
147     JMenuItem menuItem;
148 
149     menuBar = new JMenuBar();
150     menu = new JMenu("Edit");
151     menu.setMnemonic(KeyEvent.VK_E);
152     menu.getAccessibleContext().setAccessibleDescription("Edit options");
153     menuBar.add(menu);
154 
155     menu = new JMenu("View");
156     menu.setMnemonic(KeyEvent.VK_V);
157     menu.getAccessibleContext().setAccessibleDescription("View");
158     menuBar.add(menu);
159 
160     menu = new JMenu("Game");
161     menu.setMnemonic(KeyEvent.VK_G);
162     menu.getAccessibleContext().setAccessibleDescription("Start games");
163     menuBar.add(menu);
164 
165     menuItem = new JMenuItem("Start Local Game", KeyEvent.VK_L);
166     menuItem.addActionListener(new ActionListener()
167     {
168       public void actionPerformed(final ActionEvent e)
169       {
170         treatStartLocalGameMenuItemClicked(e);
171       }
172     });
173     menu.add(menuItem);
174     menuItem = new JMenuItem("Start Networked Game", KeyEvent.VK_N);
175     menuItem.addActionListener(new ActionListener()
176     {
177       public void actionPerformed(final ActionEvent e)
178       {
179         treatStartNetworkedGameMenuItemClicked(e);
180       }
181     });
182     menu.add(menuItem);
183 
184     menu = new JMenu("Help");
185     menu.setMnemonic(KeyEvent.VK_H);
186     menu.getAccessibleContext().setAccessibleDescription("Help");
187     menuItem = new JMenuItem("Help Topics", KeyEvent.VK_T);
188     menuItem.addActionListener(new ActionListener()
189     {
190       public void actionPerformed(final ActionEvent e)
191       {
192         treatHelpTopicsMenuItemClicked(e);
193       }
194     });
195 
196     menu.add(menuItem);
197     menu.addSeparator();
198     menuItem = new JMenuItem("About", KeyEvent.VK_A);
199     menuItem.addActionListener(new ActionListener()
200     {
201       public void actionPerformed(final ActionEvent e)
202       {
203         treatAboutMenuItemClicked(e);
204       }
205     });
206     menu.add(menuItem);
207 
208     menuBar.add(menu);
209 
210     _frame.setJMenuBar(menuBar);
211 
212     panel.add(_boardPanel, BorderLayout.CENTER);
213     panel.add(_infoLabel, BorderLayout.SOUTH);
214     _frame.getContentPane().add(panel);
215     /// _frame.setContentPane(_boardPanel);
216     _frame.pack();
217     _frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
218     _frame.addWindowListener(new WindowAdapter()
219     {
220       /***
221        * Allow the user to confirm wether he wants to close or not.
222        * @param e
223        */
224       public void windowClosing(WindowEvent e)
225       {
226         _logger.debug("windowClosing()");
227         boolean shouldClose = true;
228         if (_askCloseConfirm)
229         {
230           String confirmQuitMsg = "";
231           if (_game != null)
232           {
233             confirmQuitMsg += "A game is being played. \n";
234           }
235           confirmQuitMsg += "Are you sure you want to quit?";
236           int userChoice =
237               JOptionPane.showConfirmDialog(JSetClientUI.this, confirmQuitMsg,
238                                             "Confirm close", JOptionPane.YES_NO_OPTION);
239           shouldClose = (userChoice == JOptionPane.OK_OPTION);
240         }
241         if (shouldClose)
242         {
243           onWindowClose();
244         }
245       }
246     });
247     _frame.setSize(800, 600);
248     _frame.setBounds(50, 50, 800, 600);
249 
250     _frame.show();
251   }
252 
253   private void onWindowClose()
254   {
255     if (_game != null)
256     {
257       _logger.debug("closing game...");
258       _game.stop();
259     }
260     _logger.debug("Closing.");
261     setVisible(false);
262     dispose();
263     System.exit(0);
264   }
265 
266   private void setGame(final org.cb.jset.client.SetGame game)
267   {
268     if (_game == null)
269     {
270       _game = game;
271       _game.addBoardListener(_gameController);
272       _game.addGameListener(_gameController);
273       _game.start();
274     }
275   }
276 
277   private void addCards(final CardProperties[] cardProperties)
278   {
279     _boardModel.addCards(cardProperties);
280     updateInfoLabel();
281     this.validate();
282   }
283 
284   void updateInfoLabel()
285   {
286     final MatchingSetFinder lFinder = new MatchingSetFinder();
287     final CardSet[] lFoundSets = lFinder.findSets(_boardModel.getElements());
288     for (int i = 0; i < lFoundSets.length; i++)
289     {
290       final CardSet lFoundSet = lFoundSets[i];
291       _logger.debug("foundSet[" + i + "]" + lFoundSet);
292     }
293     _infoLabel.setText("There are " + lFoundSets.length + " set(s) currently on the board");
294   }
295 
296   /***
297    * An object to display empty card space on the board.
298    */
299   private static class EmptyCard extends JPanel
300   {
301     public EmptyCard()
302     {
303       init();
304     }
305 
306     void init()
307     {
308       // FIXME coded shared with JSetCardComponent. parametrize.
309       final int width = 80;
310       final int height = (int) (1.5 * width);
311       setSize(width, height);
312       setPreferredSize(getSize());
313     }
314   }
315 
316 
317   /***
318    * Control events coming from the board.
319    */
320   class BoardController implements CardBoardModelListener, SetGameClientCardListener, Serializable
321   {
322     public void intervalAdded(final CardBoardEvent e)
323     {
324       _logger.debug(e);
325       final CardBoardEvent.IndexInterval interval = (CardBoardEvent.IndexInterval) e.getBoardIndexes();
326       for (int i = interval.getIndex0(); i <= interval.getIndex1(); i++)
327       {
328         final JComponent cardComponent = getCardComponent(i);
329         _boardPanel.add(cardComponent, i);
330       }
331       _boardPanel.validate();
332     }
333 
334     public void intervalRemoved(final CardBoardEvent e)
335     {
336       _logger.debug(e);
337       final CardBoardEvent.IndexInterval interval = (CardBoardEvent.IndexInterval) e.getBoardIndexes();
338       for (int i = interval.getIndex0(); i <= interval.getIndex1(); i++)
339       {
340         final JPanel jPanel = new EmptyCard();
341         replaceCardComponent(i, jPanel);
342       }
343       _boardPanel.validate();
344     }
345 
346     public void contentsChanged(final CardBoardEvent e)
347     {
348       _logger.debug(e);
349       final CardBoardEvent.IndexList indexList = (CardBoardEvent.IndexList) e.getBoardIndexes();
350       for (int i = 0; i < indexList.getIndexesInBoard().size(); i++)
351       {
352         final Integer index = (Integer) indexList.getIndexesInBoard().get(i);
353         final JSetCard card = (JSetCard) _boardModel.getElementAt(index.intValue());
354         final JComponent newCard;
355         if (card == null)
356         {
357           newCard = new EmptyCard();
358         }
359         else
360         {
361           newCard = getCardComponent(index.intValue());
362         }
363         replaceCardComponent(index.intValue(), newCard);
364       }
365       _boardPanel.validate();
366     }
367 
368     public void notifyCardEvent(final CardEvent e)
369     {
370       _logger.debug(e);
371       final int index = _boardModel.getElements().indexOf(e.getSource());
372       final JSetCardComponent cardComponent = getCardComponent(index);
373       cardComponent.updateBorder();
374       if (cardComponent.isSelected())
375       {
376         _selectedCards.add(cardComponent.getCard());
377         _logger.debug("selecting " + cardComponent.getCard() + " still " + _selectedCards.size());
378       }
379       else
380       {
381         _selectedCards.remove(cardComponent.getCard());
382         _logger.debug("deselecting " + cardComponent.getCard() + " still " + _selectedCards.size());
383       }
384 
385       if (_selectedCards.size() == 3)
386       {
387         final CardProperties[] cardProperties = new CardProperties[3];
388         int i = 0;
389         for (Iterator iterator = _selectedCards.iterator(); iterator.hasNext();)
390         {
391           final org.cb.jset.JSetCard card = (JSetCard) iterator.next();
392           cardProperties[i++] = card.getProperties();
393         }
394         tryToMatch(new CardSet(cardProperties));
395       }
396       else if (_selectedCards.size() > 3)
397       {
398         throw new IllegalStateException("More than 3 cards selected");
399       }
400     }
401   }
402 
403   /***
404    * Control events coming from the game instance.
405    *
406    * FIXME we probably need to add multithreading handling on the model.
407    */
408   class GameController implements SetGameBoardListener, SetGameListener, Serializable
409   {
410     /***
411      * @inheritDoc
412      */
413     public void cardsAdded(final CardProperties[] cards)
414     {
415       addCards(cards);
416     }
417 
418     /***
419      * @inheritDoc
420      */
421     public void setRemoved(final CardSet set)
422     {
423       treatSuccessfulMatching(set);
424     }
425 
426     /***
427      * @inheritDoc
428      */
429     public void gameChange(final GameEvent event)
430     {
431       // FIXME
432     }
433   }
434 
435   private void replaceCardComponent(final int i, final JComponent jPanel)
436   {
437     _boardPanel.remove(i);
438     _boardPanel.add(jPanel, i);
439   }
440 
441   private JSetCardComponent getCardComponent(final int i)
442   {
443     final org.cb.jset.JSetCard card = (JSetCard) _boardModel.getElementAt(i);
444     JSetCardComponent cardComponent = (JSetCardComponent) _mapCardsToComponents.get(card);
445     // FIXME was that really necessary ?
446     if (cardComponent == null)
447     {
448       cardComponent = new JSetCardComponent(card);
449       cardComponent.addCardEventListener(_boardController);
450       _mapCardsToComponents.put(card, cardComponent);
451     }
452     // FIXME empty cache
453     return cardComponent;
454   }
455 
456   private void tryToMatch(final CardSet set)
457   {
458     boolean isMatchingSet;
459     try
460     {
461       _game.removeSet(set);
462       isMatchingSet = true;
463     }
464     catch (MatchingException e)
465     {
466       isMatchingSet = false;
467     }
468 
469     if (!isMatchingSet)
470     {
471       treatFailedMatching(set, set.getCards());
472     }
473   }
474 
475   private void treatFailedMatching(final CardSet set, final CardProperties[] cardsToHandle)
476   {
477     _logger.debug("not matching set!!" + set);
478     for (int j = 0; j < cardsToHandle.length; j++)
479     {
480       final JSetCardComponent jSetCardComponent = (JSetCardComponent) _mapCardsToComponents.get(new JSetCard(cardsToHandle[j]));
481       jSetCardComponent.setSelection(false);
482     }
483   }
484 
485   private void treatSuccessfulMatching(final CardSet set)
486   {
487     _logger.debug("matching set!! " + set);
488     // note: we don't replace, but remove then add new ones.
489     try
490     {
491       _boardModel.removeSet(set);
492     }
493     catch (BoardException e)
494     {
495       throw new IllegalStateException("We shouldn't fail here... " + e.getMessage());
496     }
497     updateInfoLabel();
498     // FIXME this shouldn't be there, but is there a clean way to clean cards we've lost track of?
499     _selectedCards.clear();
500 //    if (! _cardStack.empty())
501 //    {
502 //      CardProperties[] lCardProperties = _cardStack.pop(3);
503 //      _boardModel.addCards(lCardProperties);
504 //    }
505   }
506 
507   private void treatAboutMenuItemClicked(final ActionEvent e)
508   {
509     final String aboutMsg = "Implementation of the JSet Card Board game.\n"
510      + "Copyright (C) Jerome Lacoste (jerome@coffeebreaks.org) 2004.\n"
511      + "V. " + JSetBuild.VERSION + " built " + JSetBuild.BUILD_TIME;
512     JOptionPane.showMessageDialog(JSetClientUI.this, aboutMsg);
513   }
514   private void treatHelpTopicsMenuItemClicked(final ActionEvent e)
515   {
516 
517   }
518   private void treatStartLocalGameMenuItemClicked(final ActionEvent e)
519   {
520     setGame(new LocalGame());
521   }
522 
523   private void treatStartNetworkedGameMenuItemClicked(final ActionEvent e)
524   {
525     if (System.getSecurityManager() == null)
526     {
527       System.setSecurityManager(new RMISecurityManager());
528     }
529     try
530     {
531       final String host = "localhost";
532       final String gameName = "TestGame";
533       final RemoteSetGame game = Client.getGame(host, gameName);
534       final NetworkGame networkGame = new NetworkGame("UITestUser");
535       networkGame.setRemoteGame(game);
536       setGame(networkGame);
537     }
538     catch (Exception e1)
539     {
540       new ErrorHandler().handle(e1);
541     }
542   }
543 
544   class ErrorHandler implements Serializable
545   {
546     void handle(final Exception e)
547     {
548       final String msg;
549       if (e instanceof RemoteException)
550       {
551         msg = "Error while trying to connect :" + e.getMessage();
552       }
553       else if (e instanceof NotBoundException)
554       {
555         msg = "Error while trying to connect :" + e.getMessage();
556       }
557       else if (e instanceof MalformedURLException)
558       {
559         msg = "Error while trying to connect :" + e.getMessage();
560       }
561       else
562       {
563         msg = "Unkown error :" + e.getMessage();
564       }
565       JOptionPane.showMessageDialog(JSetClientUI.this, msg);
566     }
567   }
568 
569   public static void main(final String[] args)
570   {
571     org.apache.log4j.PropertyConfigurator.configure("src/conf/log4j.properties");
572     final JSetClientUI lDrawing = new JSetClientUI("JSet - " + JSetBuild.VERSION);
573   }
574 }