1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
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
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
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
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
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
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
446 if (cardComponent == null)
447 {
448 cardComponent = new JSetCardComponent(card);
449 cardComponent.addCardEventListener(_boardController);
450 _mapCardsToComponents.put(card, cardComponent);
451 }
452
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
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
499 _selectedCards.clear();
500
501
502
503
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 }