So, I'm trying to program a Game of Life simulation (Conway), and I want to show it in a JFrame.
For this purpose, I've created a JPanel, and it works perfectly, until I try to actually show a new generation. With prints, I've figured out, that the list is actually correct inside the newGeneration() method, but when paint(Graphics g) gets called (aka, when I try to repaint the JFrame), the list isn't updating.
I'm sure I've missed something obvious, and I'm not well versed in Java, but it's just getting so annoying. I'd really appreciate your help.
Here's my code;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Main {
public static void main(String[] args) {
new GameOfLife();
}
}
class GameOfLife {
// Initialising all class wide variables; sorted by type
JFrame frame = new JFrame("Game of Life");
JPanel panel;
Scanner gameSize = new Scanner(System.in);
String dimensions;
String splitHorizontal;
String splitVertical;
String confirmation;
Boolean accepted = false;
Integer split;
Integer horizontal;
Integer vertical;
Integer livingNeighbours;
int[][] cells;
int[][] newCells;
public GameOfLife() {
// Prompt for game Size
System.out.println("Please enter your game size in the following format; 'Horizontal,Vertical'");
// Run until viable game Size has been chosen
while (!accepted) {
dimensions = gameSize.nextLine();
// Check for correct format
if (dimensions.contains(",")) {
split = dimensions.indexOf(",");
splitHorizontal = dimensions.substring(0, split);
splitVertical = dimensions.substring(split + 1);
// Check for validity of inputs
if (splitHorizontal.matches("[0-9]+") && splitVertical.matches("[0-9]+")) {
horizontal = Integer.parseInt(dimensions.substring(0, split));
vertical = Integer.parseInt(dimensions.substring(split + 1));
// Check for game Size
if (horizontal > 1000 || vertical > 1000) {
System.out.println("A game of this Size may take too long to load.");
} else {
// Confirmation Prompt
System.out.println("Your game will contain " + horizontal + " columns, and " + vertical + " rows, please confirm (Y/N)");
confirmation = gameSize.nextLine();
// Check for confirmation, anything invalid is ignored
if (confirmation.matches("Y")) {
accepted = true;
System.out.println("Thank you for your confirmation. Please select live cells. Once your happy with your game, press Spacebar to start the Simulation.");
// Setting parameters depending on Size
frame.setSize(horizontal * 25 + 17, vertical * 25 + 40);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
}
}
// Prompt asking for new dimensions in case of invalid dimensions or non confirmation
if (!accepted) {
System.out.println("Please enter different dimensions.");
}
}
// Creating list of cells
cells = new int[horizontal][vertical];
// Showing the empty panel for selection of live cells
panel = new PaintCells(horizontal, vertical, cells);
frame.add(panel);
// Select live cells
panel.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
if (cells[(int) Math.ceil(e.getX() / 25)][(int) Math.ceil(e.getY() / 25)] == 1) {
cells[(int) Math.ceil(e.getX() / 25)][(int) Math.ceil(e.getY() / 25)] = 0;
} else {
cells[(int) Math.ceil(e.getX() / 25)][(int) Math.ceil(e.getY() / 25)] = 1;
}
frame.repaint();
}
});
// Simulation start
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar() == ' ') {
newGeneration();
}
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
});
}
// Generating new generations
void newGeneration() {
newCells = new int[horizontal][vertical];
// Pause inbetween generations
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
* Way of Life Rules:
* Living cells with 2 or 3 living neighbours live on to the next generation.
* Dead cells with exactly 3 living neighbours become living cells in the next generation.
* Every other living cell dies.
*/
// iterate through every cell
for (int l = 0; l < vertical; l++) {
for (int k = 0; k < horizontal; k++) {
livingNeighbours = 0;
// check amount of neighbours
if (k - 1 > -1) {
if (l - 1 > -1) {
if (cells[k - 1][l - 1] == 1) {
livingNeighbours++;
}
}
if (l + 1 < vertical) {
if (cells[k - 1][l + 1] == 1) {
livingNeighbours++;
}
}
if (cells[k - 1][l] == 1) {
livingNeighbours++;
}
}
if (k + 1 < horizontal) {
if (l - 1 >= 0) {
if (cells[k + 1][l - 1] == 1) {
livingNeighbours++;
}
}
if (l + 1 < vertical) {
if (cells[k + 1][l + 1] == 1) {
livingNeighbours++;
}
}
if (cells[k + 1][l] == 1) {
livingNeighbours++;
}
}
if (l - 1 >= 0) {
if (cells[k][l - 1] == 1) {
livingNeighbours++;
}
}
if (l + 1 < vertical) {
if (cells[k][l + 1] == 1) {
livingNeighbours++;
}
}
// change cell value depending on amount of neighbours
if (cells[k][l] == 1) {
if (livingNeighbours < 2 || livingNeighbours > 3) {
newCells[k][l] = 0;
} else {
newCells[k][l] = 1;
}
} else {
if (livingNeighbours == 3) {
newCells[k][l] = 1;
}
}
}
}
cells = newCells;
frame.validate();
frame.paint(frame.getGraphics());
newGeneration();
}
}
// Our canvas
class PaintCells extends JPanel {
private Integer horizontal;
private Integer vertical;
private int[][] newOriginalCells;
// Get our X and Y from the original prompts
public PaintCells(Integer originalHorizontal, Integer originalVertical, int[][] originalCells) {
this.horizontal = originalHorizontal;
this.vertical = originalVertical;
this.newOriginalCells = originalCells;
}
#Override
public void paint(Graphics g) {
for (int i = 0; i < vertical; i++) {
for (int j = 0; j < horizontal; j++) {
// Check cell value
if (newOriginalCells[j][i] == 1) {
g.setColor(Color.black);
} else {
g.setColor(Color.white);
}
// paint according to value
g.fillRect(j * 25, i * 25, 25, 25);
if (newOriginalCells[j][i] == 1) {
g.setColor(Color.white);
} else {
g.setColor(Color.black);
} // maybe change style?
g.drawRect(j * 25, i * 25, 25, 25);
}
}
}
}
I'm guessing, the problem is somewhere in newGeneration(), but other than that, I really have no idea anymore.
You have a common problem which I had myself a few months ago.
Java Swing GUI system works in thread called Event Dispatch Thread (EDT). This thread handle events like mouse clicks, typing etc. and paint the components to the screen. You should use this thread not as your main thread, but as sub-thread which working only once a certain time/when event happens, and not let him run continuously. In your code, since the user choose the cell to live, this thread run non-stop (because you started the program inside a listener, which is part of the EDT), and your GUI stuck, because it's updating only at the end of the thread.
You can solve this by using javax.swing.Timer. Timer is an object that allows you do tasks once a while, and it is perfect to this problem.
Use code like this:
ActionListener actionListaner = new ActionListener(){
public void actionPerformed(ActionEvent e){
//Put here you ne genration repeating code
}
};
int delay = 1000;//You delay between generations in millis
Timer timer = new timer(delay, actionListener);
The code in the actionPerformed method will repeat every second (or any other time you want it to repeat), and every operation of the timer will recall EDT instead of let it run non-stop.
Firstly, I know Lists are better in almost(if not all) every way. I have encountered a substantial bug in an encoder program that I am making. In this program, I have a button that resets the "wheels" responsible for encoding(One of the wheels rotates after every letter encoded). I have a final int[][] called wheelsOriginal that is supposed to store the original value of the int[][] called wheels. Both of these arrays are int[9][36]. I would like a way of making wheelsOriginal stay unchanged throughout the program instead of changing with wheels for some reason. Here is a good way to recreate the problem(Sorry for the lengthy intToChar and charToInt methods!):
Main class:
import java.awt.*;
import javax.swing.*;
public class mainClass {
public static void main(String[] args) {
JFrame frame = new JFrame("Encoder");
frame.setBackground(new Color(225,225,225));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Display d = new Display();
frame.add(d);
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
}
}
Display class:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Display extends JPanel implements ActionListener {
static JButton button;
static JLabel letter;
static int currentKey = -10;
static int wheel = 0;
static int[][] wheels = {
{-3,10,-6,2,20,-7,22,5,23,4,6,-9,3,26,0,15,21,-2,13,14,12,1,17,11,-8,-5,18,8,24,9,25,7,19,16,-4,-1},
{9,22,14,12,18,-3,3,6,16,1,-7,25,24,19,-8,8,21,20,5,-6,-2,26,15,-9,23,10,11,0,-5,4,-4,2,17,-1,13,7},
{18,20,-9,15,12,-6,16,-4,-5,14,24,-7,-8,-3,-1,1,4,7,8,25,10,11,5,6,13,22,19,21,23,-2,3,26,17,9,0,2},
{-3,10,-6,2,20,-7,22,5,23,4,6,-9,3,26,0,15,21,-2,13,14,12,1,17,11,-8,-5,18,8,24,9,25,7,19,16,-4,-1},
{9,22,14,12,18,-3,3,6,16,1,-7,25,24,19,-8,8,21,20,5,-6,-2,26,15,-9,23,10,11,0,-5,4,-4,2,17,-1,13,7},
{25,18,5,8,7,-8,4,11,6,-7,26,21,-1,24,15,23,9,-6,-2,13,16,22,-5,10,17,3,1,-9,0,12,2,19,-4,14,20,-3},
{25,18,5,8,7,-8,4,11,6,-7,26,21,-1,24,15,23,9,-6,-2,13,16,22,-5,10,17,3,1,-9,0,12,2,19,-4,14,20,-3},
{25,18,5,8,7,-8,4,11,6,-7,26,21,-1,24,15,23,9,-6,-2,13,16,22,-5,10,17,3,1,-9,0,12,2,19,-4,14,20,-3},
{9,22,14,12,18,-3,3,6,16,1,-7,25,24,19,-8,8,21,20,5,-6,-2,26,15,-9,23,10,11,0,-5,4,-4,2,17,-1,13,7}
};
final static int[][] wheelsOriginal = wheels;
public Display() {
setPreferredSize(new Dimension(250,200));
setFocusable(true);
button = new JButton("Reset");
button.setPreferredSize(new Dimension(225,50));
button.setFont(new Font(button.getFont().getFontName(), button.getFont().getStyle(), 25));
letter = new JLabel(" ", SwingConstants.CENTER);
letter.setPreferredSize(new Dimension(225,100));
letter.setFont(new Font(letter.getFont().getFontName(), Font.BOLD, 125));
letter.setForeground(new Color(0,0,0));
addKeyListener(
new KeyListener() {
public void keyPressed(KeyEvent e) {
if(currentKey == -10 && e.getKeyCode() >= 65 && e.getKeyCode() <= 90) {
currentKey = e.getKeyCode() - 64;
letter.setText(encode() + "");
}
else if(currentKey == -10 && e.getKeyCode() >= 48 && e.getKeyCode() <= 57) {
currentKey = -1 * (e.getKeyCode() - 48);
letter.setText(encode() + "");
}
}
public void keyReleased(KeyEvent e) {
currentKey = -10;
letter.setText(" ");
}
public void keyTyped(KeyEvent e) {}
}
);
button.addActionListener(this);
add(button, TOP_ALIGNMENT);
add(letter);
}
public static char encode() {
int key = currentKey;
for(int i = 0; i < 9; i++) {
key = wheels[i][key + 9];
}
for(int i = 8; i >= 0; i--) {
key = wheels[i][key + 9];
}
rotate(wheels[wheel], isEven(wheel));
if(wheel < 8) {
wheel++;
}
else {
wheel = 0;
}
return((char) key);
}
public static int[] rotate(int[] wheel, boolean positive) {
int revolve;
if(positive) {
revolve = wheel[wheel.length - 1];
for(int i = wheel.length - 2; i > 0; i--) {
wheel[i + 1] = wheel[i];
}
wheel[0] = revolve;
}
else {
revolve = wheel[0];
for(int i = 1; i < wheel.length - 1; i++) {
wheel[i - 1] = wheel[i];
}
wheel[wheel.length - 1] = revolve;
}
return wheel;
}
public static boolean isEven(int num) {
return (num/2 == Math.abs(num/2));
}
public void actionPerformed(ActionEvent e) {
if(e.getSource().equals(button)) {
reset();
grabFocus();
}
}
public static void reset() {
for(int[] i : wheels) {
for(int x : i) {
System.out.print(x + " ");
}
System.out.println("");
}
System.out.println(" ");
for(int[] i : wheelsOriginal) {
for(int x : i) {
System.out.print(x + " ");
}
System.out.println("");
}
System.out.println(" ");
wheels = wheelsOriginal;
for(int[] i : wheels) {
for(int x : i) {
System.out.print(x + " ");
}
System.out.println("");
}
wheel = 0;
letter.setText(" ");
currentKey = ' ';
System.out.println("Pressed");
}
}
Whenever a key is pressed, the encoded letter appears in the window. Even pressing the same key over and over again will usually produce different letters. Pressing the reset button should reset the encoder so that pressing the letter 'A' three times should produce S, E, and Q in that order. I also have designed this so that whenever you press the reset button, three large bulks of numbers print in the console. These show the wheels array before reset, the wheelsOriginal array, and the product wheels array in that order. If you press keys and click reset several times, you will notice that wheelsOriginal changes with wheels. Please help...
Your problem is that you are creating wheelsOriginal as reference of wheels instead of copy. Thats why when you change wheels, wheelsOriginal changes as well.
final static int[][] wheelsOriginal = wheels;
Something like this loop can be used to create a copy of wheels
int[][] wheelsOriginal = new int[wheels.length][];
for( int i = 0; i < wheelsOriginal.length; i++ )
{
wheelsOriginal[i] = Arrays.copyOf( wheels[i], wheels[i].length );
}
Also, for your charToInt and IntToChar methods - you could use the fact that chars are numbers and a->z A->Z 0->9 are grouped together to shorten them significantly
I didn't test that - in case you decide to use something like this - think and test yourself
public int charToInt( char c )
{
if( c >= '0' && c <= '9' ) {
return '0' - c;
} else if( c >= 'A' && c <= 'Z' ) {
return c - 'A' + 1;
} else if( c >= 'a' && c <= 'z' ) {
return c - 'a' + 1;
} else {
return -10;
}
}
public char intToChar( int c )
{
if( c >= -9 && c <= 0 ){
return (char)('0' - c);
} else if( c >= 1 && c <= 26 ){
return (char)(c + 'A' - 1);
} else{
return ' ';
}
}