S.Neum S.Neum - 5 months ago 5
Java Question

Game of Life problems with structure not Swing related

I am currently trying to develop a game of life implementation. I managed to find solutions for nearly every problem, but I am struggling with the "patterns". Nearly everything I tried won't offer the correct patterns like the "blinker". I think that the GUI / Swing might be the reason for the wrong patterns due to a delay, but I am not sure about that or even a way to fix that.

I am going to attach a screenshot of the static patterns and my current code. Any advice is appreciated. The implemented Strategy Interface only offers some Integers (3 = MAX, 2 = MIN). To get myself to work with interfaces.

GameOfLife Patterns

GameOfLifeFrame

public class GameOfLifeFrame extends JFrame implements ActionListener{
private GameOfLifeBoard gameBoard;
private GameOfLifeInterface gameInterface;
private JPanel btnPanel;
private JPanel jPanel;
private JMenuBar jMenuBar;
private JMenu jMenu;
private JButton btnStart;
private JButton btnStop;

private JMenuItem jMenuItemStart;
private JMenuItem jMenuItemStop;
private JMenuItem jMenuItemReset;
private JMenuItem jMenuItemExit;
* Constructor
*/
public GameOfLifeFrame(){
initComponents();
}

private void initComponents(){
gameBoard = new GameOfLifeBoard();
gameInterface = new GameOfLifeInterface(gameBoard);
add(gameInterface);

btnPanel = new JPanel();
jPanel = new JPanel();
setJMenu();

setButtons();

setTitle("Game of Life");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationByPlatform(true);

Dimension minDim = new Dimension(500,500);
Dimension prefDim = new Dimension(500,500);
setMinimumSize(minDim);
setPreferredSize(prefDim);

add(jMenuBar, BorderLayout.PAGE_START);
add(jPanel, BorderLayout.NORTH);
add(btnPanel, BorderLayout.SOUTH);

pack();
setVisible(true);
}

/**
* private void setJMenu
* Creates JMenu for GameOfLife Frame
*/
private void setJMenu(){
jMenuBar = new JMenuBar();
jMenu = new JMenu("Menü");
jMenuItemStart = new JMenuItem("Start");
jMenuItemStop = new JMenuItem("Stop");
jMenuItemReset = new JMenuItem("Zurücksetzen");
jMenuItemExit = new JMenuItem("Verlassen");

//Menu ActionListener
jMenuItemStart.addActionListener(this);
jMenuItemStop.addActionListener(this);
jMenuItemReset.addActionListener(this);
jMenuItemExit.addActionListener(this);

//Adding MenuItem to Menu & Menu to MenuBar
jMenu.add(jMenuItemStart);
jMenu.add(jMenuItemStop);
jMenu.add(jMenuItemReset);
jMenu.add(jMenuItemExit);
jMenuBar.add(jMenu);
}

/**
* actionPerformed
* get Action on GUI. Reaction depends on Buttons.
*
* @param e ActionEvent
*/
@Override
public void actionPerformed(ActionEvent e){
if(e.getSource() == jMenuItemStart){
gameInterface.setActivity(true);
if(btnStart.getText() == "Resume"){
btnStart.setText("Start");
}
}
else if(e.getSource() == jMenuItemStop){
gameInterface.setActivity(false);
if(btnStart.getText() == "Start"){
btnStart.setText("Resume");
}
}
else if(e.getSource() == jMenuItemReset){
gameBoard.setGameBoard(gameBoard.clearGameBoard());
if(!(btnStart.getText() == "Start")){
btnStart.setText("Start");
}
gameBoard.randomize();
}
else if(e.getSource() == jMenuItemExit){
System.exit(0);
}
else if(e.getSource() == btnStart){
gameInterface.setActivity(true);
if(btnStart.getText() == "Resume"){
btnStart.setText("Start");
}
}
else if(e.getSource() == btnStop){
gameInterface.setActivity(false);
btnStart.setText("Resume");
}
}

/**
* setButtons
* sets Buttons Start and Stop and adds them to the Panel & ActionListener
*/
private void setButtons(){
btnStart = new JButton("Start");
btnStop = new JButton("Stop");
btnPanel.add(btnStart);
btnPanel.add(btnStop);
btnStart.addActionListener(this);
btnStop.addActionListener(this);
}

/**
* Main Method, creates an instance of GameOfLifeFrame(GameOfLifeBoard)
* @param args Main Method
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new GameOfLifeFrame();
}
});
}


}

GameOfLifeBoard

public class GameOfLifeBoard implements Strategy {
private boolean[][] gameBoard;

public GameOfLifeBoard() {
this.gameBoard = new boolean[ROW][COL];
for (int i = 0; i < gameBoard.length; i++) {
for (int j = 0; j < gameBoard[i].length; j++) {
gameBoard[i][j] = false;
}
}
}

/**
* getGameBoard
* @return gameBoard
*/
boolean[][] getGameBoard(){
return gameBoard;
}

/**
* setGameBoard
* @param boolArray two-dimensional Array
*/
void setGameBoard(boolean[][] boolArray){
for (int i = 0; i < boolArray.length; i++){
for (int j = 0; j < boolArray[i].length; j++){
gameBoard[i][j] = boolArray[i][j];
}
}
}

/**
* clearGameBoard clears the current gameBoard by setting all boolean to false
* @return clGameBoard returns a blank gameBoard
*/
boolean[][] clearGameBoard(){

for (int i = 0; i < gameBoard.length; i++) {
for (int j = 0; j < gameBoard[i].length; j++) {
gameBoard[i][j] = false;
}
}
return gameBoard;
}

/**
* nextGeneration calculates the new Generation (gameBoard)
* using the static variables 3 and 2 (MAX and MIN) from Strategy (interface)
* by applying the rules from Strategy
*
* nextGeneration uses a temporary 2D boolean Array to replace the old Generation with the new Generation
* by looping through the current gameBoard and replacing each cell with the new status of the cell
* in the new generation
*/
void nextGeneration(){
boolean[][] newGen = new boolean[ROW][COL];;

for (int i = 0; i < gameBoard.length; i++) {
for (int j = 0; j < gameBoard[i].length; j++) {

newGen[i][j] = gameBoard[i][j];

switch (getAliveNeighbourCells(i,j)){
case MAX:
if(getCellState(i,j)){
newGen[i][j] = true;
}
else if(!getCellState(i,j)){
newGen[i][j] = true;
}
break;
case MIN:
if(getCellState(i,j)){
newGen[i][j] = true;
}
else if(!getCellState(i,j)){
newGen[i][j] = false;
}
break;
default:
newGen[i][j] = false;
break;
}
}
}
for (int i = 0; i < gameBoard.length; i++) {
for (int j = 0; j < gameBoard[i].length; j++) {
gameBoard[i][j] = newGen[i][j];
}
}

}

/**
* randomize randomizes each cell on the gameBoard by setting it to true or false (25% true)
*/
void randomize(){
for(int i = 0; i < gameBoard.length; i++){
for(int j = 0; j < gameBoard[i].length; j++){
double d = Math.random();
if(d <= 0.25){
gameBoard[i][j] = true;
}
else{
gameBoard[i][j] = false;
}
}
}
}

/**
* getNeighbourCells, counts the surrounding cells next to the current cell
* @param x delivers position of current cell
* @param y delivers position of current cell
* @return counter-1, because the loops count the current cell itself
*/

private int getAliveNeighbourCells(int x, int y) {
int counter = 0;
for (int i = x-1; i <= x + 1; i++) {
for (int j = y-1; j <= y + 1; j++) {
if(i >= 0 && i < gameBoard.length-1 && j >= 0 && j < gameBoard[i].length-1){
if(gameBoard[i][j]){
counter++;
}
}
}
}
return counter;
}

/**
* getCellState returns CellState of a specific cell on the gameBoard
* @param i delivers position of current cell
* @param j delivers position of current cell
* @return gameBoard[i][j] returns current CellState on gameBoard at position [i][j]
*/
@Override
public boolean getCellState(int i, int j) {
return gameBoard[i][j];
}


GameOfLifeInterface

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;


public class GameOfLifeInterface extends JPanel implements ActionListener{
private Timer time = new Timer(150, this);
private GameOfLifeBoard gameBoard;
private boolean isActive;

/**
* GameOfLifeInterface randomizes the gameBoard
* @param gameBoard gets randomized by GameOfLifeBoard.randomize
*/
public GameOfLifeInterface(GameOfLifeBoard gameBoard){
this.gameBoard = gameBoard;
gameBoard.randomize();
}

/**
* paintComponent draws the current Generation (Dead Cell will be painted in white, Alive Cell in Black)
* and restarts or stops the Timer time
* @param graph Graphics
*/

public void paintComponent(Graphics graph){
super.paintComponent(graph);
int iBox = 2;
for (int i = 0; i < gameBoard.getGameBoard().length; i++) {
for (int j = 0; j < gameBoard.getGameBoard()[i].length; j++) {
graph.setColor(!gameBoard.getGameBoard()[i][j]? Color.WHITE : Color.BLACK);
graph.fillRect(i * iBox, j * iBox, iBox, iBox);
}
}
if(isActive){
time.restart();
}
else{
time.stop();
repaint();
}
}

/**
* setActivity sets private boolean: true or false
* @param activity boolean stores decision of User (Buttons: Stop / Start)
*/
public void setActivity(boolean activity){
isActive = activity;
}

/**
* actionPerformed if Timer time has past, the current Generation will be replaced with the new Generation
* GameBoard gets repainted to show the new Generation
* @param e ActionEvent
*/
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource().equals(time)){
gameBoard.nextGeneration();
repaint();
}
}


Interface Strategy

public interface Strategy {
int ROW = 500;
int COL = 500;
int MIN = 2;
int MAX = 3;

boolean getCellState(int i, int j);


}

Answer

Your getAliveNeighborCells method is wrong. It counts the value of the center cell, when it should only count the surrounding cells.

So your code will check the following pattern:

XXX
XXX
XXX

When it should only count these cells:

XXX
X X
XXX

The simplest solution is just to add the condition (i != x || j != y) to your if statement, like this:

if(gameBoard[i][j] && (i != x || j != y)){ //...

There still could be other bugs in your code, but you left out a lot of important parts, so I can't compile and test it.

Here's a few suggestions to improve/correct your code:

Instead of adding your JMenuBar like a normal component, you should use the setJMenuBar method, which is explicitly for that purpose.

This is more of a stylistic decision, but I'd change your setJMenu method to a getJMenuBar method, which returns a new JMenuBar, and have the caller assign it to the field. Somewhat related to this, I'd also recommend to make any fields that shouldn't be changed after initialization final.

In your actionPerformed method, you compare Strings using ==. That's a bad idea, which technically works in this situation, but will almost certainly fail unexpectedly for you at some point. You should compare Strings with equals instead.

In GameOfLifeBoard, I'd suggest removing the getGameBoard method and replacing it with accessor methods to get the necessary information. The methods I'd recommend would be bool getBoardItem(int row, int col) (this one's already covered by getCellState), int getBoardRows() and int getBoardColumns(int row). Those will let you get the same information, but not let the caller modify the board, which gives you more control over its state.

In nextGeneration, you manually copy over each field in newGen into gameBoard. That works, but it would be faster to just update the reference, with gameBoard = newBoard.

Having ROW and COL defined in Strategy seems like it unnecessarily locks you in to one size. You could replace that with a method declaration of int getRowSize() and int getColSize(), and have the implementation return the values you want. That would let different implementations use whatever size they want.

Those are the things that stand out to me as possible improvements.

Comments