Game-of-Life-WSU-CEG4180  v0.1
RefactoringtheGameofLife
StandaloneGameOfLife.java
Go to the documentation of this file.
1 /* This page is part of the Game of Life source code */
2 
3 /**
4  * Game of Life v1.4 Standalone version
5  * The standalone version extends the applet version.
6  * Copyright 1996-2004 Edwin Martin <edwin@bitstorm.nl>
7  *
8  * @author Edwin Martin
9  *
10  */
11 
12 package org.bitstorm.gameoflife;
13 
14 import java.awt.AWTEvent;
15 import java.awt.Color;
16 import java.awt.Dimension;
17 import java.awt.Event;
18 import java.awt.Frame;
19 import java.awt.GridBagConstraints;
20 import java.awt.GridBagLayout;
21 import java.awt.Image;
22 import java.awt.Menu;
23 import java.awt.MenuBar;
24 import java.awt.MenuItem;
25 import java.awt.Point;
26 import java.awt.Toolkit;
27 import java.awt.datatransfer.DataFlavor;
28 import java.awt.datatransfer.Transferable;
29 import java.awt.dnd.DnDConstants;
30 import java.awt.dnd.DropTarget;
31 import java.awt.dnd.DropTargetDragEvent;
32 import java.awt.dnd.DropTargetDropEvent;
33 import java.awt.dnd.DropTargetEvent;
34 import java.awt.dnd.DropTargetListener;
35 import java.awt.event.ActionEvent;
36 import java.awt.event.ActionListener;
37 import java.io.File;
38 import java.io.FileNotFoundException;
39 import java.io.IOException;
40 import java.net.URL;
41 import java.util.Enumeration;
42 import java.util.Properties;
43 import java.util.Vector;
44 
46 import org.bitstorm.util.AlertBox;
47 import org.bitstorm.util.EasyFile;
50 
51 /**
52  * Turns GameOfLife applet into application.
53  * It adds a menu, a window, drag-n-drop etc.
54  * It can be run stand alone.
55  *
56  * @author Edwin Martin
57  */
58 public class StandaloneGameOfLife extends GameOfLife {
59  private Frame appletFrame;
60  private String[] args;
62  /**
63  * main() for standalone version.
64  * @param args Not used.
65  */
66  public static void main(String args[]) {
67  StandaloneGameOfLife gameOfLife = new StandaloneGameOfLife();
68  gameOfLife.args = args;
69  new AppletFrame( "Game of Life", gameOfLife );
70  }
71 
72  /**
73  * Initialize UI.
74  * @param parent Parent frame.
75  * @see java.applet.Applet#init()
76  */
77  public void init( Frame parent ) {
78  appletFrame = parent;
79  getParams();
80 
81  // set background colour
82  setBackground(new Color(0x999999));
83 
84  // TODO: casten naar interface
85  // create StandAloneGameOfLifeGrid
87  gridIO = new GameOfLifeGridIO( gameOfLifeGrid );
88 
89  // create GameOfLifeCanvas
91 
92  try {
93  // Make GameOfLifeCanvas a drop target
94  DropTarget dt = new DropTarget( gameOfLifeCanvas, DnDConstants.ACTION_COPY_OR_MOVE, new MyDropListener() );
95  } catch (NoClassDefFoundError e) {
96  // Ignore. Older Java version don't support dnd
97  }
98 
99  // create GameOfLifeControls
102 
103  // put it all together
104  GridBagLayout gridbag = new GridBagLayout();
105  GridBagConstraints canvasContraints = new GridBagConstraints();
106  setLayout(gridbag);
107  canvasContraints.fill = GridBagConstraints.BOTH;
108  canvasContraints.weightx = 1;
109  canvasContraints.weighty = 1;
110  canvasContraints.gridx = GridBagConstraints.REMAINDER;
111  canvasContraints.gridy = 0;
112  canvasContraints.anchor = GridBagConstraints.CENTER;
113  gridbag.setConstraints(gameOfLifeCanvas, canvasContraints);
114  add(gameOfLifeCanvas);
115  GridBagConstraints controlsContraints = new GridBagConstraints();
116  canvasContraints.gridx = GridBagConstraints.REMAINDER;
117  canvasContraints.gridy = 1;
118  controlsContraints.gridx = GridBagConstraints.REMAINDER;
119  gridbag.setConstraints(controls, controlsContraints);
120  add(controls);
121  setVisible(true);
122  validate();
123  }
124 
125  /**
126  * Set the shape.
127  *
128  * This is not done in init(), because the window resize in GameOfLifeGridIO.setShape(Shape)
129  * needs a fully opened window to do new size calculations.
130  */
131  public void readShape() {
132  if ( args.length > 0 ) {
133  gridIO.openShape(args[0]);
134  reset();
135  } else {
136  try {
137  setShape( ShapeCollection.getShapeByName( "Glider" ) );
138  } catch (ShapeException e) {
139  // Ignore. It's not going to happen here.
140  }
141  }
142  }
143 
144  /**
145  * Override method, called by applet.
146  * @see java.applet.Applet#getParameter(java.lang.String)
147  */
148  public String getParameter( String parm ) {
149  return System.getProperty( parm );
150  }
151 
152  /**
153  * Shows an alert
154  * @param s text to show
155  */
156  public void alert( String s ) {
157  new AlertBox( appletFrame, "Alert", s );
158  }
159 
160  /**
161  * Do not use showStatus() of the applet.
162  * @see java.applet.Applet#showStatus(java.lang.String)
163  */
164  public void showStatus( String s ) {
165  // do nothing
166  }
167 
168  /**
169  * get GameOfLifeGridIO
170  * @return GameOfLifeGridIO object
171  */
173  return gridIO;
174  }
175 
176  /**
177  * Handles drag and drops to the canvas.
178  *
179  * This class does handle the dropping of files and URL's to the canvas.
180  * The code is based on the dnd-code from the book Professional Java Programming by Brett Spell.
181  *
182  * @author Edwin Martin
183  *
184  */
185  class MyDropListener implements DropTargetListener {
186  private final DataFlavor urlFlavor = new DataFlavor("application/x-java-url; class=java.net.URL", "Game of Life URL");
187 
188  /**
189  * The canvas only supports Files and URL's
190  * @see java.awt.dnd.DropTargetListener#dragEnter(java.awt.dnd.DropTargetDragEvent)
191  */
192  public void dragEnter(DropTargetDragEvent event) {
193  if ( event.isDataFlavorSupported( DataFlavor.javaFileListFlavor ) || event.isDataFlavorSupported( urlFlavor ) ) {
194  return;
195  }
196  event.rejectDrag();
197  }
198 
199  /**
200  * @see java.awt.dnd.DropTargetListener#dragExit(java.awt.dnd.DropTargetEvent)
201  */
202  public void dragExit(DropTargetEvent event) {
203  }
204 
205  /**
206  * @see java.awt.dnd.DropTargetListener#dragOver(java.awt.dnd.DropTargetDragEvent)
207  */
208  public void dragOver(DropTargetDragEvent event) {
209  }
210 
211  /**
212  * @see java.awt.dnd.DropTargetListener#dropActionChanged(java.awt.dnd.DropTargetDragEvent)
213  */
214  public void dropActionChanged(DropTargetDragEvent event) {
215  }
216 
217  /**
218  * The file or URL has been dropped.
219  * @see java.awt.dnd.DropTargetListener#drop(java.awt.dnd.DropTargetDropEvent)
220  */
221  public void drop(DropTargetDropEvent event) {
222  // important to first try urlFlavor
223  if ( event.isDataFlavorSupported( urlFlavor ) ) {
224  try {
225  event.acceptDrop(DnDConstants.ACTION_COPY);
226  Transferable trans = event.getTransferable();
227  URL url = (URL)( trans.getTransferData( urlFlavor ) );
228  String urlStr = url.toString();
229  gridIO.openShape( url );
230  reset();
231  event.dropComplete(true);
232  } catch (Exception e) {
233  event.dropComplete(false);
234  }
235  } else if ( event.isDataFlavorSupported( DataFlavor.javaFileListFlavor ) ) {
236  try {
237  event.acceptDrop(DnDConstants.ACTION_COPY);
238  Transferable trans = event.getTransferable();
239  java.util.List list = (java.util.List)( trans.getTransferData( DataFlavor.javaFileListFlavor ) );
240  File droppedFile = (File) list.get(0); // More than one file -> get only first file
241  gridIO.openShape( droppedFile.getPath() );
242  reset();
243  event.dropComplete(true);
244  } catch (Exception e) {
245  event.dropComplete(false);
246  }
247  }
248  }
249  }
250 
251  /**
252  * File open and save operations for GameOfLifeGrid.
253  */
255  public final String FILE_EXTENSION = ".cells";
257  private String filename;
258 
259  /**
260  * Constructor.
261  * @param grid grid to read/write files from/to
262  */
264  this.grid = grid;
265  }
266 
267  /**
268  * Load shape from disk
269  */
270  public void openShape() {
271  openShape( (String)null );
272  }
273 
274  /**
275  * Load shape from disk
276  * @param filename filename to load shape from, or null when no filename given.
277  */
278  public void openShape( String filename ) {
279  int col = 0;
280  int row = 0;
281  boolean cell;
282  // Cope with different line endings ("\r\n", "\r", "\n")
283  boolean nextLine = false;
284  EasyFile file;
285  try {
286  if ( filename != null ) {
287  file = new EasyFile( filename );
288  } else {
289  file = new EasyFile( appletFrame, "Open Game of Life file" );
290  }
291  openShape( file );
292  } catch (FileNotFoundException e) {
293  new AlertBox( appletFrame, "File not found", "Couldn't open this file.\n"+e.getMessage());
294  } catch (IOException e) {
295  new AlertBox( appletFrame, "File read error", "Couldn't read this file.\n"+e.getMessage());
296  }
297  }
298 
299  /**
300  * Open shape from URL.
301  * @param url URL pointing to GameOfLife-file
302  */
303  public void openShape( URL url ) {
304  int col = 0;
305  int row = 0;
306  boolean cell;
307  // Cope with different line endings ("\r\n", "\r", "\n")
308  boolean nextLine = false;
309  EasyFile file;
310  String text;
311  try {
312  if ( url != null ) {
313  file = new EasyFile( url );
314  openShape( file );
315  }
316  } catch (FileNotFoundException e) {
317  new AlertBox( appletFrame, "URL not found", "Couldn't open this URL.\n"+e.getMessage());
318  } catch (IOException e) {
319  new AlertBox( appletFrame, "URL read error", "Couldn't read this URL.\n"+e.getMessage());
320  }
321  }
322 
323  /**
324  * Use EasyFile object to read GameOfLife-file from.
325  * @param file EasyFile-object
326  * @throws IOException
327  * @see org.bitstorm.util.EasyFile
328  */
329  public void openShape( EasyFile file ) throws IOException {
330  Shape shape = makeShape( file.getFileName(), file.readText() );
331  setShape( shape );
332  }
333 
334  /**
335  * Set a shape and optionally resizes window.
336  * @param shape Shape to set
337  */
338  public void setShape( Shape shape ) {
339  int width, height;
340  Dimension shapeDim = shape.getDimension();
341  Dimension gridDim = grid.getDimension();
342  if ( shapeDim.width > gridDim.width || shapeDim.height > gridDim.height ) {
343  // Window has to be made larger
344  Toolkit toolkit = getToolkit();
345  Dimension screenDim = toolkit.getScreenSize();
346  Dimension frameDim = appletFrame.getSize();
347  int cellSize = getCellSize();
348  // Calculate new window size
349  width = frameDim.width + cellSize*(shapeDim.width - gridDim.width);
350  height = frameDim.height + cellSize*(shapeDim.height - gridDim.height);
351  // Does it fit on the screen?
352  if ( width > screenDim.width || height > screenDim.height ) {
353  // With current cellSize, it doesn't fit on the screen
354  // GameOfLifeControls.SIZE_SMALL corresponds with GameOfLifeControls.SMALL
355  int newCellSize = GameOfLifeControls.SIZE_SMALL;
356  width = frameDim.width + newCellSize*shapeDim.width - cellSize*gridDim.width;
357  height = frameDim.height + newCellSize*shapeDim.height - cellSize*gridDim.height;
358  // a little kludge to prevent de window from resizing twice
359  // setNewCellSize only has effect at the next resize
360  gameOfLifeCanvas.setAfterWindowResize( shape, newCellSize );
361  // The UI has to be adjusted, too
363  } else {
364  // Now resize the window (and optionally set the new cellSize)
365  gameOfLifeCanvas.setAfterWindowResize( shape, cellSize );
366  }
367  if ( width < 400 )
368  width = 400;
369  appletFrame.setSize( width, height );
370  return;
371  }
372  try {
373  gameOfLifeCanvas.setShape( shape );
374  } catch (ShapeException e) {
375  // ignore
376  }
377  }
378 
379  /**
380  * "Draw" the shape on the grid. (Okay, it's not really drawing).
381  * The lines of text represent the cells of the shape.
382  *
383  * @param name name of shape
384  * @param text lines of text
385  */
386  public Shape makeShape( String name, String text ) {
387  int col = 0;
388  int row = 0;
389  boolean cell;
390  // Cope with different line endings ("\r\n", "\r", "\n")
391  int[][] cellArray;
392  Vector cells = new Vector();
393 
394  if ( text.length() == 0 )
395  return null;
396 
397  grid.clear();
398 
399  Enumeration enm = new LineEnumerator( text );
400  while ( enm.hasMoreElements() ) {
401  String line = (String) enm.nextElement();
402  if ( line.startsWith("#") || line.startsWith("!") )
403  continue;
404 
405  char[] ca = line.toCharArray();
406  for ( col=0; col < ca.length; col++ ) {
407  switch( ca[col] ) {
408  case '*':
409  case 'O':
410  case 'o':
411  case 'X':
412  case 'x':
413  case '1':
414  cell = true;
415  break;
416  default:
417  cell = false;
418  break;
419  }
420  if ( cell )
421  cells.addElement(new int[] {col, row});
422  }
423  row++;
424  }
425 
426  cellArray = new int[cells.size()][];
427  for ( int i=0; i<cells.size(); i++ )
428  cellArray[i] = (int[]) cells.get(i);
429  return new Shape( name, cellArray );
430  }
431 
432  /**
433  * Write shape to disk.
434  */
435  public void saveShape() {
436  int colEnd = 0;
437  int rowEnd = 0;
438  Dimension dim = grid.getDimension();
439  int colStart = dim.width;
440  int rowStart = dim.height;
441 
442  String lineSeparator = System.getProperty( "line.separator" );
443  StringBuffer text = new StringBuffer("!Generator: Game of Life (http://www.bitstorm.org/gameoflife/)"+lineSeparator+"!Variation: 23/3"+lineSeparator+"!"+lineSeparator);
444 
445  for ( int row = 0; row < dim.height; row++ ) {
446  for ( int col = 0; col < dim.width; col++ ) {
447  if ( grid.getCell( col, row ) ) {
448  if ( row < rowStart )
449  rowStart = row;
450  if ( col < colStart )
451  colStart = col;
452  if ( row > rowEnd )
453  rowEnd = row;
454  if ( col > colEnd )
455  colEnd = col;
456  }
457  }
458  }
459 
460  for ( int row = rowStart; row <= rowEnd; row++ ) {
461  for ( int col = colStart; col <= colEnd; col++ ) {
462  text.append( grid.getCell( col, row ) ? 'O' : '-' );
463  }
464  text.append( lineSeparator );
465  }
466  EasyFile file;
467  try {
468  file = new EasyFile( appletFrame, "Save Game of Life file" );
469  file.setFileName( filename );
470  file.setFileExtension( FILE_EXTENSION );
471  file.writeText( text.toString() );
472  } catch (FileNotFoundException e) {
473  new AlertBox( appletFrame, "File error", "Couldn't open this file.\n"+e.getMessage());
474  } catch (IOException e) {
475  new AlertBox( appletFrame, "File error", "Couldn't write to this file.\n"+e.getMessage());
476  }
477  }
478  }
479 }
480 
481 /**
482  * The window with the applet. Extra is the menu bar.
483  *
484  * @author Edwin Martin
485  */
486 class AppletFrame extends Frame {
487  private final GameOfLife applet;
488  /**
489  * Constructor.
490  * @param title title of window
491  * @param applet applet to show
492  */
493  public AppletFrame(String title, StandaloneGameOfLife applet) {
494  super( title );
495  this.applet = applet;
496 
497  URL iconURL = this.getClass().getResource("icon.gif");
498  Image icon = Toolkit.getDefaultToolkit().getImage( iconURL );
499  this.setIconImage( icon );
500 
501  enableEvents(Event.WINDOW_DESTROY);
502 
503  MenuBar menubar = new MenuBar();
504  Menu fileMenu = new Menu("File", true);
505  MenuItem readMenuItem = new MenuItem( "Open...");
506  readMenuItem.addActionListener(
507  new ActionListener() {
508  public synchronized void actionPerformed(ActionEvent e) {
511  }
512  }
513 
514  );
515  MenuItem writeMenuItem = new MenuItem( "Save...");
516  writeMenuItem.addActionListener(
517  new ActionListener() {
518  public synchronized void actionPerformed(ActionEvent e) {
520  }
521  }
522 
523  );
524  MenuItem quitMenuItem = new MenuItem( "Exit");
525  quitMenuItem.addActionListener(
526  new ActionListener() {
527  public synchronized void actionPerformed(ActionEvent e) {
528  System.exit(0);
529  }
530  }
531 
532  );
533  Menu helpMenu = new Menu("Help", true);
534  MenuItem manualMenuItem = new MenuItem( "Manual");
535  manualMenuItem.addActionListener(
536  new ActionListener() {
537  public synchronized void actionPerformed(ActionEvent e) {
539  }
540  }
541  );
542  MenuItem licenseMenuItem = new MenuItem( "License");
543  licenseMenuItem.addActionListener(
544  new ActionListener() {
545  public synchronized void actionPerformed(ActionEvent e) {
547  }
548  }
549  );
550  MenuItem aboutMenuItem = new MenuItem( "About");
551  aboutMenuItem.addActionListener(
552  new ActionListener() {
553  public synchronized void actionPerformed(ActionEvent e) {
554  showAboutDialog();
555  }
556  }
557  );
558  fileMenu.add(readMenuItem);
559  fileMenu.add(writeMenuItem);
560  fileMenu.addSeparator();
561  fileMenu.add(quitMenuItem);
562  helpMenu.add(manualMenuItem);
563  helpMenu.add(licenseMenuItem);
564  helpMenu.add(aboutMenuItem);
565  menubar.add(fileMenu);
566  menubar.add(helpMenu);
567 
568  GridBagLayout gridbag = new GridBagLayout();
569  GridBagConstraints appletContraints = new GridBagConstraints();
570  setLayout(gridbag);
571  appletContraints.fill = GridBagConstraints.BOTH;
572  appletContraints.weightx = 1;
573  appletContraints.weighty = 1;
574  gridbag.setConstraints(applet, appletContraints);
575  setMenuBar(menubar);
576  setResizable(true);
577  add(applet);
578  Toolkit screen = getToolkit();
579  Dimension screenSize = screen.getScreenSize();
580  // Java in Windows opens windows in the upper left corner, which is ugly! Center instead.
581  if ( screenSize.width >= 640 && screenSize.height >= 480 )
582  setLocation((screenSize.width-550)/2, (screenSize.height-400)/2);
583  applet.init( this );
584  applet.start();
585  pack();
586  // Read shape after initialization
587  applet.readShape();
588  // Bring to front. Sometimes it stays behind other windows.
589  show();
590  toFront();
591  }
592 
593  /**
594  * Process close window button.
595  * @see java.awt.Component#processEvent(java.awt.AWTEvent)
596  */
597  public void processEvent( AWTEvent e ) {
598  if ( e.getID() == Event.WINDOW_DESTROY )
599  System.exit(0);
600  }
601 
602  /**
603  * Show about dialog.
604  */
605  private void showAboutDialog() {
606  Properties properties = System.getProperties();
607  String jvmProperties = "Java VM "+properties.getProperty("java.version")+" from "+properties.getProperty("java.vendor");
608  Point p = getLocation();
609  new AboutDialog( this, "About the Game of Life", new String[] {"Version 1.5 - Copyright 1996-2004 Edwin Martin", "http://www.bitstorm.org/gameoflife/", jvmProperties}, "about.jpg", p.x+100, p.y+60 );
610  }
611 
612  /**
613  * Show manual.
614  */
615  private void showManualDialog() {
616  Point p = getLocation();
617  new TextFileDialog( this, "Game of Life Manual", "manual.txt", p.x+60, p.y+60 );
618  }
619 
620  /**
621  * Show license.
622  */
623  private void showLicenseDialog() {
624  Point p = getLocation();
625  new TextFileDialog( this, "Game of Life License", "license.txt", p.x+60, p.y+60 );
626  }
627 
628  /**
629  * Get StandaloneGameOfLife object.
630  *
631  * @return StandaloneGameOfLife
632  */
634  return (StandaloneGameOfLife) applet;
635  }
636 }
synchronized boolean getCell(int col, int row)
void setFileExtension(String s)
Definition: EasyFile.java:179
synchronized void setShape(Shape shape)
static Shape getShapeByName(String name)
void setAfterWindowResize(Shape newShape, int newCellSize)
AppletFrame(String title, StandaloneGameOfLife applet)
void setFileName(String s)
Definition: EasyFile.java:163
void writeText(String text)
Definition: EasyFile.java:134
void addGameOfLifeControlsListener(GameOfLifeControlsListener listener)