Write a java program that implements a general "Game of Life", a
"cellular automaton" world consisting of a rectangular array of cells,
each of which is either Alive or Dead. It is important that these
guidelines for the question are followed. The world starts in an
initial state (i.e. a pattern of Alive and Dead cells), and then moves
through a series of successive states. Each state is called a
generation. State-transition rules determine the way the pattern of
Alive/Dead cells change from one generation to the next. There are 4
transition possibilities for a cell:
Dead -> Dead: "stasis"
Dead -> Alive: "birth"
Alive -> Alive: "survival"
Alive -> Dead: "death"
The fate of a cell in going from one generation to the next depends on
whether it is Alive or Dead in the current generation, and on the
number of Alive neighbors it has in the current generation. Different
state-transition rules lead to different "Games of Life" with worlds
that evolve in different ways. A particularly interesting state
transition rule discovered by the mathematician John Horton Conway is
this: A "birth" happens only with 3 Alive neighbors, "survival"
happens only with 2 or 3 Alive neighbors, and "stasis" or "death"
happens otherwise. This Conway Rule leads to many interesting patterns
developing in the game.
A neighbor of a cell is defined as one of its 8 adjacent cells (upper
left, above, upper right, left, right, lower left, below, lower
right), so a cell can have at most 8 Alive neighbors. The world is a
finite rectangle with a certain number of rows and columns, and there
are no Alive cells outside the world. The program shall be run as
follows:
java Life <FILENAME>
The initial state of the world is read from the file whose name is
given as the first command line argument (format specified below) A
window is created, which contains these components:
1. A central panel, which displays the initial state of the world, and
subsequent generations, in a form described below. The user can change
the state of the world by clicking the mouse in a displayed cell.
Doing so changes the state of that cell (to Alive it was Dead, or to
Dead if it was Alive). The result of such a change is displayed.
2. A text field, initialized to "10", in which the user can enter a
nonnegative integer which is the number of successive generations to
compute and display. This text field should have a label associated
with it, that indicates to the user that it represents the number of
generations.
3. A button labeled "Start", which when pushed computes and displays
as many subsequent generations as are indicated by the number in the
text field, at a rate of approximately two per second.
4. An output label that displays a running count of the number of
generations displayed, and error messages as appropriate.
File format: The initial state of the world will be read from a file.
This file is a binary file, with format as follows:
The first 4 bytes of the file are an int, indicating the number of
rows in the world, R. The next 4 bytes in the file are another int,
indicating the number of columns in the world, C. Then there follow
R*C bytes. The first C bytes specify the states of the cells in the
top row of the world, left to right; and so on. A byte that is equal
to 0 indicates a Dead cell; a nonzero byte indicates a Alive cell. If
there are more bytes in the file than required by the world size, the
extra ones are to be ignored. If there are fewer bytes in the file
than required by the world size, the unspecified cells are assumed to
be Dead.
Display format: When run, the Life program first reads a world state
specification file, as described above. It displays this world in a
central panel in the application window, and permits the user to
change the state of some cells by clicking on them. Each cell in the
world should be displayed on the central panel as a filled circle 10
pixels in diameter, and the central panel should be the exact size
required to display all cells in the world. The panel background,
Alive cells, and Dead cells should be displayed in contrasting colors
so they can be distinguished from each other.
When the user clicks the start button, new generations are computed
and displayed. The application pauses after the required generations
have been displayed, permitting the user to continue if they want to,
or to exit the application by clicking on the "close window" button
provided by the window system.
These are the Class interface specifications. The Life class should
be defined to extend JFrame. An instance of the Life class will
correspond to the top-level application window.
Define this protected instance variable in the class:
protected boolean[][] world;
The elements of the world array represent the states of cells in an
instance of the game of Life. Each cell is in one of two states (Alive
or Dead), so a boolean array is appropriate. The number of rows and
columns of this two-dimensional array must be exactly the same as the
number of rows and columns in this instance of Life. (And note that it
must have protected visibility, so subclasses can access it as
needed.)
Define at least these public methods in the Life class:
/** Constructor: initialize a new Life object to represent
* a world with the given number of rows and columns of cells.
* All cells are initially Dead. This constructor also creates
* appropriate components and containers and adds them to the
* application container, and creates and registers appropriate
* listener objects. It does not make any GUI objects visible.
*/
public Life(int rows, int cols)
/** Read a world initial state specification file (format described
* in the P7 README) with name given by its argument.
* Create and return a Life object whose world is in the state
specified
* by the file.
* @throws java.io.IOException containing an appropriate message if
* the file is badly formatted or unreadable, or if an I/O error
occurs.
*/
public static Life fromFile(String filename)
throws IOException
/** Update the state of the protected boolean[][] world array of
* this Life object (i.e., compute the next generation) according
* to the Conway state-transition rule.
* This method does not do any displaying.
*/
public void next()
/** The entry point for the Life program.
* Creates a Life object initialized from the file given as
* the first command line argument, and makes it visible.
*/
public static void main(String args[])
You may define any other private static or instance methods or
variables, or inner classes that you wish.
Here are some suggestions just to help you out
1. The hard part is supposed to be implementing the next() method. It
is quite hard to compute the state of the next generation using only
the world array that is representing the current generation; so it is
suggested you create another array to hold the new generation while
you are computing it, and then make the world array pointer point to
it when done. In any case, when next() returns, the protected world
variable must point to the new generation.
2. To compute a new generation, apply the rule of the game to each
cell. Be sure to take into account that the world "ends" at its edges,
as mentioned above. In thinking about how to decompose the problem
into manageable pieces, you may find that methods with suggestive
names like countLiveNeighbors(int row, int col) and isAlive(int row,
int col) might be useful.
4. Each cell takes up a circle 10 pixels in diameter when displayed.
Therefore, if your world has R rows and C columns, your central panel
must have width 10*C and height 10*R pixels to display the world. The
application JFrame needs to be large enough to hold such a central
panel, the start button, the text field, the two required labels, plus
any default borders and decorations belonging to the JFrame. A trick
of JFC/AWT programming to accomplish this is to use a JPanel for the
central panel, call the setPreferredSize() method of the JPanel to set
its size, and after adding it and all other needed components to the
JFrame's content pane, to "pack" the JFrame. Note that
setPreferredSize() takes one argument, of type Dimension. Some code in
your Life constructor can look something like this:
Container c = this.getContentPane();
JPanel p = new JPanel();
p.setPreferredSize(new Dimension(10*C,10*R));
c.add(p);
this.pack();
The pack() method computes the right size for the JFrame to be able to
contain its Components, so you don't have to.
Inner classes will be necessary, and in particular anonymous inner
classes will be appropriate. When useful, include these:
a. An anonymous inner class that extends JPanel and that overrides the
paintComponent(Graphics g) method to display the current world state
in the JPanel. This method should first call super.paintComponent(g)
to clear the background and produce a better-looking animation. An
instance of this class will be the central panel in the application
window.
b. An anonymous inner class that extends MouseAdapter and that
overrides mouseClicked() to handle the mouse click events fired by the
central JPanel. An instance of this class will be registered as a
MouseEvent listener with the central JPanel. The handler method needs
to determine the x,y coordinates of the mouse click, to change the
state of the clicked cell appropriately, and to display the new state.
To display the new state, you can just call the repaint() method of
the JPanel, which takes care of calling its paintComponent() method.
c. An anonymous inner class that implements ActionListener and that
overrides actionPerformed() to handle the start button being clicked.
An instance of this class will be registered as an ActionEvent
listener with the start button. The handler method needs to get the
text in the text field, convert it to an int, and run the game of life
for that many generations (not counting the current generation),
causing each generation to be displayed in the central JPanel. If the
contents of the text field are not parseable as a positive int, an
error should be displayed in the output label. As each generation is
displayed, the number of that generation should be displayed in the
output label.
d. An anonymous inner class that extends WindowAdapter and that
overrides windowClosing() to exit the application. An instance of this
class will be registered as a WindowEvent listener with the overall
JFrame. Alternatively, you could just setDefaultCloseOperation to
JFrame.EXIT_ON_CLOSE.
6. To cause a container to display all of its components, you normally
call the container's repaint() method. However, for animations such as
the Life program, that may not work as you would hope; in an attempt
to be efficient, the runtime system can "coalesce" multiple calls to
paint() into one call, and you won't see anything but the last frame
in the animation. Instead, it is better in animations to call the
paintImmediately() method of the container, which does not do this
coalescing. Note that paintImmediately() takes 4 int arguments which
specify the rectangular area in the container to paint immediately:
paintImmediately(int x, int y, int width, int height)
You should think about calling paintImmediately for two purposes:
a. In the event handler for the start button, after a new generation
has been computed, call the paintImmediately() method of the central
JPanel. (This leads to its overrided paintComponent() method being
called.) If the JPanel is p, painting the entire JPanel immediately
can be done with a call like
p.paintImmediately(0,0,p.getWidth(),p.getHeight());
b. In the event handler for the start button, after a new generation
has been computed, you will update the text in the output label. To
make this new text appear immediately, call the paintImmediately()
method for that label's container. For convenience, you may want to
place that label in a JPanel container, and place that JPanel in a
region of the top-level JFrame.
7. You might want to create files to test the class. |