| C:\upload\java\Life\source\java\Life.java |
// WIDTH = 700 HEIGHT = 400 /* ******************************************************************** LL II FFFFFF EEEEEEE LL II FF EE LL II FFFFF EEEEEE LL II FF EE LLLLLLL II FF EEEEEEE ( L i f e ) This was originally written by Dean Moore in QBasic in Winter - Spring 1996, as an assignment for a class the writer was teaching for the University of Maryland Asian Division (Okinawa), and was translated to C/C++ in March -- May, 1997, with a few modifications and different bells and whistles. During & after Winter 2000 it was translated to Java. Actually, there are quite a few things that my students didn't have to do ... but I kept tinkering with the code. --- This program plays John Conway's game of Life. The game is played on a "large" grid, with certain counters initialized to "alive" at the outset, the rest "dead." One of many references is < http://en.wikipedia.org/wiki/Conway%27s_Game_of_Life > All births and deaths occur simultaneously, and the next generation is computed as follows: anyone alive next to two or three living neighbors survives to the next generation. Anyone alive next to no or only one living neighbor dies of loneliness. Anyone with four or more living neighbors dies of overcrowding. A "dead" cell next to *exactly* three living neighbors comes to life in the next generation. ********************************************************************* */ import java.awt.Graphics; import java.io.*; import java.awt.*; import java.awt.event.*; final public class Life extends java.applet.Applet implements Runnable, MouseListener, MouseMotionListener, ActionListener, // Next two are where // program constants & // strings live. Constants, Strings { // Global variables follow. We minimize these as reasonable. boolean painted; // We wait for the paint thread. boolean playAgain; int numberOfGames; boolean firstGame; Point default_gen_num_location; // Constants whose purpose Point default_keep_going_button_location; // should be obvious by name. Point default_edit_game_button_location; Point default_quit_game_button_location; private static Objects objects; // Holds objects. private static HandleButtons buttons; // We use buttons an awful lot, and // maintain our own copy of the // buttons object. private static Vars vars; /* Big class that holds variables. Big. There are lots of variables. Lots of objects that communicate with each other via this class, which is passed around from class to class for communications. We use 'Get' and 'Set' methods everywhere. Historical note: the writer defined Class *Vars* as the number of variables began to get unwieldy. */ private Frame parentFrame; private PopupWindow error; // For errors. Global to all. HandleASingleGeneration singleGenerationThread; // By name. Pause timeToSleepThread; // For pauses, to let // things finish. // Images follow. To avoid the flickering problem, we solely use // double-buffering. private Image offScreenImage; // We solely use private Graphics offScreenGraphics; // double-buffering. private Thread runner; // The main thread. There is a sub-thread // in the < run > method. /******************************************************************* Initialization follows: ****************************************************************** */ public void init() { objects = null; // Initialize buttons = null; // all vars = null; // globals parentFrame = null; // to error = null; // null offScreenImage = null; // for offScreenGraphics = null; // good runner = null; // measure. numberOfGames = 0; // How many games have we played? playAgain = true; // We always play at least once. firstGame = true; // At present, we are on the first // game. parentFrame = getParentFrame(); // Note this will live in both < vars > // and < objects >. if (parentFrame == null) { System.out.println("Parent frame null, < init > ?!?!"); return; } Point appletSize = new Point(this.getSize().width, this.getSize().height); // Set up the offscreen image & its color: offScreenImage = createImage(appletSize.x, appletSize.y); offScreenGraphics = offScreenImage.getGraphics(); setBackground(COLOR_BACKGROUND); objects = new Objects(parentFrame, // To what windows are attached, and offScreenGraphics); // Graphics are needed by several // objects. if (!objects.InstantiationsSucceeded()) // Did anything horrible // happen? { System.out.println(INSTANTIATION_ERROR + ". < Life : init() > " + UNRECOVERABLE_ERROR); return; } // Now we instantiate variables: vars = new Vars(parentFrame, objects.GetScreenLocations()); /* All of our non-global, "Must use 'get' and 'set' methods" variables live in here. * Note: This is "new'd" in one and only one place -- here. It is passed around our children, and from our children to their children, and this is the one and only master copy. Note it requires the parent frame. This is a must at instantiation; see the code of < Vars.java >. Get & set dimensions & location. The "variables" class < vars > *must* know width & height, or truly horrible things happen. We would not be able to set board parameters such as default number of rows & columns. */ vars.SetAppletWidth (appletSize.x); vars.SetAppletHeight(appletSize.y); // We must feed several cross references: objects.GetScreenLocations().SetVars(vars); objects.GetPaintBoardAndComputeNextGeneration().SetVars(vars); objects.GetGenNum().SetVars(vars); buttons = objects.GetButtons(); // We use buttons a lot, and maintain // our own copy of the buttons object. error = new PopupWindow(parentFrame, objects.GetScreenLocations(), ERROR_WINDOW_NAME); // Give the buttons action listeners: buttons.GetKeepGoingButton().addActionListener(this); buttons.GetEditGameButton() .addActionListener(this); buttons.GetQuitGameButton() .addActionListener(this); // Set the cursor later. // Define the gridbag layout where all this stuff will be set: GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints constraints = new GridBagConstraints(); setLayout(gridbag); // First set the canvas on which we paint the board: buildConstraints(constraints, 0,0, // At column - row (0, 0) 1,4, // One column, three rows 94,0, // Eats most of the column, // all of the rows. 1, // Almost no 1); // padding constraints.fill = GridBagConstraints.NONE; constraints.anchor = GridBagConstraints.WEST; // On left side // of board. gridbag.setConstraints(objects.GetPaintBoardAndComputeNextGeneration(), constraints); /* Set contraints for < paintBoard > canvas */ /* Now define how the gridbag allocates components: the generation number text field < genNum >, and the buttons < keepGoingButton >and < editGameButton >. */ buildConstraints(constraints, 1,0, // Column two, row one 1,1, // One row, one column 2,33,// Little of the column, // all of the row 1, // Almost no 1); // padding constraints.fill = GridBagConstraints.NONE; constraints.anchor = GridBagConstraints.WEST; // On right side. gridbag.setConstraints(objects.GetGenNum(), constraints); /* Set contraints for generation number text field */ buildConstraints(constraints, 1,1, // Column two, row two 1,1, // One row, one column 2,33, // Little of the column, // all of the row SMALL_PADDING, // Thin SMALL_PADDING); // padding constraints.fill = GridBagConstraints.NONE; constraints.anchor = GridBagConstraints.EAST; // On right side. gridbag.setConstraints(buttons.GetKeepGoingButton(), constraints);/* Set contraints for "keep going" button */ buildConstraints(constraints, 1,2, // Column two, row three 1,1, // One row, one column 2,33, // Little of the column, // all of the row SMALL_PADDING, // Thin SMALL_PADDING); // padding constraints.fill = GridBagConstraints.NONE; constraints.anchor = GridBagConstraints.EAST; // On right side. gridbag.setConstraints(buttons.GetEditGameButton(), constraints); /* Set contraints for "Edit game?" button */ buildConstraints(constraints, 1,3, // Column two, row four 1,1, // One row, one column 2,33, // Little of the column, // all of the row SMALL_PADDING, // Thin SMALL_PADDING); // padding constraints.fill = GridBagConstraints.NONE; constraints.anchor = GridBagConstraints.EAST; // On right side. gridbag.setConstraints(buttons.GetQuitGameButton(), constraints); /* Set contraints for "Quit game?" button */ // Add our widgets: add(objects.GetPaintBoardAndComputeNextGeneration()); add(objects.GetGenNum()); add(buttons.GetKeepGoingButton()); add(buttons.GetEditGameButton()); add(buttons.GetQuitGameButton()); // Register event listeners: addMouseListener(this); addMouseMotionListener(this); appletSize = null; // Ready to garbage collect. } // Declarations of methods that must be over-ridden follow: public void mousePressed (MouseEvent e) {} // Declare public void mouseReleased(MouseEvent e) {} // these; we public void mouseExited (MouseEvent e) {} // don't use them public void mouseEntered (MouseEvent e) {} // and give them public void mouseDragged (MouseEvent e) {} // null bodies. // Note: routine < mouseMoved(MouseEvent e) > defined below. /******************************************************************* The "run" method contains the real guts of the applet, and deserves more attention than the short routines. ******************************************************************** */ public void run() { /* Next handles questions for user when a game has ended. It takes a < this > pointer to have access to the < buttons > parameter and the parent frame. No-one else uses this class. */ HandleGamesEnd handleGamesEnd = new HandleGamesEnd(objects); /* First paint everybody to the screen to avoid "not painted to the screen" errors the writer encountered using Linux. */ String errorStr = ""; try { // Religiously check input: if ((objects.GetGenNum() == null) || (objects.GetPaintBoardAndComputeNextGeneration() == null) || (buttons.GetKeepGoingButton() == null) || (buttons.GetEditGameButton() == null) || (buttons.GetQuitGameButton() == null)) { errorStr += (objects.GetGenNum() == null) ? "Gen Num null " : NULL_STRING; errorStr += (objects.GetPaintBoardAndComputeNextGeneration() == null) ? "PaintBoardAndComputeNextGeneration null " : NULL_STRING; errorStr += (buttons.GetKeepGoingButton() == null) ? "Keep going button null " : NULL_STRING; errorStr += (buttons.GetEditGameButton() == null) ? "Edit Game Button null " : NULL_STRING; errorStr += (buttons.GetQuitGameButton() == null) ? "Quit Game Button null " : NULL_STRING; throw new NullPointerException(errorStr); } // Paint the generation number. objects.GetGenNum().paint(offScreenGraphics); objects.GetGenNum(). repaint(); objects.GetPaintBoardAndComputeNextGeneration().repaint(); buttons.GetKeepGoingButton(). repaint(); buttons.GetEditGameButton(). repaint(); buttons.GetQuitGameButton(). repaint(); greyOutScreen(); waitForPaint(); } catch (NullPointerException e) { String err = "Null pointer, top of < run() >, in < Life >?" + e.toString() + "error string: " + errorStr; error.ShowMessage(err, OK); // Button. } greyOutScreen(); // For good waitForPaint(); // measure. /* On the next: Everyone was working hunky-dory, then I tried a screen refresh. In short, screen locations were a mess. This saves "good" locations, */ setLocationsInCaseOfScreenRefresh(); /* Now we have set our components and painted ourself, tell the variables class < vars > where we are. */ try { vars.SetAppletLocation(this.getLocationOnScreen()); } catch (Exception e) { System.out.println("Could not set location on screen, < Life > ?!" + e.toString()); } /* Fudge explain: I couldn't get the blasted canvas to work just right, no matter what Java books I consulted. I resort to the next to force the canvas's (plural and possessive) size. Set sizes of components: */ try { objects.GetPaintBoardAndComputeNextGeneration().setSize( vars.GetBoardWidth() + PIXELS_TO_SKIP_FROM_LEFT_SIDE_OF_BOARD, vars.GetBoardHeight()); objects.GetGenNum().setSize(4*GENERATION_NUMBER_FONT_SIZE, GENERATION_NUMBER_FONT_SIZE); } catch (Exception e) { String err = e.toString()+" Could not set size, < Life >?!"; System.out.println(err); return; } /* One of the first things we must do is determine screen locations. This is so when we paint no-one paints over someone else, & in determining sizes. */ pause(TIME_TO_WAIT_FOR_BUTTON_CLICK); objects.GetScreenLocations(). SetBoardWidthAndHeightAndComponentPositions(objects); /* Now we know where things are, we set default values of things like rows and columns. */ objects.GetInitializations().SetDefaults(vars); /* error.ShowMessage("" + buttons.GetKeepGoingButton().getSize(), OK); error.ShowMessage("" + buttons.GetEditGameButton().getSize(), OK); error.ShowMessage("" + buttons.GetQuitGameButton().getSize(), OK); error.ShowMessage("" + objects.GetGenNum().getSize(), OK); */ // Several local variables follow: buttons.HideButtons(); /* Make the buttons disappear for now. They had to be visible to determine their locations. */ beforeFirstGame(); greyOutScreen(); /* Now for the main loop: this loops on whether the player wishes to play. As soon as the user no longer wishes to play, we get out. */ MainLoop: while (playAgain) { HandleLoop mainLoop = handleFirstGame(handleGamesEnd); if (mainLoop == HandleLoop.BREAK_MAIN_LOOP) { break MainLoop; } else if (mainLoop == HandleLoop.CONTINUE_MAIN_LOOP) { continue MainLoop; } else if (mainLoop == HandleLoop.KEEP_GOING_MAIN_LOOP) { ; // Do nothing. Keep going. } else { System.out.println("Something horrible has happened, value" + " of mainLoop = " + mainLoop); } /* Set a boolean that governs the game -- if it changes value (see test of the < while > loop directly below) it's over: */ boolean keepGoing = true; /* Why do we define the next two threads? First: Note that between generations, we need to do two things: 1) Compute next generation 2) Wait for either a time delay or the user to click the "Continue" button. If we don't use the thread (notably when the pixels per cell is small and there are lots of rows & columns, then the next generation computations get CPU-intensive), we would wait, say, one second, and wait to compute the next generation, and the user would wonder, "Why aren't things moving as fast as I input them to?" Or the user could wonder, "Why doesn't the next generation pop right up when I click the "Continue" button? Note this isn't perfect. If the user uses a time delay of close to zero seconds, there isn't much we can do. But if there is time, we use it in computing the next generation and setting up the off-screen graphics to be ready to display it at the instant < waitForPaint() > is called. Note we initialize it to null. This is to shut up "may not have been initialized" errors in a try-catch clause below. On the "time to sleep" thread: we "pause" on a separate thread, and if in the middle if a "pause" somewhere, the user requests either to edit the game or quit, we simply stop the thread. */ singleGenerationThread = null; timeToSleepThread = null; /* Note the loop is named: "playASingleGame." Break statements are hence more understandable. */ boolean firstGeneration = true; // First generation. keepGoing = true; // Continue game? singleGenerationThread = new HandleASingleGeneration(vars, objects, offScreenGraphics); playASingleGame : while (keepGoing && !buttons.GetQuitButtonClicked()) { // if (!firstPass) /* Two reasons: Don't paint first generation // twice, and, this makes things work properly // under Windows NT. // // I hope ... need to test it at work. // */ // { // PaintTheBoardAndComputeNextGeneration(vars); // firstPass = false; // } if (!firstGeneration) // We have already painted first generation. { waitForPaint(); // Note we repaint at top. } pause(TIME_TO_WAIT_FOR_PAINTING); buttons.SetEditGameButtonClicked(false); // Set false buttons.SetQuitButtonClicked (false); // for good buttons.SetContinueButtonClicked(false); // measure. /* Set buttons's cursors for this game. They may have changed if we edited the board. */ buttons.SetButtonsForThisGame(vars); // See above documentation on next object, a thread. /* On the next: If we wait for a time delay between generations, we set a time delay. Otherwise, we are waiting for a button click, and set zero time. But note: */ int delay; // How long we wait. May be zero milliseconds -- see next. /* The next boolean decision is bothersome. I will at least document the confusion. 1) If we are waiting for a time delay we are on the first or second generation, and , we already waited once in routine < setInitialBoard >, and have already computed the second generation due to how class < PaintBoardAndComputeNextGeneration > works -- this was the pause to go from first to second generation. If we just edited the board, don't wait. Don't repeat it. Fall into the "else" clause. 3) If we just edited the board and wait for a time delay, set this wait. 4) Or if it is first pass, and there is a time delay, AND we clicked on "alive" cells, we haven't waited yet. We did not do it in routine < setInitialBoard >. Do it now. */ if (vars.GetWaitForTimeDelay() && (vars.GetGenerationNumber() > 2) && !vars.GetJustEditedBoard() || (firstGeneration && vars.GetWaitForTimeDelay() && vars.GetClickLiveCellsViaMouse())) { delay = vars.GetTimeBetweenGenerations(); } else // Set zero time. Thread will return immediately. { delay = 0; } // But we're not done yet. We allocate time for painting; // deduct it or zero the time: delay = (delay <=TIME_TO_WAIT_FOR_PAINTING) ? 0 : delay - TIME_TO_WAIT_FOR_PAINTING; timeToSleepThread = new Pause(delay); /* Start the thread & wait for either a button click or a time delay. Note there are no problems with critical sections -- one piece of code is doing computations, the other is sleeping. */ try { if (vars.GetGenerationNumber() == 1) { singleGenerationThread.start(); // Sub-thread. } else { // singleGenerationThread.resume(); // If suspended. singleGenerationThread.SetDoneWithCurrentGeneration(false); singleGenerationThread.run(); } timeToSleepThread.start(); /* Now: if we are waiting for a button click, we must make a decision. 1) If we are not on the first generation, and, we did not just edit the board, wait for a button click. 2) "Else" clause is: it's either the first generation, or we just edited the board. Either way, we just waited for a button click. Fake it. */ if (!vars.GetWaitForTimeDelay()) { if ((vars.GetGenerationNumber() > 2) && !vars.GetJustEditedBoard()) { waitForButtonClick(); } else { buttons.SetContinueButtonClicked(true); } } vars.SetJustEditedBoard(false); // Re-set in case it changed. /* Now we wait for our two threads: while either is alive, AND, the user has not pressed the "quit game" or "edit game" button, we wait. */ waitForThreadsOrAStopOrder(timeToSleepThread, singleGenerationThread, vars); // Paint the generation number. objects.GetGenNum().paint(offScreenGraphics); }// End "try" clause. catch (ThreadDeath threadDead){ /* Do nothing */ } // If the user pressed the "quit" button, this supercedes all else. if (buttons.GetQuitButtonClicked()) // User wants out. { break playASingleGame; } /* Thread's outcome? If thread finished naturally, next reflects the state of the board, i.e., "keep going; all is well" or "quit, all cells dead or we are in a repeating pattern." If thread was interrupted by the setting of < editGameButtonClicked >, it doesn't matter; see first statement of next block. */ keepGoing = singleGenerationThread.KeepGoing(); /* If we don't edit the game, and we don't quit, we return to the top of the < playASingleGame > loop. Note: next block has bothersome coding, violating the if-the-else rule of coding, but the writer saw no nice way out. So we do apple-and-oranges comparisons. */ if (buttons.GetEditGameButtonClicked()) /* Has the user decided to edit the game? */ { keepGoing = true; /* What if everyone died? Or the pattern was found repeating? The user has decided to edit the game. This takes precedence. */ handleEditingOfGame(vars); // Handles editing of board. Which see. if (!buttons.GetQuitButtonClicked()) // User didn't quit? { /* Set buttons's cursors. These may have changed when we set the board by mouse clicks. The passed boolean tells the routine how to handle the "Continue" button. */ buttons.SetButtonsForThisGame(vars); /* Do NOT updated the board, as we do below in the next block for the "Continue" and "Wait for time delay" buttons's case. We now have a new board, and it lives in < thisGeneration >. We will return to the top of the < playASingleGame > loop, and then compute the next generation. */ } else // User clicked "quit" button while editing. { break playASingleGame; // End the current game. } } /* On the next: If we are waiting for button clicks, and the "Continue" button has been clicked, or, if we set a time delay (now passed), we set current board to next generation. */ else if ((buttons.GetContinueButtonClicked() || vars.GetWaitForTimeDelay()) && keepGoing) /* We are keeping the next generation in parameter < nextGeneration > as set in object < singleGenerationThread >, not changing it. */ { /* Now set next generation in < oldBoards >. Note the modular arithmetic in next. Generations divisible by < oldBoards.length > get the 0-index, < oldBoards[0][][] >. */ objects.GetBoardUtilities().SetFirstToSecond( vars.GetOldBoards()[ (vars.GetGenerationNumber()%vars.GetOldBoards().length) ], vars.GetThisGeneration(), vars.GetRows(), vars.GetColumns()); vars.BumpGenerationNumber(); // Next generation. /* Now set < thisGeneration > to < nextGeneration >, the next generation. We are NOT blowing away the next generation. */ objects.GetBoardUtilities().SetFirstToSecond(vars.GetThisGeneration(), vars.GetNextGeneration(), vars.GetRows(), vars.GetColumns()); singleGenerationThread.SetDoneWithCurrentGeneration(false); // singleGenerationThread.suspend(); // This thread's work is done for now. } // End bad apples-and-oranges coding. firstGeneration = false; // No longer first generation by now. /* Note we paint at the top of < playASingleGame > loop. */ } // End < playASingleGame > loop. /* When we get here, the current game is over. Note that if the pattern repeats, we leave it on the screen. Otherwise we blow it away, "not interesting" -- everyone dead or the user requested a stop. */ if (objects.GetRepeatPattern().GetPeriod() == PATTERN_DOES_NOT_REPEAT) { greyOutScreen(); // Clean up the waitForPaint(); // screen. } buttons.HideButtons(); // Make the buttons disappear for now. playAgain = handleGamesEnd.PlayAgain(singleGenerationThread, numberOfGames); singleGenerationThread = null; // Get rid of these for the timeToSleepThread = null; // sake of garbage collection. garbageCollect(); // Suggest to the JVM that we garbage collect. } // End "while (playAgain)" loop. // When we get here, it means the user is playing no more games killApp(); // The last roundup. } // End routine run() // Done with main code; time for subroutines & a class at the end. /******************************************************************* Nonsense that needs done before first game. Shoved in subroutine for modularity. ******************************************************************** */ private void beforeFirstGame() { /* Now give some initial information to the user. The next used object, < UI ui >, tells the user the rules and calls another routine that asks for input of what the user wants, i.e., things like how many rows and columns. */ try { /* Next sets default values in all of our children. This also instantiates boards. */ objects.GetInitializations().InitializeEveryone(vars, objects); /* The next fires up a question, "Do you wish to see the instructions?" If the answer is a "yes," the UI code in object < ui > presents the rules of the game. This is intentionally "invisible" to the main program. */ objects.GetUi().AskIfUserWantsToSeeInstructionsOrQuit(vars); didUIRequestQuit(); // If a quit is requested, entire application // is killed. Road is no return. if (objects.GetUi().UserWantsToSeeInstructions()) { // We show the user a few boards. showTheUserAFewBoards(vars); /* Show the rest of the instructions, a discussion of the UI presented in < ui.ShowUserInterfaceInstructions() >: */ greyOutScreen(); // Clear all screen graphics. objects.GetUi().ShowUserInterfaceInstructions(vars); // Show // instructions. // Same exact code as above follows, so short did not make // a subroutine: didUIRequestQuit(); // If a quit is requested, entire application } // is killed. Road is no return. } catch (NullPointerException e) // Pointer null?!?! { String errorStr = "objects = " + objects + "objects.GetUi() = " + objects.GetUi() + "objects.GetInitializations() = " + objects.GetInitializations(); error.ShowMessage(POINTER_NULL + " " + IN + " " + "< Life run() >?!?! " + errorStr, OK); } } /******************************************************************* Nonsense that needs done to get first game off the ground. Shoved in subroutine for modularity. ******************************************************************** */ private HandleLoop handleFirstGame(HandleGamesEnd handleGamesEnd) { HandleLoop breakMainLoop = HandleLoop.KEEP_GOING_MAIN_LOOP; numberOfGames++; // Keep track of which game we are on. vars.SetGenerationNumber(1); // Start generations at one. if ((firstGame) || // On first game, or ... (handleGamesEnd.GameChanged())) // Not first game, but changed // from last game. { firstGame = false; // Next returns < false > if we do not play, otherwise // we simply keep going. if (!objects.GetUi().GetGameParametersScreen(vars)) /* Fires up the big UI */ { // that gets gameparameters. return HandleLoop.BREAK_MAIN_LOOP; // Get out. } } // End "if ((firstGame) || .... " block. /* The next routine hides a multiplicity of sins, using the values obtained in routine < getGameParameters >: making the boards, and initializing objects so they know things like how many rows and columns the board has, and number of pixels. It also nulls out the boards. */ objects.GetInitializations().InitializeEveryone(vars, objects); /* Set the initial board. Next calls routine < PaintTheBoardAndComputeNextGeneration > to paint the initial board. */ setInitialBoard(vars); /* If we set the initial board and the user did not request that we get out, we simply fall through. But if the user requested to get out, we ask, "Play again?" */ if (buttons.GetQuitButtonClicked()) // User requested a "quit?" { greyOutScreen(); // Get rid of any waitForPaint(); // graphics on the screen. /* Note we pass a null parameter to the "Keep playing?" class. The parameter of type < HandleASingleGeneration > has not been yet instantiated. We don't need it for this call. */ playAgain = handleGamesEnd.PlayAgain(numberOfGames); // Done with "first game" code. From here on out we have played // at least one. if (playAgain) { buttons.SetQuitButtonClicked(false); // Re-null it. breakMainLoop = HandleLoop.CONTINUE_MAIN_LOOP; // Re-start top } // everything, // top of loop else { breakMainLoop = HandleLoop.BREAK_MAIN_LOOP; // Get out entirely. } } // End of block, "user never wanted this game in the first place." return breakMainLoop; } /******************************************************************* Next sets the board at the start of a game. We define this for modularity so as to not clutter the main code. ******************************************************************** */ private void setInitialBoard(Vars vars) { vars.SetGenerationNumber(1); // Paint the generation number. objects.GetGenNum().paint(offScreenGraphics); /* Now we set initial "alive" and "dead" cells on board. Why do we define the next boolean? We save old values, as they are set as defaults in going from one game to another. So we use a temp boolean. Thing is, if the user sets the board probalistically and then decides to edit it, we need to run the next < while > loop again. We don't want to blow away the current value of < clickLiveCellsViaMouse >, so set it in a temp boolean. Bothersome coding, yes. But otherwise there might be a subtle bug -- the value of < clickLiveCellsViaMouse > could change, and the changed value would set as a board default on next game's pass. */ boolean clickOnCells = vars.GetClickLiveCellsViaMouse(); /* Which buttons do we set? This needs two parameters to make its decision; see the routine < SetWhichButtonsAreVisibleBeforeSettingBoard > in class < HandleButtons > for more. */ buttons.SetWhichButtonsAreVisibleBeforeSettingBoard(vars); setBoardLoop : while(true) // The loop goes until user quits. { buttons.SetEditGameButtonClicked(false); // Null buttons.SetContinueButtonClicked(false); // these buttons.SetQuitButtonClicked (false); // flags. if (clickOnCells) { setBoardByMouseClicks(vars, objects); // Decide board by clicking. break setBoardLoop; // Get out. } else // Decide game probalistically. { buttons.GetKeepGoingButton().setLabel(START_GAME); // Change this button accordingly. objects.GetInitializations().Probalistic(vars.GetThisGeneration(), vars.GetProbability(), vars.GetRows(), vars.GetColumns()); greyOutScreen(); // Grey out screen. /* Now we have set the initial board, it's time to set up the off-screen graphics & show the current board, as delineated in class < setUpOffScreenGraphicsForPrintingBoard drawOffScreenGraphics >. */ PaintTheBoardAndComputeNextGeneration(objects); waitForPaint(); /* On the next: If we wait for a time delay between generations, we set it. Otherwise, we are waiting for a button click, and set zero time. */ Pause timeToSleepThread = new Pause( vars.GetWaitForTimeDelay() ? vars.GetTimeBetweenGenerations() : 0); timeToSleepThread.start(); /* Start sleeping -- could be zero time; see above. */ if (!vars.GetWaitForTimeDelay()) // Are we waiting for a button click? { waitForButtonClick(); } else /* Note if we set time zero above, < timeToSleepThread.isAlive() > will be < false > now or pretty fast. */ { while (timeToSleepThread.isAlive() && // Thread running? !buttons.GetQuitButtonClicked() && // Didn't click !buttons.GetEditGameButtonClicked()) // these buttons? { pause(TIME_TO_WAIT_FOR_THREADS); } timeToSleepThread.stop(); // Just in case this is still running. /* If the time delay thread died and no buttons were clicked, get out. */ if (!buttons.GetQuitButtonClicked() && !buttons.GetEditGameButtonClicked()) { break setBoardLoop; } } /* By now there has been a time delay and / or a button click. We have to handle all button possibilites. */ if (buttons.GetContinueButtonClicked()) // It only makes sense to check for this if the { // the user is going from generation to break setBoardLoop; // generation via mouse clicks, but this check } // will cause no problems if there is a time delay. else if (buttons.GetQuitButtonClicked()) { timeToSleepThread.stop(); /* Stop the "sleep" thread, if it is running. */ break setBoardLoop; // Get out. } else if (buttons.GetEditGameButtonClicked()) /* We are going through the loop again. If we did it probalistically at first, now we do it via mouse clicks. Otherwise the user would get a new probalistic setting. */ { timeToSleepThread.stop(); // Stop the "sleep" thread, if it is running. clickOnCells = true; } // No more buttons to handle. The user simply waited for a timeout. } // End "decide the game probalistically" block. } // End "setBoardLoop : while(true)" loop. buttons.GetKeepGoingButton().setLabel(CONTINUE); // Change this button accordingly. /* Set the first old board. We use the call to < vars.GetGenerationNumber() > for good measure; this will be one. */ objects.GetBoardUtilities().SetFirstToSecond( vars.GetOldBoards()[vars.GetGenerationNumber()], // One. vars.GetNextGeneration(), vars.GetRows(), vars.GetColumns()); } /******************************************************************* Next handles the editing of the board. It primarily exists for modularity. ******************************************************************** */ private void handleEditingOfGame(Vars vars) { /* This code tries to think ahead. We must backtrack. Generations are backed up one. All generations. */ objects.GetBoardUtilities().SetFirstToSecond(vars.GetNextGeneration(), vars.GetThisGeneration(), vars.GetRows(), vars.GetColumns()); objects.GetBoardUtilities().SetFirstToSecond(vars.GetThisGeneration(), vars.GetLastGeneration(), vars.GetRows(), vars.GetColumns()); /* Blow away < oldBoards > entirely. Why? The board has changed from its "natural" pattern. For example, the user could edit a single cell due to die on the edge, away from what would be a repeating pattern. The cell dies; the rest of the pattern repeats, and the user is told that the pattern with the single cell due to die is repeating? */ objects.GetInitializations().NullOutOldBoards(vars); // Now drop the generation number: vars.SetGenerationNumber(vars.GetGenerationNumber() - 1); // Now set by mouse clicks: setBoardByMouseClicks(vars, objects); } /******************************************************************* Next sets the board by mouse clicks. Note the use of the < ManageBoardCoordinates boardCoordinates >. This takes care of managing board coordinates in one place, as class SetUpOffScreenGraphicsForPrintingBoard also uses board coordinates. ******************************************************************** */ private void setBoardByMouseClicks(Vars vars, Objects objects) { if (buttons.GetQuitButtonClicked() || buttons.GetContinueButtonClicked()) { return; // User wants out } // First get local copies of things we need. PaintBoardAndComputeNextGeneration paintBoardAndComputeNextGeneration = objects.GetPaintBoardAndComputeNextGeneration(); ManageBoardCoordinates boardCoordinates = objects.GetBoardCoordinates(); vars.SetEditingGame (true); // We are editing the game. buttons.SetContinueButtonClicked(false); // Null these buttons.SetEditGameButtonClicked(false); // for good buttons.SetQuitButtonClicked (false); // measure. if (vars.GetGenerationNumber() == 1) // First generation? { buttons.GetKeepGoingButton().setLabel(START_GAME); // Change this button accordingly. } else { buttons.GetKeepGoingButton().setLabel(QUIT_EDITING); // Change this button accordingly. } /* We no longer need the "edit game" button -- we ARE editing the game. We must have the "Continue" button. */ buttons.DeactivateEditGameButton(); /* Note: we need the "Continue" button. This may now have a "default" cursor, so set it to an "active" cursor. */ buttons.ActivateKeepGoingButton(); // Next are where the mouse is clicked. Local variables. int xVal = NOT_ON_BOARD, yVal = NOT_ON_BOARD; // Set the colors. All are same color now, and "about to come to life" is "dead." paintBoardAndComputeNextGeneration.setAliveColor(COLOR_ALIVE); paintBoardAndComputeNextGeneration.setSoonDeadColor(COLOR_ALIVE); paintBoardAndComputeNextGeneration.setAboutToComeToLifeColor(COLOR_DEAD); // Show the user the board: setBackground(COLOR_BACKGROUND); PaintTheBoardAndComputeNextGeneration(objects); waitForPaint(); // Paint board with a different color scheme. vars.SetMouseClicked(false); // Set both of these buttons.SetContinueButtonClicked(false); // for good measure. bigLoop: while ( (!buttons.GetContinueButtonClicked()) && ( !buttons.GetQuitButtonClicked())) { // We wait for a mouse click on the board. while (!vars.GetMouseClicked()) { // We are waiting for the mouse to be clicked. if (buttons.GetQuitButtonClicked() || buttons.GetContinueButtonClicked()) { break bigLoop; // Time to get out. Otherwise it is mouse } // input which we catch. pause(50); // Sleep a short time -- nothing going on. } if (buttons.GetQuitButtonClicked() || buttons.GetContinueButtonClicked()) { break bigLoop; // User wants out } // If we get here, we presume the mouse has been clicked. // Re-set it. vars.SetMouseClicked(false); if (buttons.GetQuitButtonClicked() || buttons.GetContinueButtonClicked()) { break bigLoop; // User wants out } // Where on the board (board, not pixel coordinates) did the user click the mouse? Point where = boardCoordinates.PixelsToCoordinates(vars); xVal = (int) where.getX(); // Point returns double, yVal = (int) where.getY(); // requires cast. // Next question: where did the user click? Does it make // sense as a cell? Was it on the board? if ((xVal == NOT_ON_BOARD) || (yVal == NOT_ON_BOARD)) { ; // Do nothing. } else // Value was on the board. { try // Be careful! { /* X-values go across. These are columns. Y-values go up and down. These are rows. We usually refer arrays by things like "thisGeneration[x][y]," but the problem is obvious. This explains the seeming uncongruity of the "thisGeneration[yVal][xVal]" usage below. Switch the cell, from "dead" to "alive" or vice-versa. */ { vars.GetThisGeneration()[yVal][xVal] = (vars.GetThisGeneration()[yVal][xVal] == 1) ? 0 : 1; } } catch (ArrayIndexOutOfBoundsException e) // Array index out of bounds!? { String errorStr = ARRAY_BOUNDS_EXCEPTION + " " + IN + " " + " setBoardByMouseClicks: " + ROWS + " = " + vars.GetRows() + ", " + COLUMNS + " = " + vars.GetColumns() + ", xVal = " + xVal + ", " + AND + " " + " yVal = " + yVal + ". " + ERROR_MESSAGE_COLON + " = " + e.getMessage(); error.ShowMessage(errorStr, OK); // Button } /* Set the single pixel we change. Notably, after < paintBoard's > paint() routine is called, we "default" to painting the entire board again. */ paintBoardAndComputeNextGeneration.SetSingleCellToChange(xVal, yVal); // Now display the board as it is: setBackground(COLOR_BACKGROUND); PaintTheBoardAndComputeNextGeneration(objects); // Do this now ... waitForPaint(); } // End of "else" clause. } // End "bigLoop" // When we get here we are done editing the board. // Re-set color scheme: paintBoardAndComputeNextGeneration.setAliveColor(COLOR_ALIVE); paintBoardAndComputeNextGeneration.setSoonDeadColor(COLOR_SOON_TO_DIE); paintBoardAndComputeNextGeneration.setAboutToComeToLifeColor(COLOR_ABOUT_TO_COME_TO_LIFE); vars.SetEditingGame(false); // We are no longer editing the game. Get out. vars.SetJustEditedBoard(true); // But we did just edit the board. buttons.GetKeepGoingButton().setLabel(CONTINUE); // Re-set name of button. buttons.SetButtonsForThisGame(vars); // Re-set all buttons to former values. waitForPaint(); // Show board. } /******************************************************************* Next shows the user (who requests this) a few sample boards. ******************************************************************** */ /* This routine violates coding standards left and right via the use of magic numbers. */ private void showTheUserAFewBoards(Vars vars) { HandleASingleGeneration singleGenerationThread = null; Pause timeToSleepThread = null; vars.SetGenerationNumber(1); // For good measure. // Get a local copies of objects we use a lot: Initializations initializations = objects.GetInitializations(); BoardUtilities boardUtilities = objects.GetBoardUtilities(); int [][] thisGeneration = vars.GetThisGeneration(); int[][] nextGeneration = vars.GetNextGeneration(); int rows = vars.GetRows(); int columns = vars.GetColumns(); int i; // Loop variable. int rowVal, colVal; PopupWindow message = new PopupWindow(parentFrame, objects.GetScreenLocations(), SAMPLE_BOARDS); if ((rows < MIN_DIMENSIONS_TO_DISPLAY_SAMPLE_BOARDS) || (columns < MIN_DIMENSIONS_TO_DISPLAY_SAMPLE_BOARDS)) { PopupWindow ms = new PopupWindow(parentFrame, objects.GetScreenLocations(), BOARD_TOO_SMALL); ms.ShowMessage(BOARD_TOO_SMALL_TO_SHOW_SAMPLE_BOARDS, OK); // Button return; // Board is too small to do anything, and we will // probably generate errors. Best just get out. } // We do things with both pointer and arrays. Use a try-catch clause. try { objects.GetInitializations().NullOutBoards(thisGeneration, nextGeneration); greyOutScreen(); String [] stringArr = {OK, START_PLAYING, QUIT}; message.ShowMessage(EXAMPLE_BOARDS_ONE_FOLLOW_SYMMETRY, stringArr); // Button. if ((message.WhichButtonWasPressed()).equals(START_PLAYING)) { cleanUpAfterShowingBoards(); return; } else if ((message.WhichButtonWasPressed()).equals(QUIT)) { killApp(); } // Set up the example: rowVal = rows/2; colVal = columns /2; thisGeneration[rowVal][colVal] = 1; // This little routine thisGeneration[rowVal][colVal+1] = 1; // initializes the thisGeneration[rowVal-1][colVal+2] = 1; // board with a pattern. thisGeneration[rowVal-1][colVal-1] = 1; thisGeneration[rowVal][colVal-1] = 1; // The value one indicates an thisGeneration[rowVal+1][colVal] = 1; // alive. for (i=1; i <= 35; i++) // A magic number, in violation of coding standards. { timeToSleepThread = new Pause((int)(DEFAULT_SHORT_TIME_TO_PAUSE / i*i)); singleGenerationThread = new HandleASingleGeneration(vars, objects, offScreenGraphics); timeToSleepThread.start(); singleGenerationThread.start(); // Note buttons are turned off and no stop order may be issued. waitForThreadsOrAStopOrder(timeToSleepThread, singleGenerationThread, vars); // Now set thisGeneration to nextGeneration, the next generation. boardUtilities.SetFirstToSecond(thisGeneration, nextGeneration, rows, columns); setBackground(COLOR_BACKGROUND); waitForPaint(); // we go, hence "i*i" vars.BumpGenerationNumber(); } greyOutScreen(); waitForPaint(); // We have just cleared the board. Re-paint it. vars.SetGenerationNumber(1); objects.GetInitializations().NullOutBoards(thisGeneration, nextGeneration); message.ShowMessage(EXAMPLE_BOARDS_TWO_FOLLOW_REPEATING_PATTERNS, stringArr); if ((message.WhichButtonWasPressed()).equals(START_PLAYING)) { cleanUpAfterShowingBoards(); return; } else if ((message.WhichButtonWasPressed()).equals(QUIT)) { killApp(); } // Set up second example: thisGeneration[1][3] = 1; // Makes a pattern thisGeneration[2][3] = 1; // that alternates thisGeneration[3][3] = 1; // in two generations. int xBase = 6; int yBase = 6; thisGeneration[yBase] [xBase] = 1; thisGeneration[yBase + 1][xBase] = 1; thisGeneration[yBase] [xBase + 1] = 1; thisGeneration[yBase + 1][xBase + 1] = 1; rowVal = rows-5; colVal = columns/2; for (i = colVal; i<=colVal+2; i++) { thisGeneration[rowVal][i] = 1; thisGeneration[rowVal+5][i] = 1; thisGeneration[rowVal][i+6] = 1; thisGeneration[rowVal+5][i+6] = 1; } for (i = rowVal+2; i <= rowVal+4; i++) { thisGeneration[i][colVal- 2] = 1; thisGeneration[i][colVal+ 3] = 1; thisGeneration[i][colVal+ 5] = 1; thisGeneration[i][colVal+10] = 1; } for (i = 1; i <= 8; i++) // A magic number, in violation of coding standards. { timeToSleepThread = new Pause((int)(DEFAULT_SHORT_TIME_TO_PAUSE / i*i)); singleGenerationThread = new HandleASingleGeneration(vars, objects, offScreenGraphics); timeToSleepThread.start(); singleGenerationThread.start(); // Note buttons are turned off and no stop order may be issued. waitForThreadsOrAStopOrder(timeToSleepThread, singleGenerationThread, vars); // Now set thisGeneration to nextGeneration, the next generation. boardUtilities.SetFirstToSecond(thisGeneration, nextGeneration, rows, columns); setBackground(COLOR_BACKGROUND); waitForPaint(); // we go, hence "i*i" vars.BumpGenerationNumber(); } waitForPaint(); // We have just cleared the board. Re-paint it. vars.SetGenerationNumber(1); objects.GetInitializations().NullOutBoards(thisGeneration, nextGeneration); message.ShowMessage(EXAMPLE_BOARDS_THREE_FOLLOW_WALKERS, stringArr); if ((message.WhichButtonWasPressed()).equals(START_PLAYING)) { cleanUpAfterShowingBoards(); return; } else if ((message.WhichButtonWasPressed()).equals(QUIT)) { killApp(); } // Set up the example: thisGeneration[3][1] = 1; thisGeneration[3][2] = 1; thisGeneration[3][3] = 1; thisGeneration[2][3] = 1; thisGeneration[1][2] = 1; for (i = 1 ; i <= 10; i++) // A magic number, in violation of coding standards. { timeToSleepThread = new Pause(DEFAULT_SHORT_TIME_TO_PAUSE / i*i); singleGenerationThread = new HandleASingleGeneration(vars, objects, offScreenGraphics); timeToSleepThread.start(); singleGenerationThread.start(); // Note buttons are turned off and no stop order may be issued. waitForThreadsOrAStopOrder(timeToSleepThread, singleGenerationThread, vars); // Now set thisGeneration to nextGeneration, the next generation. boardUtilities.SetFirstToSecond(thisGeneration, nextGeneration, rows, columns); setBackground(COLOR_BACKGROUND); waitForPaint(); // we go, hence "i*i" vars.BumpGenerationNumber(); } } // End "try" clause. catch (ArrayIndexOutOfBoundsException e) // Array index out of bounds!? { String errorStr = e.getMessage(); errorStr = errorStr + ARRAY_BOUNDS_EXCEPTION + " < Life showTheUserAFewBoards >"; error.ShowMessage(errorStr, OK); // Button } catch (NullPointerException e) { error.ShowMessage(POINTER_NULL + ", < Life showTheUserAFewBoards >?!?!", OK); // Button } cleanUpAfterShowingBoards(); } /* ****************************************************************** ****************************************************************** Done with "big" routines. We follow with the short routines. ********************************************************************* ********************************************************************* */ /******************************************************************* The next handles the painting of our two canvases. Very short and simple. ******************************************************************** */ private void PaintTheBoardAndComputeNextGeneration(Objects objects) { objects.GetGenNum().paint(offScreenGraphics); // Paint the generation number. objects.GetPaintBoardAndComputeNextGeneration().paint(offScreenGraphics); // Does actual painting of the board. } /******************************************************************* The next handles buttons. ******************************************************************** */ public void actionPerformed(ActionEvent e) { // Nullify any previous action: buttons.SetContinueButtonClicked(false); buttons.SetEditGameButtonClicked(false); buttons.SetQuitButtonClicked(false); String buttonLabel = ((Button) e.getSource()).getLabel(); // Who was // punched? if(e.getSource() instanceof Button) // Had better be a button! { if (buttonLabel.equals(EDITGAME)) { buttons.SetEditGameButtonClicked(true); StopThreads(); } else if (buttonLabel.equals(QUIT_GAME)) { buttons.SetQuitButtonClicked(true); StopThreads(); } else if (buttonLabel.equals(CONTINUE) || buttonLabel.equals(QUIT_EDITING) || buttonLabel.equals(START_GAME) ) { buttons.SetContinueButtonClicked(true); } else { error.ShowMessage(UNRECOVERABLE_ERROR + " " + UNRECOGNIZED_BUTTON, OK); // Button } } else { error.ShowMessage(UNRECOVERABLE_ERROR + " " + INPUT_NOT_A_BUTTON + ".", OK); } } /******************************************************************* Repeated code in *run* routine. If UI user requests a "quit," kill entire application. ******************************************************************** */ private void didUIRequestQuit() { if (objects.GetUi().Quit()) // Simply get out. { killApp(); } } // Three paint-related routines follow: update, paint, and destroy. /******************************************************************* Update routine follows. ******************************************************************** */ public void update(Graphics g) { paint(g); } /******************************************************************* Paint routine follows; this is standard Java. ******************************************************************** */ public void paint(Graphics g) { if (offScreenImage != null) { g.drawImage(offScreenImage, 0, 0, getBackground(),this); } synchronized(this) // No one else can mess with me now. { painted = true; notify(); } } // End paint routine. /******************************************************************* The next gets rid of off-screen graphics. Clean-up. ******************************************************************** */ public void destroy() { offScreenGraphics.dispose(); } /* ****************************************************************** Next does nothing for a passed-in time. Short and brainless. Used when we don't need a threaded pause. ****************************************************************** */ public void pause(int time) { try { { Thread.sleep(time); } // Kill some time. } catch (InterruptedException e) // Should never, ever happen. { error.ShowMessage(INTERRUPTED_PAUSE, OK); // Button } } /******************************************************************* Next waits for threads or orders to stop. *************************************************************** */ private void waitForThreadsOrAStopOrder(Thread timeToSleepThread, HandleASingleGeneration singleGenerationThread, Vars vars) { try { while (!buttons.QuitOrEditButtonClicked() && /* If one of these buttons is clicked, get out. */ (timeToSleepThread.isAlive() || // Still sleeping? !singleGenerationThread.GetDoneWithCurrentGeneration())/* Waiting for next generation to be computed? */ ) { pause(TIME_TO_WAIT_FOR_THREADS); // Kill a little time. } } catch (ThreadDeath threadDead){ /* Do nothing */ } } /******************************************************************* Next stops all global threads. *************************************************************** */ private void StopThreads() { // Stop our children, if they are still running: if ((singleGenerationThread != null ) && (singleGenerationThread.isAlive())) { singleGenerationThread.stop(); } if((timeToSleepThread != null) && (timeToSleepThread.isAlive())) { timeToSleepThread.stop(); } } /******************************************************************* Next builds Java constraints for GridBagLayouts. Short and brainless. *************************************************************** */ private void buildConstraints(GridBagConstraints gbc, int gx, int gy, int gw, int gh, int wx, int wy, int xPadding, int yPadding) { gbc.gridx = gx; gbc.gridy = gy; gbc.gridwidth = gw; gbc.gridheight = gh; gbc.weightx = wx; gbc.weighty = wy; gbc.ipadx = xPadding; gbc.ipady = yPadding; } /* ****************************************************************** Tells the main if the mouse has been clicked, and sets (x, y) coordinates of this in global variables. ********************************************************************* */ public void mouseClicked(MouseEvent e) { vars.SetMouseClicked(true); vars.SetMouseX(e.getX());// Where did user click mouse? Global variable. vars.SetMouseY(e.getY());// Where did user click mouse? Global variable. } /******************************************************************* The next sets the cursor to "hand" in the following case: 1) we are editing the game (see the boolean test), and, 2) the cursor is in the board area. Otherwise it is set to the "default" cursor. ******************************************************************** */ public void mouseMoved(MouseEvent e) { boolean varsNull = (vars != null); if (varsNull && vars.GetEditingGame()) // We only care about the // cursor if we are editing { // the game // Next mess of boolean tests verify: is the cursor in the board area? if ((e.getX() > PIXELS_TO_SKIP_FROM_LEFT_SIDE_OF_BOARD) && (e.getX() < PIXELS_TO_SKIP_FROM_LEFT_SIDE_OF_BOARD + vars.GetColumns()*vars.GetPixelsPerCell()) && (e.getY() > PIXELS_TO_SKIP_FROM_TOP_AND_BOTTOM) && (e.getY() < PIXELS_TO_SKIP_FROM_TOP_AND_BOTTOM + vars.GetRows()*vars.GetPixelsPerCell())) { setCursor(vars.GetHandCursor()); } else { setCursor(vars.GetDefaultCursor()); } } else { if (varsNull) { ; // Ignore it. } } } /******************************************************************* Next waits for painting ******************************************************************** */ private void waitForPaint() { painted = false; repaint(); while (!painted) { try { pause(TIME_TO_WAIT_FOR_PAINTING); } catch (Exception e) {} } } /******************************************************************* The next gets a parent frame for the main code. ******************************************************************** */ public Frame getParentFrame() { Object anchorPoint = getParent(); while (! (anchorPoint instanceof Frame)) { anchorPoint = ((Component)anchorPoint).getParent(); } return ((Frame) anchorPoint); } private void setLocationsInCaseOfScreenRefresh() { // Default locations of widgets: int height = this.getSize().height; int offSet = (int) (height / 6); default_gen_num_location = new Point(this.getSize().width - buttons.GetQuitGameButton().getSize().width, height); default_keep_going_button_location = new Point(this.getSize().width - buttons.GetKeepGoingButton().getSize().width, height*2); default_edit_game_button_location = new Point(this.getSize().width - buttons.GetEditGameButton().getSize().width, height*3); default_quit_game_button_location = new Point(this.getSize().width - buttons.GetQuitGameButton().getSize().width, height*4); // The next sets are due to problems when re-loading the applet: vars.SetKeepGoingButtonLocation(default_keep_going_button_location); vars.SetQuitGameButtonLocation (default_quit_game_button_location); vars.SetEditGameButtonLocation (default_edit_game_button_location); vars.SetGenNumLocation (default_gen_num_location); } /******************************************************************* We wait for buttons to be pressed in so many places, we have the following short routine to handle this. ******************************************************************** */ public void waitForButtonClick() { buttons.SetContinueButtonClicked(false); // Set so we buttons.SetEditGameButtonClicked(false); // don't do buttons.SetQuitButtonClicked(false); // anything naughty. while (!buttons.GetContinueButtonClicked() && !buttons.GetEditGameButtonClicked() && !buttons.GetQuitButtonClicked()) { garbageCollect(); pause(TIME_TO_WAIT_FOR_BUTTON_CLICK); // Not long. } } /******************************************************************* Next starts the thread. Short and brainless. *************************************************************** */ public void start() { if (runner == null) // Start the thread. { runner = new Thread(this); runner.start(); } } /******************************************************************* Next stops the thread. Short and brainless. ******************************************************************** */ public void stop() { if (runner != null) { runner.stop(); runner = null; } } /******************************************************************* Next gets rid of whatever is on the screen and greys it out. ******************************************************************** */ private void greyOutScreen() { try { offScreenGraphics.clearRect(0,0, vars.GetAppletWidth(), vars.GetAppletHeight()); offScreenGraphics.setColor(COLOR_BACKGROUND); // Gray background. offScreenGraphics.fillRect(0,0, vars.GetAppletWidth(), vars.GetAppletHeight()); waitForPaint(); } catch (NullPointerException e) { String errorStr = ""; errorStr += (vars == null) ? "vars null ? " : NULL_STRING; errorStr += (offScreenGraphics == null) ? "offScreenGraphics null ? " : NULL_STRING; System.out.println(e.toString() + " " + errorStr); } } private void cleanUpAfterShowingBoards() { pause(DEFAULT_SHORT_TIME_TO_PAUSE); objects.GetInitializations().NullOutBoards(vars.GetThisGeneration(), vars.GetNextGeneration()); vars.SetGenerationNumber(1); greyOutScreen(); garbageCollect(); } private void killApp() { greyOutScreen(); if (buttons != null) { buttons.HideButtons(); } // Clean waitForPaint(); // screen. System.exit(0); // The last roundup. garbageCollect(); } /******************************************************************* Next suggests to the JVM that we garbage collect, called at strategic times when this makes sense. No guarantee it will be called. ******************************************************************** */ private void garbageCollect() { Runtime rt = Runtime.getRuntime(); rt.gc(); } }